commit ff5e105937e2567f50a7cbd9d84601de5a3069b7 Author: xsean Date: Wed Oct 15 10:58:21 2025 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6175ccd --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +Pods/ +build/ diff --git a/build.py b/build.py new file mode 100644 index 0000000..f532a5d --- /dev/null +++ b/build.py @@ -0,0 +1,51 @@ +import subprocess +import sys +import os +import json + +def build_json(json_path): + with open(json_path, 'r') as f: + config = json.loads(f.read()) + t = config["type"] + if t == "max": + build_max(json_path) + else: + build_topon(json_path) + +def build_max(json_path): + build_path = "/Users/mac/codes/ios/empty_topon/max" + build_script(build_path, json_path, "playb-max.ipa") + + +def build_topon(json_path): + build_path = "/Users/mac/codes/ios/empty_topon/topon" + build_script(build_path, json_path, "playb-topon.ipa") + +def build_script(build_path, json_path, ipa_name): + cmd = """ + cd {0} + python build.py {1} + """.format(build_path, json_path) + + subprocess.call(cmd, shell=True) + + src_ipa = os.path.join(build_path,"build/ipas", ipa_name) + target_path = os.path.dirname(json_path) + cmd = """ + cp -fv {0} {1} + """.format(src_ipa, target_path) + + subprocess.call(cmd, shell=True) + + +def build(base_path): + print(base_path) + for root, dirs, files in os.walk(base_path): + for file in files: + full_path = os.path.join(root, file) + name = os.path.basename(full_path) + if name == 'build.json': + build_json(full_path) + +if __name__ == '__main__': + build(sys.argv[1]) \ No newline at end of file diff --git a/install_all.py b/install_all.py new file mode 100644 index 0000000..ad3109e --- /dev/null +++ b/install_all.py @@ -0,0 +1,23 @@ +import subprocess +import os + + +def install_path(path, type): + cmd = """ + sshpass -p alpine pscp -l root -A -h ./ips.txt {0} /User/Downloads + sshpass -p alpine pssh -l root -A -ih ./ips.txt "appinst /User/Downloads/playb-{1}.ipa" + """.format(path, type) + subprocess.call(cmd, shell=True) + +def install_all(): + for root, dirs, files in os.walk("/Users/mac/codes/ios/empty_topon/ipas/lux"): + for file in files: + full_path:str = os.path.join(root, file) + if full_path.endswith("max.ipa"): + install_path(full_path, "max") + elif full_path.endswith("topon.ipa"): + install_path(full_path, "topon") + + +install_all() + \ No newline at end of file diff --git a/max/build.py b/max/build.py new file mode 100644 index 0000000..3fad4f1 --- /dev/null +++ b/max/build.py @@ -0,0 +1,116 @@ +import subprocess +import json +import sys +import os + + +def build(ad_key, ad_ids, app_display_name,app_bundle_id,app_version,app_icon): + + cmd = """ + rm -rfv build + mkdir build + cp -rfv template/playbtest ./build/ + """ + + subprocess.call(cmd, shell=True) + # 创建编译目录复制模版到build目录 + + print("--------------------") + # 读取配置 + + + + + ### 复制icon + + cmd = f""" + cp -fv {app_icon} ./build/playbtest/playbtest/Assets.xcassets/AppIcon.appiconset/1024x1024.png + """ + subprocess.call(cmd, shell=True) + + ## 修改ad信息 + print("修改ad信息") + code_path = './build/playbtest/playbtest/playB/bbbAdManager.swift' + with open(code_path, 'r') as f: + code = f.read() + code = code.replace("{adKey}", ad_key) + code = code.replace("{allAdIds}", '"' + "\",\"".join(ad_ids) + '"') + with open(code_path, 'w') as f: + f.write(code) + + print("修改项目信息") + prj_path = "./build/playbtest/playbtest.xcodeproj/project.pbxproj" + with open(prj_path, 'r') as f: + code = f.read() + code = code.replace("{DisplayName}", app_display_name) + code = code.replace("{BundleId}", app_bundle_id) + code = code.replace("{Version}", app_version) + with open(prj_path, 'w') as f: + f.write(code) + + print("\n开始编译\n") + cmd = """ + cd ./build/playbtest + pod install --repo-update + xcodebuild clean build -workspace playbtest.xcworkspace -configuration Release -scheme playbtest -derivedDataPath "../Target" -destination "platform=iOS,id=00008110-000815AE1179801E" + cd ../../ + """ + subprocess.call(cmd, shell=True) + + + print("\n开始打包\n") + +# codesign --entitlements ./template/Filza.entitlements -f -s "iPhone Distribution: Shumei Luo (T23C6PFSKY)" ./build/ipas/playbtest.app +# find "./build/ipas/playbtest.app" -name "*.framework" -exec codesign -f -s "iPhone Distribution: Shumei Luo (T23C6PFSKY)" {} \; +# find "./build/ipas/playbtest.app" -name "*.dylib" -exec codesign -f -s "iPhone Distribution: Shumei Luo (T23C6PFSKY)" {} \; +# cp ./template/embedded.mobileprovision ./build/ipas/playbtest.app/embedded.mobileprovision +# codesign --entitlements ./template/Filza.entitlements -f -s "iPhone Distribution: Shumei Luo (T23C6PFSKY)" ./build/ipas/playbtest.app + cmd = """ + mkdir ./build/ipas + cp -rf ./build/Target/Build/Products/Release-iphoneos/playbtest.app ./build/ipas/ + + find "./build/ipas/playbtest.app" -name "*.framework" -exec codesign -f -s "Apple Distribution: Haiyang Tang (544LFS79WN)" {} \; + find "./build/ipas/playbtest.app" -name "*.dylib" -exec codesign -f -s "Apple Distribution: Haiyang Tang (544LFS79WN)" {} \; + cp ./template/embedded.mobileprovision ./build/ipas/playbtest.app/embedded.mobileprovision + echo "codesign" + codesign --entitlements ./template/Filza.entitlements -f -s "Apple Distribution: Haiyang Tang (544LFS79WN)" ./build/ipas/playbtest.app + + mkdir ./build/ipas/Payload + mv ./build/ipas/playbtest.app ./build/ipas/Payload + cd ./build/ipas + zip -r playb-max.ipa Payload/ + """ + + subprocess.call(cmd, shell=True) + + + +if __name__ == '__main__': + app_icon = "/Users/mac/codes/ios/empty_topon/ipas/lux/SpeedyColor/appicon.png" + if len(sys.argv) < 2: + ad_key = "Dd37BrtbLDlaeiDhxVzaDbsI67Mc1h5lAGIinzo4v2IbkpufdtVmT5Tag9O3aGexzkS4txEPigaEexktewANIk" + ad_ids = ["7468dbe129ab2afe","7b61678bf643a84a","c6b7477edc2aff7e","28fd7967e71c9203"] + app_display_name = "TimeCompass:Zones" + app_bundle_id = "com.timeCompass.timeCompassZones" + app_version = "1.1" + + build(ad_key, ad_ids, app_display_name, app_bundle_id, app_version, app_icon) + else: + json_path = sys.argv[1] + with open(json_path, 'r') as f: + config = json.loads(f.read()) + + print(config) + + ad_key = config["sdk_key"] + ad_ids = config["ad_ids"] + app_display_name = config["app_name"] + app_bundle_id = config["app_pkg_name"] + app_version = config["app_ver"] + app_icon_tmp:str = config["app_icon"] + if not app_icon_tmp is None and app_icon_tmp.strip() != '': + app_icon = os.path.join(os.path.dirname(json_path), app_icon_tmp) + + build(ad_key, ad_ids, app_display_name, app_bundle_id, app_version, app_icon) + + diff --git a/max/template/Filza.entitlements b/max/template/Filza.entitlements new file mode 100644 index 0000000..f9b6577 --- /dev/null +++ b/max/template/Filza.entitlements @@ -0,0 +1,26 @@ + + + + + application-identifier + 544LFS79WN.com.keycraft.keyCraft + aps-environment + production + com.apple.developer.associated-domains + * + com.apple.developer.team-identifier + 544LFS79WN + com.apple.security.application-groups + + group.com.keycraft.keyCraft.appshortcallid + group.com.keycraft.keyCraft.appcallid + + get-task-allow + + keychain-access-groups + + 544LFS79WN.* + com.apple.token + + + \ No newline at end of file diff --git a/max/template/embedded.mobileprovision b/max/template/embedded.mobileprovision new file mode 100644 index 0000000..fcf7ced Binary files /dev/null and b/max/template/embedded.mobileprovision differ diff --git a/max/template/playbtest/Podfile b/max/template/playbtest/Podfile new file mode 100644 index 0000000..40f4d21 --- /dev/null +++ b/max/template/playbtest/Podfile @@ -0,0 +1,19 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' + +target 'playbtest' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + # Pods for playbtest + + pod 'AppLovinSDK','13.3.1' + pod 'AppLovinDSPLinkedInAdapter' + pod 'AppLovinMediationVungleAdapter' + pod 'AppLovinMediationMintegralAdapter' + +# pod 'Firebase/Core' +# pod 'Firebase/AnalyticsWithoutAdIdSupport' +# pod 'FirebaseRemoteConfig' + +end diff --git a/max/template/playbtest/playbtest.xcodeproj/project.pbxproj b/max/template/playbtest/playbtest.xcodeproj/project.pbxproj new file mode 100644 index 0000000..997059d --- /dev/null +++ b/max/template/playbtest/playbtest.xcodeproj/project.pbxproj @@ -0,0 +1,604 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 046535802D802D9900B2D19B /* bbbAdManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0465357F2D802D9900B2D19B /* bbbAdManager.swift */; }; + 048D61A22D67204900FDE496 /* XUDPClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 048D61A12D67204900FDE496 /* XUDPClient.m */; }; + 048D61A32D67204900FDE496 /* GCDAsyncUdpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 048D619E2D67204900FDE496 /* GCDAsyncUdpSocket.m */; }; + 04C0D91E2D83D5030012DDF5 /* MyTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04C0D91D2D83D5030012DDF5 /* MyTableViewCell.swift */; }; + 751A9FFB2CE1E31700BFC689 /* closeWindows.swift in Sources */ = {isa = PBXBuildFile; fileRef = 751A9FFA2CE1E31700BFC689 /* closeWindows.swift */; }; + 7577E56C2CDA085400B7EDBF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7577E56B2CDA085400B7EDBF /* AppDelegate.swift */; }; + 7577E5702CDA085400B7EDBF /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7577E56F2CDA085400B7EDBF /* ViewController.swift */; }; + 7577E5732CDA085400B7EDBF /* Base in Resources */ = {isa = PBXBuildFile; fileRef = 7577E5722CDA085400B7EDBF /* Base */; }; + 7577E5752CDA085900B7EDBF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7577E5742CDA085900B7EDBF /* Assets.xcassets */; }; + 7577E5782CDA085900B7EDBF /* Base in Resources */ = {isa = PBXBuildFile; fileRef = 7577E5772CDA085900B7EDBF /* Base */; }; + 7577E5892CDA135500B7EDBF /* YL_PlayVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7577E5872CDA135500B7EDBF /* YL_PlayVC.swift */; }; + 7577E58A2CDA135500B7EDBF /* YL_PlayVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7577E5882CDA135500B7EDBF /* YL_PlayVC.xib */; }; + 75B9949E2D23A4DC0046561D /* getIphone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B9949D2D23A4DC0046561D /* getIphone.swift */; }; + 75B994A02D23AED00046561D /* idfa.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B9949F2D23AED00046561D /* idfa.swift */; }; + 75E053D22CDB060E0097DA69 /* YL_NetWorkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75E053D12CDB060E0097DA69 /* YL_NetWorkManager.swift */; }; + 75E053D62CDB07F00097DA69 /* closeAD.m in Sources */ = {isa = PBXBuildFile; fileRef = 75E053D52CDB07F00097DA69 /* closeAD.m */; }; + 97D9032452C29760C7B26B57 /* Pods_playbtest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED1D072A2AFA6387E393A835 /* Pods_playbtest.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 044C8D012E0CECA500625F9A /* Embed Libraries */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Libraries"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0465357F2D802D9900B2D19B /* bbbAdManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = bbbAdManager.swift; sourceTree = ""; }; + 048D619C2D67204900FDE496 /* CocoaAsyncSocket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CocoaAsyncSocket.h; sourceTree = ""; }; + 048D619D2D67204900FDE496 /* GCDAsyncUdpSocket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GCDAsyncUdpSocket.h; sourceTree = ""; }; + 048D619E2D67204900FDE496 /* GCDAsyncUdpSocket.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncUdpSocket.m; sourceTree = ""; }; + 048D61A02D67204900FDE496 /* XUDPClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XUDPClient.h; sourceTree = ""; }; + 048D61A12D67204900FDE496 /* XUDPClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XUDPClient.m; sourceTree = ""; }; + 04C0D91D2D83D5030012DDF5 /* MyTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyTableViewCell.swift; sourceTree = ""; }; + 365725DAEE7A265FA302BB10 /* Pods-playbtest.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-playbtest.release.xcconfig"; path = "Target Support Files/Pods-playbtest/Pods-playbtest.release.xcconfig"; sourceTree = ""; }; + 751A9FFA2CE1E31700BFC689 /* closeWindows.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = closeWindows.swift; sourceTree = ""; }; + 7577E5682CDA085400B7EDBF /* playbtest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = playbtest.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 7577E56B2CDA085400B7EDBF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7577E56F2CDA085400B7EDBF /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 7577E5722CDA085400B7EDBF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 7577E5742CDA085900B7EDBF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 7577E5772CDA085900B7EDBF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 7577E5792CDA085900B7EDBF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 7577E5872CDA135500B7EDBF /* YL_PlayVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YL_PlayVC.swift; sourceTree = ""; }; + 7577E5882CDA135500B7EDBF /* YL_PlayVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = YL_PlayVC.xib; sourceTree = ""; }; + 75B9949D2D23A4DC0046561D /* getIphone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = getIphone.swift; sourceTree = ""; }; + 75B9949F2D23AED00046561D /* idfa.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = idfa.swift; sourceTree = ""; }; + 75E053D12CDB060E0097DA69 /* YL_NetWorkManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YL_NetWorkManager.swift; sourceTree = ""; }; + 75E053D32CDB07F00097DA69 /* playbtest-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "playbtest-Bridging-Header.h"; sourceTree = ""; }; + 75E053D42CDB07F00097DA69 /* closeAD.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = closeAD.h; sourceTree = ""; }; + 75E053D52CDB07F00097DA69 /* closeAD.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = closeAD.m; sourceTree = ""; }; + 75E053D72CDB0D1D0097DA69 /* playb.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = playb.pch; sourceTree = ""; }; + AE60AC68993A5FDD50BCF762 /* Pods-playbtest.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-playbtest.debug.xcconfig"; path = "Target Support Files/Pods-playbtest/Pods-playbtest.debug.xcconfig"; sourceTree = ""; }; + ED1D072A2AFA6387E393A835 /* Pods_playbtest.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_playbtest.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 7577E5652CDA085400B7EDBF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 97D9032452C29760C7B26B57 /* Pods_playbtest.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 048D619F2D67204900FDE496 /* GCD */ = { + isa = PBXGroup; + children = ( + 048D619D2D67204900FDE496 /* GCDAsyncUdpSocket.h */, + 048D619E2D67204900FDE496 /* GCDAsyncUdpSocket.m */, + ); + path = GCD; + sourceTree = ""; + }; + 157CA50DD7C7A6B816F415DB /* Frameworks */ = { + isa = PBXGroup; + children = ( + ED1D072A2AFA6387E393A835 /* Pods_playbtest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 7577E55F2CDA085400B7EDBF = { + isa = PBXGroup; + children = ( + 7577E56A2CDA085400B7EDBF /* playbtest */, + 7577E5692CDA085400B7EDBF /* Products */, + 7D7F1D418E852DCC300E917E /* Pods */, + 157CA50DD7C7A6B816F415DB /* Frameworks */, + ); + sourceTree = ""; + }; + 7577E5692CDA085400B7EDBF /* Products */ = { + isa = PBXGroup; + children = ( + 7577E5682CDA085400B7EDBF /* playbtest.app */, + ); + name = Products; + sourceTree = ""; + }; + 7577E56A2CDA085400B7EDBF /* playbtest */ = { + isa = PBXGroup; + children = ( + 7577E5812CDA0B0800B7EDBF /* PlayB */, + 7577E56B2CDA085400B7EDBF /* AppDelegate.swift */, + 7577E56F2CDA085400B7EDBF /* ViewController.swift */, + 7577E5712CDA085400B7EDBF /* Main.storyboard */, + 7577E5742CDA085900B7EDBF /* Assets.xcassets */, + 7577E5762CDA085900B7EDBF /* LaunchScreen.storyboard */, + 7577E5792CDA085900B7EDBF /* Info.plist */, + ); + path = playbtest; + sourceTree = ""; + }; + 7577E5812CDA0B0800B7EDBF /* PlayB */ = { + isa = PBXGroup; + children = ( + 048D619C2D67204900FDE496 /* CocoaAsyncSocket.h */, + 048D619F2D67204900FDE496 /* GCD */, + 048D61A02D67204900FDE496 /* XUDPClient.h */, + 048D61A12D67204900FDE496 /* XUDPClient.m */, + 75E053D12CDB060E0097DA69 /* YL_NetWorkManager.swift */, + 7577E5872CDA135500B7EDBF /* YL_PlayVC.swift */, + 7577E5882CDA135500B7EDBF /* YL_PlayVC.xib */, + 75E053D42CDB07F00097DA69 /* closeAD.h */, + 75E053D52CDB07F00097DA69 /* closeAD.m */, + 75E053D32CDB07F00097DA69 /* playbtest-Bridging-Header.h */, + 75E053D72CDB0D1D0097DA69 /* playb.pch */, + 751A9FFA2CE1E31700BFC689 /* closeWindows.swift */, + 75B9949D2D23A4DC0046561D /* getIphone.swift */, + 75B9949F2D23AED00046561D /* idfa.swift */, + 0465357F2D802D9900B2D19B /* bbbAdManager.swift */, + 04C0D91D2D83D5030012DDF5 /* MyTableViewCell.swift */, + ); + path = PlayB; + sourceTree = ""; + }; + 7D7F1D418E852DCC300E917E /* Pods */ = { + isa = PBXGroup; + children = ( + AE60AC68993A5FDD50BCF762 /* Pods-playbtest.debug.xcconfig */, + 365725DAEE7A265FA302BB10 /* Pods-playbtest.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 7577E5672CDA085400B7EDBF /* playbtest */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7577E57C2CDA085900B7EDBF /* Build configuration list for PBXNativeTarget "playbtest" */; + buildPhases = ( + 8F58E6E75C52EB4FEEF2490F /* [CP] Check Pods Manifest.lock */, + 7577E5642CDA085400B7EDBF /* Sources */, + 7577E5652CDA085400B7EDBF /* Frameworks */, + 7577E5662CDA085400B7EDBF /* Resources */, + 6E50B8AEC76C920BE22FC478 /* [CP] Embed Pods Frameworks */, + 9E8498E018A09B2E436F82F6 /* [CP] Copy Pods Resources */, + 044C8D012E0CECA500625F9A /* Embed Libraries */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = playbtest; + productName = playbtest; + productReference = 7577E5682CDA085400B7EDBF /* playbtest.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 7577E5602CDA085400B7EDBF /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1530; + LastUpgradeCheck = 1530; + TargetAttributes = { + 7577E5672CDA085400B7EDBF = { + CreatedOnToolsVersion = 15.3; + LastSwiftMigration = 1530; + }; + }; + }; + buildConfigurationList = 7577E5632CDA085400B7EDBF /* Build configuration list for PBXProject "playbtest" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 7577E55F2CDA085400B7EDBF; + productRefGroup = 7577E5692CDA085400B7EDBF /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 7577E5672CDA085400B7EDBF /* playbtest */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 7577E5662CDA085400B7EDBF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7577E5752CDA085900B7EDBF /* Assets.xcassets in Resources */, + 7577E58A2CDA135500B7EDBF /* YL_PlayVC.xib in Resources */, + 7577E5782CDA085900B7EDBF /* Base in Resources */, + 7577E5732CDA085400B7EDBF /* Base in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 6E50B8AEC76C920BE22FC478 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-playbtest/Pods-playbtest-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-playbtest/Pods-playbtest-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-playbtest/Pods-playbtest-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 8F58E6E75C52EB4FEEF2490F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-playbtest-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 9E8498E018A09B2E436F82F6 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-playbtest/Pods-playbtest-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-playbtest/Pods-playbtest-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-playbtest/Pods-playbtest-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 7577E5642CDA085400B7EDBF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 048D61A22D67204900FDE496 /* XUDPClient.m in Sources */, + 048D61A32D67204900FDE496 /* GCDAsyncUdpSocket.m in Sources */, + 75E053D62CDB07F00097DA69 /* closeAD.m in Sources */, + 7577E5702CDA085400B7EDBF /* ViewController.swift in Sources */, + 75B994A02D23AED00046561D /* idfa.swift in Sources */, + 04C0D91E2D83D5030012DDF5 /* MyTableViewCell.swift in Sources */, + 7577E56C2CDA085400B7EDBF /* AppDelegate.swift in Sources */, + 7577E5892CDA135500B7EDBF /* YL_PlayVC.swift in Sources */, + 75B9949E2D23A4DC0046561D /* getIphone.swift in Sources */, + 751A9FFB2CE1E31700BFC689 /* closeWindows.swift in Sources */, + 75E053D22CDB060E0097DA69 /* YL_NetWorkManager.swift in Sources */, + 046535802D802D9900B2D19B /* bbbAdManager.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 7577E5712CDA085400B7EDBF /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 7577E5722CDA085400B7EDBF /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 7577E5762CDA085900B7EDBF /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 7577E5772CDA085900B7EDBF /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 7577E57A2CDA085900B7EDBF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.4; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 7577E57B2CDA085900B7EDBF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.4; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 7577E57D2CDA085900B7EDBF /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AE60AC68993A5FDD50BCF762 /* Pods-playbtest.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGNING_ALLOWED = NO; + CODE_SIGN_IDENTITY = "Don't Code Sign"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = T23C6PFSKY; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + GCC_PREFIX_HEADER = "$(SRCROOT)/playbtest/PlayB/playb.pch"; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = playbtest/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "{DisplayName}"; + INFOPLIST_KEY_NSUserTrackingUsageDescription = "\"\" needs to request tracking permissions to provide a personalized advertising experience. We respect and protect your privacy and will not sell your data to third parties."; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/iphoneos/universal/GNUSparseFile.0", + "$(PROJECT_DIR)/iphoneos/arm64e", + "$(PROJECT_DIR)/iphoneos/arm64", + "$(PROJECT_DIR)/iphoneos/universal", + "$(PROJECT_DIR)", + ); + MARKETING_VERSION = {Version}; + PRODUCT_BUNDLE_IDENTIFIER = {BundleId}; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/playbtest/PlayB/playbtest-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Debug; + }; + 7577E57E2CDA085900B7EDBF /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 365725DAEE7A265FA302BB10 /* Pods-playbtest.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGNING_ALLOWED = NO; + CODE_SIGN_IDENTITY = "Don't Code Sign"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = T23C6PFSKY; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + GCC_PREFIX_HEADER = "$(SRCROOT)/playbtest/PlayB/playb.pch"; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = playbtest/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "{DisplayName}"; + INFOPLIST_KEY_NSUserTrackingUsageDescription = "\"\" needs to request tracking permissions to provide a personalized advertising experience. We respect and protect your privacy and will not sell your data to third parties."; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/iphoneos/universal/GNUSparseFile.0", + "$(PROJECT_DIR)/iphoneos/arm64e", + "$(PROJECT_DIR)/iphoneos/arm64", + "$(PROJECT_DIR)/iphoneos/universal", + "$(PROJECT_DIR)", + ); + MARKETING_VERSION = {Version}; + PRODUCT_BUNDLE_IDENTIFIER = {BundleId}; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/playbtest/PlayB/playbtest-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 7577E5632CDA085400B7EDBF /* Build configuration list for PBXProject "playbtest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7577E57A2CDA085900B7EDBF /* Debug */, + 7577E57B2CDA085900B7EDBF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7577E57C2CDA085900B7EDBF /* Build configuration list for PBXNativeTarget "playbtest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7577E57D2CDA085900B7EDBF /* Debug */, + 7577E57E2CDA085900B7EDBF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 7577E5602CDA085400B7EDBF /* Project object */; +} diff --git a/max/template/playbtest/playbtest.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/max/template/playbtest/playbtest.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/max/template/playbtest/playbtest.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/max/template/playbtest/playbtest.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/max/template/playbtest/playbtest.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/max/template/playbtest/playbtest.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/max/template/playbtest/playbtest.xcodeproj/project.xcworkspace/xcuserdata/yihai16.xcuserdatad/UserInterfaceState.xcuserstate b/max/template/playbtest/playbtest.xcodeproj/project.xcworkspace/xcuserdata/yihai16.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..efcff92 Binary files /dev/null and b/max/template/playbtest/playbtest.xcodeproj/project.xcworkspace/xcuserdata/yihai16.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/max/template/playbtest/playbtest.xcodeproj/xcshareddata/xcschemes/playbtest.xcscheme b/max/template/playbtest/playbtest.xcodeproj/xcshareddata/xcschemes/playbtest.xcscheme new file mode 100644 index 0000000..8a77293 --- /dev/null +++ b/max/template/playbtest/playbtest.xcodeproj/xcshareddata/xcschemes/playbtest.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/max/template/playbtest/playbtest.xcodeproj/xcuserdata/mac.xcuserdatad/xcschemes/xcschememanagement.plist b/max/template/playbtest/playbtest.xcodeproj/xcuserdata/mac.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..372eae3 --- /dev/null +++ b/max/template/playbtest/playbtest.xcodeproj/xcuserdata/mac.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + playbtest.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/max/template/playbtest/playbtest.xcodeproj/xcuserdata/yihai16.xcuserdatad/xcschemes/xcschememanagement.plist b/max/template/playbtest/playbtest.xcodeproj/xcuserdata/yihai16.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..e39c1f4 --- /dev/null +++ b/max/template/playbtest/playbtest.xcodeproj/xcuserdata/yihai16.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,22 @@ + + + + + SchemeUserState + + playbtest.xcscheme_^#shared#^_ + + orderHint + 32 + + + SuppressBuildableAutocreation + + 7577E5672CDA085400B7EDBF + + primary + + + + + diff --git a/max/template/playbtest/playbtest.xcworkspace/contents.xcworkspacedata b/max/template/playbtest/playbtest.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..85025c3 --- /dev/null +++ b/max/template/playbtest/playbtest.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/max/template/playbtest/playbtest.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/max/template/playbtest/playbtest.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/max/template/playbtest/playbtest.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/max/template/playbtest/playbtest.xcworkspace/xcuserdata/mac.xcuserdatad/UserInterfaceState.xcuserstate b/max/template/playbtest/playbtest.xcworkspace/xcuserdata/mac.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..fe7907a Binary files /dev/null and b/max/template/playbtest/playbtest.xcworkspace/xcuserdata/mac.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/max/template/playbtest/playbtest.xcworkspace/xcuserdata/mac.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/max/template/playbtest/playbtest.xcworkspace/xcuserdata/mac.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..a5a1e6d --- /dev/null +++ b/max/template/playbtest/playbtest.xcworkspace/xcuserdata/mac.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/max/template/playbtest/playbtest.xcworkspace/xcuserdata/yihai16.xcuserdatad/UserInterfaceState.xcuserstate b/max/template/playbtest/playbtest.xcworkspace/xcuserdata/yihai16.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..295e189 Binary files /dev/null and b/max/template/playbtest/playbtest.xcworkspace/xcuserdata/yihai16.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/max/template/playbtest/playbtest.xcworkspace/xcuserdata/yihai16.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/max/template/playbtest/playbtest.xcworkspace/xcuserdata/yihai16.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..6369b06 --- /dev/null +++ b/max/template/playbtest/playbtest.xcworkspace/xcuserdata/yihai16.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/max/template/playbtest/playbtest/AppDelegate.swift b/max/template/playbtest/playbtest/AppDelegate.swift new file mode 100644 index 0000000..801d6fe --- /dev/null +++ b/max/template/playbtest/playbtest/AppDelegate.swift @@ -0,0 +1,46 @@ +// +// AppDelegate.swift +// playbtest +// +// Created by 忆海16 on 2024/11/5. +// + +import UIKit +import AppLovinSDK + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + static var shared: AppDelegate { + return UIApplication.shared.delegate as! AppDelegate + } + var window:UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + + window = UIWindow() + window?.frame = UIScreen.main.bounds + let vc = YL_PlayVC() + + window?.rootViewController = UINavigationController(rootViewController: vc) + + BbbAdManager.shared.initConfig() + + BbbAdManager.shared.initAd() + + + window?.makeKeyAndVisible() + + return true + } + + func applicationWillEnterForeground(_ application: UIApplication) { + print("应用从后台进入前台") +// closeWindows.removeADVCByDelayTime(0) + closeAD.removeADVC(byDelayTime: 0) + + } + + +} + diff --git a/max/template/playbtest/playbtest/Assets.xcassets/AccentColor.colorset/Contents.json b/max/template/playbtest/playbtest/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/max/template/playbtest/playbtest/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/max/template/playbtest/playbtest/Assets.xcassets/AppIcon.appiconset/1024x1024.png b/max/template/playbtest/playbtest/Assets.xcassets/AppIcon.appiconset/1024x1024.png new file mode 100644 index 0000000..856435e Binary files /dev/null and b/max/template/playbtest/playbtest/Assets.xcassets/AppIcon.appiconset/1024x1024.png differ diff --git a/max/template/playbtest/playbtest/Assets.xcassets/AppIcon.appiconset/Contents.json b/max/template/playbtest/playbtest/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..dc869d4 --- /dev/null +++ b/max/template/playbtest/playbtest/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "1024x1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/max/template/playbtest/playbtest/Assets.xcassets/Contents.json b/max/template/playbtest/playbtest/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/max/template/playbtest/playbtest/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/max/template/playbtest/playbtest/Base.lproj/LaunchScreen.storyboard b/max/template/playbtest/playbtest/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/max/template/playbtest/playbtest/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/max/template/playbtest/playbtest/Base.lproj/Main.storyboard b/max/template/playbtest/playbtest/Base.lproj/Main.storyboard new file mode 100644 index 0000000..25a7638 --- /dev/null +++ b/max/template/playbtest/playbtest/Base.lproj/Main.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/max/template/playbtest/playbtest/Info.plist b/max/template/playbtest/playbtest/Info.plist new file mode 100644 index 0000000..46d101b --- /dev/null +++ b/max/template/playbtest/playbtest/Info.plist @@ -0,0 +1,507 @@ + + + + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + SKAdNetworkItems + + + SKAdNetworkIdentifier + 22mmun2rn5.skadnetwork + + SKAdNetworkIdentifier + 238da6jt44.skadnetwork + + SKAdNetworkIdentifier + 24t9a8vw3c.skadnetwork + + SKAdNetworkIdentifier + 24zw6aqk47.skadnetwork + + SKAdNetworkIdentifier + 252b5q8x7y.skadnetwork + + SKAdNetworkIdentifier + 275upjj5gd.skadnetwork + + SKAdNetworkIdentifier + 294l99pt4k.skadnetwork + + SKAdNetworkIdentifier + 2fnua5tdw4.skadnetwork + + SKAdNetworkIdentifier + 2u9pt9hc89.skadnetwork + + SKAdNetworkIdentifier + 32z4fx6l9h.skadnetwork + + SKAdNetworkIdentifier + 3l6bd9hu43.skadnetwork + + SKAdNetworkIdentifier + 3qcr597p9d.skadnetwork + + SKAdNetworkIdentifier + 3qy4746246.skadnetwork + + SKAdNetworkIdentifier + 3rd42ekr43.skadnetwork + + SKAdNetworkIdentifier + 3sh42y64q3.skadnetwork + + SKAdNetworkIdentifier + 424m5254lk.skadnetwork + + SKAdNetworkIdentifier + 4468km3ulz.skadnetwork + + SKAdNetworkIdentifier + 44jx6755aq.skadnetwork + + SKAdNetworkIdentifier + 44n7hlldy6.skadnetwork + + SKAdNetworkIdentifier + 47vhws6wlr.skadnetwork + + SKAdNetworkIdentifier + 488r3q3dtq.skadnetwork + + SKAdNetworkIdentifier + 4dzt52r2t5.skadnetwork + + SKAdNetworkIdentifier + 4fzdc2evr5.skadnetwork + + SKAdNetworkIdentifier + 4mn522wn87.skadnetwork + + SKAdNetworkIdentifier + 4pfyvq9l8r.skadnetwork + + SKAdNetworkIdentifier + 4w7y6s5ca2.skadnetwork + + SKAdNetworkIdentifier + 523jb4fst2.skadnetwork + + SKAdNetworkIdentifier + 52fl2v3hgk.skadnetwork + + SKAdNetworkIdentifier + 54nzkqm89y.skadnetwork + + SKAdNetworkIdentifier + 55644vm79v.skadnetwork + + SKAdNetworkIdentifier + 55y65gfgn7.skadnetwork + + SKAdNetworkIdentifier + 578prtvx9j.skadnetwork + + SKAdNetworkIdentifier + 5a6flpkh64.skadnetwork + + SKAdNetworkIdentifier + 5l3tpt7t6e.skadnetwork + + SKAdNetworkIdentifier + 5lm9lj6jb7.skadnetwork + + SKAdNetworkIdentifier + 5tjdwbrq8w.skadnetwork + + SKAdNetworkIdentifier + 6964rsfnh4.skadnetwork + + SKAdNetworkIdentifier + 6g9af3uyq4.skadnetwork + + SKAdNetworkIdentifier + 6p4ks3rnbw.skadnetwork + + SKAdNetworkIdentifier + 6v7lgmsu45.skadnetwork + + SKAdNetworkIdentifier + 6xzpu9s2p8.skadnetwork + + SKAdNetworkIdentifier + 6yxyv74ff7.skadnetwork + + SKAdNetworkIdentifier + 737z793b9f.skadnetwork + + SKAdNetworkIdentifier + 74b6s63p6l.skadnetwork + + SKAdNetworkIdentifier + 7953jerfzd.skadnetwork + + SKAdNetworkIdentifier + 79pbpufp6p.skadnetwork + + SKAdNetworkIdentifier + 7fmhfwg9en.skadnetwork + + SKAdNetworkIdentifier + 7rz58n8ntl.skadnetwork + + SKAdNetworkIdentifier + 7ug5zh24hu.skadnetwork + + SKAdNetworkIdentifier + 84993kbrcf.skadnetwork + + SKAdNetworkIdentifier + 87u5trcl3r.skadnetwork + + SKAdNetworkIdentifier + 89z7zv988g.skadnetwork + + SKAdNetworkIdentifier + 8c4e2ghe7u.skadnetwork + + SKAdNetworkIdentifier + 8m87ys6875.skadnetwork + + SKAdNetworkIdentifier + 8r8llnkz5a.skadnetwork + + SKAdNetworkIdentifier + 8s468mfl3y.skadnetwork + + SKAdNetworkIdentifier + 97r2b46745.skadnetwork + + SKAdNetworkIdentifier + 9b89h5y424.skadnetwork + + SKAdNetworkIdentifier + 9nlqeag3gk.skadnetwork + + SKAdNetworkIdentifier + 9rd848q2bz.skadnetwork + + SKAdNetworkIdentifier + 9t245vhmpl.skadnetwork + + SKAdNetworkIdentifier + 9vvzujtq5s.skadnetwork + + SKAdNetworkIdentifier + 9yg77x724h.skadnetwork + + SKAdNetworkIdentifier + a2p9lx4jpn.skadnetwork + + SKAdNetworkIdentifier + a7xqa6mtl2.skadnetwork + + SKAdNetworkIdentifier + a8cz6cu7e5.skadnetwork + + SKAdNetworkIdentifier + av6w8kgt66.skadnetwork + + SKAdNetworkIdentifier + b9bk5wbcq9.skadnetwork + + SKAdNetworkIdentifier + bvpn9ufa9b.skadnetwork + + SKAdNetworkIdentifier + bxvub5ada5.skadnetwork + + SKAdNetworkIdentifier + c3frkrj4fj.skadnetwork + + SKAdNetworkIdentifier + c6k4g5qg8m.skadnetwork + + SKAdNetworkIdentifier + cg4yq2srnc.skadnetwork + + SKAdNetworkIdentifier + cj5566h2ga.skadnetwork + + SKAdNetworkIdentifier + cp8zw746q7.skadnetwork + + SKAdNetworkIdentifier + cs644xg564.skadnetwork + + SKAdNetworkIdentifier + cstr6suwn9.skadnetwork + + SKAdNetworkIdentifier + cwn433xbcr.skadnetwork + + SKAdNetworkIdentifier + dbu4b84rxf.skadnetwork + + SKAdNetworkIdentifier + dkc879ngq3.skadnetwork + + SKAdNetworkIdentifier + dzg6xy7pwj.skadnetwork + + SKAdNetworkIdentifier + e5fvkxwrpn.skadnetwork + + SKAdNetworkIdentifier + ecpz2srf59.skadnetwork + + SKAdNetworkIdentifier + eh6m2bh4zr.skadnetwork + + SKAdNetworkIdentifier + ejvt5qm6ak.skadnetwork + + SKAdNetworkIdentifier + f38h382jlk.skadnetwork + + SKAdNetworkIdentifier + f73kdq92p3.skadnetwork + + SKAdNetworkIdentifier + f7s53z58qe.skadnetwork + + SKAdNetworkIdentifier + feyaarzu9v.skadnetwork + + SKAdNetworkIdentifier + fq6vru337s.skadnetwork + + SKAdNetworkIdentifier + fz2k2k5tej.skadnetwork + + SKAdNetworkIdentifier + g28c52eehv.skadnetwork + + SKAdNetworkIdentifier + g2y4y55b64.skadnetwork + + SKAdNetworkIdentifier + g6gcrrvk4p.skadnetwork + + SKAdNetworkIdentifier + ggvn48r87g.skadnetwork + + SKAdNetworkIdentifier + glqzh8vgby.skadnetwork + + SKAdNetworkIdentifier + gta8lk7p23.skadnetwork + + SKAdNetworkIdentifier + gta9lk7p23.skadnetwork + + SKAdNetworkIdentifier + hb56zgv37p.skadnetwork + + SKAdNetworkIdentifier + hdw39hrw9y.skadnetwork + + SKAdNetworkIdentifier + hs6bdukanm.skadnetwork + + SKAdNetworkIdentifier + k674qkevps.skadnetwork + + SKAdNetworkIdentifier + kbd757ywx3.skadnetwork + + SKAdNetworkIdentifier + kbmxgpxpgc.skadnetwork + + SKAdNetworkIdentifier + klf5c3l5u5.skadnetwork + + SKAdNetworkIdentifier + krvm3zuq6h.skadnetwork + + SKAdNetworkIdentifier + lr83yxwka7.skadnetwork + + SKAdNetworkIdentifier + ludvb6z3bs.skadnetwork + + SKAdNetworkIdentifier + m297p6643m.skadnetwork + + SKAdNetworkIdentifier + m5mvw97r93.skadnetwork + + SKAdNetworkIdentifier + m8dbw4sv7c.skadnetwork + + SKAdNetworkIdentifier + mj797d8u6f.skadnetwork + + SKAdNetworkIdentifier + mlmmfzh3r3.skadnetwork + + SKAdNetworkIdentifier + mls7yz5dvl.skadnetwork + + SKAdNetworkIdentifier + mp6xlyr22a.skadnetwork + + SKAdNetworkIdentifier + mqn7fxpca7.skadnetwork + + SKAdNetworkIdentifier + mtkv5xtk9e.skadnetwork + + SKAdNetworkIdentifier + n38lu8286q.skadnetwork + + SKAdNetworkIdentifier + n66cz3y3bx.skadnetwork + + SKAdNetworkIdentifier + n6fk4nfna4.skadnetwork + + SKAdNetworkIdentifier + n9x2a789qt.skadnetwork + + SKAdNetworkIdentifier + ns5j362hk7.skadnetwork + + SKAdNetworkIdentifier + nzq8sh4pbs.skadnetwork + + SKAdNetworkIdentifier + p78axxw29g.skadnetwork + + SKAdNetworkIdentifier + ppxm28t8ap.skadnetwork + + SKAdNetworkIdentifier + prcb7njmu6.skadnetwork + + SKAdNetworkIdentifier + pwa73g5rt2.skadnetwork + + SKAdNetworkIdentifier + pwdxu55a5a.skadnetwork + + SKAdNetworkIdentifier + qqp299437r.skadnetwork + + SKAdNetworkIdentifier + qu637u8glc.skadnetwork + + SKAdNetworkIdentifier + r45fhb6rf7.skadnetwork + + SKAdNetworkIdentifier + rvh3l7un93.skadnetwork + + SKAdNetworkIdentifier + rx5hdcabgc.skadnetwork + + SKAdNetworkIdentifier + s39g8k73mm.skadnetwork + + SKAdNetworkIdentifier + s69wq72ugq.skadnetwork + + SKAdNetworkIdentifier + su67r6k2v3.skadnetwork + + SKAdNetworkIdentifier + t38b2kh725.skadnetwork + + SKAdNetworkIdentifier + t6d3zquu66.skadnetwork + + SKAdNetworkIdentifier + tl55sbb4fm.skadnetwork + + SKAdNetworkIdentifier + tmhh9296z4.skadnetwork + + SKAdNetworkIdentifier + u679fj5vs4.skadnetwork + + SKAdNetworkIdentifier + uw77j35x4d.skadnetwork + + SKAdNetworkIdentifier + v4nxqhlyqp.skadnetwork + + SKAdNetworkIdentifier + v72qych5uu.skadnetwork + + SKAdNetworkIdentifier + v79kvwwj4g.skadnetwork + + SKAdNetworkIdentifier + v9wttpbfk9.skadnetwork + + SKAdNetworkIdentifier + vcra2ehyfk.skadnetwork + + SKAdNetworkIdentifier + vhf287vqwu.skadnetwork + + SKAdNetworkIdentifier + vutu7akeur.skadnetwork + + SKAdNetworkIdentifier + w9q455wk68.skadnetwork + + SKAdNetworkIdentifier + wg4vff78zm.skadnetwork + + SKAdNetworkIdentifier + wzmmz9fp6w.skadnetwork + + SKAdNetworkIdentifier + x44k69ngh6.skadnetwork + + SKAdNetworkIdentifier + x5l83yy675.skadnetwork + + SKAdNetworkIdentifier + x8jxxk4ff5.skadnetwork + + SKAdNetworkIdentifier + x8uqf25wch.skadnetwork + + SKAdNetworkIdentifier + xga6mpmplv.skadnetwork + + SKAdNetworkIdentifier + xy9t38ct57.skadnetwork + + SKAdNetworkIdentifier + y45688jllp.skadnetwork + + SKAdNetworkIdentifier + y5ghdn5j9k.skadnetwork + + SKAdNetworkIdentifier + yclnxrl5pm.skadnetwork + + SKAdNetworkIdentifier + ydx93a7ass.skadnetwork + + SKAdNetworkIdentifier + zmvfpc5aq8.skadnetwork + + SKAdNetworkIdentifier + zq492l623r.skadnetwork + + + + diff --git a/max/template/playbtest/playbtest/PlayB/CocoaAsyncSocket.h b/max/template/playbtest/playbtest/PlayB/CocoaAsyncSocket.h new file mode 100755 index 0000000..b7ac3b3 --- /dev/null +++ b/max/template/playbtest/playbtest/PlayB/CocoaAsyncSocket.h @@ -0,0 +1,19 @@ +// +// CocoaAsyncSocket.h +// CocoaAsyncSocket +// +// Created by Derek Clarkson on 10/08/2015. +// CocoaAsyncSocket project is in the public domain. +// + +#import + +//! Project version number for CocoaAsyncSocket. +FOUNDATION_EXPORT double cocoaAsyncSocketVersionNumber; + +//! Project version string for CocoaAsyncSocket. +FOUNDATION_EXPORT const unsigned char cocoaAsyncSocketVersionString[]; + + +#import "GCD/GCDAsyncUdpSocket.h" + diff --git a/max/template/playbtest/playbtest/PlayB/GCD/GCDAsyncUdpSocket.h b/max/template/playbtest/playbtest/PlayB/GCD/GCDAsyncUdpSocket.h new file mode 100755 index 0000000..af327e0 --- /dev/null +++ b/max/template/playbtest/playbtest/PlayB/GCD/GCDAsyncUdpSocket.h @@ -0,0 +1,1036 @@ +// +// GCDAsyncUdpSocket +// +// This class is in the public domain. +// Originally created by Robbie Hanson of Deusty LLC. +// Updated and maintained by Deusty LLC and the Apple development community. +// +// https://github.com/robbiehanson/CocoaAsyncSocket +// + +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN +extern NSString *const GCDAsyncUdpSocketException; +extern NSString *const GCDAsyncUdpSocketErrorDomain; + +extern NSString *const GCDAsyncUdpSocketQueueName; +extern NSString *const GCDAsyncUdpSocketThreadName; + +typedef NS_ERROR_ENUM(GCDAsyncUdpSocketErrorDomain, GCDAsyncUdpSocketError) { + GCDAsyncUdpSocketNoError = 0, // Never used + GCDAsyncUdpSocketBadConfigError, // Invalid configuration + GCDAsyncUdpSocketBadParamError, // Invalid parameter was passed + GCDAsyncUdpSocketSendTimeoutError, // A send operation timed out + GCDAsyncUdpSocketClosedError, // The socket was closed + GCDAsyncUdpSocketOtherError, // Description provided in userInfo +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@class GCDAsyncUdpSocket; + +@protocol GCDAsyncUdpSocketDelegate +@optional + +/** + * By design, UDP is a connectionless protocol, and connecting is not needed. + * However, you may optionally choose to connect to a particular host for reasons + * outlined in the documentation for the various connect methods listed above. + * + * This method is called if one of the connect methods are invoked, and the connection is successful. +**/ +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address; + +/** + * By design, UDP is a connectionless protocol, and connecting is not needed. + * However, you may optionally choose to connect to a particular host for reasons + * outlined in the documentation for the various connect methods listed above. + * + * This method is called if one of the connect methods are invoked, and the connection fails. + * This may happen, for example, if a domain name is given for the host and the domain name is unable to be resolved. +**/ +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError * _Nullable)error; + +/** + * Called when the datagram with the given tag has been sent. +**/ +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag; + +/** + * Called if an error occurs while trying to send a datagram. + * This could be due to a timeout, or something more serious such as the data being too large to fit in a sigle packet. +**/ +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError * _Nullable)error; + +/** + * Called when the socket has received the requested datagram. +**/ +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data + fromAddress:(NSData *)address + withFilterContext:(nullable id)filterContext; + +/** + * Called when the socket is closed. +**/ +- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError * _Nullable)error; + +@end + +/** + * You may optionally set a receive filter for the socket. + * A filter can provide several useful features: + * + * 1. Many times udp packets need to be parsed. + * Since the filter can run in its own independent queue, you can parallelize this parsing quite easily. + * The end result is a parallel socket io, datagram parsing, and packet processing. + * + * 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited. + * The filter can prevent such packets from arriving at the delegate. + * And because the filter can run in its own independent queue, this doesn't slow down the delegate. + * + * - Since the udp protocol does not guarantee delivery, udp packets may be lost. + * Many protocols built atop udp thus provide various resend/re-request algorithms. + * This sometimes results in duplicate packets arriving. + * A filter may allow you to architect the duplicate detection code to run in parallel to normal processing. + * + * - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive. + * Such packets need to be ignored. + * + * 3. Sometimes traffic shapers are needed to simulate real world environments. + * A filter allows you to write custom code to simulate such environments. + * The ability to code this yourself is especially helpful when your simulated environment + * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), + * or the system tools to handle this aren't available (e.g. on a mobile device). + * + * @param data - The packet that was received. + * @param address - The address the data was received from. + * See utilities section for methods to extract info from address. + * @param context - Out parameter you may optionally set, which will then be passed to the delegate method. + * For example, filter block can parse the data and then, + * pass the parsed data to the delegate. + * + * @returns - YES if the received packet should be passed onto the delegate. + * NO if the received packet should be discarded, and not reported to the delegete. + * + * Example: + * + * GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) { + * + * MyProtocolMessage *msg = [MyProtocol parseMessage:data]; + * + * *context = response; + * return (response != nil); + * }; + * [udpSocket setReceiveFilter:filter withQueue:myParsingQueue]; + * +**/ +typedef BOOL (^GCDAsyncUdpSocketReceiveFilterBlock)(NSData *data, NSData *address, id __nullable * __nonnull context); + +/** + * You may optionally set a send filter for the socket. + * A filter can provide several interesting possibilities: + * + * 1. Optional caching of resolved addresses for domain names. + * The cache could later be consulted, resulting in fewer system calls to getaddrinfo. + * + * 2. Reusable modules of code for bandwidth monitoring. + * + * 3. Sometimes traffic shapers are needed to simulate real world environments. + * A filter allows you to write custom code to simulate such environments. + * The ability to code this yourself is especially helpful when your simulated environment + * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), + * or the system tools to handle this aren't available (e.g. on a mobile device). + * + * @param data - The packet that was received. + * @param address - The address the data was received from. + * See utilities section for methods to extract info from address. + * @param tag - The tag that was passed in the send method. + * + * @returns - YES if the packet should actually be sent over the socket. + * NO if the packet should be silently dropped (not sent over the socket). + * + * Regardless of the return value, the delegate will be informed that the packet was successfully sent. + * +**/ +typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, long tag); + + +@interface GCDAsyncUdpSocket : NSObject + +/** + * GCDAsyncUdpSocket uses the standard delegate paradigm, + * but executes all delegate callbacks on a given delegate dispatch queue. + * This allows for maximum concurrency, while at the same time providing easy thread safety. + * + * You MUST set a delegate AND delegate dispatch queue before attempting to + * use the socket, or you will get an error. + * + * The socket queue is optional. + * If you pass NULL, GCDAsyncSocket will automatically create its own socket queue. + * If you choose to provide a socket queue, the socket queue must not be a concurrent queue, + * then please see the discussion for the method markSocketQueueTargetQueue. + * + * The delegate queue and socket queue can optionally be the same. +**/ +- (instancetype)init; +- (instancetype)initWithSocketQueue:(nullable dispatch_queue_t)sq; +- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq; +- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq NS_DESIGNATED_INITIALIZER; + +#pragma mark Configuration + +- (nullable id)delegate; +- (void)setDelegate:(nullable id)delegate; +- (void)synchronouslySetDelegate:(nullable id)delegate; + +- (nullable dispatch_queue_t)delegateQueue; +- (void)setDelegateQueue:(nullable dispatch_queue_t)delegateQueue; +- (void)synchronouslySetDelegateQueue:(nullable dispatch_queue_t)delegateQueue; + +- (void)getDelegate:(id __nullable * __nullable)delegatePtr delegateQueue:(dispatch_queue_t __nullable * __nullable)delegateQueuePtr; +- (void)setDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; +- (void)synchronouslySetDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; + +/** + * By default, both IPv4 and IPv6 are enabled. + * + * This means GCDAsyncUdpSocket automatically supports both protocols, + * and can send to IPv4 or IPv6 addresses, + * as well as receive over IPv4 and IPv6. + * + * For operations that require DNS resolution, GCDAsyncUdpSocket supports both IPv4 and IPv6. + * If a DNS lookup returns only IPv4 results, GCDAsyncUdpSocket will automatically use IPv4. + * If a DNS lookup returns only IPv6 results, GCDAsyncUdpSocket will automatically use IPv6. + * If a DNS lookup returns both IPv4 and IPv6 results, then the protocol used depends on the configured preference. + * If IPv4 is preferred, then IPv4 is used. + * If IPv6 is preferred, then IPv6 is used. + * If neutral, then the first IP version in the resolved array will be used. + * + * Starting with Mac OS X 10.7 Lion and iOS 5, the default IP preference is neutral. + * On prior systems the default IP preference is IPv4. + **/ +- (BOOL)isIPv4Enabled; +- (void)setIPv4Enabled:(BOOL)flag; + +- (BOOL)isIPv6Enabled; +- (void)setIPv6Enabled:(BOOL)flag; + +- (BOOL)isIPv4Preferred; +- (BOOL)isIPv6Preferred; +- (BOOL)isIPVersionNeutral; + +- (void)setPreferIPv4; +- (void)setPreferIPv6; +- (void)setIPVersionNeutral; + +/** + * Gets/Sets the maximum size of the buffer that will be allocated for receive operations. + * The default maximum size is 65535 bytes. + * + * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535. + * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295. + * + * Since the OS/GCD notifies us of the size of each received UDP packet, + * the actual allocated buffer size for each packet is exact. + * And in practice the size of UDP packets is generally much smaller than the max. + * Indeed most protocols will send and receive packets of only a few bytes, + * or will set a limit on the size of packets to prevent fragmentation in the IP layer. + * + * If you set the buffer size too small, the sockets API in the OS will silently discard + * any extra data, and you will not be notified of the error. +**/ +- (uint16_t)maxReceiveIPv4BufferSize; +- (void)setMaxReceiveIPv4BufferSize:(uint16_t)max; + +- (uint32_t)maxReceiveIPv6BufferSize; +- (void)setMaxReceiveIPv6BufferSize:(uint32_t)max; + +/** + * Gets/Sets the maximum size of the buffer that will be allocated for send operations. + * The default maximum size is 65535 bytes. + * + * Given that a typical link MTU is 1500 bytes, a large UDP datagram will have to be + * fragmented, and that’s both expensive and risky (if one fragment goes missing, the + * entire datagram is lost). You are much better off sending a large number of smaller + * UDP datagrams, preferably using a path MTU algorithm to avoid fragmentation. + * + * You must set it before the sockt is created otherwise it won't work. + * + **/ +- (uint16_t)maxSendBufferSize; +- (void)setMaxSendBufferSize:(uint16_t)max; + +/** + * User data allows you to associate arbitrary information with the socket. + * This data is not used internally in any way. +**/ +- (nullable id)userData; +- (void)setUserData:(nullable id)arbitraryUserData; + +#pragma mark Diagnostics + +/** + * Returns the local address info for the socket. + * + * The localAddress method returns a sockaddr structure wrapped in a NSData object. + * The localHost method returns the human readable IP address as a string. + * + * Note: Address info may not be available until after the socket has been binded, connected + * or until after data has been sent. +**/ +- (nullable NSData *)localAddress; +- (nullable NSString *)localHost; +- (uint16_t)localPort; + +- (nullable NSData *)localAddress_IPv4; +- (nullable NSString *)localHost_IPv4; +- (uint16_t)localPort_IPv4; + +- (nullable NSData *)localAddress_IPv6; +- (nullable NSString *)localHost_IPv6; +- (uint16_t)localPort_IPv6; + +/** + * Returns the remote address info for the socket. + * + * The connectedAddress method returns a sockaddr structure wrapped in a NSData object. + * The connectedHost method returns the human readable IP address as a string. + * + * Note: Since UDP is connectionless by design, connected address info + * will not be available unless the socket is explicitly connected to a remote host/port. + * If the socket is not connected, these methods will return nil / 0. +**/ +- (nullable NSData *)connectedAddress; +- (nullable NSString *)connectedHost; +- (uint16_t)connectedPort; + +/** + * Returns whether or not this socket has been connected to a single host. + * By design, UDP is a connectionless protocol, and connecting is not needed. + * If connected, the socket will only be able to send/receive data to/from the connected host. +**/ +- (BOOL)isConnected; + +/** + * Returns whether or not this socket has been closed. + * The only way a socket can be closed is if you explicitly call one of the close methods. +**/ +- (BOOL)isClosed; + +/** + * Returns whether or not this socket is IPv4. + * + * By default this will be true, unless: + * - IPv4 is disabled (via setIPv4Enabled:) + * - The socket is explicitly bound to an IPv6 address + * - The socket is connected to an IPv6 address +**/ +- (BOOL)isIPv4; + +/** + * Returns whether or not this socket is IPv6. + * + * By default this will be true, unless: + * - IPv6 is disabled (via setIPv6Enabled:) + * - The socket is explicitly bound to an IPv4 address + * _ The socket is connected to an IPv4 address + * + * This method will also return false on platforms that do not support IPv6. + * Note: The iPhone does not currently support IPv6. +**/ +- (BOOL)isIPv6; + +#pragma mark Binding + +/** + * Binds the UDP socket to the given port. + * Binding should be done for server sockets that receive data prior to sending it. + * Client sockets can skip binding, + * as the OS will automatically assign the socket an available port when it starts sending data. + * + * You may optionally pass a port number of zero to immediately bind the socket, + * yet still allow the OS to automatically assign an available port. + * + * You cannot bind a socket after its been connected. + * You can only bind a socket once. + * You can still connect a socket (if desired) after binding. + * + * On success, returns YES. + * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr. +**/ +- (BOOL)bindToPort:(uint16_t)port error:(NSError **)errPtr; + +/** + * Binds the UDP socket to the given port and optional interface. + * Binding should be done for server sockets that receive data prior to sending it. + * Client sockets can skip binding, + * as the OS will automatically assign the socket an available port when it starts sending data. + * + * You may optionally pass a port number of zero to immediately bind the socket, + * yet still allow the OS to automatically assign an available port. + * + * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). + * You may also use the special strings "localhost" or "loopback" to specify that + * the socket only accept packets from the local machine. + * + * You cannot bind a socket after its been connected. + * You can only bind a socket once. + * You can still connect a socket (if desired) after binding. + * + * On success, returns YES. + * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr. +**/ +- (BOOL)bindToPort:(uint16_t)port interface:(nullable NSString *)interface error:(NSError **)errPtr; + +/** + * Binds the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object. + * + * If you have an existing struct sockaddr you can convert it to a NSData object like so: + * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; + * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; + * + * Binding should be done for server sockets that receive data prior to sending it. + * Client sockets can skip binding, + * as the OS will automatically assign the socket an available port when it starts sending data. + * + * You cannot bind a socket after its been connected. + * You can only bind a socket once. + * You can still connect a socket (if desired) after binding. + * + * On success, returns YES. + * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr. +**/ +- (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr; + +#pragma mark Connecting + +/** + * Connects the UDP socket to the given host and port. + * By design, UDP is a connectionless protocol, and connecting is not needed. + * + * Choosing to connect to a specific host/port has the following effect: + * - You will only be able to send data to the connected host/port. + * - You will only be able to receive data from the connected host/port. + * - You will receive ICMP messages that come from the connected host/port, such as "connection refused". + * + * The actual process of connecting a UDP socket does not result in any communication on the socket. + * It simply changes the internal state of the socket. + * + * You cannot bind a socket after it has been connected. + * You can only connect a socket once. + * + * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2"). + * + * This method is asynchronous as it requires a DNS lookup to resolve the given host name. + * If an obvious error is detected, this method immediately returns NO and sets errPtr. + * If you don't care about the error, you can pass nil for errPtr. + * Otherwise, this method returns YES and begins the asynchronous connection process. + * The result of the asynchronous connection process will be reported via the delegate methods. + **/ +- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr; + +/** + * Connects the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object. + * + * If you have an existing struct sockaddr you can convert it to a NSData object like so: + * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; + * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; + * + * By design, UDP is a connectionless protocol, and connecting is not needed. + * + * Choosing to connect to a specific address has the following effect: + * - You will only be able to send data to the connected address. + * - You will only be able to receive data from the connected address. + * - You will receive ICMP messages that come from the connected address, such as "connection refused". + * + * Connecting a UDP socket does not result in any communication on the socket. + * It simply changes the internal state of the socket. + * + * You cannot bind a socket after its been connected. + * You can only connect a socket once. + * + * On success, returns YES. + * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. + * + * Note: Unlike the connectToHost:onPort:error: method, this method does not require a DNS lookup. + * Thus when this method returns, the connection has either failed or fully completed. + * In other words, this method is synchronous, unlike the asynchronous connectToHost::: method. + * However, for compatibility and simplification of delegate code, if this method returns YES + * then the corresponding delegate method (udpSocket:didConnectToHost:port:) is still invoked. +**/ +- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; + +#pragma mark Multicast + +/** + * Join multicast group. + * Group should be an IP address (eg @"225.228.0.1"). + * + * On success, returns YES. + * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. +**/ +- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr; + +/** + * Join multicast group. + * Group should be an IP address (eg @"225.228.0.1"). + * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). + * + * On success, returns YES. + * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. +**/ +- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr; + +- (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr; +- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr; + +/** + * Send multicast on a specified interface. + * For IPv4, interface should be the the IP address of the interface (eg @"192.168.10.1"). + * For IPv6, interface should be the a network interface name (eg @"en0"). + * + * On success, returns YES. + * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. +**/ + +- (BOOL)sendIPv4MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr; +- (BOOL)sendIPv6MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr; + +#pragma mark Reuse Port + +/** + * By default, only one socket can be bound to a given IP address + port at a time. + * To enable multiple processes to simultaneously bind to the same address+port, + * you need to enable this functionality in the socket. All processes that wish to + * use the address+port simultaneously must all enable reuse port on the socket + * bound to that port. + **/ +- (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr; + +#pragma mark Broadcast + +/** + * By default, the underlying socket in the OS will not allow you to send broadcast messages. + * In order to send broadcast messages, you need to enable this functionality in the socket. + * + * A broadcast is a UDP message to addresses like "192.168.255.255" or "255.255.255.255" that is + * delivered to every host on the network. + * The reason this is generally disabled by default (by the OS) is to prevent + * accidental broadcast messages from flooding the network. +**/ +- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr; + +#pragma mark Sending + +/** + * Asynchronously sends the given data, with the given timeout and tag. + * + * This method may only be used with a connected socket. + * Recall that connecting is optional for a UDP socket. + * For connected sockets, data can only be sent to the connected address. + * For non-connected sockets, the remote destination is specified for each packet. + * For more information about optionally connecting udp sockets, see the documentation for the connect methods above. + * + * @param data + * The data to send. + * If data is nil or zero-length, this method does nothing. + * If passing NSMutableData, please read the thread-safety notice below. + * + * @param timeout + * The timeout for the send opeartion. + * If the timeout value is negative, the send operation will not use a timeout. + * + * @param tag + * The tag is for your convenience. + * It is not sent or received over the socket in any manner what-so-ever. + * It is reported back as a parameter in the udpSocket:didSendDataWithTag: + * or udpSocket:didNotSendDataWithTag:dueToError: methods. + * You can use it as an array index, state id, type constant, etc. + * + * + * Thread-Safety Note: + * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while + * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method + * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying + * that this particular send operation has completed. + * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data. + * It simply retains it for performance reasons. + * Often times, if NSMutableData is passed, it is because a request/response was built up in memory. + * Copying this data adds an unwanted/unneeded overhead. + * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket + * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time + * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method. +**/ +- (void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; + +/** + * Asynchronously sends the given data, with the given timeout and tag, to the given host and port. + * + * This method cannot be used with a connected socket. + * Recall that connecting is optional for a UDP socket. + * For connected sockets, data can only be sent to the connected address. + * For non-connected sockets, the remote destination is specified for each packet. + * For more information about optionally connecting udp sockets, see the documentation for the connect methods above. + * + * @param data + * The data to send. + * If data is nil or zero-length, this method does nothing. + * If passing NSMutableData, please read the thread-safety notice below. + * + * @param host + * The destination to send the udp packet to. + * May be specified as a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2"). + * You may also use the convenience strings of "loopback" or "localhost". + * + * @param port + * The port of the host to send to. + * + * @param timeout + * The timeout for the send opeartion. + * If the timeout value is negative, the send operation will not use a timeout. + * + * @param tag + * The tag is for your convenience. + * It is not sent or received over the socket in any manner what-so-ever. + * It is reported back as a parameter in the udpSocket:didSendDataWithTag: + * or udpSocket:didNotSendDataWithTag:dueToError: methods. + * You can use it as an array index, state id, type constant, etc. + * + * + * Thread-Safety Note: + * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while + * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method + * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying + * that this particular send operation has completed. + * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data. + * It simply retains it for performance reasons. + * Often times, if NSMutableData is passed, it is because a request/response was built up in memory. + * Copying this data adds an unwanted/unneeded overhead. + * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket + * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time + * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method. +**/ +- (void)sendData:(NSData *)data + toHost:(NSString *)host + port:(uint16_t)port + withTimeout:(NSTimeInterval)timeout + tag:(long)tag; + +/** + * Asynchronously sends the given data, with the given timeout and tag, to the given address. + * + * This method cannot be used with a connected socket. + * Recall that connecting is optional for a UDP socket. + * For connected sockets, data can only be sent to the connected address. + * For non-connected sockets, the remote destination is specified for each packet. + * For more information about optionally connecting udp sockets, see the documentation for the connect methods above. + * + * @param data + * The data to send. + * If data is nil or zero-length, this method does nothing. + * If passing NSMutableData, please read the thread-safety notice below. + * + * @param remoteAddr + * The address to send the data to (specified as a sockaddr structure wrapped in a NSData object). + * + * @param timeout + * The timeout for the send opeartion. + * If the timeout value is negative, the send operation will not use a timeout. + * + * @param tag + * The tag is for your convenience. + * It is not sent or received over the socket in any manner what-so-ever. + * It is reported back as a parameter in the udpSocket:didSendDataWithTag: + * or udpSocket:didNotSendDataWithTag:dueToError: methods. + * You can use it as an array index, state id, type constant, etc. + * + * + * Thread-Safety Note: + * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while + * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method + * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying + * that this particular send operation has completed. + * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data. + * It simply retains it for performance reasons. + * Often times, if NSMutableData is passed, it is because a request/response was built up in memory. + * Copying this data adds an unwanted/unneeded overhead. + * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket + * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time + * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method. +**/ +- (void)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag; + +/** + * You may optionally set a send filter for the socket. + * A filter can provide several interesting possibilities: + * + * 1. Optional caching of resolved addresses for domain names. + * The cache could later be consulted, resulting in fewer system calls to getaddrinfo. + * + * 2. Reusable modules of code for bandwidth monitoring. + * + * 3. Sometimes traffic shapers are needed to simulate real world environments. + * A filter allows you to write custom code to simulate such environments. + * The ability to code this yourself is especially helpful when your simulated environment + * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), + * or the system tools to handle this aren't available (e.g. on a mobile device). + * + * For more information about GCDAsyncUdpSocketSendFilterBlock, see the documentation for its typedef. + * To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue. + * + * Note: This method invokes setSendFilter:withQueue:isAsynchronous: (documented below), + * passing YES for the isAsynchronous parameter. +**/ +- (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue; + +/** + * The receive filter can be run via dispatch_async or dispatch_sync. + * Most typical situations call for asynchronous operation. + * + * However, there are a few situations in which synchronous operation is preferred. + * Such is the case when the filter is extremely minimal and fast. + * This is because dispatch_sync is faster than dispatch_async. + * + * If you choose synchronous operation, be aware of possible deadlock conditions. + * Since the socket queue is executing your block via dispatch_sync, + * then you cannot perform any tasks which may invoke dispatch_sync on the socket queue. + * For example, you can't query properties on the socket. +**/ +- (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock + withQueue:(nullable dispatch_queue_t)filterQueue + isAsynchronous:(BOOL)isAsynchronous; + +#pragma mark Receiving + +/** + * There are two modes of operation for receiving packets: one-at-a-time & continuous. + * + * In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet. + * Receiving packets one-at-a-time may be better suited for implementing certain state machine code, + * where your state machine may not always be ready to process incoming packets. + * + * In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received. + * Receiving packets continuously is better suited to real-time streaming applications. + * + * You may switch back and forth between one-at-a-time mode and continuous mode. + * If the socket is currently in continuous mode, calling this method will switch it to one-at-a-time mode. + * + * When a packet is received (and not filtered by the optional receive filter), + * the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked. + * + * If the socket is able to begin receiving packets, this method returns YES. + * Otherwise it returns NO, and sets the errPtr with appropriate error information. + * + * An example error: + * You created a udp socket to act as a server, and immediately called receive. + * You forgot to first bind the socket to a port number, and received a error with a message like: + * "Must bind socket before you can receive data." +**/ +- (BOOL)receiveOnce:(NSError **)errPtr; + +/** + * There are two modes of operation for receiving packets: one-at-a-time & continuous. + * + * In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet. + * Receiving packets one-at-a-time may be better suited for implementing certain state machine code, + * where your state machine may not always be ready to process incoming packets. + * + * In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received. + * Receiving packets continuously is better suited to real-time streaming applications. + * + * You may switch back and forth between one-at-a-time mode and continuous mode. + * If the socket is currently in one-at-a-time mode, calling this method will switch it to continuous mode. + * + * For every received packet (not filtered by the optional receive filter), + * the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked. + * + * If the socket is able to begin receiving packets, this method returns YES. + * Otherwise it returns NO, and sets the errPtr with appropriate error information. + * + * An example error: + * You created a udp socket to act as a server, and immediately called receive. + * You forgot to first bind the socket to a port number, and received a error with a message like: + * "Must bind socket before you can receive data." +**/ +- (BOOL)beginReceiving:(NSError **)errPtr; + +/** + * If the socket is currently receiving (beginReceiving has been called), this method pauses the receiving. + * That is, it won't read any more packets from the underlying OS socket until beginReceiving is called again. + * + * Important Note: + * GCDAsyncUdpSocket may be running in parallel with your code. + * That is, your delegate is likely running on a separate thread/dispatch_queue. + * When you invoke this method, GCDAsyncUdpSocket may have already dispatched delegate methods to be invoked. + * Thus, if those delegate methods have already been dispatch_async'd, + * your didReceive delegate method may still be invoked after this method has been called. + * You should be aware of this, and program defensively. +**/ +- (void)pauseReceiving; + +/** + * You may optionally set a receive filter for the socket. + * This receive filter may be set to run in its own queue (independent of delegate queue). + * + * A filter can provide several useful features. + * + * 1. Many times udp packets need to be parsed. + * Since the filter can run in its own independent queue, you can parallelize this parsing quite easily. + * The end result is a parallel socket io, datagram parsing, and packet processing. + * + * 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited. + * The filter can prevent such packets from arriving at the delegate. + * And because the filter can run in its own independent queue, this doesn't slow down the delegate. + * + * - Since the udp protocol does not guarantee delivery, udp packets may be lost. + * Many protocols built atop udp thus provide various resend/re-request algorithms. + * This sometimes results in duplicate packets arriving. + * A filter may allow you to architect the duplicate detection code to run in parallel to normal processing. + * + * - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive. + * Such packets need to be ignored. + * + * 3. Sometimes traffic shapers are needed to simulate real world environments. + * A filter allows you to write custom code to simulate such environments. + * The ability to code this yourself is especially helpful when your simulated environment + * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), + * or the system tools to handle this aren't available (e.g. on a mobile device). + * + * Example: + * + * GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) { + * + * MyProtocolMessage *msg = [MyProtocol parseMessage:data]; + * + * *context = response; + * return (response != nil); + * }; + * [udpSocket setReceiveFilter:filter withQueue:myParsingQueue]; + * + * For more information about GCDAsyncUdpSocketReceiveFilterBlock, see the documentation for its typedef. + * To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue. + * + * Note: This method invokes setReceiveFilter:withQueue:isAsynchronous: (documented below), + * passing YES for the isAsynchronous parameter. +**/ +- (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue; + +/** + * The receive filter can be run via dispatch_async or dispatch_sync. + * Most typical situations call for asynchronous operation. + * + * However, there are a few situations in which synchronous operation is preferred. + * Such is the case when the filter is extremely minimal and fast. + * This is because dispatch_sync is faster than dispatch_async. + * + * If you choose synchronous operation, be aware of possible deadlock conditions. + * Since the socket queue is executing your block via dispatch_sync, + * then you cannot perform any tasks which may invoke dispatch_sync on the socket queue. + * For example, you can't query properties on the socket. +**/ +- (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock + withQueue:(nullable dispatch_queue_t)filterQueue + isAsynchronous:(BOOL)isAsynchronous; + +#pragma mark Closing + +/** + * Immediately closes the underlying socket. + * Any pending send operations are discarded. + * + * The GCDAsyncUdpSocket instance may optionally be used again. + * (it will setup/configure/use another unnderlying BSD socket). +**/ +- (void)close; + +/** + * Closes the underlying socket after all pending send operations have been sent. + * + * The GCDAsyncUdpSocket instance may optionally be used again. + * (it will setup/configure/use another unnderlying BSD socket). +**/ +- (void)closeAfterSending; + +#pragma mark Advanced +/** + * GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue. + * In most cases, the instance creates this queue itself. + * However, to allow for maximum flexibility, the internal queue may be passed in the init method. + * This allows for some advanced options such as controlling socket priority via target queues. + * However, when one begins to use target queues like this, they open the door to some specific deadlock issues. + * + * For example, imagine there are 2 queues: + * dispatch_queue_t socketQueue; + * dispatch_queue_t socketTargetQueue; + * + * If you do this (pseudo-code): + * socketQueue.targetQueue = socketTargetQueue; + * + * Then all socketQueue operations will actually get run on the given socketTargetQueue. + * This is fine and works great in most situations. + * But if you run code directly from within the socketTargetQueue that accesses the socket, + * you could potentially get deadlock. Imagine the following code: + * + * - (BOOL)socketHasSomething + * { + * __block BOOL result = NO; + * dispatch_block_t block = ^{ + * result = [self someInternalMethodToBeRunOnlyOnSocketQueue]; + * } + * if (is_executing_on_queue(socketQueue)) + * block(); + * else + * dispatch_sync(socketQueue, block); + * + * return result; + * } + * + * What happens if you call this method from the socketTargetQueue? The result is deadlock. + * This is because the GCD API offers no mechanism to discover a queue's targetQueue. + * Thus we have no idea if our socketQueue is configured with a targetQueue. + * If we had this information, we could easily avoid deadlock. + * But, since these API's are missing or unfeasible, you'll have to explicitly set it. + * + * IF you pass a socketQueue via the init method, + * AND you've configured the passed socketQueue with a targetQueue, + * THEN you should pass the end queue in the target hierarchy. + * + * For example, consider the following queue hierarchy: + * socketQueue -> ipQueue -> moduleQueue + * + * This example demonstrates priority shaping within some server. + * All incoming client connections from the same IP address are executed on the same target queue. + * And all connections for a particular module are executed on the same target queue. + * Thus, the priority of all networking for the entire module can be changed on the fly. + * Additionally, networking traffic from a single IP cannot monopolize the module. + * + * Here's how you would accomplish something like that: + * - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock + * { + * dispatch_queue_t socketQueue = dispatch_queue_create("", NULL); + * dispatch_queue_t ipQueue = [self ipQueueForAddress:address]; + * + * dispatch_set_target_queue(socketQueue, ipQueue); + * dispatch_set_target_queue(iqQueue, moduleQueue); + * + * return socketQueue; + * } + * - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket + * { + * [clientConnections addObject:newSocket]; + * [newSocket markSocketQueueTargetQueue:moduleQueue]; + * } + * + * Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue. + * This is often NOT the case, as such queues are used solely for execution shaping. + **/ +- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue; +- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue; + +/** + * It's not thread-safe to access certain variables from outside the socket's internal queue. + * + * For example, the socket file descriptor. + * File descriptors are simply integers which reference an index in the per-process file table. + * However, when one requests a new file descriptor (by opening a file or socket), + * the file descriptor returned is guaranteed to be the lowest numbered unused descriptor. + * So if we're not careful, the following could be possible: + * + * - Thread A invokes a method which returns the socket's file descriptor. + * - The socket is closed via the socket's internal queue on thread B. + * - Thread C opens a file, and subsequently receives the file descriptor that was previously the socket's FD. + * - Thread A is now accessing/altering the file instead of the socket. + * + * In addition to this, other variables are not actually objects, + * and thus cannot be retained/released or even autoreleased. + * An example is the sslContext, of type SSLContextRef, which is actually a malloc'd struct. + * + * Although there are internal variables that make it difficult to maintain thread-safety, + * it is important to provide access to these variables + * to ensure this class can be used in a wide array of environments. + * This method helps to accomplish this by invoking the current block on the socket's internal queue. + * The methods below can be invoked from within the block to access + * those generally thread-unsafe internal variables in a thread-safe manner. + * The given block will be invoked synchronously on the socket's internal queue. + * + * If you save references to any protected variables and use them outside the block, you do so at your own peril. +**/ +- (void)performBlock:(dispatch_block_t)block; + +/** + * These methods are only available from within the context of a performBlock: invocation. + * See the documentation for the performBlock: method above. + * + * Provides access to the socket's file descriptor(s). + * If the socket isn't connected, or explicity bound to a particular interface, + * it might actually have multiple internal socket file descriptors - one for IPv4 and one for IPv6. +**/ +- (int)socketFD; +- (int)socket4FD; +- (int)socket6FD; + +#if TARGET_OS_IPHONE + +/** + * These methods are only available from within the context of a performBlock: invocation. + * See the documentation for the performBlock: method above. + * + * Returns (creating if necessary) a CFReadStream/CFWriteStream for the internal socket. + * + * Generally GCDAsyncUdpSocket doesn't use CFStream. (It uses the faster GCD API's.) + * However, if you need one for any reason, + * these methods are a convenient way to get access to a safe instance of one. +**/ +- (nullable CFReadStreamRef)readStream; +- (nullable CFWriteStreamRef)writeStream; + +/** + * This method is only available from within the context of a performBlock: invocation. + * See the documentation for the performBlock: method above. + * + * Configures the socket to allow it to operate when the iOS application has been backgrounded. + * In other words, this method creates a read & write stream, and invokes: + * + * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); + * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); + * + * Returns YES if successful, NO otherwise. + * + * Example usage: + * + * [asyncUdpSocket performBlock:^{ + * [asyncUdpSocket enableBackgroundingOnSocket]; + * }]; + * + * + * NOTE : Apple doesn't currently support backgrounding UDP sockets. (Only TCP for now). +**/ +//- (BOOL)enableBackgroundingOnSockets; + +#endif + +#pragma mark Utilities + +/** + * Extracting host/port/family information from raw address data. +**/ + ++ (nullable NSString *)hostFromAddress:(NSData *)address; ++ (uint16_t)portFromAddress:(NSData *)address; ++ (int)familyFromAddress:(NSData *)address; + ++ (BOOL)isIPv4Address:(NSData *)address; ++ (BOOL)isIPv6Address:(NSData *)address; + ++ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr fromAddress:(NSData *)address; ++ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr family:(int * __nullable)afPtr fromAddress:(NSData *)address; + +@end + +NS_ASSUME_NONNULL_END diff --git a/max/template/playbtest/playbtest/PlayB/GCD/GCDAsyncUdpSocket.m b/max/template/playbtest/playbtest/PlayB/GCD/GCDAsyncUdpSocket.m new file mode 100755 index 0000000..af0cbf2 --- /dev/null +++ b/max/template/playbtest/playbtest/PlayB/GCD/GCDAsyncUdpSocket.m @@ -0,0 +1,5632 @@ +// +// GCDAsyncUdpSocket +// +// This class is in the public domain. +// Originally created by Robbie Hanson of Deusty LLC. +// Updated and maintained by Deusty LLC and the Apple development community. +// +// https://github.com/robbiehanson/CocoaAsyncSocket +// + +#import "GCDAsyncUdpSocket.h" + +#if ! __has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +// For more information see: https://github.com/robbiehanson/CocoaAsyncSocket/wiki/ARC +#endif + +#if TARGET_OS_IPHONE + #import + #import +#endif + +#import +#import +#import +#import +#import +#import +#import + + +#if 0 + +// Logging Enabled - See log level below + +// Logging uses the CocoaLumberjack framework (which is also GCD based). +// https://github.com/robbiehanson/CocoaLumberjack +// +// It allows us to do a lot of logging without significantly slowing down the code. +#import "DDLog.h" + +#define LogAsync NO +#define LogContext 65535 + +#define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__) +#define LogC(flg, frmt, ...) LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__) + +#define LogError(frmt, ...) LogObjc(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) +#define LogWarn(frmt, ...) LogObjc(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) +#define LogInfo(frmt, ...) LogObjc(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) +#define LogVerbose(frmt, ...) LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) + +#define LogCError(frmt, ...) LogC(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) +#define LogCWarn(frmt, ...) LogC(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) +#define LogCInfo(frmt, ...) LogC(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) +#define LogCVerbose(frmt, ...) LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) + +#define LogTrace() LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD) +#define LogCTrace() LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__) + +// Log levels : off, error, warn, info, verbose +static const int logLevel = LOG_LEVEL_VERBOSE; + +#else + +// Logging Disabled + +#define LogError(frmt, ...) {} +#define LogWarn(frmt, ...) {} +#define LogInfo(frmt, ...) {} +#define LogVerbose(frmt, ...) {} + +#define LogCError(frmt, ...) {} +#define LogCWarn(frmt, ...) {} +#define LogCInfo(frmt, ...) {} +#define LogCVerbose(frmt, ...) {} + +#define LogTrace() {} +#define LogCTrace(frmt, ...) {} + +#endif + +/** + * Seeing a return statements within an inner block + * can sometimes be mistaken for a return point of the enclosing method. + * This makes inline blocks a bit easier to read. +**/ +#define return_from_block return + +/** + * A socket file descriptor is really just an integer. + * It represents the index of the socket within the kernel. + * This makes invalid file descriptor comparisons easier to read. +**/ +#define SOCKET_NULL -1 + +/** + * Just to type less code. +**/ +#define AutoreleasedBlock(block) ^{ @autoreleasepool { block(); }} + + +@class GCDAsyncUdpSendPacket; + +NSString *const GCDAsyncUdpSocketException = @"GCDAsyncUdpSocketException"; +NSString *const GCDAsyncUdpSocketErrorDomain = @"GCDAsyncUdpSocketErrorDomain"; + +NSString *const GCDAsyncUdpSocketQueueName = @"GCDAsyncUdpSocket"; +NSString *const GCDAsyncUdpSocketThreadName = @"GCDAsyncUdpSocket-CFStream"; + +enum GCDAsyncUdpSocketFlags +{ + kDidCreateSockets = 1 << 0, // If set, the sockets have been created. + kDidBind = 1 << 1, // If set, bind has been called. + kConnecting = 1 << 2, // If set, a connection attempt is in progress. + kDidConnect = 1 << 3, // If set, socket is connected. + kReceiveOnce = 1 << 4, // If set, one-at-a-time receive is enabled + kReceiveContinuous = 1 << 5, // If set, continuous receive is enabled + kIPv4Deactivated = 1 << 6, // If set, socket4 was closed due to bind or connect on IPv6. + kIPv6Deactivated = 1 << 7, // If set, socket6 was closed due to bind or connect on IPv4. + kSend4SourceSuspended = 1 << 8, // If set, send4Source is suspended. + kSend6SourceSuspended = 1 << 9, // If set, send6Source is suspended. + kReceive4SourceSuspended = 1 << 10, // If set, receive4Source is suspended. + kReceive6SourceSuspended = 1 << 11, // If set, receive6Source is suspended. + kSock4CanAcceptBytes = 1 << 12, // If set, we know socket4 can accept bytes. If unset, it's unknown. + kSock6CanAcceptBytes = 1 << 13, // If set, we know socket6 can accept bytes. If unset, it's unknown. + kForbidSendReceive = 1 << 14, // If set, no new send or receive operations are allowed to be queued. + kCloseAfterSends = 1 << 15, // If set, close as soon as no more sends are queued. + kFlipFlop = 1 << 16, // Used to alternate between IPv4 and IPv6 sockets. +#if TARGET_OS_IPHONE + kAddedStreamListener = 1 << 17, // If set, CFStreams have been added to listener thread +#endif +}; + +enum GCDAsyncUdpSocketConfig +{ + kIPv4Disabled = 1 << 0, // If set, IPv4 is disabled + kIPv6Disabled = 1 << 1, // If set, IPv6 is disabled + kPreferIPv4 = 1 << 2, // If set, IPv4 is preferred over IPv6 + kPreferIPv6 = 1 << 3, // If set, IPv6 is preferred over IPv4 +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface GCDAsyncUdpSocket () +{ +#if __has_feature(objc_arc_weak) + __weak id delegate; +#else + __unsafe_unretained id delegate; +#endif + dispatch_queue_t delegateQueue; + + GCDAsyncUdpSocketReceiveFilterBlock receiveFilterBlock; + dispatch_queue_t receiveFilterQueue; + BOOL receiveFilterAsync; + + GCDAsyncUdpSocketSendFilterBlock sendFilterBlock; + dispatch_queue_t sendFilterQueue; + BOOL sendFilterAsync; + + uint32_t flags; + uint16_t config; + + uint16_t max4ReceiveSize; + uint32_t max6ReceiveSize; + + uint16_t maxSendSize; + + int socket4FD; + int socket6FD; + + dispatch_queue_t socketQueue; + + dispatch_source_t send4Source; + dispatch_source_t send6Source; + dispatch_source_t receive4Source; + dispatch_source_t receive6Source; + dispatch_source_t sendTimer; + + GCDAsyncUdpSendPacket *currentSend; + NSMutableArray *sendQueue; + + unsigned long socket4FDBytesAvailable; + unsigned long socket6FDBytesAvailable; + + uint32_t pendingFilterOperations; + + NSData *cachedLocalAddress4; + NSString *cachedLocalHost4; + uint16_t cachedLocalPort4; + + NSData *cachedLocalAddress6; + NSString *cachedLocalHost6; + uint16_t cachedLocalPort6; + + NSData *cachedConnectedAddress; + NSString *cachedConnectedHost; + uint16_t cachedConnectedPort; + int cachedConnectedFamily; + + void *IsOnSocketQueueOrTargetQueueKey; + +#if TARGET_OS_IPHONE + CFStreamClientContext streamContext; + CFReadStreamRef readStream4; + CFReadStreamRef readStream6; + CFWriteStreamRef writeStream4; + CFWriteStreamRef writeStream6; +#endif + + id userData; +} + +- (void)resumeSend4Source; +- (void)resumeSend6Source; +- (void)resumeReceive4Source; +- (void)resumeReceive6Source; +- (void)closeSockets; + +- (void)maybeConnect; +- (BOOL)connectWithAddress4:(NSData *)address4 error:(NSError **)errPtr; +- (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr; + +- (void)maybeDequeueSend; +- (void)doPreSend; +- (void)doSend; +- (void)endCurrentSend; +- (void)setupSendTimerWithTimeout:(NSTimeInterval)timeout; + +- (void)doReceive; +- (void)doReceiveEOF; + +- (void)closeWithError:(NSError *)error; + +- (BOOL)performMulticastRequest:(int)requestType forGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr; + +#if TARGET_OS_IPHONE +- (BOOL)createReadAndWriteStreams:(NSError **)errPtr; +- (BOOL)registerForStreamCallbacks:(NSError **)errPtr; +- (BOOL)addStreamsToRunLoop:(NSError **)errPtr; +- (BOOL)openStreams:(NSError **)errPtr; +- (void)removeStreamsFromRunLoop; +- (void)closeReadAndWriteStreams; +#endif + ++ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4; ++ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6; ++ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4; ++ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6; + +#if TARGET_OS_IPHONE +// Forward declaration ++ (void)listenerThread:(id)unused; +#endif + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * The GCDAsyncUdpSendPacket encompasses the instructions for a single send/write. +**/ +@interface GCDAsyncUdpSendPacket : NSObject { +@public + NSData *buffer; + NSTimeInterval timeout; + long tag; + + BOOL resolveInProgress; + BOOL filterInProgress; + + NSArray *resolvedAddresses; + NSError *resolveError; + + NSData *address; + int addressFamily; +} + +- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i NS_DESIGNATED_INITIALIZER; + +@end + +@implementation GCDAsyncUdpSendPacket + +// Cover the superclass' designated initializer +- (instancetype)init NS_UNAVAILABLE +{ + NSAssert(0, @"Use the designated initializer"); + return nil; +} + +- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i +{ + if ((self = [super init])) + { + buffer = d; + timeout = t; + tag = i; + + resolveInProgress = NO; + } + return self; +} + + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface GCDAsyncUdpSpecialPacket : NSObject { +@public +// uint8_t type; + + BOOL resolveInProgress; + + NSArray *addresses; + NSError *error; +} + +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +@end + +@implementation GCDAsyncUdpSpecialPacket + +- (instancetype)init +{ + self = [super init]; + return self; +} + + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation GCDAsyncUdpSocket + +- (instancetype)init +{ + LogTrace(); + + return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL]; +} + +- (instancetype)initWithSocketQueue:(dispatch_queue_t)sq +{ + LogTrace(); + + return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq]; +} + +- (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq +{ + LogTrace(); + + return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL]; +} + +- (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq +{ + LogTrace(); + + if ((self = [super init])) + { + delegate = aDelegate; + + if (dq) + { + delegateQueue = dq; + #if !OS_OBJECT_USE_OBJC + dispatch_retain(delegateQueue); + #endif + } + + max4ReceiveSize = 65535; + max6ReceiveSize = 65535; + + maxSendSize = 65535; + + socket4FD = SOCKET_NULL; + socket6FD = SOCKET_NULL; + + if (sq) + { + NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), + @"The given socketQueue parameter must not be a concurrent queue."); + NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), + @"The given socketQueue parameter must not be a concurrent queue."); + NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), + @"The given socketQueue parameter must not be a concurrent queue."); + + socketQueue = sq; + #if !OS_OBJECT_USE_OBJC + dispatch_retain(socketQueue); + #endif + } + else + { + socketQueue = dispatch_queue_create([GCDAsyncUdpSocketQueueName UTF8String], NULL); + } + + // The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter. + // From the documentation: + // + // > Keys are only compared as pointers and are never dereferenced. + // > Thus, you can use a pointer to a static variable for a specific subsystem or + // > any other value that allows you to identify the value uniquely. + // + // We're just going to use the memory address of an ivar. + // Specifically an ivar that is explicitly named for our purpose to make the code more readable. + // + // However, it feels tedious (and less readable) to include the "&" all the time: + // dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey) + // + // So we're going to make it so it doesn't matter if we use the '&' or not, + // by assigning the value of the ivar to the address of the ivar. + // Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey; + + IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey; + + void *nonNullUnusedPointer = (__bridge void *)self; + dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL); + + currentSend = nil; + sendQueue = [[NSMutableArray alloc] initWithCapacity:5]; + + #if TARGET_OS_IPHONE + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(applicationWillEnterForeground:) + name:UIApplicationWillEnterForegroundNotification + object:nil]; + #endif + } + return self; +} + +- (void)dealloc +{ + LogInfo(@"%@ - %@ (start)", THIS_METHOD, self); + +#if TARGET_OS_IPHONE + [[NSNotificationCenter defaultCenter] removeObserver:self]; +#endif + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + [self closeWithError:nil]; + } + else + { + dispatch_sync(socketQueue, ^{ + [self closeWithError:nil]; + }); + } + + delegate = nil; + #if !OS_OBJECT_USE_OBJC + if (delegateQueue) dispatch_release(delegateQueue); + #endif + delegateQueue = NULL; + + #if !OS_OBJECT_USE_OBJC + if (socketQueue) dispatch_release(socketQueue); + #endif + socketQueue = NULL; + + LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Configuration +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (id)delegate +{ + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return delegate; + } + else + { + __block id result = nil; + + dispatch_sync(socketQueue, ^{ + result = self->delegate; + }); + + return result; + } +} + +- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously +{ + dispatch_block_t block = ^{ + self->delegate = newDelegate; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { + block(); + } + else { + if (synchronously) + dispatch_sync(socketQueue, block); + else + dispatch_async(socketQueue, block); + } +} + +- (void)setDelegate:(id)newDelegate +{ + [self setDelegate:newDelegate synchronously:NO]; +} + +- (void)synchronouslySetDelegate:(id)newDelegate +{ + [self setDelegate:newDelegate synchronously:YES]; +} + +- (dispatch_queue_t)delegateQueue +{ + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return delegateQueue; + } + else + { + __block dispatch_queue_t result = NULL; + + dispatch_sync(socketQueue, ^{ + result = self->delegateQueue; + }); + + return result; + } +} + +- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously +{ + dispatch_block_t block = ^{ + + #if !OS_OBJECT_USE_OBJC + if (self->delegateQueue) dispatch_release(self->delegateQueue); + if (newDelegateQueue) dispatch_retain(newDelegateQueue); + #endif + + self->delegateQueue = newDelegateQueue; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { + block(); + } + else { + if (synchronously) + dispatch_sync(socketQueue, block); + else + dispatch_async(socketQueue, block); + } +} + +- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue +{ + [self setDelegateQueue:newDelegateQueue synchronously:NO]; +} + +- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue +{ + [self setDelegateQueue:newDelegateQueue synchronously:YES]; +} + +- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr +{ + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + if (delegatePtr) *delegatePtr = delegate; + if (delegateQueuePtr) *delegateQueuePtr = delegateQueue; + } + else + { + __block id dPtr = NULL; + __block dispatch_queue_t dqPtr = NULL; + + dispatch_sync(socketQueue, ^{ + dPtr = self->delegate; + dqPtr = self->delegateQueue; + }); + + if (delegatePtr) *delegatePtr = dPtr; + if (delegateQueuePtr) *delegateQueuePtr = dqPtr; + } +} + +- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously +{ + dispatch_block_t block = ^{ + + self->delegate = newDelegate; + + #if !OS_OBJECT_USE_OBJC + if (self->delegateQueue) dispatch_release(self->delegateQueue); + if (newDelegateQueue) dispatch_retain(newDelegateQueue); + #endif + + self->delegateQueue = newDelegateQueue; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { + block(); + } + else { + if (synchronously) + dispatch_sync(socketQueue, block); + else + dispatch_async(socketQueue, block); + } +} + +- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue +{ + [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO]; +} + +- (void)synchronouslySetDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue +{ + [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES]; +} + +- (BOOL)isIPv4Enabled +{ + // Note: YES means kIPv4Disabled is OFF + + __block BOOL result = NO; + + dispatch_block_t block = ^{ + + result = ((self->config & kIPv4Disabled) == 0); + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (void)setIPv4Enabled:(BOOL)flag +{ + // Note: YES means kIPv4Disabled is OFF + + dispatch_block_t block = ^{ + + LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO")); + + if (flag) + self->config &= ~kIPv4Disabled; + else + self->config |= kIPv4Disabled; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (BOOL)isIPv6Enabled +{ + // Note: YES means kIPv6Disabled is OFF + + __block BOOL result = NO; + + dispatch_block_t block = ^{ + + result = ((self->config & kIPv6Disabled) == 0); + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (void)setIPv6Enabled:(BOOL)flag +{ + // Note: YES means kIPv6Disabled is OFF + + dispatch_block_t block = ^{ + + LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO")); + + if (flag) + self->config &= ~kIPv6Disabled; + else + self->config |= kIPv6Disabled; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (BOOL)isIPv4Preferred +{ + __block BOOL result = NO; + + dispatch_block_t block = ^{ + result = (self->config & kPreferIPv4) ? YES : NO; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (BOOL)isIPv6Preferred +{ + __block BOOL result = NO; + + dispatch_block_t block = ^{ + result = (self->config & kPreferIPv6) ? YES : NO; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (BOOL)isIPVersionNeutral +{ + __block BOOL result = NO; + + dispatch_block_t block = ^{ + result = (self->config & (kPreferIPv4 | kPreferIPv6)) == 0; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (void)setPreferIPv4 +{ + dispatch_block_t block = ^{ + + LogTrace(); + + self->config |= kPreferIPv4; + self->config &= ~kPreferIPv6; + + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (void)setPreferIPv6 +{ + dispatch_block_t block = ^{ + + LogTrace(); + + self->config &= ~kPreferIPv4; + self->config |= kPreferIPv6; + + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (void)setIPVersionNeutral +{ + dispatch_block_t block = ^{ + + LogTrace(); + + self->config &= ~kPreferIPv4; + self->config &= ~kPreferIPv6; + + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (uint16_t)maxReceiveIPv4BufferSize +{ + __block uint16_t result = 0; + + dispatch_block_t block = ^{ + + result = self->max4ReceiveSize; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (void)setMaxReceiveIPv4BufferSize:(uint16_t)max +{ + dispatch_block_t block = ^{ + + LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); + + self->max4ReceiveSize = max; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (uint32_t)maxReceiveIPv6BufferSize +{ + __block uint32_t result = 0; + + dispatch_block_t block = ^{ + + result = self->max6ReceiveSize; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (void)setMaxReceiveIPv6BufferSize:(uint32_t)max +{ + dispatch_block_t block = ^{ + + LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); + + self->max6ReceiveSize = max; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (void)setMaxSendBufferSize:(uint16_t)max +{ + dispatch_block_t block = ^{ + + LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); + + self->maxSendSize = max; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (uint16_t)maxSendBufferSize +{ + __block uint16_t result = 0; + + dispatch_block_t block = ^{ + + result = self->maxSendSize; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (id)userData +{ + __block id result = nil; + + dispatch_block_t block = ^{ + + result = self->userData; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (void)setUserData:(id)arbitraryUserData +{ + dispatch_block_t block = ^{ + + if (self->userData != arbitraryUserData) + { + self->userData = arbitraryUserData; + } + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Delegate Helpers +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)notifyDidConnectToAddress:(NSData *)anAddress +{ + LogTrace(); + + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didConnectToAddress:)]) + { + NSData *address = [anAddress copy]; // In case param is NSMutableData + + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate udpSocket:self didConnectToAddress:address]; + }}); + } +} + +- (void)notifyDidNotConnect:(NSError *)error +{ + LogTrace(); + + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotConnect:)]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate udpSocket:self didNotConnect:error]; + }}); + } +} + +- (void)notifyDidSendDataWithTag:(long)tag +{ + LogTrace(); + + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didSendDataWithTag:)]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate udpSocket:self didSendDataWithTag:tag]; + }}); + } +} + +- (void)notifyDidNotSendDataWithTag:(long)tag dueToError:(NSError *)error +{ + LogTrace(); + + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotSendDataWithTag:dueToError:)]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate udpSocket:self didNotSendDataWithTag:tag dueToError:error]; + }}); + } +} + +- (void)notifyDidReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)context +{ + LogTrace(); + + SEL selector = @selector(udpSocket:didReceiveData:fromAddress:withFilterContext:); + + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:selector]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate udpSocket:self didReceiveData:data fromAddress:address withFilterContext:context]; + }}); + } +} + +- (void)notifyDidCloseWithError:(NSError *)error +{ + LogTrace(); + + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocketDidClose:withError:)]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate udpSocketDidClose:self withError:error]; + }}); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Errors +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (NSError *)badConfigError:(NSString *)errMsg +{ + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + + return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain + code:GCDAsyncUdpSocketBadConfigError + userInfo:userInfo]; +} + +- (NSError *)badParamError:(NSString *)errMsg +{ + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + + return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain + code:GCDAsyncUdpSocketBadParamError + userInfo:userInfo]; +} + +- (NSError *)gaiError:(int)gai_error +{ + NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + + return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo]; +} + +- (NSError *)errnoErrorWithReason:(NSString *)reason +{ + NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)]; + NSDictionary *userInfo; + + if (reason) + userInfo = @{NSLocalizedDescriptionKey : errMsg, + NSLocalizedFailureReasonErrorKey : reason}; + else + userInfo = @{NSLocalizedDescriptionKey : errMsg}; + + return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; +} + +- (NSError *)errnoError +{ + return [self errnoErrorWithReason:nil]; +} + +/** + * Returns a standard send timeout error. +**/ +- (NSError *)sendTimeoutError +{ + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketSendTimeoutError", + @"GCDAsyncUdpSocket", [NSBundle mainBundle], + @"Send operation timed out", nil); + + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + + return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain + code:GCDAsyncUdpSocketSendTimeoutError + userInfo:userInfo]; +} + +- (NSError *)socketClosedError +{ + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketClosedError", + @"GCDAsyncUdpSocket", [NSBundle mainBundle], + @"Socket closed", nil); + + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + + return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain code:GCDAsyncUdpSocketClosedError userInfo:userInfo]; +} + +- (NSError *)otherError:(NSString *)errMsg +{ + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + + return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain + code:GCDAsyncUdpSocketOtherError + userInfo:userInfo]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Utilities +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)preOp:(NSError **)errPtr +{ + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + if (delegate == nil) // Must have delegate set + { + if (errPtr) + { + NSString *msg = @"Attempting to use socket without a delegate. Set a delegate first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + if (delegateQueue == NULL) // Must have delegate queue set + { + if (errPtr) + { + NSString *msg = @"Attempting to use socket without a delegate queue. Set a delegate queue first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + return YES; +} + +/** + * This method executes on a global concurrent queue. + * When complete, it executes the given completion block on the socketQueue. +**/ +- (void)asyncResolveHost:(NSString *)aHost + port:(uint16_t)port + withCompletionBlock:(void (^)(NSArray *addresses, NSError *error))completionBlock +{ + LogTrace(); + + // Check parameter(s) + + if (aHost == nil) + { + NSString *msg = @"The host param is nil. Should be domain name or IP address string."; + NSError *error = [self badParamError:msg]; + + // We should still use dispatch_async since this method is expected to be asynchronous + + dispatch_async(socketQueue, ^{ @autoreleasepool { + + completionBlock(nil, error); + }}); + + return; + } + + // It's possible that the given aHost parameter is actually a NSMutableString. + // So we want to copy it now, within this block that will be executed synchronously. + // This way the asynchronous lookup block below doesn't have to worry about it changing. + + NSString *host = [aHost copy]; + + + dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool { + + NSMutableArray *addresses = [NSMutableArray arrayWithCapacity:2]; + NSError *error = nil; + + if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"]) + { + // Use LOOPBACK address + struct sockaddr_in sockaddr4; + memset(&sockaddr4, 0, sizeof(sockaddr4)); + + sockaddr4.sin_len = sizeof(struct sockaddr_in); + sockaddr4.sin_family = AF_INET; + sockaddr4.sin_port = htons(port); + sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + struct sockaddr_in6 sockaddr6; + memset(&sockaddr6, 0, sizeof(sockaddr6)); + + sockaddr6.sin6_len = sizeof(struct sockaddr_in6); + sockaddr6.sin6_family = AF_INET6; + sockaddr6.sin6_port = htons(port); + sockaddr6.sin6_addr = in6addr_loopback; + + // Wrap the native address structures and add to list + [addresses addObject:[NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]]; + [addresses addObject:[NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]]; + } + else + { + NSString *portStr = [NSString stringWithFormat:@"%hu", port]; + + struct addrinfo hints, *res, *res0; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + + int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0); + + if (gai_error) + { + error = [self gaiError:gai_error]; + } + else + { + for(res = res0; res; res = res->ai_next) + { + if (res->ai_family == AF_INET) + { + // Found IPv4 address + // Wrap the native address structure and add to list + + [addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]]; + } + else if (res->ai_family == AF_INET6) + { + + // Fixes connection issues with IPv6, it is the same solution for udp socket. + // https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158 + struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)(void *)res->ai_addr; + in_port_t *portPtr = &sockaddr->sin6_port; + if ((portPtr != NULL) && (*portPtr == 0)) { + *portPtr = htons(port); + } + + // Found IPv6 address + // Wrap the native address structure and add to list + [addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]]; + } + } + freeaddrinfo(res0); + + if ([addresses count] == 0) + { + error = [self gaiError:EAI_FAIL]; + } + } + } + + dispatch_async(self->socketQueue, ^{ @autoreleasepool { + + completionBlock(addresses, error); + }}); + + }}); +} + +/** + * This method picks an address from the given list of addresses. + * The address picked depends upon which protocols are disabled, deactived, & preferred. + * + * Returns the address family (AF_INET or AF_INET6) of the picked address, + * or AF_UNSPEC and the corresponding error is there's a problem. +**/ +- (int)getAddress:(NSData **)addressPtr error:(NSError **)errorPtr fromAddresses:(NSArray *)addresses +{ + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert([addresses count] > 0, @"Expected at least one address"); + + int resultAF = AF_UNSPEC; + NSData *resultAddress = nil; + NSError *resultError = nil; + + // Check for problems + + BOOL resolvedIPv4Address = NO; + BOOL resolvedIPv6Address = NO; + + for (NSData *address in addresses) + { + switch ([[self class] familyFromAddress:address]) + { + case AF_INET : resolvedIPv4Address = YES; break; + case AF_INET6 : resolvedIPv6Address = YES; break; + + default : NSAssert(NO, @"Addresses array contains invalid address"); + } + } + + BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; + + if (isIPv4Disabled && !resolvedIPv6Address) + { + NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address(es)."; + resultError = [self otherError:msg]; + + if (addressPtr) *addressPtr = resultAddress; + if (errorPtr) *errorPtr = resultError; + + return resultAF; + } + + if (isIPv6Disabled && !resolvedIPv4Address) + { + NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address(es)."; + resultError = [self otherError:msg]; + + if (addressPtr) *addressPtr = resultAddress; + if (errorPtr) *errorPtr = resultError; + + return resultAF; + } + + BOOL isIPv4Deactivated = (flags & kIPv4Deactivated) ? YES : NO; + BOOL isIPv6Deactivated = (flags & kIPv6Deactivated) ? YES : NO; + + if (isIPv4Deactivated && !resolvedIPv6Address) + { + NSString *msg = @"IPv4 has been deactivated due to bind/connect, and DNS lookup found no IPv6 address(es)."; + resultError = [self otherError:msg]; + + if (addressPtr) *addressPtr = resultAddress; + if (errorPtr) *errorPtr = resultError; + + return resultAF; + } + + if (isIPv6Deactivated && !resolvedIPv4Address) + { + NSString *msg = @"IPv6 has been deactivated due to bind/connect, and DNS lookup found no IPv4 address(es)."; + resultError = [self otherError:msg]; + + if (addressPtr) *addressPtr = resultAddress; + if (errorPtr) *errorPtr = resultError; + + return resultAF; + } + + // Extract first IPv4 and IPv6 address in list + + BOOL ipv4WasFirstInList = YES; + NSData *address4 = nil; + NSData *address6 = nil; + + for (NSData *address in addresses) + { + int af = [[self class] familyFromAddress:address]; + + if (af == AF_INET) + { + if (address4 == nil) + { + address4 = address; + + if (address6) + break; + else + ipv4WasFirstInList = YES; + } + } + else // af == AF_INET6 + { + if (address6 == nil) + { + address6 = address; + + if (address4) + break; + else + ipv4WasFirstInList = NO; + } + } + } + + // Determine socket type + + BOOL preferIPv4 = (config & kPreferIPv4) ? YES : NO; + BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO; + + BOOL useIPv4 = ((preferIPv4 && address4) || (address6 == nil)); + BOOL useIPv6 = ((preferIPv6 && address6) || (address4 == nil)); + + NSAssert(!(preferIPv4 && preferIPv6), @"Invalid config state"); + NSAssert(!(useIPv4 && useIPv6), @"Invalid logic"); + + if (useIPv4 || (!useIPv6 && ipv4WasFirstInList)) + { + resultAF = AF_INET; + resultAddress = address4; + } + else + { + resultAF = AF_INET6; + resultAddress = address6; + } + + if (addressPtr) *addressPtr = resultAddress; + if (errorPtr) *errorPtr = resultError; + + return resultAF; +} + +/** + * Finds the address(es) of an interface description. + * An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34). +**/ +- (void)convertIntefaceDescription:(NSString *)interfaceDescription + port:(uint16_t)port + intoAddress4:(NSData **)interfaceAddr4Ptr + address6:(NSData **)interfaceAddr6Ptr +{ + NSData *addr4 = nil; + NSData *addr6 = nil; + + if (interfaceDescription == nil) + { + // ANY address + + struct sockaddr_in sockaddr4; + memset(&sockaddr4, 0, sizeof(sockaddr4)); + + sockaddr4.sin_len = sizeof(sockaddr4); + sockaddr4.sin_family = AF_INET; + sockaddr4.sin_port = htons(port); + sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY); + + struct sockaddr_in6 sockaddr6; + memset(&sockaddr6, 0, sizeof(sockaddr6)); + + sockaddr6.sin6_len = sizeof(sockaddr6); + sockaddr6.sin6_family = AF_INET6; + sockaddr6.sin6_port = htons(port); + sockaddr6.sin6_addr = in6addr_any; + + addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; + addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; + } + else if ([interfaceDescription isEqualToString:@"localhost"] || + [interfaceDescription isEqualToString:@"loopback"]) + { + // LOOPBACK address + + struct sockaddr_in sockaddr4; + memset(&sockaddr4, 0, sizeof(sockaddr4)); + + sockaddr4.sin_len = sizeof(struct sockaddr_in); + sockaddr4.sin_family = AF_INET; + sockaddr4.sin_port = htons(port); + sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + struct sockaddr_in6 sockaddr6; + memset(&sockaddr6, 0, sizeof(sockaddr6)); + + sockaddr6.sin6_len = sizeof(struct sockaddr_in6); + sockaddr6.sin6_family = AF_INET6; + sockaddr6.sin6_port = htons(port); + sockaddr6.sin6_addr = in6addr_loopback; + + addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; + addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; + } + else + { + const char *iface = [interfaceDescription UTF8String]; + + struct ifaddrs *addrs; + const struct ifaddrs *cursor; + + if ((getifaddrs(&addrs) == 0)) + { + cursor = addrs; + while (cursor != NULL) + { + if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET)) + { + // IPv4 + + struct sockaddr_in *addr = (struct sockaddr_in *)(void *)cursor->ifa_addr; + + if (strcmp(cursor->ifa_name, iface) == 0) + { + // Name match + + struct sockaddr_in nativeAddr4 = *addr; + nativeAddr4.sin_port = htons(port); + + addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; + } + else + { + char ip[INET_ADDRSTRLEN]; + + const char *conversion; + conversion = inet_ntop(AF_INET, &addr->sin_addr, ip, sizeof(ip)); + + if ((conversion != NULL) && (strcmp(ip, iface) == 0)) + { + // IP match + + struct sockaddr_in nativeAddr4 = *addr; + nativeAddr4.sin_port = htons(port); + + addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; + } + } + } + else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6)) + { + // IPv6 + + const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)cursor->ifa_addr; + + if (strcmp(cursor->ifa_name, iface) == 0) + { + // Name match + + struct sockaddr_in6 nativeAddr6 = *addr; + nativeAddr6.sin6_port = htons(port); + + addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; + } + else + { + char ip[INET6_ADDRSTRLEN]; + + const char *conversion; + conversion = inet_ntop(AF_INET6, &addr->sin6_addr, ip, sizeof(ip)); + + if ((conversion != NULL) && (strcmp(ip, iface) == 0)) + { + // IP match + + struct sockaddr_in6 nativeAddr6 = *addr; + nativeAddr6.sin6_port = htons(port); + + addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; + } + } + } + + cursor = cursor->ifa_next; + } + + freeifaddrs(addrs); + } + } + + if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4; + if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6; +} + +/** + * Converts a numeric hostname into its corresponding address. + * The hostname is expected to be an IPv4 or IPv6 address represented as a human-readable string. (e.g. 192.168.4.34) +**/ +- (void)convertNumericHost:(NSString *)numericHost + port:(uint16_t)port + intoAddress4:(NSData **)addr4Ptr + address6:(NSData **)addr6Ptr +{ + NSData *addr4 = nil; + NSData *addr6 = nil; + + if (numericHost) + { + NSString *portStr = [NSString stringWithFormat:@"%hu", port]; + + struct addrinfo hints, *res, *res0; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + hints.ai_flags = AI_NUMERICHOST; // No name resolution should be attempted + + if (getaddrinfo([numericHost UTF8String], [portStr UTF8String], &hints, &res0) == 0) + { + for (res = res0; res; res = res->ai_next) + { + if ((addr4 == nil) && (res->ai_family == AF_INET)) + { + // Found IPv4 address + // Wrap the native address structure + addr4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; + } + else if ((addr6 == nil) && (res->ai_family == AF_INET6)) + { + // Found IPv6 address + // Wrap the native address structure + addr6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; + } + } + freeaddrinfo(res0); + } + } + + if (addr4Ptr) *addr4Ptr = addr4; + if (addr6Ptr) *addr6Ptr = addr6; +} + +- (BOOL)isConnectedToAddress4:(NSData *)someAddr4 +{ + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(flags & kDidConnect, @"Not connected"); + NSAssert(cachedConnectedAddress, @"Expected cached connected address"); + + if (cachedConnectedFamily != AF_INET) + { + return NO; + } + + const struct sockaddr_in *sSockaddr4 = (const struct sockaddr_in *)[someAddr4 bytes]; + const struct sockaddr_in *cSockaddr4 = (const struct sockaddr_in *)[cachedConnectedAddress bytes]; + + if (memcmp(&sSockaddr4->sin_addr, &cSockaddr4->sin_addr, sizeof(struct in_addr)) != 0) + { + return NO; + } + if (memcmp(&sSockaddr4->sin_port, &cSockaddr4->sin_port, sizeof(in_port_t)) != 0) + { + return NO; + } + + return YES; +} + +- (BOOL)isConnectedToAddress6:(NSData *)someAddr6 +{ + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(flags & kDidConnect, @"Not connected"); + NSAssert(cachedConnectedAddress, @"Expected cached connected address"); + + if (cachedConnectedFamily != AF_INET6) + { + return NO; + } + + const struct sockaddr_in6 *sSockaddr6 = (const struct sockaddr_in6 *)[someAddr6 bytes]; + const struct sockaddr_in6 *cSockaddr6 = (const struct sockaddr_in6 *)[cachedConnectedAddress bytes]; + + if (memcmp(&sSockaddr6->sin6_addr, &cSockaddr6->sin6_addr, sizeof(struct in6_addr)) != 0) + { + return NO; + } + if (memcmp(&sSockaddr6->sin6_port, &cSockaddr6->sin6_port, sizeof(in_port_t)) != 0) + { + return NO; + } + + return YES; +} + +- (unsigned int)indexOfInterfaceAddr4:(NSData *)interfaceAddr4 +{ + if (interfaceAddr4 == nil) + return 0; + if ([interfaceAddr4 length] != sizeof(struct sockaddr_in)) + return 0; + + int result = 0; + const struct sockaddr_in *ifaceAddr = (const struct sockaddr_in *)[interfaceAddr4 bytes]; + + struct ifaddrs *addrs; + const struct ifaddrs *cursor; + + if ((getifaddrs(&addrs) == 0)) + { + cursor = addrs; + while (cursor != NULL) + { + if (cursor->ifa_addr->sa_family == AF_INET) + { + // IPv4 + + const struct sockaddr_in *addr = (const struct sockaddr_in *)(const void *)cursor->ifa_addr; + + if (memcmp(&addr->sin_addr, &ifaceAddr->sin_addr, sizeof(struct in_addr)) == 0) + { + result = if_nametoindex(cursor->ifa_name); + break; + } + } + + cursor = cursor->ifa_next; + } + + freeifaddrs(addrs); + } + + return result; +} + +- (unsigned int)indexOfInterfaceAddr6:(NSData *)interfaceAddr6 +{ + if (interfaceAddr6 == nil) + return 0; + if ([interfaceAddr6 length] != sizeof(struct sockaddr_in6)) + return 0; + + int result = 0; + const struct sockaddr_in6 *ifaceAddr = (const struct sockaddr_in6 *)[interfaceAddr6 bytes]; + + struct ifaddrs *addrs; + const struct ifaddrs *cursor; + + if ((getifaddrs(&addrs) == 0)) + { + cursor = addrs; + while (cursor != NULL) + { + if (cursor->ifa_addr->sa_family == AF_INET6) + { + // IPv6 + + const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)cursor->ifa_addr; + + if (memcmp(&addr->sin6_addr, &ifaceAddr->sin6_addr, sizeof(struct in6_addr)) == 0) + { + result = if_nametoindex(cursor->ifa_name); + break; + } + } + + cursor = cursor->ifa_next; + } + + freeifaddrs(addrs); + } + + return result; +} + +- (void)setupSendAndReceiveSourcesForSocket4 +{ + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + send4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket4FD, 0, socketQueue); + receive4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue); + + // Setup event handlers + + dispatch_source_set_event_handler(send4Source, ^{ @autoreleasepool { + + LogVerbose(@"send4EventBlock"); + LogVerbose(@"dispatch_source_get_data(send4Source) = %lu", dispatch_source_get_data(send4Source)); + + self->flags |= kSock4CanAcceptBytes; + + // If we're ready to send data, do so immediately. + // Otherwise pause the send source or it will continue to fire over and over again. + + if (self->currentSend == nil) + { + LogVerbose(@"Nothing to send"); + [self suspendSend4Source]; + } + else if (self->currentSend->resolveInProgress) + { + LogVerbose(@"currentSend - waiting for address resolve"); + [self suspendSend4Source]; + } + else if (self->currentSend->filterInProgress) + { + LogVerbose(@"currentSend - waiting on sendFilter"); + [self suspendSend4Source]; + } + else + { + [self doSend]; + } + + }}); + + dispatch_source_set_event_handler(receive4Source, ^{ @autoreleasepool { + + LogVerbose(@"receive4EventBlock"); + + self->socket4FDBytesAvailable = dispatch_source_get_data(self->receive4Source); + LogVerbose(@"socket4FDBytesAvailable: %lu", socket4FDBytesAvailable); + + if (self->socket4FDBytesAvailable > 0) + [self doReceive]; + else + [self doReceiveEOF]; + + }}); + + // Setup cancel handlers + + __block int socketFDRefCount = 2; + + int theSocketFD = socket4FD; + + #if !OS_OBJECT_USE_OBJC + dispatch_source_t theSendSource = send4Source; + dispatch_source_t theReceiveSource = receive4Source; + #endif + + dispatch_source_set_cancel_handler(send4Source, ^{ + + LogVerbose(@"send4CancelBlock"); + + #if !OS_OBJECT_USE_OBJC + LogVerbose(@"dispatch_release(send4Source)"); + dispatch_release(theSendSource); + #endif + + if (--socketFDRefCount == 0) + { + LogVerbose(@"close(socket4FD)"); + close(theSocketFD); + } + }); + + dispatch_source_set_cancel_handler(receive4Source, ^{ + + LogVerbose(@"receive4CancelBlock"); + + #if !OS_OBJECT_USE_OBJC + LogVerbose(@"dispatch_release(receive4Source)"); + dispatch_release(theReceiveSource); + #endif + + if (--socketFDRefCount == 0) + { + LogVerbose(@"close(socket4FD)"); + close(theSocketFD); + } + }); + + // We will not be able to receive until the socket is bound to a port, + // either explicitly via bind, or implicitly by connect or by sending data. + // + // But we should be able to send immediately. + + socket4FDBytesAvailable = 0; + flags |= kSock4CanAcceptBytes; + + flags |= kSend4SourceSuspended; + flags |= kReceive4SourceSuspended; +} + +- (void)setupSendAndReceiveSourcesForSocket6 +{ + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + send6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket6FD, 0, socketQueue); + receive6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket6FD, 0, socketQueue); + + // Setup event handlers + + dispatch_source_set_event_handler(send6Source, ^{ @autoreleasepool { + + LogVerbose(@"send6EventBlock"); + LogVerbose(@"dispatch_source_get_data(send6Source) = %lu", dispatch_source_get_data(send6Source)); + + self->flags |= kSock6CanAcceptBytes; + + // If we're ready to send data, do so immediately. + // Otherwise pause the send source or it will continue to fire over and over again. + + if (self->currentSend == nil) + { + LogVerbose(@"Nothing to send"); + [self suspendSend6Source]; + } + else if (self->currentSend->resolveInProgress) + { + LogVerbose(@"currentSend - waiting for address resolve"); + [self suspendSend6Source]; + } + else if (self->currentSend->filterInProgress) + { + LogVerbose(@"currentSend - waiting on sendFilter"); + [self suspendSend6Source]; + } + else + { + [self doSend]; + } + + }}); + + dispatch_source_set_event_handler(receive6Source, ^{ @autoreleasepool { + + LogVerbose(@"receive6EventBlock"); + + self->socket6FDBytesAvailable = dispatch_source_get_data(self->receive6Source); + LogVerbose(@"socket6FDBytesAvailable: %lu", socket6FDBytesAvailable); + + if (self->socket6FDBytesAvailable > 0) + [self doReceive]; + else + [self doReceiveEOF]; + + }}); + + // Setup cancel handlers + + __block int socketFDRefCount = 2; + + int theSocketFD = socket6FD; + + #if !OS_OBJECT_USE_OBJC + dispatch_source_t theSendSource = send6Source; + dispatch_source_t theReceiveSource = receive6Source; + #endif + + dispatch_source_set_cancel_handler(send6Source, ^{ + + LogVerbose(@"send6CancelBlock"); + + #if !OS_OBJECT_USE_OBJC + LogVerbose(@"dispatch_release(send6Source)"); + dispatch_release(theSendSource); + #endif + + if (--socketFDRefCount == 0) + { + LogVerbose(@"close(socket6FD)"); + close(theSocketFD); + } + }); + + dispatch_source_set_cancel_handler(receive6Source, ^{ + + LogVerbose(@"receive6CancelBlock"); + + #if !OS_OBJECT_USE_OBJC + LogVerbose(@"dispatch_release(receive6Source)"); + dispatch_release(theReceiveSource); + #endif + + if (--socketFDRefCount == 0) + { + LogVerbose(@"close(socket6FD)"); + close(theSocketFD); + } + }); + + // We will not be able to receive until the socket is bound to a port, + // either explicitly via bind, or implicitly by connect or by sending data. + // + // But we should be able to send immediately. + + socket6FDBytesAvailable = 0; + flags |= kSock6CanAcceptBytes; + + flags |= kSend6SourceSuspended; + flags |= kReceive6SourceSuspended; +} + +- (BOOL)createSocket4:(BOOL)useIPv4 socket6:(BOOL)useIPv6 error:(NSError * __autoreleasing *)errPtr +{ + LogTrace(); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(((flags & kDidCreateSockets) == 0), @"Sockets have already been created"); + + // CreateSocket Block + // This block will be invoked below. + + int(^createSocket)(int) = ^int (int domain) { + + int socketFD = socket(domain, SOCK_DGRAM, 0); + + if (socketFD == SOCKET_NULL) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error in socket() function"]; + + return SOCKET_NULL; + } + + int status; + + // Set socket options + + status = fcntl(socketFD, F_SETFL, O_NONBLOCK); + if (status == -1) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error enabling non-blocking IO on socket (fcntl)"]; + + close(socketFD); + return SOCKET_NULL; + } + + int reuseaddr = 1; + status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)); + if (status == -1) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error enabling address reuse (setsockopt)"]; + + close(socketFD); + return SOCKET_NULL; + } + + int nosigpipe = 1; + status = setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); + if (status == -1) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error disabling sigpipe (setsockopt)"]; + + close(socketFD); + return SOCKET_NULL; + } + + /** + * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535. + * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295. + * + * The default maximum size of the UDP buffer in iOS is 9216 bytes. + * + * This is the reason of #222(GCD does not necessarily return the size of an entire UDP packet) and + * #535(GCDAsyncUDPSocket can not send data when data is greater than 9K) + * + * + * Enlarge the maximum size of UDP packet. + * I can not ensure the protocol type now so that the max size is set to 65535 :) + **/ + + status = setsockopt(socketFD, SOL_SOCKET, SO_SNDBUF, (const char*)&self->maxSendSize, sizeof(int)); + if (status == -1) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error setting send buffer size (setsockopt)"]; + close(socketFD); + return SOCKET_NULL; + } + + status = setsockopt(socketFD, SOL_SOCKET, SO_RCVBUF, (const char*)&self->maxSendSize, sizeof(int)); + if (status == -1) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error setting receive buffer size (setsockopt)"]; + close(socketFD); + return SOCKET_NULL; + } + + + return socketFD; + }; + + // Create sockets depending upon given configuration. + + if (useIPv4) + { + LogVerbose(@"Creating IPv4 socket"); + + socket4FD = createSocket(AF_INET); + if (socket4FD == SOCKET_NULL) + { + // errPtr set in local createSocket() block + return NO; + } + } + + if (useIPv6) + { + LogVerbose(@"Creating IPv6 socket"); + + socket6FD = createSocket(AF_INET6); + if (socket6FD == SOCKET_NULL) + { + // errPtr set in local createSocket() block + + if (socket4FD != SOCKET_NULL) + { + close(socket4FD); + socket4FD = SOCKET_NULL; + } + + return NO; + } + } + + // Setup send and receive sources + + if (useIPv4) + [self setupSendAndReceiveSourcesForSocket4]; + if (useIPv6) + [self setupSendAndReceiveSourcesForSocket6]; + + flags |= kDidCreateSockets; + return YES; +} + +- (BOOL)createSockets:(NSError **)errPtr +{ + LogTrace(); + + BOOL useIPv4 = [self isIPv4Enabled]; + BOOL useIPv6 = [self isIPv6Enabled]; + + return [self createSocket4:useIPv4 socket6:useIPv6 error:errPtr]; +} + +- (void)suspendSend4Source +{ + if (send4Source && !(flags & kSend4SourceSuspended)) + { + LogVerbose(@"dispatch_suspend(send4Source)"); + + dispatch_suspend(send4Source); + flags |= kSend4SourceSuspended; + } +} + +- (void)suspendSend6Source +{ + if (send6Source && !(flags & kSend6SourceSuspended)) + { + LogVerbose(@"dispatch_suspend(send6Source)"); + + dispatch_suspend(send6Source); + flags |= kSend6SourceSuspended; + } +} + +- (void)resumeSend4Source +{ + if (send4Source && (flags & kSend4SourceSuspended)) + { + LogVerbose(@"dispatch_resume(send4Source)"); + + dispatch_resume(send4Source); + flags &= ~kSend4SourceSuspended; + } +} + +- (void)resumeSend6Source +{ + if (send6Source && (flags & kSend6SourceSuspended)) + { + LogVerbose(@"dispatch_resume(send6Source)"); + + dispatch_resume(send6Source); + flags &= ~kSend6SourceSuspended; + } +} + +- (void)suspendReceive4Source +{ + if (receive4Source && !(flags & kReceive4SourceSuspended)) + { + LogVerbose(@"dispatch_suspend(receive4Source)"); + + dispatch_suspend(receive4Source); + flags |= kReceive4SourceSuspended; + } +} + +- (void)suspendReceive6Source +{ + if (receive6Source && !(flags & kReceive6SourceSuspended)) + { + LogVerbose(@"dispatch_suspend(receive6Source)"); + + dispatch_suspend(receive6Source); + flags |= kReceive6SourceSuspended; + } +} + +- (void)resumeReceive4Source +{ + if (receive4Source && (flags & kReceive4SourceSuspended)) + { + LogVerbose(@"dispatch_resume(receive4Source)"); + + dispatch_resume(receive4Source); + flags &= ~kReceive4SourceSuspended; + } +} + +- (void)resumeReceive6Source +{ + if (receive6Source && (flags & kReceive6SourceSuspended)) + { + LogVerbose(@"dispatch_resume(receive6Source)"); + + dispatch_resume(receive6Source); + flags &= ~kReceive6SourceSuspended; + } +} + +- (void)closeSocket4 +{ + if (socket4FD != SOCKET_NULL) + { + LogVerbose(@"dispatch_source_cancel(send4Source)"); + dispatch_source_cancel(send4Source); + + LogVerbose(@"dispatch_source_cancel(receive4Source)"); + dispatch_source_cancel(receive4Source); + + // For some crazy reason (in my opinion), cancelling a dispatch source doesn't + // invoke the cancel handler if the dispatch source is paused. + // So we have to unpause the source if needed. + // This allows the cancel handler to be run, which in turn releases the source and closes the socket. + + [self resumeSend4Source]; + [self resumeReceive4Source]; + + // The sockets will be closed by the cancel handlers of the corresponding source + + send4Source = NULL; + receive4Source = NULL; + + socket4FD = SOCKET_NULL; + + // Clear socket states + + socket4FDBytesAvailable = 0; + flags &= ~kSock4CanAcceptBytes; + + // Clear cached info + + cachedLocalAddress4 = nil; + cachedLocalHost4 = nil; + cachedLocalPort4 = 0; + } +} + +- (void)closeSocket6 +{ + if (socket6FD != SOCKET_NULL) + { + LogVerbose(@"dispatch_source_cancel(send6Source)"); + dispatch_source_cancel(send6Source); + + LogVerbose(@"dispatch_source_cancel(receive6Source)"); + dispatch_source_cancel(receive6Source); + + // For some crazy reason (in my opinion), cancelling a dispatch source doesn't + // invoke the cancel handler if the dispatch source is paused. + // So we have to unpause the source if needed. + // This allows the cancel handler to be run, which in turn releases the source and closes the socket. + + [self resumeSend6Source]; + [self resumeReceive6Source]; + + send6Source = NULL; + receive6Source = NULL; + + // The sockets will be closed by the cancel handlers of the corresponding source + + socket6FD = SOCKET_NULL; + + // Clear socket states + + socket6FDBytesAvailable = 0; + flags &= ~kSock6CanAcceptBytes; + + // Clear cached info + + cachedLocalAddress6 = nil; + cachedLocalHost6 = nil; + cachedLocalPort6 = 0; + } +} + +- (void)closeSockets +{ + [self closeSocket4]; + [self closeSocket6]; + + flags &= ~kDidCreateSockets; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Diagnostics +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)getLocalAddress:(NSData **)dataPtr + host:(NSString **)hostPtr + port:(uint16_t *)portPtr + forSocket:(int)socketFD + withFamily:(int)socketFamily +{ + + NSData *data = nil; + NSString *host = nil; + uint16_t port = 0; + + if (socketFamily == AF_INET) + { + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); + + if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) + { + data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len]; + host = [[self class] hostFromSockaddr4:&sockaddr4]; + port = [[self class] portFromSockaddr4:&sockaddr4]; + } + else + { + LogWarn(@"Error in getsockname: %@", [self errnoError]); + } + } + else if (socketFamily == AF_INET6) + { + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); + + if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) + { + data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len]; + host = [[self class] hostFromSockaddr6:&sockaddr6]; + port = [[self class] portFromSockaddr6:&sockaddr6]; + } + else + { + LogWarn(@"Error in getsockname: %@", [self errnoError]); + } + } + + if (dataPtr) *dataPtr = data; + if (hostPtr) *hostPtr = host; + if (portPtr) *portPtr = port; + + return (data != nil); +} + +- (void)maybeUpdateCachedLocalAddress4Info +{ + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + if ( cachedLocalAddress4 || ((flags & kDidBind) == 0) || (socket4FD == SOCKET_NULL) ) + { + return; + } + + NSData *address = nil; + NSString *host = nil; + uint16_t port = 0; + + if ([self getLocalAddress:&address host:&host port:&port forSocket:socket4FD withFamily:AF_INET]) + { + + cachedLocalAddress4 = address; + cachedLocalHost4 = host; + cachedLocalPort4 = port; + } +} + +- (void)maybeUpdateCachedLocalAddress6Info +{ + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + if ( cachedLocalAddress6 || ((flags & kDidBind) == 0) || (socket6FD == SOCKET_NULL) ) + { + return; + } + + NSData *address = nil; + NSString *host = nil; + uint16_t port = 0; + + if ([self getLocalAddress:&address host:&host port:&port forSocket:socket6FD withFamily:AF_INET6]) + { + + cachedLocalAddress6 = address; + cachedLocalHost6 = host; + cachedLocalPort6 = port; + } +} + +- (NSData *)localAddress +{ + __block NSData *result = nil; + + dispatch_block_t block = ^{ + + if (self->socket4FD != SOCKET_NULL) + { + [self maybeUpdateCachedLocalAddress4Info]; + result = self->cachedLocalAddress4; + } + else + { + [self maybeUpdateCachedLocalAddress6Info]; + result = self->cachedLocalAddress6; + } + + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (NSString *)localHost +{ + __block NSString *result = nil; + + dispatch_block_t block = ^{ + + if (self->socket4FD != SOCKET_NULL) + { + [self maybeUpdateCachedLocalAddress4Info]; + result = self->cachedLocalHost4; + } + else + { + [self maybeUpdateCachedLocalAddress6Info]; + result = self->cachedLocalHost6; + } + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (uint16_t)localPort +{ + __block uint16_t result = 0; + + dispatch_block_t block = ^{ + + if (self->socket4FD != SOCKET_NULL) + { + [self maybeUpdateCachedLocalAddress4Info]; + result = self->cachedLocalPort4; + } + else + { + [self maybeUpdateCachedLocalAddress6Info]; + result = self->cachedLocalPort6; + } + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (NSData *)localAddress_IPv4 +{ + __block NSData *result = nil; + + dispatch_block_t block = ^{ + + [self maybeUpdateCachedLocalAddress4Info]; + result = self->cachedLocalAddress4; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (NSString *)localHost_IPv4 +{ + __block NSString *result = nil; + + dispatch_block_t block = ^{ + + [self maybeUpdateCachedLocalAddress4Info]; + result = self->cachedLocalHost4; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (uint16_t)localPort_IPv4 +{ + __block uint16_t result = 0; + + dispatch_block_t block = ^{ + + [self maybeUpdateCachedLocalAddress4Info]; + result = self->cachedLocalPort4; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (NSData *)localAddress_IPv6 +{ + __block NSData *result = nil; + + dispatch_block_t block = ^{ + + [self maybeUpdateCachedLocalAddress6Info]; + result = self->cachedLocalAddress6; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (NSString *)localHost_IPv6 +{ + __block NSString *result = nil; + + dispatch_block_t block = ^{ + + [self maybeUpdateCachedLocalAddress6Info]; + result = self->cachedLocalHost6; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (uint16_t)localPort_IPv6 +{ + __block uint16_t result = 0; + + dispatch_block_t block = ^{ + + [self maybeUpdateCachedLocalAddress6Info]; + result = self->cachedLocalPort6; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (void)maybeUpdateCachedConnectedAddressInfo +{ + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + if (cachedConnectedAddress || (flags & kDidConnect) == 0) + { + return; + } + + NSData *data = nil; + NSString *host = nil; + uint16_t port = 0; + int family = AF_UNSPEC; + + if (socket4FD != SOCKET_NULL) + { + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); + + if (getpeername(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) + { + data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len]; + host = [[self class] hostFromSockaddr4:&sockaddr4]; + port = [[self class] portFromSockaddr4:&sockaddr4]; + family = AF_INET; + } + else + { + LogWarn(@"Error in getpeername: %@", [self errnoError]); + } + } + else if (socket6FD != SOCKET_NULL) + { + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); + + if (getpeername(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) + { + data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len]; + host = [[self class] hostFromSockaddr6:&sockaddr6]; + port = [[self class] portFromSockaddr6:&sockaddr6]; + family = AF_INET6; + } + else + { + LogWarn(@"Error in getpeername: %@", [self errnoError]); + } + } + + + cachedConnectedAddress = data; + cachedConnectedHost = host; + cachedConnectedPort = port; + cachedConnectedFamily = family; +} + +- (NSData *)connectedAddress +{ + __block NSData *result = nil; + + dispatch_block_t block = ^{ + + [self maybeUpdateCachedConnectedAddressInfo]; + result = self->cachedConnectedAddress; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (NSString *)connectedHost +{ + __block NSString *result = nil; + + dispatch_block_t block = ^{ + + [self maybeUpdateCachedConnectedAddressInfo]; + result = self->cachedConnectedHost; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (uint16_t)connectedPort +{ + __block uint16_t result = 0; + + dispatch_block_t block = ^{ + + [self maybeUpdateCachedConnectedAddressInfo]; + result = self->cachedConnectedPort; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (BOOL)isConnected +{ + __block BOOL result = NO; + + dispatch_block_t block = ^{ + result = (self->flags & kDidConnect) ? YES : NO; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (BOOL)isClosed +{ + __block BOOL result = YES; + + dispatch_block_t block = ^{ + + result = (self->flags & kDidCreateSockets) ? NO : YES; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (BOOL)isIPv4 +{ + __block BOOL result = NO; + + dispatch_block_t block = ^{ + + if (self->flags & kDidCreateSockets) + { + result = (self->socket4FD != SOCKET_NULL); + } + else + { + result = [self isIPv4Enabled]; + } + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (BOOL)isIPv6 +{ + __block BOOL result = NO; + + dispatch_block_t block = ^{ + + if (self->flags & kDidCreateSockets) + { + result = (self->socket6FD != SOCKET_NULL); + } + else + { + result = [self isIPv6Enabled]; + } + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Binding +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * This method runs through the various checks required prior to a bind attempt. + * It is shared between the various bind methods. +**/ +- (BOOL)preBind:(NSError **)errPtr +{ + if (![self preOp:errPtr]) + { + return NO; + } + + if (flags & kDidBind) + { + if (errPtr) + { + NSString *msg = @"Cannot bind a socket more than once."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + if ((flags & kConnecting) || (flags & kDidConnect)) + { + if (errPtr) + { + NSString *msg = @"Cannot bind after connecting. If needed, bind first, then connect."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; + + if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled + { + if (errPtr) + { + NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + return YES; +} + +- (BOOL)bindToPort:(uint16_t)port error:(NSError **)errPtr +{ + return [self bindToPort:port interface:nil error:errPtr]; +} + +- (BOOL)bindToPort:(uint16_t)port interface:(NSString *)interface error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + // Run through sanity checks + + if (![self preBind:&err]) + { + return_from_block; + } + + // Check the given interface + + NSData *interface4 = nil; + NSData *interface6 = nil; + + [self convertIntefaceDescription:interface port:port intoAddress4:&interface4 address6:&interface6]; + + if ((interface4 == nil) && (interface6 == nil)) + { + NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; + err = [self badParamError:msg]; + + return_from_block; + } + + BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO; + + if (isIPv4Disabled && (interface6 == nil)) + { + NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6."; + err = [self badParamError:msg]; + + return_from_block; + } + + if (isIPv6Disabled && (interface4 == nil)) + { + NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4."; + err = [self badParamError:msg]; + + return_from_block; + } + + // Determine protocol(s) + + BOOL useIPv4 = !isIPv4Disabled && (interface4 != nil); + BOOL useIPv6 = !isIPv6Disabled && (interface6 != nil); + + // Create the socket(s) if needed + + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err]) + { + return_from_block; + } + } + + // Bind the socket(s) + + LogVerbose(@"Binding socket to port(%hu) interface(%@)", port, interface); + + if (useIPv4) + { + int status = bind(self->socket4FD, (const struct sockaddr *)[interface4 bytes], (socklen_t)[interface4 length]); + if (status == -1) + { + [self closeSockets]; + + NSString *reason = @"Error in bind() function"; + err = [self errnoErrorWithReason:reason]; + + return_from_block; + } + } + + if (useIPv6) + { + int status = bind(self->socket6FD, (const struct sockaddr *)[interface6 bytes], (socklen_t)[interface6 length]); + if (status == -1) + { + [self closeSockets]; + + NSString *reason = @"Error in bind() function"; + err = [self errnoErrorWithReason:reason]; + + return_from_block; + } + } + + // Update flags + + self->flags |= kDidBind; + + if (!useIPv4) self->flags |= kIPv4Deactivated; + if (!useIPv6) self->flags |= kIPv6Deactivated; + + result = YES; + + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (err) + LogError(@"Error binding to port/interface: %@", err); + + if (errPtr) + *errPtr = err; + + return result; +} + +- (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + // Run through sanity checks + + if (![self preBind:&err]) + { + return_from_block; + } + + // Check the given address + + int addressFamily = [[self class] familyFromAddress:localAddr]; + + if (addressFamily == AF_UNSPEC) + { + NSString *msg = @"A valid IPv4 or IPv6 address was not given"; + err = [self badParamError:msg]; + + return_from_block; + } + + NSData *localAddr4 = (addressFamily == AF_INET) ? localAddr : nil; + NSData *localAddr6 = (addressFamily == AF_INET6) ? localAddr : nil; + + BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO; + + if (isIPv4Disabled && localAddr4) + { + NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed."; + err = [self badParamError:msg]; + + return_from_block; + } + + if (isIPv6Disabled && localAddr6) + { + NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed."; + err = [self badParamError:msg]; + + return_from_block; + } + + // Determine protocol(s) + + BOOL useIPv4 = !isIPv4Disabled && (localAddr4 != nil); + BOOL useIPv6 = !isIPv6Disabled && (localAddr6 != nil); + + // Create the socket(s) if needed + + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err]) + { + return_from_block; + } + } + + // Bind the socket(s) + + if (useIPv4) + { + LogVerbose(@"Binding socket to address(%@:%hu)", + [[self class] hostFromAddress:localAddr4], + [[self class] portFromAddress:localAddr4]); + + int status = bind(self->socket4FD, (const struct sockaddr *)[localAddr4 bytes], (socklen_t)[localAddr4 length]); + if (status == -1) + { + [self closeSockets]; + + NSString *reason = @"Error in bind() function"; + err = [self errnoErrorWithReason:reason]; + + return_from_block; + } + } + else + { + LogVerbose(@"Binding socket to address(%@:%hu)", + [[self class] hostFromAddress:localAddr6], + [[self class] portFromAddress:localAddr6]); + + int status = bind(self->socket6FD, (const struct sockaddr *)[localAddr6 bytes], (socklen_t)[localAddr6 length]); + if (status == -1) + { + [self closeSockets]; + + NSString *reason = @"Error in bind() function"; + err = [self errnoErrorWithReason:reason]; + + return_from_block; + } + } + + // Update flags + + self->flags |= kDidBind; + + if (!useIPv4) self->flags |= kIPv4Deactivated; + if (!useIPv6) self->flags |= kIPv6Deactivated; + + result = YES; + + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (err) + LogError(@"Error binding to address: %@", err); + + if (errPtr) + *errPtr = err; + + return result; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Connecting +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * This method runs through the various checks required prior to a connect attempt. + * It is shared between the various connect methods. +**/ +- (BOOL)preConnect:(NSError **)errPtr +{ + if (![self preOp:errPtr]) + { + return NO; + } + + if ((flags & kConnecting) || (flags & kDidConnect)) + { + if (errPtr) + { + NSString *msg = @"Cannot connect a socket more than once."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; + + if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled + { + if (errPtr) + { + NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + return YES; +} + +- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + // Run through sanity checks. + + if (![self preConnect:&err]) + { + return_from_block; + } + + // Check parameter(s) + + if (host == nil) + { + NSString *msg = @"The host param is nil. Should be domain name or IP address string."; + err = [self badParamError:msg]; + + return_from_block; + } + + // Create the socket(s) if needed + + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } + + // Create special connect packet + + GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init]; + packet->resolveInProgress = YES; + + // Start asynchronous DNS resolve for host:port on background queue + + LogVerbose(@"Dispatching DNS resolve for connect..."); + + [self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, NSError *error) { + + // The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue, + // and immediately returns. Once the async resolve task completes, + // this block is executed on our socketQueue. + + packet->resolveInProgress = NO; + + packet->addresses = addresses; + packet->error = error; + + [self maybeConnect]; + }]; + + // Updates flags, add connect packet to send queue, and pump send queue + + self->flags |= kConnecting; + + [self->sendQueue addObject:packet]; + [self maybeDequeueSend]; + + result = YES; + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (err) + LogError(@"Error connecting to host/port: %@", err); + + if (errPtr) + *errPtr = err; + + return result; +} + +- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + // Run through sanity checks. + + if (![self preConnect:&err]) + { + return_from_block; + } + + // Check parameter(s) + + if (remoteAddr == nil) + { + NSString *msg = @"The address param is nil. Should be a valid address."; + err = [self badParamError:msg]; + + return_from_block; + } + + // Create the socket(s) if needed + + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } + + // The remoteAddr parameter could be of type NSMutableData. + // So we copy it to be safe. + + NSData *address = [remoteAddr copy]; + NSArray *addresses = [NSArray arrayWithObject:address]; + + GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init]; + packet->addresses = addresses; + + // Updates flags, add connect packet to send queue, and pump send queue + + self->flags |= kConnecting; + + [self->sendQueue addObject:packet]; + [self maybeDequeueSend]; + + result = YES; + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (err) + LogError(@"Error connecting to address: %@", err); + + if (errPtr) + *errPtr = err; + + return result; +} + +- (void)maybeConnect +{ + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + + BOOL sendQueueReady = [currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]]; + + if (sendQueueReady) + { + GCDAsyncUdpSpecialPacket *connectPacket = (GCDAsyncUdpSpecialPacket *)currentSend; + + if (connectPacket->resolveInProgress) + { + LogVerbose(@"Waiting for DNS resolve..."); + } + else + { + if (connectPacket->error) + { + [self notifyDidNotConnect:connectPacket->error]; + } + else + { + NSData *address = nil; + NSError *error = nil; + + int addressFamily = [self getAddress:&address error:&error fromAddresses:connectPacket->addresses]; + + // Perform connect + + BOOL result = NO; + + switch (addressFamily) + { + case AF_INET : result = [self connectWithAddress4:address error:&error]; break; + case AF_INET6 : result = [self connectWithAddress6:address error:&error]; break; + } + + if (result) + { + flags |= kDidBind; + flags |= kDidConnect; + + cachedConnectedAddress = address; + cachedConnectedHost = [[self class] hostFromAddress:address]; + cachedConnectedPort = [[self class] portFromAddress:address]; + cachedConnectedFamily = addressFamily; + + [self notifyDidConnectToAddress:address]; + } + else + { + [self notifyDidNotConnect:error]; + } + } + + flags &= ~kConnecting; + + [self endCurrentSend]; + [self maybeDequeueSend]; + } + } +} + +- (BOOL)connectWithAddress4:(NSData *)address4 error:(NSError **)errPtr +{ + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + int status = connect(socket4FD, (const struct sockaddr *)[address4 bytes], (socklen_t)[address4 length]); + if (status != 0) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error in connect() function"]; + + return NO; + } + + [self closeSocket6]; + flags |= kIPv6Deactivated; + + return YES; +} + +- (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr +{ + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + int status = connect(socket6FD, (const struct sockaddr *)[address6 bytes], (socklen_t)[address6 length]); + if (status != 0) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error in connect() function"]; + + return NO; + } + + [self closeSocket4]; + flags |= kIPv4Deactivated; + + return YES; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Multicast +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)preJoin:(NSError **)errPtr +{ + if (![self preOp:errPtr]) + { + return NO; + } + + if (!(flags & kDidBind)) + { + if (errPtr) + { + NSString *msg = @"Must bind a socket before joining a multicast group."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + if ((flags & kConnecting) || (flags & kDidConnect)) + { + if (errPtr) + { + NSString *msg = @"Cannot join a multicast group if connected."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + return YES; +} + +- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr +{ + return [self joinMulticastGroup:group onInterface:nil error:errPtr]; +} + +- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr +{ + // IP_ADD_MEMBERSHIP == IPV6_JOIN_GROUP + return [self performMulticastRequest:IP_ADD_MEMBERSHIP forGroup:group onInterface:interface error:errPtr]; +} + +- (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr +{ + return [self leaveMulticastGroup:group onInterface:nil error:errPtr]; +} + +- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr +{ + // IP_DROP_MEMBERSHIP == IPV6_LEAVE_GROUP + return [self performMulticastRequest:IP_DROP_MEMBERSHIP forGroup:group onInterface:interface error:errPtr]; +} + +- (BOOL)performMulticastRequest:(int)requestType + forGroup:(NSString *)group + onInterface:(NSString *)interface + error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + // Run through sanity checks + + if (![self preJoin:&err]) + { + return_from_block; + } + + // Convert group to address + + NSData *groupAddr4 = nil; + NSData *groupAddr6 = nil; + + [self convertNumericHost:group port:0 intoAddress4:&groupAddr4 address6:&groupAddr6]; + + if ((groupAddr4 == nil) && (groupAddr6 == nil)) + { + NSString *msg = @"Unknown group. Specify valid group IP address."; + err = [self badParamError:msg]; + + return_from_block; + } + + // Convert interface to address + + NSData *interfaceAddr4 = nil; + NSData *interfaceAddr6 = nil; + + [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6]; + + if ((interfaceAddr4 == nil) && (interfaceAddr6 == nil)) + { + NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; + err = [self badParamError:msg]; + + return_from_block; + } + + // Perform join + + if ((self->socket4FD != SOCKET_NULL) && groupAddr4 && interfaceAddr4) + { + const struct sockaddr_in *nativeGroup = (const struct sockaddr_in *)[groupAddr4 bytes]; + const struct sockaddr_in *nativeIface = (const struct sockaddr_in *)[interfaceAddr4 bytes]; + + struct ip_mreq imreq; + imreq.imr_multiaddr = nativeGroup->sin_addr; + imreq.imr_interface = nativeIface->sin_addr; + + int status = setsockopt(self->socket4FD, IPPROTO_IP, requestType, (const void *)&imreq, sizeof(imreq)); + if (status != 0) + { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + + return_from_block; + } + + // Using IPv4 only + [self closeSocket6]; + + result = YES; + } + else if ((self->socket6FD != SOCKET_NULL) && groupAddr6 && interfaceAddr6) + { + const struct sockaddr_in6 *nativeGroup = (const struct sockaddr_in6 *)[groupAddr6 bytes]; + + struct ipv6_mreq imreq; + imreq.ipv6mr_multiaddr = nativeGroup->sin6_addr; + imreq.ipv6mr_interface = [self indexOfInterfaceAddr6:interfaceAddr6]; + + int status = setsockopt(self->socket6FD, IPPROTO_IPV6, requestType, (const void *)&imreq, sizeof(imreq)); + if (status != 0) + { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + + return_from_block; + } + + // Using IPv6 only + [self closeSocket4]; + + result = YES; + } + else + { + NSString *msg = @"Socket, group, and interface do not have matching IP versions"; + err = [self badParamError:msg]; + + return_from_block; + } + + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (errPtr) + *errPtr = err; + + return result; +} + +- (BOOL)sendIPv4MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + if (![self preOp:&err]) + { + return_from_block; + } + + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } + + // Convert interface to address + + NSData *interfaceAddr4 = nil; + NSData *interfaceAddr6 = nil; + + [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6]; + + if (interfaceAddr4 == nil) + { + NSString *msg = @"Unknown interface. Specify valid interface by IP address."; + err = [self badParamError:msg]; + return_from_block; + } + + if (self->socket4FD != SOCKET_NULL) { + const struct sockaddr_in *nativeIface = (struct sockaddr_in *)[interfaceAddr4 bytes]; + struct in_addr interface_addr = nativeIface->sin_addr; + int status = setsockopt(self->socket4FD, IPPROTO_IP, IP_MULTICAST_IF, &interface_addr, sizeof(interface_addr)); + if (status != 0) { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + return_from_block; + } + result = YES; + } + + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (errPtr) + *errPtr = err; + + return result; +} + +- (BOOL)sendIPv6MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + if (![self preOp:&err]) + { + return_from_block; + } + + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } + + // Convert interface to address + + NSData *interfaceAddr4 = nil; + NSData *interfaceAddr6 = nil; + + [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6]; + + if (interfaceAddr6 == nil) + { + NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\")."; + err = [self badParamError:msg]; + return_from_block; + } + + if ((self->socket6FD != SOCKET_NULL)) { + uint32_t scope_id = [self indexOfInterfaceAddr6:interfaceAddr6]; + int status = setsockopt(self->socket6FD, IPPROTO_IPV6, IPV6_MULTICAST_IF, &scope_id, sizeof(scope_id)); + if (status != 0) { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + return_from_block; + } + result = YES; + } + + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (errPtr) + *errPtr = err; + + return result; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Reuse port +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + if (![self preOp:&err]) + { + return_from_block; + } + + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } + + int value = flag ? 1 : 0; + if (self->socket4FD != SOCKET_NULL) + { + int error = setsockopt(self->socket4FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value)); + + if (error) + { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + + return_from_block; + } + result = YES; + } + + if (self->socket6FD != SOCKET_NULL) + { + int error = setsockopt(self->socket6FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value)); + + if (error) + { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + + return_from_block; + } + result = YES; + } + + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (errPtr) + *errPtr = err; + + return result; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Broadcast +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + if (![self preOp:&err]) + { + return_from_block; + } + + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } + + if (self->socket4FD != SOCKET_NULL) + { + int value = flag ? 1 : 0; + int error = setsockopt(self->socket4FD, SOL_SOCKET, SO_BROADCAST, (const void *)&value, sizeof(value)); + + if (error) + { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + + return_from_block; + } + result = YES; + } + + // IPv6 does not implement broadcast, the ability to send a packet to all hosts on the attached link. + // The same effect can be achieved by sending a packet to the link-local all hosts multicast group. + + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (errPtr) + *errPtr = err; + + return result; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Sending +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)sendData:(NSData *)data withTag:(long)tag +{ + [self sendData:data withTimeout:-1.0 tag:tag]; +} + +- (void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag +{ + LogTrace(); + + if ([data length] == 0) + { + LogWarn(@"Ignoring attempt to send nil/empty data."); + return; + } + + + + GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; + + dispatch_async(socketQueue, ^{ @autoreleasepool { + + [self->sendQueue addObject:packet]; + [self maybeDequeueSend]; + }}); + +} + +- (void)sendData:(NSData *)data + toHost:(NSString *)host + port:(uint16_t)port + withTimeout:(NSTimeInterval)timeout + tag:(long)tag +{ + LogTrace(); + + if ([data length] == 0) + { + LogWarn(@"Ignoring attempt to send nil/empty data."); + return; + } + + GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; + packet->resolveInProgress = YES; + + [self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, NSError *error) { + + // The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue, + // and immediately returns. Once the async resolve task completes, + // this block is executed on our socketQueue. + + packet->resolveInProgress = NO; + + packet->resolvedAddresses = addresses; + packet->resolveError = error; + + if (packet == self->currentSend) + { + LogVerbose(@"currentSend - address resolved"); + [self doPreSend]; + } + }]; + + dispatch_async(socketQueue, ^{ @autoreleasepool { + + [self->sendQueue addObject:packet]; + [self maybeDequeueSend]; + + }}); + +} + +- (void)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag +{ + LogTrace(); + + if ([data length] == 0) + { + LogWarn(@"Ignoring attempt to send nil/empty data."); + return; + } + + GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; + packet->addressFamily = [GCDAsyncUdpSocket familyFromAddress:remoteAddr]; + packet->address = remoteAddr; + + dispatch_async(socketQueue, ^{ @autoreleasepool { + + [self->sendQueue addObject:packet]; + [self maybeDequeueSend]; + }}); +} + +- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue +{ + [self setSendFilter:filterBlock withQueue:filterQueue isAsynchronous:YES]; +} + +- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock + withQueue:(dispatch_queue_t)filterQueue + isAsynchronous:(BOOL)isAsynchronous +{ + GCDAsyncUdpSocketSendFilterBlock newFilterBlock = NULL; + dispatch_queue_t newFilterQueue = NULL; + + if (filterBlock) + { + NSAssert(filterQueue, @"Must provide a dispatch_queue in which to run the filter block."); + + newFilterBlock = [filterBlock copy]; + newFilterQueue = filterQueue; + #if !OS_OBJECT_USE_OBJC + dispatch_retain(newFilterQueue); + #endif + } + + dispatch_block_t block = ^{ + + #if !OS_OBJECT_USE_OBJC + if (self->sendFilterQueue) dispatch_release(self->sendFilterQueue); + #endif + + self->sendFilterBlock = newFilterBlock; + self->sendFilterQueue = newFilterQueue; + self->sendFilterAsync = isAsynchronous; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (void)maybeDequeueSend +{ + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + // If we don't have a send operation already in progress + if (currentSend == nil) + { + // Create the sockets if needed + if ((flags & kDidCreateSockets) == 0) + { + NSError *err = nil; + if (![self createSockets:&err]) + { + [self closeWithError:err]; + return; + } + } + + while ([sendQueue count] > 0) + { + // Dequeue the next object in the queue + currentSend = [sendQueue objectAtIndex:0]; + [sendQueue removeObjectAtIndex:0]; + + if ([currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]]) + { + [self maybeConnect]; + + return; // The maybeConnect method, if it connects, will invoke this method again + } + else if (currentSend->resolveError) + { + // Notify delegate + [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:currentSend->resolveError]; + + // Clear currentSend + currentSend = nil; + + continue; + } + else + { + // Start preprocessing checks on the send packet + [self doPreSend]; + + break; + } + } + + if ((currentSend == nil) && (flags & kCloseAfterSends)) + { + [self closeWithError:nil]; + } + } +} + +/** + * This method is called after a sendPacket has been dequeued. + * It performs various preprocessing checks on the packet, + * and queries the sendFilter (if set) to determine if the packet can be sent. + * + * If the packet passes all checks, it will be passed on to the doSend method. +**/ +- (void)doPreSend +{ + LogTrace(); + + // + // 1. Check for problems with send packet + // + + BOOL waitingForResolve = NO; + NSError *error = nil; + + if (flags & kDidConnect) + { + // Connected socket + + if (currentSend->resolveInProgress || currentSend->resolvedAddresses || currentSend->resolveError) + { + NSString *msg = @"Cannot specify destination of packet for connected socket"; + error = [self badConfigError:msg]; + } + else + { + currentSend->address = cachedConnectedAddress; + currentSend->addressFamily = cachedConnectedFamily; + } + } + else + { + // Non-Connected socket + + if (currentSend->resolveInProgress) + { + // We're waiting for the packet's destination to be resolved. + waitingForResolve = YES; + } + else if (currentSend->resolveError) + { + error = currentSend->resolveError; + } + else if (currentSend->address == nil) + { + if (currentSend->resolvedAddresses == nil) + { + NSString *msg = @"You must specify destination of packet for a non-connected socket"; + error = [self badConfigError:msg]; + } + else + { + // Pick the proper address to use (out of possibly several resolved addresses) + + NSData *address = nil; + int addressFamily = AF_UNSPEC; + + addressFamily = [self getAddress:&address error:&error fromAddresses:currentSend->resolvedAddresses]; + + currentSend->address = address; + currentSend->addressFamily = addressFamily; + } + } + } + + if (waitingForResolve) + { + // We're waiting for the packet's destination to be resolved. + + LogVerbose(@"currentSend - waiting for address resolve"); + + if (flags & kSock4CanAcceptBytes) { + [self suspendSend4Source]; + } + if (flags & kSock6CanAcceptBytes) { + [self suspendSend6Source]; + } + + return; + } + + if (error) + { + // Unable to send packet due to some error. + // Notify delegate and move on. + + [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:error]; + [self endCurrentSend]; + [self maybeDequeueSend]; + + return; + } + + // + // 2. Query sendFilter (if applicable) + // + + if (sendFilterBlock && sendFilterQueue) + { + // Query sendFilter + + if (sendFilterAsync) + { + // Scenario 1 of 3 - Need to asynchronously query sendFilter + + currentSend->filterInProgress = YES; + GCDAsyncUdpSendPacket *sendPacket = currentSend; + + dispatch_async(sendFilterQueue, ^{ @autoreleasepool { + + BOOL allowed = self->sendFilterBlock(sendPacket->buffer, sendPacket->address, sendPacket->tag); + + dispatch_async(self->socketQueue, ^{ @autoreleasepool { + + sendPacket->filterInProgress = NO; + if (sendPacket == self->currentSend) + { + if (allowed) + { + [self doSend]; + } + else + { + LogVerbose(@"currentSend - silently dropped by sendFilter"); + + [self notifyDidSendDataWithTag:self->currentSend->tag]; + [self endCurrentSend]; + [self maybeDequeueSend]; + } + } + }}); + }}); + } + else + { + // Scenario 2 of 3 - Need to synchronously query sendFilter + + __block BOOL allowed = YES; + + dispatch_sync(sendFilterQueue, ^{ @autoreleasepool { + + allowed = self->sendFilterBlock(self->currentSend->buffer, self->currentSend->address, self->currentSend->tag); + }}); + + if (allowed) + { + [self doSend]; + } + else + { + LogVerbose(@"currentSend - silently dropped by sendFilter"); + + [self notifyDidSendDataWithTag:currentSend->tag]; + [self endCurrentSend]; + [self maybeDequeueSend]; + } + } + } + else // if (!sendFilterBlock || !sendFilterQueue) + { + // Scenario 3 of 3 - No sendFilter. Just go straight into sending. + + [self doSend]; + } +} + +/** + * This method performs the actual sending of data in the currentSend packet. + * It should only be called if the +**/ +- (void)doSend +{ + LogTrace(); + + NSAssert(currentSend != nil, @"Invalid logic"); + + // Perform the actual send + + ssize_t result = 0; + + if (flags & kDidConnect) + { + // Connected socket + + const void *buffer = [currentSend->buffer bytes]; + size_t length = (size_t)[currentSend->buffer length]; + + if (currentSend->addressFamily == AF_INET) + { + result = send(socket4FD, buffer, length, 0); + LogVerbose(@"send(socket4FD) = %d", result); + } + else + { + result = send(socket6FD, buffer, length, 0); + LogVerbose(@"send(socket6FD) = %d", result); + } + } + else + { + // Non-Connected socket + + const void *buffer = [currentSend->buffer bytes]; + size_t length = (size_t)[currentSend->buffer length]; + + const void *dst = [currentSend->address bytes]; + socklen_t dstSize = (socklen_t)[currentSend->address length]; + + if (currentSend->addressFamily == AF_INET) + { + result = sendto(socket4FD, buffer, length, 0, dst, dstSize); + LogVerbose(@"sendto(socket4FD) = %d", result); + } + else + { + result = sendto(socket6FD, buffer, length, 0, dst, dstSize); + LogVerbose(@"sendto(socket6FD) = %d", result); + } + } + + // If the socket wasn't bound before, it is now + + if ((flags & kDidBind) == 0) + { + flags |= kDidBind; + } + + // Check the results. + // + // From the send() & sendto() manpage: + // + // Upon successful completion, the number of bytes which were sent is returned. + // Otherwise, -1 is returned and the global variable errno is set to indicate the error. + + BOOL waitingForSocket = NO; + NSError *socketError = nil; + + if (result == 0) + { + waitingForSocket = YES; + } + else if (result < 0) + { + if (errno == EAGAIN) + waitingForSocket = YES; + else + socketError = [self errnoErrorWithReason:@"Error in send() function."]; + } + + if (waitingForSocket) + { + // Not enough room in the underlying OS socket send buffer. + // Wait for a notification of available space. + + LogVerbose(@"currentSend - waiting for socket"); + + if (!(flags & kSock4CanAcceptBytes)) { + [self resumeSend4Source]; + } + if (!(flags & kSock6CanAcceptBytes)) { + [self resumeSend6Source]; + } + + if ((sendTimer == NULL) && (currentSend->timeout >= 0.0)) + { + // Unable to send packet right away. + // Start timer to timeout the send operation. + + [self setupSendTimerWithTimeout:currentSend->timeout]; + } + } + else if (socketError) + { + [self closeWithError:socketError]; + } + else // done + { + [self notifyDidSendDataWithTag:currentSend->tag]; + [self endCurrentSend]; + [self maybeDequeueSend]; + } +} + +/** + * Releases all resources associated with the currentSend. +**/ +- (void)endCurrentSend +{ + if (sendTimer) + { + dispatch_source_cancel(sendTimer); + #if !OS_OBJECT_USE_OBJC + dispatch_release(sendTimer); + #endif + sendTimer = NULL; + } + + currentSend = nil; +} + +/** + * Performs the operations to timeout the current send operation, and move on. +**/ +- (void)doSendTimeout +{ + LogTrace(); + + [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:[self sendTimeoutError]]; + [self endCurrentSend]; + [self maybeDequeueSend]; +} + +/** + * Sets up a timer that fires to timeout the current send operation. + * This method should only be called once per send packet. +**/ +- (void)setupSendTimerWithTimeout:(NSTimeInterval)timeout +{ + NSAssert(sendTimer == NULL, @"Invalid logic"); + NSAssert(timeout >= 0.0, @"Invalid logic"); + + LogTrace(); + + sendTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); + + dispatch_source_set_event_handler(sendTimer, ^{ @autoreleasepool { + + [self doSendTimeout]; + }}); + + dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)); + + dispatch_source_set_timer(sendTimer, tt, DISPATCH_TIME_FOREVER, 0); + dispatch_resume(sendTimer); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Receiving +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)receiveOnce:(NSError **)errPtr +{ + LogTrace(); + + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ + + if ((self->flags & kReceiveOnce) == 0) + { + if ((self->flags & kDidCreateSockets) == 0) + { + NSString *msg = @"Must bind socket before you can receive data. " + @"You can do this explicitly via bind, or implicitly via connect or by sending data."; + + err = [self badConfigError:msg]; + return_from_block; + } + + self->flags |= kReceiveOnce; // Enable + self->flags &= ~kReceiveContinuous; // Disable + + dispatch_async(self->socketQueue, ^{ @autoreleasepool { + + [self doReceive]; + }}); + } + + result = YES; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (err) + LogError(@"Error in beginReceiving: %@", err); + + if (errPtr) + *errPtr = err; + + return result; +} + +- (BOOL)beginReceiving:(NSError **)errPtr +{ + LogTrace(); + + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ + + if ((self->flags & kReceiveContinuous) == 0) + { + if ((self->flags & kDidCreateSockets) == 0) + { + NSString *msg = @"Must bind socket before you can receive data. " + @"You can do this explicitly via bind, or implicitly via connect or by sending data."; + + err = [self badConfigError:msg]; + return_from_block; + } + + self->flags |= kReceiveContinuous; // Enable + self->flags &= ~kReceiveOnce; // Disable + + dispatch_async(self->socketQueue, ^{ @autoreleasepool { + + [self doReceive]; + }}); + } + + result = YES; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (err) + LogError(@"Error in beginReceiving: %@", err); + + if (errPtr) + *errPtr = err; + + return result; +} + +- (void)pauseReceiving +{ + LogTrace(); + + dispatch_block_t block = ^{ + + self->flags &= ~kReceiveOnce; // Disable + self->flags &= ~kReceiveContinuous; // Disable + + if (self->socket4FDBytesAvailable > 0) { + [self suspendReceive4Source]; + } + if (self->socket6FDBytesAvailable > 0) { + [self suspendReceive6Source]; + } + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue +{ + [self setReceiveFilter:filterBlock withQueue:filterQueue isAsynchronous:YES]; +} + +- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock + withQueue:(dispatch_queue_t)filterQueue + isAsynchronous:(BOOL)isAsynchronous +{ + GCDAsyncUdpSocketReceiveFilterBlock newFilterBlock = NULL; + dispatch_queue_t newFilterQueue = NULL; + + if (filterBlock) + { + NSAssert(filterQueue, @"Must provide a dispatch_queue in which to run the filter block."); + + newFilterBlock = [filterBlock copy]; + newFilterQueue = filterQueue; + #if !OS_OBJECT_USE_OBJC + dispatch_retain(newFilterQueue); + #endif + } + + dispatch_block_t block = ^{ + + #if !OS_OBJECT_USE_OBJC + if (self->receiveFilterQueue) dispatch_release(self->receiveFilterQueue); + #endif + + self->receiveFilterBlock = newFilterBlock; + self->receiveFilterQueue = newFilterQueue; + self->receiveFilterAsync = isAsynchronous; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (void)doReceive +{ + LogTrace(); + + if ((flags & (kReceiveOnce | kReceiveContinuous)) == 0) + { + LogVerbose(@"Receiving is paused..."); + + if (socket4FDBytesAvailable > 0) { + [self suspendReceive4Source]; + } + if (socket6FDBytesAvailable > 0) { + [self suspendReceive6Source]; + } + + return; + } + + if ((flags & kReceiveOnce) && (pendingFilterOperations > 0)) + { + LogVerbose(@"Receiving is temporarily paused (pending filter operations)..."); + + if (socket4FDBytesAvailable > 0) { + [self suspendReceive4Source]; + } + if (socket6FDBytesAvailable > 0) { + [self suspendReceive6Source]; + } + + return; + } + + if ((socket4FDBytesAvailable == 0) && (socket6FDBytesAvailable == 0)) + { + LogVerbose(@"No data available to receive..."); + + if (socket4FDBytesAvailable == 0) { + [self resumeReceive4Source]; + } + if (socket6FDBytesAvailable == 0) { + [self resumeReceive6Source]; + } + + return; + } + + // Figure out if we should receive on socket4 or socket6 + + BOOL doReceive4; + + if (flags & kDidConnect) + { + // Connected socket + + doReceive4 = (socket4FD != SOCKET_NULL); + } + else + { + // Non-Connected socket + + if (socket4FDBytesAvailable > 0) + { + if (socket6FDBytesAvailable > 0) + { + // Bytes available on socket4 & socket6 + + doReceive4 = (flags & kFlipFlop) ? YES : NO; + + flags ^= kFlipFlop; // flags = flags xor kFlipFlop; (toggle flip flop bit) + } + else { + // Bytes available on socket4, but not socket6 + doReceive4 = YES; + } + } + else { + // Bytes available on socket6, but not socket4 + doReceive4 = NO; + } + } + + // Perform socket IO + + ssize_t result = 0; + + NSData *data = nil; + NSData *addr4 = nil; + NSData *addr6 = nil; + + if (doReceive4) + { + NSAssert(socket4FDBytesAvailable > 0, @"Invalid logic"); + LogVerbose(@"Receiving on IPv4"); + + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); + + // #222: GCD does not necessarily return the size of an entire UDP packet + // from dispatch_source_get_data(), so we must use the maximum packet size. + size_t bufSize = max4ReceiveSize; + void *buf = malloc(bufSize); + + result = recvfrom(socket4FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr4, &sockaddr4len); + LogVerbose(@"recvfrom(socket4FD) = %i", (int)result); + + if (result > 0) + { + if ((size_t)result >= socket4FDBytesAvailable) + socket4FDBytesAvailable = 0; + else + socket4FDBytesAvailable -= result; + + if ((size_t)result != bufSize) { + buf = realloc(buf, result); + } + + data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES]; + addr4 = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len]; + } + else + { + LogVerbose(@"recvfrom(socket4FD) = %@", [self errnoError]); + socket4FDBytesAvailable = 0; + free(buf); + } + } + else + { + NSAssert(socket6FDBytesAvailable > 0, @"Invalid logic"); + LogVerbose(@"Receiving on IPv6"); + + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); + + // #222: GCD does not necessarily return the size of an entire UDP packet + // from dispatch_source_get_data(), so we must use the maximum packet size. + size_t bufSize = max6ReceiveSize; + void *buf = malloc(bufSize); + + result = recvfrom(socket6FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr6, &sockaddr6len); + LogVerbose(@"recvfrom(socket6FD) -> %i", (int)result); + + if (result > 0) + { + if ((size_t)result >= socket6FDBytesAvailable) + socket6FDBytesAvailable = 0; + else + socket6FDBytesAvailable -= result; + + if ((size_t)result != bufSize) { + buf = realloc(buf, result); + } + + data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES]; + addr6 = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len]; + } + else + { + LogVerbose(@"recvfrom(socket6FD) = %@", [self errnoError]); + socket6FDBytesAvailable = 0; + free(buf); + } + } + + + BOOL waitingForSocket = NO; + BOOL notifiedDelegate = NO; + BOOL ignored = NO; + + NSError *socketError = nil; + + if (result == 0) + { + waitingForSocket = YES; + } + else if (result < 0) + { + if (errno == EAGAIN) + waitingForSocket = YES; + else + socketError = [self errnoErrorWithReason:@"Error in recvfrom() function"]; + } + else + { + if (flags & kDidConnect) + { + if (addr4 && ![self isConnectedToAddress4:addr4]) + ignored = YES; + if (addr6 && ![self isConnectedToAddress6:addr6]) + ignored = YES; + } + + NSData *addr = (addr4 != nil) ? addr4 : addr6; + + if (!ignored) + { + if (receiveFilterBlock && receiveFilterQueue) + { + // Run data through filter, and if approved, notify delegate + + __block id filterContext = nil; + __block BOOL allowed = NO; + + if (receiveFilterAsync) + { + pendingFilterOperations++; + dispatch_async(receiveFilterQueue, ^{ @autoreleasepool { + + allowed = self->receiveFilterBlock(data, addr, &filterContext); + + // Transition back to socketQueue to get the current delegate / delegateQueue + dispatch_async(self->socketQueue, ^{ @autoreleasepool { + + self->pendingFilterOperations--; + + if (allowed) + { + [self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext]; + } + else + { + LogVerbose(@"received packet silently dropped by receiveFilter"); + } + + if (self->flags & kReceiveOnce) + { + if (allowed) + { + // The delegate has been notified, + // so our receive once operation has completed. + self->flags &= ~kReceiveOnce; + } + else if (self->pendingFilterOperations == 0) + { + // All pending filter operations have completed, + // and none were allowed through. + // Our receive once operation hasn't completed yet. + [self doReceive]; + } + } + }}); + }}); + } + else // if (!receiveFilterAsync) + { + dispatch_sync(receiveFilterQueue, ^{ @autoreleasepool { + + allowed = self->receiveFilterBlock(data, addr, &filterContext); + }}); + + if (allowed) + { + [self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext]; + notifiedDelegate = YES; + } + else + { + LogVerbose(@"received packet silently dropped by receiveFilter"); + ignored = YES; + } + } + } + else // if (!receiveFilterBlock || !receiveFilterQueue) + { + [self notifyDidReceiveData:data fromAddress:addr withFilterContext:nil]; + notifiedDelegate = YES; + } + } + } + + if (waitingForSocket) + { + // Wait for a notification of available data. + + if (socket4FDBytesAvailable == 0) { + [self resumeReceive4Source]; + } + if (socket6FDBytesAvailable == 0) { + [self resumeReceive6Source]; + } + } + else if (socketError) + { + [self closeWithError:socketError]; + } + else + { + if (flags & kReceiveContinuous) + { + // Continuous receive mode + [self doReceive]; + } + else + { + // One-at-a-time receive mode + if (notifiedDelegate) + { + // The delegate has been notified (no set filter). + // So our receive once operation has completed. + flags &= ~kReceiveOnce; + } + else if (ignored) + { + [self doReceive]; + } + else + { + // Waiting on asynchronous receive filter... + } + } + } +} + +- (void)doReceiveEOF +{ + LogTrace(); + + [self closeWithError:[self socketClosedError]]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Closing +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)closeWithError:(NSError *)error +{ + LogVerbose(@"closeWithError: %@", error); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + if (currentSend) [self endCurrentSend]; + + [sendQueue removeAllObjects]; + + // If a socket has been created, we should notify the delegate. + BOOL shouldCallDelegate = (flags & kDidCreateSockets) ? YES : NO; + + // Close all sockets, send/receive sources, cfstreams, etc +#if TARGET_OS_IPHONE + [self removeStreamsFromRunLoop]; + [self closeReadAndWriteStreams]; +#endif + [self closeSockets]; + + // Clear all flags (config remains as is) + flags = 0; + + if (shouldCallDelegate) + { + [self notifyDidCloseWithError:error]; + } +} + +- (void)close +{ + LogTrace(); + + dispatch_block_t block = ^{ @autoreleasepool { + + [self closeWithError:nil]; + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); +} + +- (void)closeAfterSending +{ + LogTrace(); + + dispatch_block_t block = ^{ @autoreleasepool { + + self->flags |= kCloseAfterSends; + + if (self->currentSend == nil && [self->sendQueue count] == 0) + { + [self closeWithError:nil]; + } + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark CFStream +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#if TARGET_OS_IPHONE + +static NSThread *listenerThread; + ++ (void)ignore:(id)_ +{} + ++ (void)startListenerThreadIfNeeded +{ + static dispatch_once_t predicate; + dispatch_once(&predicate, ^{ + + listenerThread = [[NSThread alloc] initWithTarget:self + selector:@selector(listenerThread:) + object:nil]; + [listenerThread start]; + }); +} + ++ (void)listenerThread:(id)unused +{ + @autoreleasepool { + + [[NSThread currentThread] setName:GCDAsyncUdpSocketThreadName]; + + LogInfo(@"ListenerThread: Started"); + + // We can't run the run loop unless it has an associated input source or a timer. + // So we'll just create a timer that will never fire - unless the server runs for a decades. + [NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow] + target:self + selector:@selector(ignore:) + userInfo:nil + repeats:YES]; + + [[NSRunLoop currentRunLoop] run]; + + LogInfo(@"ListenerThread: Stopped"); + } +} + ++ (void)addStreamListener:(GCDAsyncUdpSocket *)asyncUdpSocket +{ + LogTrace(); + NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread"); + + CFRunLoopRef runLoop = CFRunLoopGetCurrent(); + + if (asyncUdpSocket->readStream4) + CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream4, runLoop, kCFRunLoopDefaultMode); + + if (asyncUdpSocket->readStream6) + CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream6, runLoop, kCFRunLoopDefaultMode); + + if (asyncUdpSocket->writeStream4) + CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream4, runLoop, kCFRunLoopDefaultMode); + + if (asyncUdpSocket->writeStream6) + CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream6, runLoop, kCFRunLoopDefaultMode); +} + ++ (void)removeStreamListener:(GCDAsyncUdpSocket *)asyncUdpSocket +{ + LogTrace(); + NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread"); + + CFRunLoopRef runLoop = CFRunLoopGetCurrent(); + + if (asyncUdpSocket->readStream4) + CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream4, runLoop, kCFRunLoopDefaultMode); + + if (asyncUdpSocket->readStream6) + CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream6, runLoop, kCFRunLoopDefaultMode); + + if (asyncUdpSocket->writeStream4) + CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream4, runLoop, kCFRunLoopDefaultMode); + + if (asyncUdpSocket->writeStream6) + CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream6, runLoop, kCFRunLoopDefaultMode); +} + +static void CFReadStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void *pInfo) +{ + @autoreleasepool { + GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo; + + switch(type) + { + case kCFStreamEventOpenCompleted: + { + LogCVerbose(@"CFReadStreamCallback - Open"); + break; + } + case kCFStreamEventHasBytesAvailable: + { + LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable"); + break; + } + case kCFStreamEventErrorOccurred: + case kCFStreamEventEndEncountered: + { + NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream); + if (error == nil && type == kCFStreamEventEndEncountered) + { + error = [asyncUdpSocket socketClosedError]; + } + + dispatch_async(asyncUdpSocket->socketQueue, ^{ @autoreleasepool { + + LogCVerbose(@"CFReadStreamCallback - %@", + (type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered"); + + if (stream != asyncUdpSocket->readStream4 && + stream != asyncUdpSocket->readStream6 ) + { + LogCVerbose(@"CFReadStreamCallback - Ignored"); + return_from_block; + } + + [asyncUdpSocket closeWithError:error]; + + }}); + + break; + } + default: + { + LogCError(@"CFReadStreamCallback - UnknownType: %i", (int)type); + } + } + } +} + +static void CFWriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void *pInfo) +{ + @autoreleasepool { + GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo; + + switch(type) + { + case kCFStreamEventOpenCompleted: + { + LogCVerbose(@"CFWriteStreamCallback - Open"); + break; + } + case kCFStreamEventCanAcceptBytes: + { + LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes"); + break; + } + case kCFStreamEventErrorOccurred: + case kCFStreamEventEndEncountered: + { + NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream); + if (error == nil && type == kCFStreamEventEndEncountered) + { + error = [asyncUdpSocket socketClosedError]; + } + + dispatch_async(asyncUdpSocket->socketQueue, ^{ @autoreleasepool { + + LogCVerbose(@"CFWriteStreamCallback - %@", + (type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered"); + + if (stream != asyncUdpSocket->writeStream4 && + stream != asyncUdpSocket->writeStream6 ) + { + LogCVerbose(@"CFWriteStreamCallback - Ignored"); + return_from_block; + } + + [asyncUdpSocket closeWithError:error]; + + }}); + + break; + } + default: + { + LogCError(@"CFWriteStreamCallback - UnknownType: %i", (int)type); + } + } + } +} + +- (BOOL)createReadAndWriteStreams:(NSError **)errPtr +{ + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + NSError *err = nil; + + if (readStream4 || writeStream4 || readStream6 || writeStream6) + { + // Streams already created + return YES; + } + + if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL) + { + err = [self otherError:@"Cannot create streams without a file descriptor"]; + goto Failed; + } + + // Create streams + + LogVerbose(@"Creating read and write stream(s)..."); + + if (socket4FD != SOCKET_NULL) + { + CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socket4FD, &readStream4, &writeStream4); + if (!readStream4 || !writeStream4) + { + err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv4]"]; + goto Failed; + } + } + + if (socket6FD != SOCKET_NULL) + { + CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socket6FD, &readStream6, &writeStream6); + if (!readStream6 || !writeStream6) + { + err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv6]"]; + goto Failed; + } + } + + // Ensure the CFStream's don't close our underlying socket + + CFReadStreamSetProperty(readStream4, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); + CFWriteStreamSetProperty(writeStream4, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); + + CFReadStreamSetProperty(readStream6, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); + CFWriteStreamSetProperty(writeStream6, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); + + return YES; + +Failed: + if (readStream4) + { + CFReadStreamClose(readStream4); + CFRelease(readStream4); + readStream4 = NULL; + } + if (writeStream4) + { + CFWriteStreamClose(writeStream4); + CFRelease(writeStream4); + writeStream4 = NULL; + } + if (readStream6) + { + CFReadStreamClose(readStream6); + CFRelease(readStream6); + readStream6 = NULL; + } + if (writeStream6) + { + CFWriteStreamClose(writeStream6); + CFRelease(writeStream6); + writeStream6 = NULL; + } + + if (errPtr) + *errPtr = err; + + return NO; +} + +- (BOOL)registerForStreamCallbacks:(NSError **)errPtr +{ + LogTrace(); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null"); + + NSError *err = nil; + + streamContext.version = 0; + streamContext.info = (__bridge void *)self; + streamContext.retain = nil; + streamContext.release = nil; + streamContext.copyDescription = nil; + + CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; + CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; + +// readStreamEvents |= (kCFStreamEventOpenCompleted | kCFStreamEventHasBytesAvailable); +// writeStreamEvents |= (kCFStreamEventOpenCompleted | kCFStreamEventCanAcceptBytes); + + if (socket4FD != SOCKET_NULL) + { + if (readStream4 == NULL || writeStream4 == NULL) + { + err = [self otherError:@"Read/Write stream4 is null"]; + goto Failed; + } + + BOOL r1 = CFReadStreamSetClient(readStream4, readStreamEvents, &CFReadStreamCallback, &streamContext); + BOOL r2 = CFWriteStreamSetClient(writeStream4, writeStreamEvents, &CFWriteStreamCallback, &streamContext); + + if (!r1 || !r2) + { + err = [self otherError:@"Error in CFStreamSetClient(), [IPv4]"]; + goto Failed; + } + } + + if (socket6FD != SOCKET_NULL) + { + if (readStream6 == NULL || writeStream6 == NULL) + { + err = [self otherError:@"Read/Write stream6 is null"]; + goto Failed; + } + + BOOL r1 = CFReadStreamSetClient(readStream6, readStreamEvents, &CFReadStreamCallback, &streamContext); + BOOL r2 = CFWriteStreamSetClient(writeStream6, writeStreamEvents, &CFWriteStreamCallback, &streamContext); + + if (!r1 || !r2) + { + err = [self otherError:@"Error in CFStreamSetClient() [IPv6]"]; + goto Failed; + } + } + + return YES; + +Failed: + if (readStream4) { + CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL); + } + if (writeStream4) { + CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL); + } + if (readStream6) { + CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL); + } + if (writeStream6) { + CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL); + } + + if (errPtr) *errPtr = err; + return NO; +} + +- (BOOL)addStreamsToRunLoop:(NSError **)errPtr +{ + LogTrace(); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null"); + + if (!(flags & kAddedStreamListener)) + { + [[self class] startListenerThreadIfNeeded]; + [[self class] performSelector:@selector(addStreamListener:) + onThread:listenerThread + withObject:self + waitUntilDone:YES]; + + flags |= kAddedStreamListener; + } + + return YES; +} + +- (BOOL)openStreams:(NSError **)errPtr +{ + LogTrace(); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null"); + + NSError *err = nil; + + if (socket4FD != SOCKET_NULL) + { + BOOL r1 = CFReadStreamOpen(readStream4); + BOOL r2 = CFWriteStreamOpen(writeStream4); + + if (!r1 || !r2) + { + err = [self otherError:@"Error in CFStreamOpen() [IPv4]"]; + goto Failed; + } + } + + if (socket6FD != SOCKET_NULL) + { + BOOL r1 = CFReadStreamOpen(readStream6); + BOOL r2 = CFWriteStreamOpen(writeStream6); + + if (!r1 || !r2) + { + err = [self otherError:@"Error in CFStreamOpen() [IPv6]"]; + goto Failed; + } + } + + return YES; + +Failed: + if (errPtr) *errPtr = err; + return NO; +} + +- (void)removeStreamsFromRunLoop +{ + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + if (flags & kAddedStreamListener) + { + [[self class] performSelector:@selector(removeStreamListener:) + onThread:listenerThread + withObject:self + waitUntilDone:YES]; + + flags &= ~kAddedStreamListener; + } +} + +- (void)closeReadAndWriteStreams +{ + LogTrace(); + + if (readStream4) + { + CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL); + CFReadStreamClose(readStream4); + CFRelease(readStream4); + readStream4 = NULL; + } + if (writeStream4) + { + CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL); + CFWriteStreamClose(writeStream4); + CFRelease(writeStream4); + writeStream4 = NULL; + } + if (readStream6) + { + CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL); + CFReadStreamClose(readStream6); + CFRelease(readStream6); + readStream6 = NULL; + } + if (writeStream6) + { + CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL); + CFWriteStreamClose(writeStream6); + CFRelease(writeStream6); + writeStream6 = NULL; + } +} + +#endif + +#if TARGET_OS_IPHONE +- (void)applicationWillEnterForeground:(NSNotification *)notification +{ + LogTrace(); + + // If the application was backgrounded, then iOS may have shut down our sockets. + // So we take a quick look to see if any of them received an EOF. + + dispatch_block_t block = ^{ @autoreleasepool { + + [self resumeReceive4Source]; + [self resumeReceive6Source]; + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Advanced +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * See header file for big discussion of this method. + **/ +- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketNewTargetQueue +{ + void *nonNullUnusedPointer = (__bridge void *)self; + dispatch_queue_set_specific(socketNewTargetQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL); +} + +/** + * See header file for big discussion of this method. + **/ +- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketOldTargetQueue +{ + dispatch_queue_set_specific(socketOldTargetQueue, IsOnSocketQueueOrTargetQueueKey, NULL, NULL); +} + +- (void)performBlock:(dispatch_block_t)block +{ + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); +} + +- (int)socketFD +{ + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", + THIS_FILE, THIS_METHOD); + return SOCKET_NULL; + } + + if (socket4FD != SOCKET_NULL) + return socket4FD; + else + return socket6FD; +} + +- (int)socket4FD +{ + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", + THIS_FILE, THIS_METHOD); + return SOCKET_NULL; + } + + return socket4FD; +} + +- (int)socket6FD +{ + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", + THIS_FILE, THIS_METHOD); + return SOCKET_NULL; + } + + return socket6FD; +} + +#if TARGET_OS_IPHONE + +- (CFReadStreamRef)readStream +{ + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", + THIS_FILE, THIS_METHOD); + return NULL; + } + + NSError *err = nil; + if (![self createReadAndWriteStreams:&err]) + { + LogError(@"Error creating CFStream(s): %@", err); + return NULL; + } + + // Todo... + + if (readStream4) + return readStream4; + else + return readStream6; +} + +- (CFWriteStreamRef)writeStream +{ + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", + THIS_FILE, THIS_METHOD); + return NULL; + } + + NSError *err = nil; + if (![self createReadAndWriteStreams:&err]) + { + LogError(@"Error creating CFStream(s): %@", err); + return NULL; + } + + if (writeStream4) + return writeStream4; + else + return writeStream6; +} + +- (BOOL)enableBackgroundingOnSockets +{ + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", + THIS_FILE, THIS_METHOD); + return NO; + } + + // Why is this commented out? + // See comments below. + +// NSError *err = nil; +// if (![self createReadAndWriteStreams:&err]) +// { +// LogError(@"Error creating CFStream(s): %@", err); +// return NO; +// } +// +// LogVerbose(@"Enabling backgrouding on socket"); +// +// BOOL r1, r2; +// +// if (readStream4 && writeStream4) +// { +// r1 = CFReadStreamSetProperty(readStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); +// r2 = CFWriteStreamSetProperty(writeStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); +// +// if (!r1 || !r2) +// { +// LogError(@"Error setting voip type (IPv4)"); +// return NO; +// } +// } +// +// if (readStream6 && writeStream6) +// { +// r1 = CFReadStreamSetProperty(readStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); +// r2 = CFWriteStreamSetProperty(writeStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); +// +// if (!r1 || !r2) +// { +// LogError(@"Error setting voip type (IPv6)"); +// return NO; +// } +// } +// +// return YES; + + // The above code will actually appear to work. + // The methods will return YES, and everything will appear fine. + // + // One tiny problem: the sockets will still get closed when the app gets backgrounded. + // + // Apple does not officially support backgrounding UDP sockets. + + return NO; +} + +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Class Methods +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ++ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 +{ + char addrBuf[INET_ADDRSTRLEN]; + + if (inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) + { + addrBuf[0] = '\0'; + } + + return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; +} + ++ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 +{ + char addrBuf[INET6_ADDRSTRLEN]; + + if (inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) + { + addrBuf[0] = '\0'; + } + + return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; +} + ++ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 +{ + return ntohs(pSockaddr4->sin_port); +} + ++ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 +{ + return ntohs(pSockaddr6->sin6_port); +} + ++ (NSString *)hostFromAddress:(NSData *)address +{ + NSString *host = nil; + [self getHost:&host port:NULL family:NULL fromAddress:address]; + + return host; +} + ++ (uint16_t)portFromAddress:(NSData *)address +{ + uint16_t port = 0; + [self getHost:NULL port:&port family:NULL fromAddress:address]; + + return port; +} + ++ (int)familyFromAddress:(NSData *)address +{ + int af = AF_UNSPEC; + [self getHost:NULL port:NULL family:&af fromAddress:address]; + + return af; +} + ++ (BOOL)isIPv4Address:(NSData *)address +{ + int af = AF_UNSPEC; + [self getHost:NULL port:NULL family:&af fromAddress:address]; + + return (af == AF_INET); +} + ++ (BOOL)isIPv6Address:(NSData *)address +{ + int af = AF_UNSPEC; + [self getHost:NULL port:NULL family:&af fromAddress:address]; + + return (af == AF_INET6); +} + ++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address +{ + return [self getHost:hostPtr port:portPtr family:NULL fromAddress:address]; +} + ++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(int *)afPtr fromAddress:(NSData *)address +{ + if ([address length] >= sizeof(struct sockaddr)) + { + const struct sockaddr *addrX = (const struct sockaddr *)[address bytes]; + + if (addrX->sa_family == AF_INET) + { + if ([address length] >= sizeof(struct sockaddr_in)) + { + const struct sockaddr_in *addr4 = (const struct sockaddr_in *)(const void *)addrX; + + if (hostPtr) *hostPtr = [self hostFromSockaddr4:addr4]; + if (portPtr) *portPtr = [self portFromSockaddr4:addr4]; + if (afPtr) *afPtr = AF_INET; + + return YES; + } + } + else if (addrX->sa_family == AF_INET6) + { + if ([address length] >= sizeof(struct sockaddr_in6)) + { + const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)(const void *)addrX; + + if (hostPtr) *hostPtr = [self hostFromSockaddr6:addr6]; + if (portPtr) *portPtr = [self portFromSockaddr6:addr6]; + if (afPtr) *afPtr = AF_INET6; + + return YES; + } + } + } + + if (hostPtr) *hostPtr = nil; + if (portPtr) *portPtr = 0; + if (afPtr) *afPtr = AF_UNSPEC; + + return NO; +} + +@end diff --git a/max/template/playbtest/playbtest/PlayB/MyTableViewCell.swift b/max/template/playbtest/playbtest/PlayB/MyTableViewCell.swift new file mode 100644 index 0000000..52a98a0 --- /dev/null +++ b/max/template/playbtest/playbtest/PlayB/MyTableViewCell.swift @@ -0,0 +1,31 @@ +// +// MyTableViewCell.swift +// playbtest +// +// Created by mac on 2025/3/14. +// + +import UIKit + +class MyTableViewCell: UITableViewCell { + + @IBOutlet weak var contentData:UILabel! + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func awakeFromNib() { + super.awakeFromNib() + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + } + +} + diff --git a/max/template/playbtest/playbtest/PlayB/XUDPClient.h b/max/template/playbtest/playbtest/PlayB/XUDPClient.h new file mode 100644 index 0000000..b2499a0 --- /dev/null +++ b/max/template/playbtest/playbtest/PlayB/XUDPClient.h @@ -0,0 +1,27 @@ +// +// XUDPClient.h +// xcmd +// +// Created by mac on 2025/2/17. +// + +#ifndef XUDPClient_h +#define XUDPClient_h + +#import + +#import "CocoaAsyncSocket.h" + +typedef void (^SendCallback) (NSString *msg); + +@interface XUDPClient : NSObject + +@property (nonatomic, copy) SendCallback hintBlock; + + ++(instancetype)sharedInstance; +- (void) onShow: (NSDictionary *)data; +- (void) onEnd: (NSDictionary *)data; +@end + +#endif /* XUDPClient_h */ diff --git a/max/template/playbtest/playbtest/PlayB/XUDPClient.m b/max/template/playbtest/playbtest/PlayB/XUDPClient.m new file mode 100644 index 0000000..bab68ae --- /dev/null +++ b/max/template/playbtest/playbtest/PlayB/XUDPClient.m @@ -0,0 +1,143 @@ +// +// XUDPClient.m +// xcmd +// +// Created by mac on 2025/2/17. +// + +#import + +#import "XUDPClient.h" + +#define HOST @"127.0.0.1" +#define PORT 6001 + + +@interface XUDPClient() { +@private + GCDAsyncUdpSocket *_udpSocket; +} + +@end + + +@implementation XUDPClient ++(instancetype)sharedInstance +{ + static XUDPClient* _sharedInstance = nil; + static dispatch_once_t oncePredicate; + dispatch_once (&oncePredicate, ^{ + _sharedInstance = [[XUDPClient alloc] init]; + }); + return _sharedInstance; +} +-(instancetype)init { + if (self = [super init]) { + [self start]; + return self; + } + return nil; +} + +- (void) start +{ + if (!_udpSocket) + { + _udpSocket=nil; + } + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + _udpSocket = [[GCDAsyncUdpSocket alloc]initWithDelegate:self delegateQueue:queue]; + NSError *error = nil; + if (![_udpSocket bindToPort:0 error:&error]) + { + NSLog(@"Error binding: %@", error); + return; + } + if (![_udpSocket beginReceiving:&error]) + { + NSLog(@"Error receiving: %@", error); + return; + } +} +- (void) close +{ + if(_udpSocket) { + [_udpSocket closeAfterSending]; + } +} +- (NSString *)dic2Json: (NSDictionary *)dict { + NSError *error; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict + options:NSJSONWritingPrettyPrinted + error:&error]; + if (error) { + NSLog(@"dic2json err:%@", error); + } + return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; +} + +- (void) onShow: (NSDictionary *)data { + NSDictionary *rq = @{ + @"url": @"/adtask/show", + @"body": data + }; + [self send:[self dic2Json: rq]]; +} + +- (void) onEnd: (NSDictionary *)data { + NSDictionary *rq = @{ + @"url": @"/adtask/end", + @"body": data + }; + [self send:[self dic2Json: rq]]; +} + +- (void) send: (NSString*) msg { + NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding]; + [_udpSocket sendData:data toHost:HOST port:PORT withTimeout:-1 tag:300]; +} +-(void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address +{ + NSError *error = nil; + NSLog(@"Message didConnectToAddress: %@",[[NSString alloc]initWithData:address encoding:NSUTF8StringEncoding]); + [_udpSocket beginReceiving:&error]; +} + +-(void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError *)error +{ + NSLog(@"Message didNotConnect: %@",error); +} + +-(void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error +{ + NSLog(@"Message didNotSendDataWithTag: %@",error); +} + +- (NSDictionary *) json2dic: (NSString *) jsstr { + NSError *jsonError; + NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:[jsstr dataUsingEncoding:NSUTF8StringEncoding] + options:NSJSONReadingMutableContainers + error:&jsonError]; + if (jsonError) { + NSLog(@"json2dic error: %@", jsonError); + } + return dic; +} + +-(void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)filterContext +{ + NSString *revDada =[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; + NSLog(@"Message didReceiveData :%@", revDada); + if(self.hintBlock) { + self.hintBlock(revDada); + } + +} + +-(void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag +{ + NSLog(@"Message 发送成功"); +} + + +@end diff --git a/max/template/playbtest/playbtest/PlayB/YL_NetWorkManager.swift b/max/template/playbtest/playbtest/PlayB/YL_NetWorkManager.swift new file mode 100644 index 0000000..0cb19e4 --- /dev/null +++ b/max/template/playbtest/playbtest/PlayB/YL_NetWorkManager.swift @@ -0,0 +1,1354 @@ +// +// YL_NetWorkManager.swift +// wallpaper_project +// Created by 忆海16 on 2024/9/29. +// + +import Foundation +import UIKit +import AdSupport +import CoreTelephony +import Network +import SystemConfiguration +import AVFoundation +import OpenGLES +import AppTrackingTransparency +import CommonCrypto + + +/// <#Description#> +class YL_NetWorkManager{ + static var kBaseUrl = YL_NetWorkManager.bFaceLocalUrl() + + static var kURL_AD_Start = "/top_selection/save_app_start_log" + static var kURL_AD_Load = "/top_selection/save_ad_load_log" + static var kURL_AD_Show = "/top_selection/save_ad_show_log" + static var kURL_save_logs = "/ios/top_selection/save_iphone_logs" + + var ipLoadTime: DispatchTime? + + static var kLocalBaseUrl = BbbAdManager.config.adbrush_local_url + + + static var kLocalUrlStr: (String) -> String { + return { path in + return "\(self.kLocalBaseUrl)/\(path)" + } + } + + + static func bFaceLocalUrl() -> String { + + return BbbAdManager.config.adbrush_base_url + + } + + + + typealias LuxNetManagerCallback = (Error?, Bool, [String: Any]?) -> Void + + + + static func uploadData(mdic: [String: Any], urlPath: String, callback: @escaping (Error?, Bool, [String: Any]?) -> Void) { + guard let url = URL(string: urlPath) else { + print("Invalid URL") + return + } + + print("---------mdic \(mdic)") + + print("---------urlPath\(urlPath)") + + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + do { + let data = try JSONSerialization.data(withJSONObject: mdic, options: []) + request.httpBody = data + } catch { + print("Error serializing JSON: \(error.localizedDescription)") + + + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: [ + "text":"Error serializing JSON: \(error.localizedDescription)" + ]) + } + + return + } + + let task = URLSession.shared.dataTask(with: request) { (data, response, error) in + if let error = error { + callback(error, false, nil) + print("Network error: \(error.localizedDescription)") + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: [ + "text":"Network error: \(error.localizedDescription)" + ]) + } + } else { + guard let data = data else { + callback(nil, false, nil) + print("No data received") + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: [ + "text":"No data received" + ]) + } + return + } + + do { + if let result = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { + print("Server response: \(result)") + callback(nil, true, result) + + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: [ + "text":"Server response: \(result)" + ]) + } + + } + } catch { + callback(error, false, nil) + + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: [ + "text":"Error parsing response JSON: \(error.localizedDescription)" + ]) + } + + + + print("Error parsing response JSON: \(error.localizedDescription)") + } + } + } + + task.resume() + } + + + // 请求远程IP + func requestRemoteIp() { + ipLoadTime = DispatchTime.now() + + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "获取ip中..."]) + + guard let url = URL(string: "https://openapi.lux-ad.com/app/common/getIPInfo") else { return } + let request = URLRequest(url: url) + let task = URLSession.shared.dataTask(with: request) { data, response, error in + if let error = error { + print("解析ip失败....") + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "解析ip失败...."]) + } + return + } + + guard let data = data, + let result = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], + let ip = result["data"] as? [String: Any], + let ipAddress = ip["ip"] as? String, + let isoCode = ip["isoCode"] as? String else { + print("解析ip失败....") + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "解析ip失败...."]) + } + return + } + + if !ipAddress.isEmpty { + UserDefaults.standard.set(ipAddress, forKey: "kIP_key") + UserDefaults.standard.set(isoCode, forKey: "kIP_key_isoCode") + UserDefaults.standard.synchronize() + + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "当前ip: \(ipAddress) \n国家: \(isoCode) "]) + } + + if let startTime = self.ipLoadTime { + let loadDuration = self.IpcalculateElapsedTime(since: startTime) + BbbAdManager.config.ipTime = loadDuration + } + + } else { + print("解析ip失败....") + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "解析ip失败...."]) + } + } + } + task.resume() + } + + + // 辅助函数以毫秒为单位计算经过的时间 + private func IpcalculateElapsedTime(since startTime: DispatchTime) -> Int { + let endTime = DispatchTime.now() + let nanoseconds = endTime.uptimeNanoseconds - startTime.uptimeNanoseconds + return Int(nanoseconds / 1_000_000) // 转换为毫秒 + } + + + // DeviceId + + static func getDeviceId() -> String { + var adbrushDeviceId = UserDefaults.standard.dictionary(forKey: "bfaceDictKey")?["adbrush_deviceid"] as? String + if adbrushDeviceId == nil { + adbrushDeviceId = UIDevice.current.identifierForVendor?.uuidString + var bfaceDict = UserDefaults.standard.dictionary(forKey: "bfaceDictKey") ?? [:] + bfaceDict["adbrush_deviceid"] = adbrushDeviceId + UserDefaults.standard.set(bfaceDict, forKey: "bfaceDictKey") + UserDefaults.standard.synchronize() + } + + return adbrushDeviceId ?? "" + } + + + // 本地IP + static func getLocalIp() -> String { + var address = "an error occurred when obtaining ip address" + var interfaces: UnsafeMutablePointer? = nil + + if getifaddrs(&interfaces) == 0 { + var tempAddr = interfaces + + while tempAddr != nil { + let addrFamily = tempAddr!.pointee.ifa_addr.pointee.sa_family + let name = String(cString: tempAddr!.pointee.ifa_name) + + if addrFamily == AF_INET && name == "en0" { + var ipAddress = sockaddr_in() + memcpy(&ipAddress, tempAddr!.pointee.ifa_addr, MemoryLayout.size) + address = String(cString: inet_ntoa(ipAddress.sin_addr)) + } + tempAddr = tempAddr!.pointee.ifa_next + } + freeifaddrs(interfaces) + } + return address + } + + + // 包名 + static func getPackageName() -> String { + return Bundle.main.bundleIdentifier ?? "" + } + + // 版本 + static func getappVersion() -> String { + + return Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "" + + } + + // 手机型号 + static func getiphone() -> String { + + return UIDevice.current.localizedModel + + } + + // 系统版本 + static func getosVersion() -> String { + + return UIDevice.current.systemVersion + + } + + // 电池电量 + static func getBatteryLevel() -> Float? { + return UIDevice.current.batteryLevel + } + + // 是否在充电 + static func isDeviceCharging() -> Bool { + return UIDevice.current.batteryState == .charging || UIDevice.current.batteryState == .full + } + + // 获取deviceId + static func getPhoneDeviceId() -> String? { + return UIDevice.current.identifierForVendor?.uuidString + } + + // 当前的无线接入技术 + static func getCurrentRadioAccessTechnology() -> String? { + let networkInfo = CTTelephonyNetworkInfo() + let radioAccessTechnology = networkInfo.currentRadioAccessTechnology + return radioAccessTechnology + } + + + + // 时间 + static func getCurrentTime() -> String { + let date = Date() // 获取当前日期和时间 + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" // 设置日期格式 + formatter.locale = Locale(identifier: "en_US_POSIX") // 确保使用 ISO 8601 标准 + return formatter.string(from: date) + } + + // 字体 + static func getCurrentFont() -> String { + return "\(UIFont.systemFont(ofSize: UIFont.systemFontSize))" + } + + // 语言 + static func getlanguageCode() -> String { + return Locale.current.languageCode ?? "" + } + + + //GAID + + static func getGaid() -> String { + return ASIdentifierManager.shared().advertisingIdentifier.uuidString + } + + + // IDFA + static func getIdfa() -> String { + if ASIdentifierManager.shared().isAdvertisingTrackingEnabled { + let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString + return idfa + } else { + return "" + } + } + + // monotonicRawClockTimeMillis 单调时钟时间戳 + static func getMonotonicRawClockTimeMillis() -> UInt64 { + let time = mach_absolute_time() + let nanoseconds = Double(time) / Double(NSEC_PER_SEC) // Convert to nanoseconds + return UInt64(nanoseconds / 1000000) // Convert to milliseconds + } + + // 内核启动时间戳 + static func getKernelBootTimeMillis() -> Int64? { + // 定义变量以存储系统启动时间 + var bootTime = timeval() + var size = MemoryLayout.stride + + // 获取系统启动时间 + let mib: [Int32] = [CTL_KERN, KERN_BOOTTIME] + let result = sysctl(UnsafeMutablePointer(mutating: mib), 2, &bootTime, &size, nil, 0) + + guard result == 0 else { + print("获取系统启动时间失败") + return nil + } + + // 系统启动时间(秒 + 微秒) + let bootTimeInSeconds = Double(bootTime.tv_sec) + Double(bootTime.tv_usec) / 1_000_000 + + // 当前时间 + let currentTime = Date().timeIntervalSince1970 + + // 计算内核运行的时间(秒) + let kernelBootTimeInSeconds = currentTime - bootTimeInSeconds + + // 转换为毫秒 + return Int64(kernelBootTimeInSeconds * 1_000) + } + + + // 获取运营商信息 + static func getCarrierInfo() -> String { + var carrierInfo = "" + let networkInfo = CTTelephonyNetworkInfo() + let carrier = networkInfo.serviceSubscriberCellularProviders?.first?.value + if let carrier = carrier { + carrierInfo = "\(carrier.mobileCountryCode ?? "") \(carrier.mobileNetworkCode ?? "")" + } + return carrierInfo + } + + // 获取网络连接类型 + static func getNetworkType() -> String { + let monitor = NWPathMonitor() + let queue = DispatchQueue.global(qos: .background) + + var type:String = "" + + monitor.pathUpdateHandler = { path in + if path.status == .satisfied { + if path.usesInterfaceType(.wifi) { + type = "Wi-Fi" + } else if path.usesInterfaceType(.cellular) { + type = "Cellular" + } else if path.usesInterfaceType(.wiredEthernet) { + type = "Wired Ethernet" + } else if path.usesInterfaceType(.loopback) { + type = "Loopback" + } else { + type = "Other" + } + } else { + type = "Unavailable" + } + monitor.cancel() // 停止监控,释放资源 + } + + monitor.start(queue: queue) + return type + } + + // 是否连接VPN + static func isVPNOn() -> Bool { + var flag = false + let version = UIDevice.current.systemVersion + + // Check if the system version is >= 9.0 + if let versionDouble = Double(version), versionDouble >= 9.0 { + // For iOS 9.0 and above + if let dict = CFNetworkCopySystemProxySettings() as? Unmanaged { + let bridgedDict = dict.takeUnretainedValue() as? [String: Any] + + if let scopedSettings = bridgedDict?["__SCOPED__"] as? [String: Any] { + for key in scopedSettings.keys { + if key.contains("tap") || key.contains("tun") || key.contains("ipsec") || key.contains("ppp") { + flag = true + break + } + } + } + } + } else { + // For iOS versions below 9.0 + var interfaces: UnsafeMutablePointer? + let success = getifaddrs(&interfaces) + + if success == 0 { + var tempAddr = interfaces + while tempAddr != nil { + if let name = String(validatingUTF8: (tempAddr?.pointee.ifa_name)!) { + if name.contains("tap") || name.contains("tun") || name.contains("ipsec") || name.contains("ppp") { + flag = true + break + } + } + tempAddr = tempAddr?.pointee.ifa_next + } + } + + // Free memory + freeifaddrs(interfaces) + } + + return flag + } + + + + static func appId() -> String { + return Bundle.main.bundleIdentifier ?? "" + } + + + // 获取音量 + static func isDeviceMuted() -> Bool { + let audioSession = AVAudioSession.sharedInstance() + do { + try audioSession.setActive(true) + // 获取当前音量 + let currentVolume = audioSession.outputVolume + return currentVolume == 0 + } catch { + print("Error setting audio session active: \(error.localizedDescription)") + return false + } + } + + // 追踪状态 + static func getAdvertisingTrackingStatus() -> String { + if #available(iOS 14, *) { + switch ATTrackingManager.trackingAuthorizationStatus { + case .notDetermined: + return "notDetermined" + case .restricted: + return "restricted" + case .denied: + return "denied" + case .authorized: + return "authorized" + @unknown default: + return "unknown" + } + } else { + // iOS 14 以下版本 + let isTrackingEnabled = ASIdentifierManager.shared().isAdvertisingTrackingEnabled + return isTrackingEnabled ? "authorized" : "denied" + } + } + + // 是否启用追踪 + static func isAdvertisingTrackingEnabled() -> Bool { + if #available(iOS 14, *) { + switch ATTrackingManager.trackingAuthorizationStatus { + case .authorized: + return true // 用户允许广告追踪 + default: + return false // 用户未授权、受限制或拒绝 + } + } else { + // iOS 14 以下版本 + return ASIdentifierManager.shared().isAdvertisingTrackingEnabled + } + } + + // 获取设备总内存 + + static func getTotalMemoryInBytes() -> UInt64 { + return ProcessInfo.processInfo.physicalMemory + } + + static func getTotalMemoryInReadableFormat() -> String { + let totalMemoryBytes = getTotalMemoryInBytes() + let formatter = ByteCountFormatter() + formatter.allowedUnits = [.useGB] // 只显示为 GB + formatter.countStyle = .memory + return formatter.string(fromByteCount: Int64(totalMemoryBytes)) + } + + + // 是否是刘海屏 + static func isNotchScreen() -> Bool { + // 确保在主线程中访问 + if Thread.isMainThread { + guard let window = UIApplication.shared.windows.first else { return false } + let topSafeArea = window.safeAreaInsets.top + return topSafeArea > 20 + } else { + // 在非主线程中调用时,通过调度回主线程同步获取结果 + return DispatchQueue.main.sync { + guard let window = UIApplication.shared.windows.first else { return false } + let topSafeArea = window.safeAreaInsets.top + return topSafeArea > 20 + } + } + } + + + // 屏幕方向 + + static func getScreenOrientation() -> String { + let orientation = UIDevice.current.orientation + + switch orientation { + case .portrait: + return "portrait" + case .portraitUpsideDown: + return "portraitUpsideDown" + case .landscapeLeft: + return "landscapeLeft" + case .landscapeRight: + return "landscapeRight" + case .faceUp: + return "faceUp" + case .faceDown: + return "faceDown" + case .unknown: + return "unknown" + @unknown default: + return "unknown" + } + } + + // 获取 OpenGL版本 + static func getOpenGLVersion() -> String { + // 尝试初始化 OpenGL ES 3.0 上下文 + if let _ = EAGLContext(api: .openGLES3) { + return "openGLES3" + } + // 尝试初始化 OpenGL ES 2.0 上下文 + else if let _ = EAGLContext(api: .openGLES2) { + return "openGLES2" + } + // 如果上述都失败,则返回 OpenGL ES 1.0 + else if let _ = EAGLContext(api: .openGLES1) { + return "openGLES1" + } else { + return "NO OpenGL ES" + } + } + + // 获取时区偏移量 + static func getTimeZoneOffsetFromUS() -> Int { + // 获取当前设备的时区 + let currentTimeZone = TimeZone.current + + // 获取美国东部时间的时区(美区时区) + let usTimeZone = TimeZone(identifier: "America/New_York") + + // 获取两个时区的偏移量,单位为秒 + let currentOffset = currentTimeZone.secondsFromGMT() + let usOffset = usTimeZone?.secondsFromGMT() ?? 0 + + // 计算偏移差,并将结果转为分钟 + let offsetDifferenceInMinutes = (currentOffset - usOffset) / 60 + return offsetDifferenceInMinutes + } + + // 设备键盘类型 + static func getKeyboardLayoutType() -> [String] { + var keyboardLayouts: [String] = [] + + // 获取当前设备的语言环境 + let currentLanguage = Locale.current.languageCode ?? "" + + // 根据语言环境推测键盘布局类型 + switch currentLanguage { + case "en": + // 英语一般使用 QWERTY 布局 + keyboardLayouts.append("QWERTY") + case "fr": + // 法语一般使用 AZERTY 布局 + keyboardLayouts.append("AZERTY") + case "de": + // 德语一般使用 QWERTZ 布局 + keyboardLayouts.append("QWERTZ") + case "es": + // 西班牙语一般使用 QWERTY 布局 + keyboardLayouts.append("QWERTY") + case "it": + // 意大利语一般使用 QWERTY 布局 + keyboardLayouts.append("QWERTY") + case "ja": + // 日语一般使用日本输入法 + keyboardLayouts.append("Japanese") + case "zh": + // 中文一般使用拼音输入法 + keyboardLayouts.append("Pinyin") + default: + // 其他语言暂时返回 QWERTY + keyboardLayouts.append("QWERTY") + } + + return keyboardLayouts + } + + + // 设备型号修订版 + static func getDeviceHardwareIdentifier() -> String { + var size: Int = 0 // 使用 Int 类型而不是 UInt32 + sysctlbyname("hw.model", nil, &size, nil, 0) + + var model = [CChar](repeating: 0, count: size) + sysctlbyname("hw.model", &model, &size, nil, 0) + + let modelString = String(cString: model) + return modelString + } + + // static func mapHardwareIdentifierToModelNumber(_ identifier: String) -> String { + // switch identifier { + // // iPhone 系列 + // case "iPhone13,2": + // return "A2172" // iPhone 12 + // case "iPhone13,3": + // return "A2341" // iPhone 12 Pro + // case "iPhone13,4": + // return "A2342" // iPhone 12 Pro Max + // case "iPhone13,1": + // return "A2176" // iPhone 12 mini + // case "iPhone12,1": + // return "A2111" // iPhone 11 + // case "iPhone12,3": + // return "A2160" // iPhone 11 Pro + // case "iPhone12,5": + // return "A2161" // iPhone 11 Pro Max + // case "iPhone11,8": + // return "A1882" // iPhone XR + // case "iPhone11,2": + // return "A1920" // iPhone XS + // case "iPhone11,6": + // return "A1921" // iPhone XS Max + // case "iPhone10,3": + // return "A1865" // iPhone X + // case "iPhone10,6": + // return "A1901" // iPhone X + // case "iPhone9,1": + // return "A1660" // iPhone 7 + // case "iPhone9,2": + // return "A1661" // iPhone 7 Plus + // case "iPhone8,1": + // return "A1549" // iPhone 6s + // case "iPhone8,2": + // return "A1586" // iPhone 6s Plus + // case "iPhone7,1": + // return "A1522" // iPhone 6 Plus + // case "iPhone7,2": + // return "A1549" // iPhone 6 + // case "iPhone6,1": + // return "A1528" // iPhone 5s + // case "iPhone6,2": + // return "A1530" // iPhone 5s + // case "iPhone5,1": + // return "A1428" // iPhone 5 + // case "iPhone5,2": + // return "A1429" // iPhone 5 + // + // // iPad 系列 + // case "iPad8,1": + // return "A1893" // iPad Air 3 + // case "iPad8,2": + // return "A1894" // iPad Air 3 + // case "iPad8,3": + // return "A1954" // iPad Pro 10.5" + // case "iPad8,4": + // return "A1955" // iPad Pro 10.5" + // case "iPad8,5": + // return "A1701" // iPad Pro 12.9" (2nd generation) + // case "iPad8,6": + // return "A1709" // iPad Pro 12.9" (2nd generation) + // case "iPad8,7": + // return "A1673" // iPad Pro 12.9" (3rd generation) + // case "iPad8,8": + // return "A1674" // iPad Pro 12.9" (3rd generation) + // + // // iPad Mini 系列 + // case "iPad4,4": + // return "A1454" // iPad Mini 2 + // case "iPad4,5": + // return "A1455" // iPad Mini 2 + // case "iPad4,6": + // return "A1456" // iPad Mini 2 + // case "iPad5,1": + // return "A1822" // iPad Mini 4 + // case "iPad5,2": + // return "A1823" // iPad Mini 4 + // + // // iPad Pro 系列 + // case "iPad6,7": + // return "A1652" // iPad Pro 12.9" (1st generation) + // case "iPad6,8": + // return "A1670" // iPad Pro 12.9" (1st generation) + // case "iPad7,1": + // return "A1674" // iPad Pro 12.9" (2nd generation) + // case "iPad7,2": + // return "A1673" // iPad Pro 12.9" (2nd generation) + // + // // Apple Watch 系列 + // case "Watch1,1": + // return "A1554" // Apple Watch (1st generation) + // case "Watch1,2": + // return "A1553" // Apple Watch (1st generation) + // case "Watch2,6": + // return "A1792" // Apple Watch Series 1 + // case "Watch2,7": + // return "A1791" // Apple Watch Series 1 + // case "Watch3,1": + // return "A1860" // Apple Watch Series 3 + // case "Watch3,2": + // return "A1861" // Apple Watch Series 3 + // case "Watch3,3": + // return "A1889" // Apple Watch Series 3 + // case "Watch3,4": + // return "A1890" // Apple Watch Series 3 + // case "Watch4,1": + // return "A1975" // Apple Watch Series 4 + // case "Watch4,2": + // return "A1976" // Apple Watch Series 4 + // case "Watch4,3": + // return "A1977" // Apple Watch Series 4 + // case "Watch4,4": + // return "A1978" // Apple Watch Series 4 + // + // // Apple TV 系列 + // case "AppleTV5,3": + // return "A1469" // Apple TV (4th generation) + // case "AppleTV6,2": + // return "A1842" // Apple TV 4K (1st generation) + // case "AppleTV11,1": + // return "A2169" // Apple TV 4K (2nd generation) + // default: + // return "Unknown model" + // } + // } + + + +// static func uploadAD_Load(adid:String,ecpm:Double,network:String,countryCode:String,platformResponseTime:TimeInterval,dsp:String,loadTime:Int){ +// +// var mdic: [String: Any] = [:] +// +// mdic["deviceId"] = starManager.shared.adbrush_deviceid +// mdic["gaid"] = self.getGaid() +// mdic["localIp"] = starManager.shared.adbrush_localip +// +// // let remoteIp = UserDefaults.standard.string(forKey: "kIP_key") ?? "" +// mdic["remoteIp"] = starManager.shared.remouteIP +// +// mdic["packageName"] = appId() +// mdic["adPlatform"] = "MAX" +// mdic["countryCode"] = countryCode +// mdic["adId"] = adid +// mdic["platformResponseTime"] = platformResponseTime +// mdic["shelfNumber"] = "uploadAD_Load" +// mdic["ecpm"] = ecpm +// mdic["getIpResponseTime"] = starManager.shared.ipTime +// mdic["dsp"] = dsp +// mdic["dataId"] = starManager.shared.dataId +// mdic["linkId"] = starManager.shared.linkId +// mdic["loadTime"] = loadTime +// mdic["succeed"] = true +// mdic["network"] = network +// mdic["online"] = false +// mdic["washParam"] = starManager.shared.washParam +// +// // Log the JSON data to verify it's correctly formatted +// do { +// let jsonData = try JSONSerialization.data(withJSONObject: mdic, options: .prettyPrinted) +// let jsonString = String(data: jsonData, encoding: .utf8) ?? "" +// print("Uploading AD Show with data: \(jsonString)") +// +// DispatchQueue.main.sync { +// NotificationCenter.default.post(name: NSNotification.Name("adNetwork"), object: nil, userInfo: ["info": "uploadAD_Load:\n\(jsonString)"]) +// } +// +// +// YL_NetWorkManager.uploadData(mdic: mdic, urlPath: "\(kBaseUrl)\(kURL_AD_Load)") { err, state, result in +// if let error = err { +// print("Error: \(error.localizedDescription)") +// } else { +// print("Result: \(result ?? [:])") +// DispatchQueue.main.async { +// NotificationCenter.default.post(name: NSNotification.Name("adNetwork"), object: nil, userInfo: ["info": "AdID: \(adid) 广告加载成功, ecpm: \(ecpm) 加载时长: 0ms dsp: \( "MAX") network: \(network)"]) +// } +// +// } +// } +// +// +// } catch { +// print("Error serializing JSON: \(error.localizedDescription)") +// } +// +// +// } + + static func uploadAD_Load( + adid: String, + ecpm: Double, + network: String, + countryCode: String, + platformResponseTime: TimeInterval, + dsp: String, + loadTime: Int, + retryCount: Int = 3 // 默认重试3次 + ) { + var mdic: [String: Any] = [:] + + mdic["deviceId"] = BbbAdManager.config.adbrush_deviceid + mdic["gaid"] = self.getGaid() + mdic["localIp"] = BbbAdManager.config.adbrush_localip + mdic["remoteIp"] = BbbAdManager.config.remouteIP + mdic["packageName"] = appId() + mdic["adPlatform"] = "MAX" + mdic["countryCode"] = countryCode + mdic["adId"] = adid + mdic["platformResponseTime"] = platformResponseTime + mdic["shelfNumber"] = "uploadAD_Load" + mdic["ecpm"] = ecpm + mdic["getIpResponseTime"] = BbbAdManager.config.ipTime + mdic["dsp"] = dsp + mdic["dataId"] = BbbAdManager.config.dataId + mdic["linkId"] = BbbAdManager.config.linkId + mdic["loadTime"] = loadTime + mdic["succeed"] = true + mdic["network"] = network + mdic["online"] = false + mdic["washParam"] = BbbAdManager.config.washParam + + // Log the JSON data to verify it's correctly formatted + do { + let jsonData = try JSONSerialization.data(withJSONObject: mdic, options: .prettyPrinted) + let jsonString = String(data: jsonData, encoding: .utf8) ?? "" + print("Uploading AD Show with data: \(jsonString)") + + + + // 上传数据 + YL_NetWorkManager.uploadData(mdic: mdic, urlPath: "\(kBaseUrl)\(kURL_AD_Load)") { err, state, result in + if let error = err { + print("Error: \(error.localizedDescription)") + + if retryCount > 0 { + print("Retrying... Remaining attempts: \(retryCount - 1)") + // 延迟1秒后重试 + DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + 1.0) { + uploadAD_Load( + adid: adid, + ecpm: ecpm, + network: network, + countryCode: countryCode, + platformResponseTime: platformResponseTime, + dsp: dsp, + loadTime: loadTime, + retryCount: retryCount - 1 + ) + } + } else { + print("Failed to upload data after \(3 - retryCount) retries.") + } + } else { + print("Result: \(result ?? [:])") + } + } + } catch { + print("Error serializing JSON: \(error.localizedDescription)") + } + } + + + + static func uploadAD_Show(adid:String,ecpm:Double,network:String,countryCode:String,platformResponseTime:TimeInterval,dsp:String){ + + var mdic: [String: Any] = [:] + + mdic["deviceId"] = BbbAdManager.config.adbrush_deviceid + mdic["gaid"] = self.getGaid() + mdic["localIp"] = BbbAdManager.config.adbrush_localip + + // let remoteIp = UserDefaults.standard.string(forKey: "kIP_key") ?? "" + mdic["remoteIp"] = BbbAdManager.config.remouteIP + + mdic["packageName"] = appId() + mdic["adPlatform"] = "MAX" + mdic["countryCode"] = countryCode + mdic["adId"] = adid + mdic["platformResponseTime"] = platformResponseTime + mdic["shelfNumber"] = "uploadAD_Load" + mdic["ecpm"] = ecpm + mdic["getIpResponseTime"] = BbbAdManager.config.ipTime + mdic["dsp"] = dsp + mdic["dataId"] = BbbAdManager.config.dataId + mdic["linkId"] = BbbAdManager.config.linkId + mdic["loadTime"] = 0 + mdic["succeed"] = true + mdic["network"] = network + mdic["online"] = false + + do { + // Log the JSON data to verify it's correctly formatted + let jsonData = try JSONSerialization.data(withJSONObject: mdic, options: .prettyPrinted) + let jsonString = String(data: jsonData, encoding: .utf8) ?? "" + print("Uploading AD Show with data: \(jsonString)") + YL_NetWorkManager.uploadData(mdic:mdic, urlPath: "\(kBaseUrl)\(kURL_AD_Show)") { (err, state, result) in + if let error = err { + print("Error uploading AD Show: \(error.localizedDescription)") + } else { + print("Upload AD Show response: \(result ?? [:])") + } + } + } catch { + print("Error serializing JSON: \(error.localizedDescription)") + } + + + } + + + + /* + + static func showAd(idfa: String, + adId: String, + ecpm: Double?, + ad: Bool) { + + var mdic = [String: Any]() + mdic["appid"] = appId() + mdic["idfa"] = getIdfa() + mdic["ecpm"] = ecpm ?? 0 + mdic["ad"] = ad + mdic["id"] = adId + + uploadData(mdic: mdic, urlPath: kLocalUrlStr("adtask/show")) { err, state, result in + guard let status = result?["status"] as? String, + let time = result!["close"] as? NSNumber else { + + return + } + + if status == "Success" && ad { + + closeAD.removeADVC(byDelayTime: time.intValue) + // closeWindows.removeADVCByDelayTime(time.intValue) + } + DispatchQueue.main.async { + + NotificationCenter.default.post(name: NSNotification.Name("adNetwork"), object: nil, userInfo: ["info": "showAd,已上传展示AD操作,\(time.intValue / 1000)秒后关闭"]) + } + + if ad { + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adNetwork"), object: nil, userInfo: [ + "info": "AdID:\(adId),广告开始展示,展示时长\(time)" + ]) + } + } + } + } + */ + static func convertStringToDictionary(text: String) -> [String:AnyObject]? { + if let data = text.data(using: String.Encoding.utf8) { + do { + return try JSONSerialization.jsonObject(with: data, options: [JSONSerialization.ReadingOptions.init(rawValue: 0)]) as? [String:AnyObject] + } catch let error as NSError { + print(error) + } + } + return nil + } + + static func showAd(adId: String, + ecpm: Double?, + ad: Bool) { + + var mdic = [String: Any]() + mdic["appid"] = appId() + mdic["idfa"] = getIdfa() + mdic["ecpm"] = ecpm ?? 0 + mdic["ad"] = ad + mdic["id"] = adId + + let client:XUDPClient = XUDPClient.sharedInstance() + client.hintBlock = { (t:String?) in + guard let jsonStr = t else { + return + } + let result = convertStringToDictionary(text: jsonStr) + guard let status = result?["status"] as? String, + let time = result!["close"] as? NSNumber else { + + return + } + + if status == "Success" && ad { + + closeAD.removeADVC(byDelayTime: time.intValue) + // closeWindows.removeADVCByDelayTime(time.intValue) + } + + } + + client.onShow(mdic); + } + static func loadend(max_ecpm:Double){ + var mdic = [String: Any]() + mdic["appid"] = appId() + mdic["idfa"] = getIdfa() + mdic["max_ecpm"] = max_ecpm + + let client:XUDPClient = XUDPClient.sharedInstance() + client.hintBlock = { (t:String?) in + guard let jsonStr = t else { + return + } + let result = convertStringToDictionary(text: jsonStr) + + // 解析返回的 JSON 数据 + if let resultDict = result, + let status = resultDict["status"] as? String, + let restart = resultDict["restart"] as? Bool { + + // 打印返回的状态 + print("状态: \(status)") + + if restart { + // 如果 restart 为 true,重新加载广告 + print("重新加载广告展示") + // BbbAdManager.config.isadload = 0 + // 调用广告加载的函数 + + + // starManager.shared.isadsureshow = true + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "重新加载广告展示"]) + } + + + } else { + // restart 为 false,不需要处理 + print("不需要重新加载广告,程序后续会被杀掉") + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "不需要重新加载广告,程序后续会被杀掉"]) + } + } + } else { + print("返回数据格式错误") + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "返回数据格式错误"]) + } + } + } + + client.onEnd(mdic); + } + + /* + static func loadend(max_ecpm:Double){ + var mdic = [String: Any]() + mdic["appid"] = appId() + mdic["idfa"] = getIdfa() + mdic["max_ecpm"] = max_ecpm + + uploadData(mdic: mdic, urlPath:kLocalUrlStr("adtask/end")) { err, state, result in + // 检查是否有错误 + guard err == nil else { + print("请求失败: \(String(describing: err))") + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "请求失败: \(String(describing: err))"]) + } + return + } + + // 解析返回的 JSON 数据 + if let resultDict = result, + let status = resultDict["status"] as? String, + let restart = resultDict["restart"] as? Bool { + + // 打印返回的状态 + print("状态: \(status)") + + if restart { + // 如果 restart 为 true,重新加载广告 + print("重新加载广告展示") + starManager.shared.isadload = 0 + // 调用广告加载的函数 + + for (index, adId) in starManager.shared.adids.enumerated() { + DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 2) { // 每隔 4 秒加载一个广告 + YL_AdManager.shared.addInterstitialAdItem(adId: adId) + } + } + starManager.shared.isadsureshow = true + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "重新加载广告展示"]) + } + + + } else { + // restart 为 false,不需要处理 + print("不需要重新加载广告,程序后续会被杀掉") + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "不需要重新加载广告,程序后续会被杀掉"]) + } + } + } else { + print("返回数据格式错误") + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "返回数据格式错误"]) + } + } + } + + } + + */ + + + static func saveIphonelogs() { + let processInfo = ProcessInfo.processInfo + let screenSize = UIScreen.main.bounds.size + let width = Int(screenSize.width) + let height = Int(screenSize.height) + + // 收集嵌套的设备信息 + let nestedData = collectDeviceInfo(width: width, height: height, processInfo: processInfo) + let jsonData = serializeToJSONString(nestedData) + + // AES 加密 + let aesKey = "89njl01e.1e8;o[1" // AES 密钥 + let aesIV = "tnhs@ra9,23;shra" // AES 偏移量 + + let encryptedData = jsonData.aesEncrypt(key: aesKey, iv: aesIV) + print("Encrypted text: \(encryptedData ?? "" )") + + if let decrypted = encryptedData?.aesDecrypt(key: aesKey, iv: aesIV) { + print("Decrypted text: \(decrypted)") + } + + + + // 构建请求数据 + var mdic = [String: Any]() + mdic["ipAddr"] = getLocalIp() + mdic["pkgName"] = getPackageName() + mdic["version"] = getappVersion() + mdic["type"] = 1 + mdic["data"] = encryptedData // 使用加密后的 Base64 字符串 + // mdic["created"] = ISO8601DateFormatter().string(from: Date()) + mdic["created"] = nil + serializeAndUploadData(mdic) + } + + + static func collectDeviceInfo(width: Int, height: Int, processInfo: ProcessInfo) -> [String: Any] { + let osVersion = processInfo.operatingSystemVersion + let osVersionString = "\(osVersion.majorVersion).\(osVersion.minorVersion).\(osVersion.patchVersion)" + + return [ + "deviceModel": getIpne.shard.getIPhoneModel().getName(), + "osVersion": getosVersion(), + "batteryLevel": getBatteryLevel() ?? 0.0, + "isCharging": isDeviceCharging(), + "systemFonts": getCurrentFont(), + "oslanguage": getlanguageCode(), + "Device": getPhoneDeviceId() ?? "", + "processID": processInfo.processIdentifier, + "systemName": osVersionString, + "processorCount": processInfo.processorCount, + "physicalMemory": Double(processInfo.physicalMemory) / 1024.0 / 1024.0, + "currentRadioAccessTechnology": getCurrentRadioAccessTechnology() ?? "", + "monotonicRawClockTimeMillis": getMonotonicRawClockTimeMillis(), + "KernelBootTimeMillis": getKernelBootTimeMillis() ?? 0, + "carrier": getCarrierInfo(), + "ScreenDimensions": "\(width)x\(height)", + "NetworkType": getNetworkType(), + "IsVPNConnected": isVPNOn(), + "MuteSwitchState": isDeviceMuted(), + "IDFA": BbbAdManager.config.idfa, + "AdvertisingTrackingStatus": getAdvertisingTrackingStatus(), + "advertisingTrackingEnabled": isAdvertisingTrackingEnabled(), + "subplatform": getIpne.shard.getIPhoneModel().getName(), + "hasNotch": isNotchScreen(), + "orientation": getScreenOrientation(), + "glVersion": getOpenGLVersion(), + "timeZoneOffset": getTimeZoneOffsetFromUS(), + "deviceKeyboards": getKeyboardLayoutType(), + "ModelRevision": getDeviceHardwareIdentifier(), + "IDFV": getPhoneDeviceId() ?? "", + ] + } + + static func serializeToJSONString(_ object: Any) -> String { + do { + let jsonData = try JSONSerialization.data(withJSONObject: object, options: []) + return String(data: jsonData, encoding: .utf8) ?? "{}" + } catch { + print("Error serializing object to JSON string: \(error.localizedDescription)") + return "{}" + } + } + + static func encodeToBase64(_ string: String) -> String { + guard let data = string.data(using: .utf8) else { return "" } + return data.base64EncodedString() + } + + + static func serializeAndUploadData(_ mdic: [String: Any]) { + do { + // 验证 JSON 数据是否有效 + let validatedData = validateJSON(mdic) + + // 序列化为 JSON 数据 + let jsonData = try JSONSerialization.data(withJSONObject: validatedData, options: .prettyPrinted) + let jsonString = String(data: jsonData, encoding: .utf8) ?? "" + print("save_iphone_logs with data: \(jsonString)") + + + + // 上传网络请求 + YL_NetWorkManager.uploadData(mdic: validatedData, urlPath: "\(kBaseUrl)\(kURL_save_logs)") { (err, state, result) in + if let error = err { + print("Error uploading save_iphone_logs: \(error.localizedDescription)") + } else { + print("save_iphone_logs response: \(result ?? [:])") + } + } + } catch { + print("Error serializing JSON save_iphone_logs: \(error.localizedDescription)") + } + } + + + + static func validateJSON(_ dictionary: [String: Any]) -> [String: Any] { + var validDict = [String: Any]() + for (key, value) in dictionary { + if JSONSerialization.isValidJSONObject([key: value]) { + validDict[key] = value + } else { + print("Invalid JSON value for key \(key): \(value)") + validDict[key] = "\(value)" // 将不支持的值转换为字符串 + } + } + return validDict + } + +} + + + +extension String { + func aesEncrypt(key: String, iv: String) -> String? { + guard let data = self.data(using: .utf8), + let encryptedData = data.aesEncrypt(key: key, iv: iv) else { return nil } + return encryptedData.base64EncodedString() + } + + func aesDecrypt(key: String, iv: String) -> String? { + guard let data = Data(base64Encoded: self), + let decryptedData = data.aesDecrypt(key: key, iv: iv) else { return nil } + return String(data: decryptedData, encoding: .utf8) + } +} + +extension Data { + func aesEncrypt(key: String, iv: String) -> Data? { + return self.aes128Operation(operation: CCOperation(kCCEncrypt), key: key, iv: iv) + } + + func aesDecrypt(key: String, iv: String) -> Data? { + return self.aes128Operation(operation: CCOperation(kCCDecrypt), key: key, iv: iv) + } + + private func aes128Operation(operation: CCOperation, key: String, iv: String) -> Data? { + guard key.count == kCCKeySizeAES128, iv.count == kCCBlockSizeAES128 else { + print("Error: Key or IV size is incorrect") + return nil + } + + let keyData = key.data(using: .utf8)! + let ivData = iv.data(using: .utf8)! + var outLength = Int(0) + var outBytes = [UInt8](repeating: 0, count: self.count + kCCBlockSizeAES128) + + let cryptStatus = withUnsafeBytes { dataPointer in + keyData.withUnsafeBytes { keyPointer in + ivData.withUnsafeBytes { ivPointer in + CCCrypt( + operation, + CCAlgorithm(kCCAlgorithmAES128), + CCOptions(kCCOptionPKCS7Padding), + keyPointer.baseAddress, kCCKeySizeAES128, + ivPointer.baseAddress, + dataPointer.baseAddress, self.count, + &outBytes, outBytes.count, + &outLength + ) + } + } + } + + guard cryptStatus == kCCSuccess else { + print("Error: AES operation failed with status \(cryptStatus)") + return nil + } + + return Data(bytes: outBytes, count: outLength) + } +} + + + + diff --git a/max/template/playbtest/playbtest/PlayB/YL_PlayVC.swift b/max/template/playbtest/playbtest/PlayB/YL_PlayVC.swift new file mode 100644 index 0000000..55019b5 --- /dev/null +++ b/max/template/playbtest/playbtest/PlayB/YL_PlayVC.swift @@ -0,0 +1,158 @@ +// +// YL_PlayVC.swift +// playbtest +// +// Created by 忆海16 on 2024/11/5. +// + +import UIKit + +class YL_PlayVC: UIViewController,UITextViewDelegate { + + + @IBOutlet weak var textSDKView: UITextView! + + @IBOutlet weak var bundleIdLab: UILabel! + + @IBOutlet weak var deviceIdLab: UILabel! + + @IBOutlet weak var idfaLab: UILabel! + + @IBOutlet weak var ipLab: UILabel! + + @IBOutlet weak var ad1Lab: UILabel! + + @IBOutlet weak var ad2Lab: UILabel! + + @IBOutlet weak var ad3Lab: UILabel! + + private var observation: NSKeyValueObservation? + + private var observationis: NSKeyValueObservation? + + var openADTimer:Timer? + let kOpenADPerSec: CGFloat = 0.1 // 假设的秒数 + let kOpenAdCTimeLength: CGFloat = 30 // 假设的超时时长 + + static var totalTimeC: CGFloat = 0.0 + + + + override func viewDidLoad() { + super.viewDidLoad() + BbbAdManager.shared.loadAd() + let bundleId = Bundle.main.bundleIdentifier ?? "" + bundleIdLab.text = "Name:\(bundleId)" + let deviceId = BbbAdManager.config.adbrush_deviceid ?? "" + deviceIdLab.text = "DeviceID:\(deviceId)" + let locIp = BbbAdManager.config.adbrush_localip ?? "" + let remoteIp = BbbAdManager.config.remouteIP + ipLab.text = "LocIP:\(locIp),RemoteIp:\(remoteIp)" + + if #available(iOS 14, *) { + IDFA.shared.checkATT { idfa in + if let idfa = idfa { + print("IDFA: \(idfa)") + BbbAdManager.config.idfa = idfa + self.idfaLab.text = "IDFA:\(idfa)" + } else { + print("无法获取 IDFA") + } + } + } else { + IDFA.shared.getIDFAForOlderVersions { idfa in + if let idfa = idfa { + print("IDFA: \(idfa)") + BbbAdManager.config.idfa = idfa + self.idfaLab.text = "IDFA:\(idfa)" + } else { + print("无法获取 IDFA") + } + } + } + + + // 确保计时器被正确添加到 RunLoop + /* + DispatchQueue.global(qos: .utility).async { [weak self] in + guard self != nil else { return } + YL_NetWorkManager.saveIphonelogs() + } + */ + + textSDKView.delegate = self + + } + + + + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + ad1Lab.text = BbbAdManager.config.adids[0] + ad2Lab.text = BbbAdManager.config.adids[1] + ad3Lab.text = BbbAdManager.config.adids[2] + + + self.navigationController?.navigationBar.isHidden = true + + NotificationCenter.default.addObserver(self, selector: #selector(addTextToTextView), name: NSNotification.Name("adinfo"), object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(adStatusChange), name: NSNotification.Name("adStatus"), object: nil) + + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "adbrush_base_url:\(BbbAdManager.config.adbrush_base_url),adbrush_deviceid:\(String(describing: BbbAdManager.config.adbrush_deviceid)),adbrush_localip:\(String(describing: BbbAdManager.config.adbrush_localip)),adbrush_local_url:\(BbbAdManager.config.adbrush_local_url),adbrush_ecpm:\(BbbAdManager.config.adbrush_ecpm)"]) + + + } + + + + + + // 向文本末尾追加 100 行空白行 + func appendExtraLines1() { + let extraLines = String(repeating: "\n", count: 100) + let currentText = textSDKView.text ?? "" + + // 检查末尾是否已经有额外行,避免重复追加 + if !currentText.hasSuffix(extraLines) { + textSDKView.text = currentText.trimmingCharacters(in: .whitespacesAndNewlines) + extraLines + } + } + + + + @objc func addTextToTextView(notification: Notification) { + if let newText = notification.userInfo?["text"] as? String { + textSDKView.text.append("\(newText)\n\n") + } + } + + + + @objc func adStatusChange(notification: Notification) { + if let newText = notification.userInfo?["text"] as? String, let id = notification.userInfo?["id"] as? String { + var lab = ad1Lab + if id == BbbAdManager.config.adids[1] { + lab = ad2Lab + } else if id == BbbAdManager.config.adids[2] { + lab = ad3Lab + } + lab?.text = "\(id):\(newText)" + } + } + + + + + + + + deinit { + + NotificationCenter.default.removeObserver(self) + + } + +} diff --git a/max/template/playbtest/playbtest/PlayB/YL_PlayVC.xib b/max/template/playbtest/playbtest/PlayB/YL_PlayVC.xib new file mode 100644 index 0000000..20a921b --- /dev/null +++ b/max/template/playbtest/playbtest/PlayB/YL_PlayVC.xib @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/max/template/playbtest/playbtest/PlayB/bbbAdManager.swift b/max/template/playbtest/playbtest/PlayB/bbbAdManager.swift new file mode 100644 index 0000000..527f4c5 --- /dev/null +++ b/max/template/playbtest/playbtest/PlayB/bbbAdManager.swift @@ -0,0 +1,399 @@ +// +// bbbAdManager.swift +// playbtest +// +// Created by mac on 2025/3/11. +// + +import Foundation +import AppLovinSDK + + +class bConfig: NSObject { + /// 广告Key + var adKey:String = "{adKey}" + + /// 广告数组 + var allAdIds:[String] = [{allAdIds}] + /// 广告数组 + var adids:[String] = [] + ///设备ID + var adbrush_deviceid:String? + ///最低ecpm + var adbrush_ecpm:Double = 0.0005 + /// 本地ip + var adbrush_localip:String? + /// A面load show + var adbrush_base_url:String = "http://192.168.40.8:8080" + /// 本地服务 + var adbrush_local_url:String = "http://127.0.0.1:6000" + + /// 远程ip + var remouteIP:String = "" + /// dataId + var dataId:String = "" + + ///IDFA + var idfa:String = "" + + + + /// + var washParam:Bool = false + /// + var linkId:String = "" + + ///load次数 + var loadcount:Int = 0 + + var ipTime:Int = 0 + + override init() { + super.init() + if self.allAdIds.count > 3 { + self.adids = Array(self.allAdIds.shuffled().prefix(3)) + } else { + self.adids = self.allAdIds + } + } + + ///是否正在展示中 + @objc dynamic var isadsureshow:Bool = false + + + func getRandomString() -> String? { + return adids.randomElement() + } + // 判断当前是否为广告不量模式 + func isADSSMode() -> Bool { + // return true + return UserDefaults.standard.bool(forKey: "kLuxSSFaceKey") + } +} + +class AdItem :NSObject,MAAdDelegate{ + + + var interstitialAd:MAInterstitialAd! + var interstitialAdID: String = "" + + var retryAttempt = 0.0 + + // 定义广告关闭后的操作闭包 + var onAdClosed: (() -> Void)? + var onStatusChange:((_:String,_:Int, _:Double) -> Void)? + + var startLoadTime: DispatchTime? + + // 0: 初始,1:加载中,2:加载完成,3:展示中,4:关闭,5:加载失败,6:展示失败 + private(set) var status: Int = 0 + + private(set) var ecpm:Double = 0.0 + + + init(adID:String){ + + super.init() + self.interstitialAdID = adID + // loadInterstitialAd() + + } + + func changeStatus(st:Int) { + self.status = st + onStatusChange?(self.interstitialAdID, st, self.ecpm) + } + + + func loadInterstitialAd(){ + interstitialAd = MAInterstitialAd(adUnitIdentifier:interstitialAdID) + interstitialAd.delegate = self + startLoadTime = DispatchTime.now() + changeStatus(st: 1) + NSLog("XS loadInterstitialAd\(self.interstitialAdID)") + interstitialAd.load() + NSLog("XS loadInterstitialAd 2 \(self.interstitialAdID)"); + + } + + + func showAd(onAdClosed: @escaping () -> Void) { + self.onAdClosed = onAdClosed + NSLog("onAdClosed set: \(self.onAdClosed != nil)") + interstitialAd.show(forPlacement: interstitialAdID) + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "---走到了show:\(interstitialAdID)"]) + } + + func didLoad(_ ad: MAAd) { + NSLog("XS didLoad\(self.interstitialAdID)") + BbbAdManager.config.loadcount += 1 + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "加载广告: \(ad.adUnitIdentifier) 成功"]) + retryAttempt = 0 // 重置重试次数 + NSLog("成功加载广告\(ad.adUnitIdentifier)") + self.ecpm = ad.revenue + + // 计算并打印加载时间 + var time = 0 + if let startTime = startLoadTime { + let loadDuration = calculateElapsedTime(since: startTime) + print("广告 \(ad.adUnitIdentifier) 加载时间: \(loadDuration) ms") + time = loadDuration + } + + DispatchQueue.global(qos: .utility).async { [weak self] in + guard self != nil else { return } + YL_NetWorkManager.uploadAD_Load(adid: ad.adUnitIdentifier, ecpm: ad.revenue, network: ad.networkName, countryCode: ALSdk.shared().configuration.countryCode, platformResponseTime:ad.requestLatency , dsp: ad.dspName ?? "", loadTime: time) + } + // 发布广告加载成功通知 + // NotificationCenter.default.post(name: .adDidLoad, object: nil, userInfo: ["adId": ad.adUnitIdentifier]) + changeStatus(st: 2) + } + + func didFailToLoadAd(forAdUnitIdentifier adUnitIdentifier: String, withError error: MAError) { + NSLog("XS didFailToLoadAd 1 \(self.interstitialAdID)") + BbbAdManager.config.loadcount += 1 + NSLog("XS didFailToLoadAd 2 \(self.interstitialAdID) \(error)") + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "加载:\(adUnitIdentifier ) 失败"]) + NSLog("XS didFailToLoadAd 3 \(self.interstitialAdID)") + // NotificationCenter.default.post(name: .adDidFailToLoad, object: nil, userInfo: ["adId": adUnitIdentifier]) + + + NSLog("XS didFailToLoadAd 4 \(self.interstitialAdID)") + changeStatus(st: 5) + NSLog("XS didFailToLoadAd 5 \(self.interstitialAdID)") + } + + func didDisplay(_ ad: MAAd) { + + + print("成功展示了ad\(ad.adUnitIdentifier)") + // NotificationCenter.default.post(name: .adDidDisplay, object: nil, userInfo: ["adId": ad.adUnitIdentifier]) + + // let currentIDFV = UIDevice.current.identifierForVendor?.uuidString + + + DispatchQueue.global(qos: .utility).async { [weak self] in + guard self != nil else { return } + + YL_NetWorkManager.showAd(adId: ad.adUnitIdentifier, ecpm: ad.revenue, ad: true) + } + + DispatchQueue.global(qos: .utility).async { [weak self] in + guard self != nil else { return } + YL_NetWorkManager.uploadAD_Show(adid: ad.adUnitIdentifier, ecpm: ad.revenue, network: ad.networkName, countryCode: ALSdk.shared().configuration.countryCode, platformResponseTime:ad.requestLatency , dsp: ad.dspName ?? "") + } + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "成功展示了ad\(ad.adUnitIdentifier)"]) + + changeStatus(st: 3) + } + + func didHide(_ ad: MAAd) { + changeStatus(st: 4) + print("成功关闭广告\(ad.adUnitIdentifier)") + onAdClosed?() + } + + func didClick(_ ad: MAAd) { + print("点击了广告\(ad.adUnitIdentifier)") + } + + func didFail(toDisplay ad: MAAd, withError error: MAError) { + changeStatus(st: 6) + print("展示广告\(ad.adUnitIdentifier),\(error)失败") + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "展示广告\(ad.adUnitIdentifier),\(error)失败"]) + //self.onAdClosed!() + } + // 辅助函数以毫秒为单位计算经过的时间 + private func calculateElapsedTime(since startTime: DispatchTime) -> Int { + let endTime = DispatchTime.now() + let nanoseconds = endTime.uptimeNanoseconds - startTime.uptimeNanoseconds + return Int(nanoseconds / 1_000_000) // 转换为毫秒 + } + +} + + +class BbbAdManager: NSObject { + static let shared = BbbAdManager() + static let config = bConfig() + + ///是否正在展示中 + @objc dynamic var isshow:Bool = false + + // 用于存储多个广告位的管理器,Key 是广告位的 ID + private var adItems: [String: AdItem] = [:] + + var openADTimer:Timer? + let kOpenADPerSec: CGFloat = 0.1 // 假设的秒数 + let kOpenAdCTimeLength: CGFloat = 30 // 假设的超时时长 + + static var totalTimeC: CGFloat = 0.0 + + + // 添加广告位管理器 + func add(adId: String) { + let adManager = AdItem(adID: adId) + adManager.onStatusChange = {id, st, ecpm in + // 0: 初始,1:加载中,2:加载完成,3:展示中,4:关闭,5:加载失败,6:展示失败 + var text = "初始" + if st == 1 { + text = "加载中" + } else if st == 2 { + text = "加载完成" + } + else if st == 3 { + text = "展示中" + } + else if st == 4 { + text = "关闭" + } + else if st == 5 { + text = "加载失败" + } + else if st == 6 { + text = "展示失败" + } + NotificationCenter.default.post(name: NSNotification.Name("adStatus"), object: nil, userInfo: ["id": id, "text":"\(text),ecpm:\(ecpm)"]) + } + adItems[adId] = adManager + } + + override init(){ + + super.init() + if BbbAdManager.config.isADSSMode() { + self.initConfig() + } + + } + + func initConfig () { + if #available(iOS 14, *) { + IDFA.shared.checkATT { idfa in + if let idfa = idfa { + print("IDFA: \(idfa)") + BbbAdManager.config.idfa = idfa + } else { + print("无法获取 IDFA") + } + } + } else { + IDFA.shared.getIDFAForOlderVersions { idfa in + if let idfa = idfa { + print("IDFA: \(idfa)") + BbbAdManager.config.idfa = idfa + } else { + print("无法获取 IDFA") + } + } + } + if let bfaceDict = UserDefaults.standard.dictionary(forKey: "bfaceDictKey"){ + + BbbAdManager.config.adbrush_base_url = bfaceDict["adbrush_base_url"] as? String ?? "http://183.222.62.53:58078" + + BbbAdManager.config.adbrush_deviceid = bfaceDict["adbrush_deviceid"] as? String ?? "" + BbbAdManager.config.adbrush_localip = bfaceDict["adbrush_localip"] as? String ?? "" + BbbAdManager.config.remouteIP = bfaceDict["remouteIP"] as? String ?? "" + + + BbbAdManager.config.adbrush_local_url = bfaceDict["adbrush_local_url"] as? String ?? "http://127.0.0.1:6000" + BbbAdManager.config.dataId = bfaceDict["dataId"] as? String ?? "" + + + BbbAdManager.config.adbrush_ecpm = bfaceDict["adbrush_ecpm"] as? Double ?? 0.005 + BbbAdManager.config.linkId = bfaceDict["linkId"] as? String ?? "" + BbbAdManager.config.washParam = bfaceDict["washParam"] as? Bool ?? false + } else { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "获取字典失败"]) + } + } + + func initAd() { + let initConfig = ALSdkInitializationConfiguration(sdkKey: BbbAdManager.config.adKey) { builder in + + builder.mediationProvider = ALMediationProviderMAX + + // Perform any additional configuration/setting changes + + } + + ALSdk.shared().initialize(with: initConfig) { sdkConfig in + + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "初始化广告成功"]) + // YL_NetWorkManager().requestRemoteIp() + BbbAdManager.shared.loadAd2() + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + BbbAdManager.shared.start() + } + } + } + func loadAd2() { + for(index,ad) in self.adItems.values.enumerated() { + ad.loadInterstitialAd() + } + } + func loadAd() { + if BbbAdManager.config.washParam == true{ + let adid = BbbAdManager.config.getRandomString() + BbbAdManager.shared.add(adId: adid ?? "") + }else{ + for (index, adId) in BbbAdManager.config.adids.enumerated() { + //DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 1) { + BbbAdManager.shared.add(adId: adId) + //} + } + } + } + func start() { + guard openADTimer == nil else { return } + openADTimer = Timer.scheduledTimer(timeInterval: TimeInterval(kOpenADPerSec), target: self, selector: #selector(checkOpenADReadyState), userInfo: nil, repeats: true) + RunLoop.current.add(openADTimer!, forMode: .common) + } + func isEnd () -> Bool { + if(self.isshow) { + return false + } + for (_, ad) in BbbAdManager.shared.adItems { + if(ad.status <= 1) { + return false + } + if (ad.status == 2 && ad.ecpm >= BbbAdManager.config.adbrush_ecpm) { + return false + } + } + return true + } + func showAd() { + guard !self.isshow else { return } + for (_, ad) in BbbAdManager.shared.adItems { + if (ad.status == 2 && ad.ecpm >= BbbAdManager.config.adbrush_ecpm) { + ad.showAd { [weak self] in + NSLog("广告关闭") + guard let self = self else { return } + self.isshow = false + } + self.isshow = true + break + } + } + } + func closeAd(v:Int) { + closeAD.removeADVC(byDelayTime: v) + self.isshow = false + } + + @objc func checkOpenADReadyState(){ + BbbAdManager.totalTimeC += kOpenADPerSec + + if BbbAdManager.shared.isEnd() || BbbAdManager.totalTimeC >= kOpenAdCTimeLength { + openADTimer?.invalidate() + openADTimer = nil + YL_NetWorkManager.loadend(max_ecpm: 0) + } + + if BbbAdManager.shared.isshow == false{ + BbbAdManager.shared.showAd() + } + + } +} diff --git a/max/template/playbtest/playbtest/PlayB/closeAD.h b/max/template/playbtest/playbtest/PlayB/closeAD.h new file mode 100644 index 0000000..f7757b5 --- /dev/null +++ b/max/template/playbtest/playbtest/PlayB/closeAD.h @@ -0,0 +1,19 @@ +// +// closeAD.h +// playbtest +// +// Created by 忆海16 on 2024/11/6. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + + + +@interface closeAD : NSObject ++ (void)removeADVCByDelayTime:(NSInteger)delayTime; +@end + + +NS_ASSUME_NONNULL_END diff --git a/max/template/playbtest/playbtest/PlayB/closeAD.m b/max/template/playbtest/playbtest/PlayB/closeAD.m new file mode 100644 index 0000000..47c6c2a --- /dev/null +++ b/max/template/playbtest/playbtest/PlayB/closeAD.m @@ -0,0 +1,141 @@ +// +// closeAD.m +// playbtest +// +// Created by 忆海16 on 2024/11/6. +// + +#import "closeAD.h" + +@implementation closeAD ++ (void)removeADVCByDelayTime:(NSInteger)delayTime { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayTime / 1000 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [closeAD removeVungleFullScreenPresenter]; + [closeAD closeADWindow]; + }); +} + +//+ (void)closeADWindow { +// NSLog(@"已经执行closeADWindow....."); +// UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; +// [keyWindow.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { +// UIViewController *vc = obj.subviews.firstObject.nextResponder; +// if ([vc isKindOfClass:[NSClassFromString(@"ALAppLovinVideoViewController") class]]) { +// if ([vc respondsToSelector:@selector(skipVideo)]) { +// NSLog(@"执行了skipVideo....."); +// [vc performSelector:@selector(skipVideo) withObject:nil]; +// } +// if ([vc respondsToSelector:@selector(handleCloseButton)]) { +// NSLog(@"执行了handleCloseButton....."); +// [vc performSelector:@selector(handleCloseButton) withObject:nil]; +// } +// +// [closeAD checkCloseWindown]; +// +// } +// else if ([vc isKindOfClass:[NSClassFromString(@"ALVASTVideoViewController") class]]) { +// [vc performSelector:@selector(dismiss) withObject:nil]; +// +// [closeAD checkCloseWindown]; +// [closeAD removeVungleFullScreenPresenter]; +// } +// }]; +// +// +//} + ++ (void)closeADWindow { + NSLog(@"Executing closeADWindow - First Pass"); + [self performCloseADWindowActions]; + + NSLog(@"Executing closeADWindow - Second Pass"); + [self performCloseADWindowActions]; +} + ++ (void)performCloseADWindowActions { + UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; + if (!keyWindow) return; // Ensure keyWindow is non-nil + + [keyWindow.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + UIViewController *vc = obj.subviews.firstObject.nextResponder; + if (!vc || ![vc isKindOfClass:[UIViewController class]]) return; // Check for non-nil vc + + if ([vc isKindOfClass:[NSClassFromString(@"ALAppLovinVideoViewController") class]]) { + if ([vc respondsToSelector:@selector(skipVideo)]) { + NSLog(@"Executing skipVideo..."); + [vc performSelector:@selector(skipVideo) withObject:nil]; + } + if ([vc respondsToSelector:@selector(handleCloseButton)]) { + NSLog(@"Executing handleCloseButton..."); + [vc performSelector:@selector(handleCloseButton) withObject:nil]; + } + +// [closeAD checkCloseWindown]; + [closeAD removeVungleFullScreenPresenter]; + + } else if ([vc isKindOfClass:[NSClassFromString(@"ALVASTVideoViewController") class]]) { + if ([vc respondsToSelector:@selector(dismiss)]) { + [vc performSelector:@selector(dismiss) withObject:nil]; + } + +// [closeAD checkCloseWindown]; + [closeAD removeVungleFullScreenPresenter]; + } + [closeAD removeVungleFullScreenPresenter]; + }]; +} + +//+ (void)checkCloseWindown { +// static NSInteger closeCounter = 0; +// if (closeCounter >= 2) { +// NSLog(@"Stopping recursive closeADWindow calls"); +// closeCounter = 0; +// return; +// } +// closeCounter++; + +// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ +// [closeAD closeADWindow]; +// }); +//} + ++ (void)removeVungleFullScreenPresenter { + UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; + UIViewController *foundViewController = [self findFullScreenPresenterInViewController:keyWindow.rootViewController]; + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + NSDictionary *config = [userDefaults dictionaryForKey:@"xyzshell_hooks_settings_hook"]; + if (foundViewController) { + NSLog(@"Found FullScreenPresenter instance: %@", foundViewController); + + // 关闭或移除 FullScreenPresenter + [foundViewController dismissViewControllerAnimated:YES completion:^{ + NSLog(@"FullScreenPresenter dismissed."); + }]; + } else { + NSLog(@"FullScreenPresenter not found."); + } +} + ++ (UIViewController *)findFullScreenPresenterInViewController:(UIViewController *)viewController { + if ([viewController isKindOfClass:NSClassFromString(@"VungleAdsSDK.FullScreenPresenter")]) { + return viewController; + } + + // 递归遍历所有子控制器 + for (UIViewController *childViewController in viewController.childViewControllers) { + UIViewController *foundViewController = [self findFullScreenPresenterInViewController:childViewController]; + if (foundViewController) { + return foundViewController; + } + } + + // 检查 presentedViewController + if (viewController.presentedViewController) { + return [self findFullScreenPresenterInViewController:viewController.presentedViewController]; + } + + return nil; +} + +@end + diff --git a/max/template/playbtest/playbtest/PlayB/closeWindows.swift b/max/template/playbtest/playbtest/PlayB/closeWindows.swift new file mode 100644 index 0000000..267e4d7 --- /dev/null +++ b/max/template/playbtest/playbtest/PlayB/closeWindows.swift @@ -0,0 +1,78 @@ +// +// closeWindows.swift +// playbtest +// +// Created by 忆海16 on 2024/11/11. +// + +import Foundation +import UIKit + +class closeWindows { + + @objc static func removeADVCByDelayTime(_ delayTime: Int) { + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(delayTime)) { +// CloseAD.removeVungleFullScreenPresenter() + closeWindows.closeADWindow() + } + } + + @objc static func closeADWindow() { + print("Executing closeADWindow - First Pass") + performCloseADWindowActions() + + print("Executing closeADWindow - Second Pass") +// performCloseADWindowActions() + } + + private static func performCloseADWindowActions() { + guard let keyWindow = UIApplication.shared.keyWindow else { return } + + keyWindow.subviews.forEach { view in + if let vc = view.subviews.first?.next as? UIViewController { + + if vc.isKind(of: NSClassFromString("ALAppLovinVideoViewController") ?? UIViewController.self) || + vc.isKind(of: NSClassFromString("ALVASTVideoViewController") ?? UIViewController.self) { + + print("Removing ad view from window...") + view.removeFromSuperview() // Remove the ad view directly from the window’s subviews + + } + } + closeWindows.removeVungleFullScreenPresenter() + } + + } + + private static func removeVungleFullScreenPresenter() { + guard let keyWindow = UIApplication.shared.keyWindow else { return } + if let foundView = findFullScreenPresenterInViewController(keyWindow.rootViewController) { + print("Found FullScreenPresenter instance: \(foundView)") + foundView.view.removeFromSuperview() + print("FullScreenPresenter view removed from superview.") + } else { + print("FullScreenPresenter not found.") + } + } + + private static func findFullScreenPresenterInViewController(_ viewController: UIViewController?) -> UIViewController? { + guard let viewController = viewController else { return nil } + + if viewController.isKind(of: NSClassFromString("VungleAdsSDK.FullScreenPresenter") ?? UIViewController.self) { + return viewController + } + + for childViewController in viewController.children { + if let foundVC = findFullScreenPresenterInViewController(childViewController) { + return foundVC + } + } + + if let presentedVC = viewController.presentedViewController { + return findFullScreenPresenterInViewController(presentedVC) + } + + return nil + } + +} diff --git a/max/template/playbtest/playbtest/PlayB/getIphone.swift b/max/template/playbtest/playbtest/PlayB/getIphone.swift new file mode 100644 index 0000000..157ad07 --- /dev/null +++ b/max/template/playbtest/playbtest/PlayB/getIphone.swift @@ -0,0 +1,154 @@ +// +// getIphone.swift +// playbtest +// +// Created by 忆海16 on 2024/12/31. +// + +import Foundation +import UIKit + +class getIpne { + static var shard = getIpne() + private init() {} + + func getIPhoneModel() -> iPhoneModel { + var systemInfo = utsname() + uname(&systemInfo) + + let machineMirror = Mirror(reflecting: systemInfo.machine) + let identifier = machineMirror.children.reduce("") { identifier, element in + guard let value = element.value as? Int8, value != 0 else { return identifier } + return identifier + String(UnicodeScalar(UInt8(value))) + } + + switch identifier { + case "iPhone5,1", "iPhone5,2": return .iPhone5 + case "iPhone5,3", "iPhone5,4": return .iPhone5C + case "iPhone6,1", "iPhone6,2": return .iPhone5S + case "iPhone7.2": return .iPhone6 + case "iPhone7,1": return .iPhone6Plus + case "iPhone8,1": return .iPhone6s + case "iPhone8,2": return .iPhone6Plus + case "iPhone8,4": return .iPhoneSE1 + case "iPhone9,1", "iPhone9,3": return .iPhone7 + case "iPhone9,2", "iPhone9,4": return .iPhone7Plus + case "iPhone10,1", "iPhone10,4": return .iPhone8 + case "iPhone10,2", "iPhone10,5": return .iPhone8Plus + case "iPhone10,3", "iPhone10,6": return .iPhoneX + case "iPhone11,8": return .iPhoneXR + case "iPhone11,2": return .iPhoneXS + case "iPhone11,6", "iPhone11,4": return .iPhoneXSMax + case "iPhone12,1": return .iPhone11 + case "iPhone12,3": return .iPhone11Pro + case "iPhone12,5": return .iPhone11ProMax + case "iPhone12,8": return .iPhoneSE2 + case "iPhone13,1": return .iPhone12Mini + case "iPhone13,2": return .iPhone12 + case "iPhone13,3": return .iPhone12Pro + case "iPhone13,4": return .iPhone12ProMax + case "iPhone14,4": return .iPhone13Mini + case "iPhone14,5": return .iPhone13 + case "iPhone14,2": return .iPhone13Pro + case "iPhone14,3": return .iPhone13ProMax + case "iPhone14,6": return .iPhoneSE3 + case "iPhone14,7": return .iPhone14 + case "iPhone14,8": return .iPhone14Plus + case "iPhone15,2": return .iPhone14Pro + case "iPhone15,3": return .iPhone14ProMax + case "iPhone15,4": return .iPhone15 + case "iPhone15,5": return .iPhone15Plus + case "iPhone16,1": return .iPhone15Pro + case "iPhone16,2": return .iPhone15ProMax + case "i386": return .simulator + case "x86_64": return .simulator + default: return .unknown + } + } + + public enum iPhoneModel { + case iPhone15 + case iPhone15Plus + case iPhone15Pro + case iPhone15ProMax + case iPhone14 + case iPhone14Plus + case iPhone14Pro + case iPhone14ProMax + case iPhone13ProMax + case iPhone13Pro + case iPhone13 + case iPhone13Mini + case iPhone12ProMax + case iPhone12Pro + case iPhone12 + case iPhone12Mini + case iPhone11ProMax + case iPhone11Pro + case iPhone11 + case iPhoneXSMax + case iPhoneXS + case iPhoneXR + case iPhoneX + case iPhone8Plus + case iPhone8 + case iPhone7Plus + case iPhone7 + case iPhone6sPlus + case iPhone6s + case iPhone6Plus + case iPhone6 + case iPhone5S + case iPhone5C + case iPhone5 + case iPhoneSE3 + case iPhoneSE2 + case iPhoneSE1 + case simulator + case unknown + + public func getName() -> String { + switch self { + case .iPhone5: return "iPhone 5" + case .iPhone5C: return "iPhone 5C" + case .iPhone5S: return "iPhone 5S" + case .iPhone6: return "iPhone 6" + case .iPhone6Plus: return "iPhone 6 Plus" + case .iPhone6s: return "iPhone 6s" + case .iPhone6sPlus: return "iPhone 6s Plus" + case .iPhoneSE1: return "iPhone SE1" + case .iPhone7: return "iPhone 7" + case .iPhone7Plus: return "iPhone 7 Plus" + case .iPhone8: return "iPhone 8" + case .iPhone8Plus: return "iPhone 8 Plus" + case .iPhoneX: return "iPhone X" + case .iPhoneXR: return "iPhone XR" + case .iPhoneXS: return "iPhone XS" + case .iPhoneXSMax: return "iPhone XS Max" + case .iPhone11: return "iPhone 11" + case .iPhone11Pro: return "iPhone 11 Pro" + case .iPhone11ProMax: return "iPhone 11 Pro Max" + case .iPhoneSE2: return "iPhone SE2" + case .iPhone12Mini: return "iPhone 12 mini" + case .iPhone12: return "iPhone 12" + case .iPhone12Pro: return "iPhone 12 Pro" + case .iPhone12ProMax: return "iPhone 12 Pro Max" + case .iPhone13Mini: return "iPhone 13 mini" + case .iPhone13: return "iPhone 13" + case .iPhone13Pro: return "iPhone 13 Pro" + case .iPhone13ProMax: return "iPhone 13 Pro Max" + case .simulator: return "Simulator" + case .unknown: return "unknown" + case .iPhone14: return "iPhone 14" + case .iPhone14Plus: return "iPhone 14 Plus" + case .iPhone14Pro: return "iPhone 14 Pro" + case .iPhone14ProMax: return "iPhone 14 Pro Max" + case .iPhoneSE3: return "iPhone SE3" + case .iPhone15: return "iPhone 15" + case .iPhone15Plus: return "iPhone 15 Plus" + case .iPhone15Pro: return "iPhone 15 Pro" + case .iPhone15ProMax: return "iPhone 15 Pro Max" + } + } + } +} diff --git a/max/template/playbtest/playbtest/PlayB/idfa.swift b/max/template/playbtest/playbtest/PlayB/idfa.swift new file mode 100644 index 0000000..28e79f4 --- /dev/null +++ b/max/template/playbtest/playbtest/PlayB/idfa.swift @@ -0,0 +1,95 @@ +// +// idfa.swift +// playbtest +// +// Created by 忆海16 on 2024/12/31. +// + +import Foundation +import AppTrackingTransparency +import AdSupport + +class IDFA { + static let shared = IDFA() + + /// 检查并请求 ATT 授权 + @available(iOS 14, *) + func checkATT(completion: @escaping (String?) -> Void) { + let status = ATTrackingManager.trackingAuthorizationStatus + switch status { + case .notDetermined: + // 请求授权 + ATTrackingManager.requestTrackingAuthorization { newStatus in + self.handleATTStatus(newStatus, completion: completion) + } + case .authorized, .denied, .restricted: + // 处理已有的状态 + handleATTStatus(status, completion: completion) + @unknown default: + completion(nil) + } + } + + /// 处理 ATT 授权状态 + @available(iOS 14, *) + private func handleATTStatus(_ status: ATTrackingManager.AuthorizationStatus, completion: @escaping (String?) -> Void) { + switch status { + case .authorized: + // 用户已授权,获取 IDFA + let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString + completion(idfa) +// starManager.shared.idfa = idfa + case .denied, .restricted: + // 用户拒绝或受限,返回 nil + print("用户拒绝授权或功能受限") + completion(nil) + case .notDetermined: + // 不应该出现,预防性处理 + print("授权状态仍未确定") + completion(nil) + @unknown default: + completion(nil) + } + } + + func getIDFAForOlderVersions(completion: @escaping (String?) -> Void) { + if ASIdentifierManager.shared().isAdvertisingTrackingEnabled { + let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString + completion(idfa) + } else { + print("广告跟踪受限") + completion(nil) + } + } +} + + + + + +//func requestIDFA(completion: @escaping (String?) -> Void) { +// if #available(iOS 14.5, *) { +// // 检查跟踪授权状态 +// ATTrackingManager.requestTrackingAuthorization { status in +// switch status { +// case .authorized: +// // 用户授权后,获取 IDFA +// let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString +// completion(idfa) +// case .denied, .restricted, .notDetermined: +// // 用户拒绝、限制或未决定时,IDFA 将不可用 +// completion(nil) +// @unknown default: +// completion(nil) +// } +// } +// } else { +// // 对于 iOS 14.4 及以下版本,直接获取 IDFA +// if ASIdentifierManager.shared().isAdvertisingTrackingEnabled { +// let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString +// completion(idfa) +// } else { +// completion(nil) +// } +// } +//} diff --git a/max/template/playbtest/playbtest/PlayB/playb.pch b/max/template/playbtest/playbtest/PlayB/playb.pch new file mode 100644 index 0000000..87f0c0b --- /dev/null +++ b/max/template/playbtest/playbtest/PlayB/playb.pch @@ -0,0 +1,15 @@ +// +// playb.pch +// playbtest +// +// Created by 忆海16 on 2024/11/6. +// + +#ifndef playb_pch +#define playb_pch +#import "playbtest-Swift.h" + +// Include any system framework and library headers here that should be included in all compilation units. +// You will also need to set the Prefix Header build setting of one or more of your targets to reference this file. + +#endif /* playb_pch */ diff --git a/max/template/playbtest/playbtest/PlayB/playbtest-Bridging-Header.h b/max/template/playbtest/playbtest/PlayB/playbtest-Bridging-Header.h new file mode 100644 index 0000000..3e4dbd3 --- /dev/null +++ b/max/template/playbtest/playbtest/PlayB/playbtest-Bridging-Header.h @@ -0,0 +1,6 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import "closeAD.h" +#import "XUDPClient.h" diff --git a/max/template/playbtest/playbtest/ViewController.swift b/max/template/playbtest/playbtest/ViewController.swift new file mode 100644 index 0000000..0babbc8 --- /dev/null +++ b/max/template/playbtest/playbtest/ViewController.swift @@ -0,0 +1,19 @@ +// +// ViewController.swift +// playbtest +// +// Created by 忆海16 on 2024/11/5. +// + +import UIKit + +class ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + } + + +} + diff --git a/topon/build.py b/topon/build.py new file mode 100644 index 0000000..1d3541d --- /dev/null +++ b/topon/build.py @@ -0,0 +1,111 @@ +import subprocess +import json +import sys +import os + + +def build(ad_app_id, ad_key, ad_ids, app_display_name,app_bundle_id,app_version,app_icon): + cmd = """ + rm -rfv build + mkdir build + cp -rfv template/PlayBTopOn ./build/ + """ + + subprocess.call(cmd, shell=True) + # 创建编译目录复制模版到build目录 + + print("--------------------") + # 读取配置 + + + ### 复制icon + + cmd = f""" + cp -fv {app_icon} ./build/PlayBTopOn/PlayBTopOn/Assets.xcassets/AppIcon.appiconset/h687603756335b.png + """ + subprocess.call(cmd, shell=True) + + ## 修改ad信息 + print("修改ad信息") + code_path = './build/PlayBTopOn/PlayBTopOn/playB/bbbAdManager.swift' + with open(code_path, 'r') as f: + code = f.read() + code = code.replace("{appId}", ad_app_id) + code = code.replace("{adKey}", ad_key) + code = code.replace("{allAdIds}", '"' + "\",\"".join(ad_ids) + '"') + with open(code_path, 'w') as f: + f.write(code) + + print("修改项目信息") + prj_path = "./build/PlayBTopOn/PlayBTopOn.xcodeproj/project.pbxproj" + with open(prj_path, 'r') as f: + code = f.read() + code = code.replace("{DisplayName}", app_display_name) + code = code.replace("{BundleId}", app_bundle_id) + code = code.replace("{Version}", app_version) + with open(prj_path, 'w') as f: + f.write(code) + + print("\n开始编译\n") + cmd = """ + cd ./build/PlayBTopOn + xcodebuild clean build -workspace PlayBTopOn.xcworkspace -configuration Release -scheme PlayBTopOn -derivedDataPath "../Target" -destination "platform=iOS,id=00008110-000815AE1179801E" + cd ../../ + """ + subprocess.call(cmd, shell=True) + + + print("\n开始打包\n") + + cmd = """ + mkdir ./build/ipas + cp -rfv ./build/Target/Build/Products/Release-iphoneos/PlayBTopOn.app ./build/ipas/ + cp ./template/embedded.mobileprovision ./build/ipas/PlayBTopOn.app/embedded.mobileprovision + find "./build/ipas/PlayBTopOn.app" -name "*.framework" -exec codesign -f -s "iPhone Distribution: Shumei Luo (T23C6PFSKY)" {} \; + find "./build/ipas/PlayBTopOn.app" -name "*.dylib" -exec codesign -f -s "iPhone Distribution: Shumei Luo (T23C6PFSKY)" {} \; + + codesign --entitlements ./template/Filza.entitlements -f -s "iPhone Distribution: Shumei Luo (T23C6PFSKY)" ./build/ipas/PlayBTopOn.app + + mkdir ./build/ipas/Payload + mv ./build/ipas/PlayBTopOn.app ./build/ipas/Payload + cd ./build/ipas + zip -r playb-topon.ipa Payload/ + """ + + subprocess.call(cmd, shell=True) + + +if __name__ == '__main__': + + app_icon = "/Users/mac/codes/ios/empty_topon/ipas/lux/Tikr/appicon.png" + + if len(sys.argv) < 2: + ad_app_id = "h68b560a6957c8" + ad_key = "a540e4c6e557c1ed3f06b5394246c5a42" + ad_ids = ["n68b560e6e9866","n68b560e6b3b1f","n68b560e676221","n68b560e6181aa"] + + app_display_name = "WYMT Radar" + app_bundle_id = "com.wsi.WSIWeatherWYMT-Radar" + app_version = "5.17.706" + + + build(ad_app_id, ad_key, ad_ids, app_display_name, app_bundle_id, app_version, app_icon) + else: + json_path = sys.argv[1] + with open(json_path, 'r') as f: + config = json.loads(f.read()) + + print(config) + + ad_app_id = config["sdk_app_id"] + ad_key = config["sdk_key"] + ad_ids = config["ad_ids"] + app_display_name = config["app_name"] + app_bundle_id = config["app_pkg_name"] + app_version = config["app_ver"] + app_icon_tmp:str = config["app_icon"] + if not app_icon_tmp is None and app_icon_tmp.strip() != '': + app_icon = os.path.join(os.path.dirname(json_path), app_icon_tmp) + + build(ad_app_id, ad_key, ad_ids, app_display_name, app_bundle_id, app_version, app_icon) + diff --git a/topon/template/Filza.entitlements b/topon/template/Filza.entitlements new file mode 100644 index 0000000..09e8830 --- /dev/null +++ b/topon/template/Filza.entitlements @@ -0,0 +1,26 @@ + + + + + application-identifier + T23C6PFSKY.forecast.barometer.storm.radar.Weather10 + aps-environment + production + com.apple.developer.associated-domains + * + com.apple.developer.team-identifier + T23C6PFSKY + com.apple.security.application-groups + + group.forecast.barometer.storm.radar.Weather10.appshortcallid + group.forecast.barometer.storm.radar.Weather10.appcallid + + get-task-allow + + keychain-access-groups + + T23C6PFSKY.* + com.apple.token + + + \ No newline at end of file diff --git a/topon/template/PlayBTopOn/PlayBTopOn.xcodeproj/project.pbxproj b/topon/template/PlayBTopOn/PlayBTopOn.xcodeproj/project.pbxproj new file mode 100644 index 0000000..58b3c90 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn.xcodeproj/project.pbxproj @@ -0,0 +1,540 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 049B446F2DACAB6D0005EB66 /* GCDAsyncUdpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 049B446A2DACAB6D0005EB66 /* GCDAsyncUdpSocket.m */; }; + 049B44702DACAB6D0005EB66 /* bbbAdManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 049B446E2DACAB6D0005EB66 /* bbbAdManager.swift */; }; + 049B44712DACAB6D0005EB66 /* XUDPClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 049B446D2DACAB6D0005EB66 /* XUDPClient.m */; }; + 755ADA202D2D25C600C9D994 /* getIphone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755ADA1E2D2D25C600C9D994 /* getIphone.swift */; }; + 755ADA212D2D25C600C9D994 /* idfa.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755ADA1F2D2D25C600C9D994 /* idfa.swift */; }; + 75F8FFD12CE7233B008E8DF6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75F8FFD02CE7233B008E8DF6 /* AppDelegate.swift */; }; + 75F8FFD52CE7233B008E8DF6 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75F8FFD42CE7233B008E8DF6 /* ViewController.swift */; }; + 75F8FFD82CE7233B008E8DF6 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = 75F8FFD72CE7233B008E8DF6 /* Base */; }; + 75F8FFDA2CE72340008E8DF6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 75F8FFD92CE72340008E8DF6 /* Assets.xcassets */; }; + 75F8FFDD2CE72340008E8DF6 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = 75F8FFDC2CE72340008E8DF6 /* Base */; }; + 75F8FFE82CE723FF008E8DF6 /* YL_PlayVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 75F8FFE52CE723FE008E8DF6 /* YL_PlayVC.xib */; }; + 75F8FFE92CE723FF008E8DF6 /* YL_PlayVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75F8FFE62CE723FF008E8DF6 /* YL_PlayVC.swift */; }; + 75F8FFF32CE72555008E8DF6 /* initializationTopOn.m in Sources */ = {isa = PBXBuildFile; fileRef = 75F8FFF12CE72555008E8DF6 /* initializationTopOn.m */; }; + 75F8FFF72CE72B7D008E8DF6 /* YL_NetWorkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75F8FFF62CE72B7D008E8DF6 /* YL_NetWorkManager.swift */; }; + 84921A6E873B758EF39EBDFA /* Pods_PlayBTopOn.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33A6D387585D55B3F340F5DB /* Pods_PlayBTopOn.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 049B44682DACAB6D0005EB66 /* CocoaAsyncSocket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CocoaAsyncSocket.h; sourceTree = ""; }; + 049B44692DACAB6D0005EB66 /* GCDAsyncUdpSocket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GCDAsyncUdpSocket.h; sourceTree = ""; }; + 049B446A2DACAB6D0005EB66 /* GCDAsyncUdpSocket.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncUdpSocket.m; sourceTree = ""; }; + 049B446C2DACAB6D0005EB66 /* XUDPClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XUDPClient.h; sourceTree = ""; }; + 049B446D2DACAB6D0005EB66 /* XUDPClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XUDPClient.m; sourceTree = ""; }; + 049B446E2DACAB6D0005EB66 /* bbbAdManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = bbbAdManager.swift; sourceTree = ""; }; + 33A6D387585D55B3F340F5DB /* Pods_PlayBTopOn.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PlayBTopOn.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6297C68F5F2E4537B6CB3D1C /* Pods-PlayBTopOn.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PlayBTopOn.debug.xcconfig"; path = "Target Support Files/Pods-PlayBTopOn/Pods-PlayBTopOn.debug.xcconfig"; sourceTree = ""; }; + 755ADA1E2D2D25C600C9D994 /* getIphone.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = getIphone.swift; sourceTree = ""; }; + 755ADA1F2D2D25C600C9D994 /* idfa.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = idfa.swift; sourceTree = ""; }; + 75F8FFCD2CE7233B008E8DF6 /* PlayBTopOn.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PlayBTopOn.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 75F8FFD02CE7233B008E8DF6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 75F8FFD42CE7233B008E8DF6 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 75F8FFD72CE7233B008E8DF6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 75F8FFD92CE72340008E8DF6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 75F8FFDC2CE72340008E8DF6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 75F8FFDE2CE72340008E8DF6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 75F8FFE52CE723FE008E8DF6 /* YL_PlayVC.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = YL_PlayVC.xib; sourceTree = ""; }; + 75F8FFE62CE723FF008E8DF6 /* YL_PlayVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YL_PlayVC.swift; sourceTree = ""; }; + 75F8FFEE2CE7250B008E8DF6 /* MyWallPaperHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MyWallPaperHeader.h; sourceTree = ""; }; + 75F8FFEF2CE7250B008E8DF6 /* MyWallpaperPCH.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MyWallpaperPCH.pch; sourceTree = ""; }; + 75F8FFF12CE72555008E8DF6 /* initializationTopOn.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = initializationTopOn.m; sourceTree = ""; }; + 75F8FFF22CE72555008E8DF6 /* initializationTopOn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = initializationTopOn.h; sourceTree = ""; }; + 75F8FFF62CE72B7D008E8DF6 /* YL_NetWorkManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YL_NetWorkManager.swift; sourceTree = ""; }; + B423D6A110C2422F79FE6615 /* Pods-PlayBTopOn.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PlayBTopOn.release.xcconfig"; path = "Target Support Files/Pods-PlayBTopOn/Pods-PlayBTopOn.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 75F8FFCA2CE7233B008E8DF6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 84921A6E873B758EF39EBDFA /* Pods_PlayBTopOn.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 049B446B2DACAB6D0005EB66 /* GCD */ = { + isa = PBXGroup; + children = ( + 049B44692DACAB6D0005EB66 /* GCDAsyncUdpSocket.h */, + 049B446A2DACAB6D0005EB66 /* GCDAsyncUdpSocket.m */, + ); + path = GCD; + sourceTree = ""; + }; + 75F8FFC42CE7233B008E8DF6 = { + isa = PBXGroup; + children = ( + 75F8FFCF2CE7233B008E8DF6 /* PlayBTopOn */, + 75F8FFCE2CE7233B008E8DF6 /* Products */, + CFE0CCAD1DCDC6DF946C8994 /* Pods */, + EC17E36934C0C7C23F86C29A /* Frameworks */, + ); + sourceTree = ""; + }; + 75F8FFCE2CE7233B008E8DF6 /* Products */ = { + isa = PBXGroup; + children = ( + 75F8FFCD2CE7233B008E8DF6 /* PlayBTopOn.app */, + ); + name = Products; + sourceTree = ""; + }; + 75F8FFCF2CE7233B008E8DF6 /* PlayBTopOn */ = { + isa = PBXGroup; + children = ( + 75F8FFF02CE7250B008E8DF6 /* Header */, + 75F8FFE42CE723E7008E8DF6 /* playB */, + 75F8FFD02CE7233B008E8DF6 /* AppDelegate.swift */, + 75F8FFD42CE7233B008E8DF6 /* ViewController.swift */, + 75F8FFD62CE7233B008E8DF6 /* Main.storyboard */, + 75F8FFD92CE72340008E8DF6 /* Assets.xcassets */, + 75F8FFDB2CE72340008E8DF6 /* LaunchScreen.storyboard */, + 75F8FFDE2CE72340008E8DF6 /* Info.plist */, + ); + path = PlayBTopOn; + sourceTree = ""; + }; + 75F8FFE42CE723E7008E8DF6 /* playB */ = { + isa = PBXGroup; + children = ( + 049B44682DACAB6D0005EB66 /* CocoaAsyncSocket.h */, + 049B446B2DACAB6D0005EB66 /* GCD */, + 049B446C2DACAB6D0005EB66 /* XUDPClient.h */, + 049B446D2DACAB6D0005EB66 /* XUDPClient.m */, + 049B446E2DACAB6D0005EB66 /* bbbAdManager.swift */, + 755ADA1E2D2D25C600C9D994 /* getIphone.swift */, + 755ADA1F2D2D25C600C9D994 /* idfa.swift */, + 75F8FFF62CE72B7D008E8DF6 /* YL_NetWorkManager.swift */, + 75F8FFF22CE72555008E8DF6 /* initializationTopOn.h */, + 75F8FFF12CE72555008E8DF6 /* initializationTopOn.m */, + 75F8FFE62CE723FF008E8DF6 /* YL_PlayVC.swift */, + 75F8FFE52CE723FE008E8DF6 /* YL_PlayVC.xib */, + ); + path = playB; + sourceTree = ""; + }; + 75F8FFF02CE7250B008E8DF6 /* Header */ = { + isa = PBXGroup; + children = ( + 75F8FFEE2CE7250B008E8DF6 /* MyWallPaperHeader.h */, + 75F8FFEF2CE7250B008E8DF6 /* MyWallpaperPCH.pch */, + ); + path = Header; + sourceTree = ""; + }; + CFE0CCAD1DCDC6DF946C8994 /* Pods */ = { + isa = PBXGroup; + children = ( + 6297C68F5F2E4537B6CB3D1C /* Pods-PlayBTopOn.debug.xcconfig */, + B423D6A110C2422F79FE6615 /* Pods-PlayBTopOn.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + EC17E36934C0C7C23F86C29A /* Frameworks */ = { + isa = PBXGroup; + children = ( + 33A6D387585D55B3F340F5DB /* Pods_PlayBTopOn.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 75F8FFCC2CE7233B008E8DF6 /* PlayBTopOn */ = { + isa = PBXNativeTarget; + buildConfigurationList = 75F8FFE12CE72340008E8DF6 /* Build configuration list for PBXNativeTarget "PlayBTopOn" */; + buildPhases = ( + 9FD2AE384D5FEDF5FF939DD1 /* [CP] Check Pods Manifest.lock */, + 75F8FFC92CE7233B008E8DF6 /* Sources */, + 75F8FFCA2CE7233B008E8DF6 /* Frameworks */, + 75F8FFCB2CE7233B008E8DF6 /* Resources */, + C0554906157B58199E7A6576 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = PlayBTopOn; + productName = PlayBTopOn; + productReference = 75F8FFCD2CE7233B008E8DF6 /* PlayBTopOn.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 75F8FFC52CE7233B008E8DF6 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1530; + LastUpgradeCheck = 1530; + TargetAttributes = { + 75F8FFCC2CE7233B008E8DF6 = { + CreatedOnToolsVersion = 15.3; + }; + }; + }; + buildConfigurationList = 75F8FFC82CE7233B008E8DF6 /* Build configuration list for PBXProject "PlayBTopOn" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 75F8FFC42CE7233B008E8DF6; + productRefGroup = 75F8FFCE2CE7233B008E8DF6 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 75F8FFCC2CE7233B008E8DF6 /* PlayBTopOn */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 75F8FFCB2CE7233B008E8DF6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 75F8FFDA2CE72340008E8DF6 /* Assets.xcassets in Resources */, + 75F8FFE82CE723FF008E8DF6 /* YL_PlayVC.xib in Resources */, + 75F8FFDD2CE72340008E8DF6 /* Base in Resources */, + 75F8FFD82CE7233B008E8DF6 /* Base in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 9FD2AE384D5FEDF5FF939DD1 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-PlayBTopOn-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + C0554906157B58199E7A6576 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PlayBTopOn/Pods-PlayBTopOn-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PlayBTopOn/Pods-PlayBTopOn-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PlayBTopOn/Pods-PlayBTopOn-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 75F8FFC92CE7233B008E8DF6 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 75F8FFF72CE72B7D008E8DF6 /* YL_NetWorkManager.swift in Sources */, + 755ADA212D2D25C600C9D994 /* idfa.swift in Sources */, + 75F8FFF32CE72555008E8DF6 /* initializationTopOn.m in Sources */, + 049B446F2DACAB6D0005EB66 /* GCDAsyncUdpSocket.m in Sources */, + 049B44702DACAB6D0005EB66 /* bbbAdManager.swift in Sources */, + 049B44712DACAB6D0005EB66 /* XUDPClient.m in Sources */, + 75F8FFE92CE723FF008E8DF6 /* YL_PlayVC.swift in Sources */, + 75F8FFD52CE7233B008E8DF6 /* ViewController.swift in Sources */, + 755ADA202D2D25C600C9D994 /* getIphone.swift in Sources */, + 75F8FFD12CE7233B008E8DF6 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 75F8FFD62CE7233B008E8DF6 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 75F8FFD72CE7233B008E8DF6 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 75F8FFDB2CE72340008E8DF6 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 75F8FFDC2CE72340008E8DF6 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 75F8FFDF2CE72340008E8DF6 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.4; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 75F8FFE02CE72340008E8DF6 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.4; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 75F8FFE22CE72340008E8DF6 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6297C68F5F2E4537B6CB3D1C /* Pods-PlayBTopOn.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGNING_ALLOWED = NO; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 33FNMTSNA6; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_PREFIX_HEADER = "$(SRCROOT)/PlayBTopOn/Header/MyWallpaperPCH.pch"; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = PlayBTopOn/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "{DisplayName}"; + INFOPLIST_KEY_NSUserTrackingUsageDescription = "\"\" needs to request tracking permissions to provide a personalized advertising experience. We respect and protect your privacy and will not sell your data to third parties."; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = {Version}; + PRODUCT_BUNDLE_IDENTIFIER = {BundleId}; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = smart; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/PlayBTopOn/Header/MyWallPaperHeader.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Debug; + }; + 75F8FFE32CE72340008E8DF6 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B423D6A110C2422F79FE6615 /* Pods-PlayBTopOn.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGNING_ALLOWED = NO; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 33FNMTSNA6; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_PREFIX_HEADER = "$(SRCROOT)/PlayBTopOn/Header/MyWallpaperPCH.pch"; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = PlayBTopOn/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "{DisplayName}"; + INFOPLIST_KEY_NSUserTrackingUsageDescription = "\"\" needs to request tracking permissions to provide a personalized advertising experience. We respect and protect your privacy and will not sell your data to third parties."; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = {Version}; + PRODUCT_BUNDLE_IDENTIFIER = {BundleId}; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = smart; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_COMPILATION_MODE = singlefile; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/PlayBTopOn/Header/MyWallPaperHeader.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 75F8FFC82CE7233B008E8DF6 /* Build configuration list for PBXProject "PlayBTopOn" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 75F8FFDF2CE72340008E8DF6 /* Debug */, + 75F8FFE02CE72340008E8DF6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 75F8FFE12CE72340008E8DF6 /* Build configuration list for PBXNativeTarget "PlayBTopOn" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 75F8FFE22CE72340008E8DF6 /* Debug */, + 75F8FFE32CE72340008E8DF6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 75F8FFC52CE7233B008E8DF6 /* Project object */; +} diff --git a/topon/template/PlayBTopOn/PlayBTopOn.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/topon/template/PlayBTopOn/PlayBTopOn.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/topon/template/PlayBTopOn/PlayBTopOn.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/topon/template/PlayBTopOn/PlayBTopOn.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/topon/template/PlayBTopOn/PlayBTopOn.xcodeproj/project.xcworkspace/xcuserdata/yihai16.xcuserdatad/UserInterfaceState.xcuserstate b/topon/template/PlayBTopOn/PlayBTopOn.xcodeproj/project.xcworkspace/xcuserdata/yihai16.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..4088f3c Binary files /dev/null and b/topon/template/PlayBTopOn/PlayBTopOn.xcodeproj/project.xcworkspace/xcuserdata/yihai16.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/topon/template/PlayBTopOn/PlayBTopOn.xcodeproj/xcuserdata/mac.xcuserdatad/xcschemes/xcschememanagement.plist b/topon/template/PlayBTopOn/PlayBTopOn.xcodeproj/xcuserdata/mac.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..8e2db6f --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn.xcodeproj/xcuserdata/mac.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + PlayBTopOn.xcscheme_^#shared#^_ + + orderHint + 11 + + + + diff --git a/topon/template/PlayBTopOn/PlayBTopOn.xcodeproj/xcuserdata/yihai16.xcuserdatad/xcschemes/xcschememanagement.plist b/topon/template/PlayBTopOn/PlayBTopOn.xcodeproj/xcuserdata/yihai16.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..2547f56 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn.xcodeproj/xcuserdata/yihai16.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + PlayBTopOn.xcscheme_^#shared#^_ + + orderHint + 8 + + + + diff --git a/topon/template/PlayBTopOn/PlayBTopOn.xcworkspace/contents.xcworkspacedata b/topon/template/PlayBTopOn/PlayBTopOn.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..ef1492d --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/topon/template/PlayBTopOn/PlayBTopOn.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/topon/template/PlayBTopOn/PlayBTopOn.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/topon/template/PlayBTopOn/PlayBTopOn.xcworkspace/xcuserdata/mac.xcuserdatad/UserInterfaceState.xcuserstate b/topon/template/PlayBTopOn/PlayBTopOn.xcworkspace/xcuserdata/mac.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..3cb3c40 Binary files /dev/null and b/topon/template/PlayBTopOn/PlayBTopOn.xcworkspace/xcuserdata/mac.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/topon/template/PlayBTopOn/PlayBTopOn.xcworkspace/xcuserdata/mac.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/topon/template/PlayBTopOn/PlayBTopOn.xcworkspace/xcuserdata/mac.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..3aeaf5a --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn.xcworkspace/xcuserdata/mac.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/topon/template/PlayBTopOn/PlayBTopOn.xcworkspace/xcuserdata/yihai16.xcuserdatad/UserInterfaceState.xcuserstate b/topon/template/PlayBTopOn/PlayBTopOn.xcworkspace/xcuserdata/yihai16.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..46623c1 Binary files /dev/null and b/topon/template/PlayBTopOn/PlayBTopOn.xcworkspace/xcuserdata/yihai16.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/topon/template/PlayBTopOn/PlayBTopOn.xcworkspace/xcuserdata/yihai16.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/topon/template/PlayBTopOn/PlayBTopOn.xcworkspace/xcuserdata/yihai16.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..92c8596 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn.xcworkspace/xcuserdata/yihai16.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/topon/template/PlayBTopOn/PlayBTopOn/AppDelegate.swift b/topon/template/PlayBTopOn/PlayBTopOn/AppDelegate.swift new file mode 100644 index 0000000..af5885f --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/AppDelegate.swift @@ -0,0 +1,41 @@ +// +// AppDelegate.swift +// PlayBTopOn +// +// Created by 忆海16 on 2024/11/15. +// + +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + static var shared: AppDelegate { + return UIApplication.shared.delegate as! AppDelegate + } + static let appid = UUID().uuidString + var window:UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + window = UIWindow() + window?.frame = UIScreen.main.bounds + NSLog("XS- app start:\(AppDelegate.appid)") + let vc = YL_PlayVC() + + window?.rootViewController = UINavigationController(rootViewController: vc) + + BbbAdManager.shared.initConfig() + + BbbAdManager.shared.initAd() + window?.makeKeyAndVisible() + return true + } + + func applicationWillEnterForeground(_ application: UIApplication) { + print("应用从后台进入前台") + BbbAdManager.shared.closeAd(v: 0); + + } + +} + diff --git a/topon/template/PlayBTopOn/PlayBTopOn/Assets.xcassets/AccentColor.colorset/Contents.json b/topon/template/PlayBTopOn/PlayBTopOn/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/topon/template/PlayBTopOn/PlayBTopOn/Assets.xcassets/AppIcon.appiconset/Contents.json b/topon/template/PlayBTopOn/PlayBTopOn/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..c35464c --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "h687603756335b.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/topon/template/PlayBTopOn/PlayBTopOn/Assets.xcassets/AppIcon.appiconset/h687603756335b.png b/topon/template/PlayBTopOn/PlayBTopOn/Assets.xcassets/AppIcon.appiconset/h687603756335b.png new file mode 100644 index 0000000..57b28e4 Binary files /dev/null and b/topon/template/PlayBTopOn/PlayBTopOn/Assets.xcassets/AppIcon.appiconset/h687603756335b.png differ diff --git a/topon/template/PlayBTopOn/PlayBTopOn/Assets.xcassets/Contents.json b/topon/template/PlayBTopOn/PlayBTopOn/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/topon/template/PlayBTopOn/PlayBTopOn/Base.lproj/LaunchScreen.storyboard b/topon/template/PlayBTopOn/PlayBTopOn/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/topon/template/PlayBTopOn/PlayBTopOn/Base.lproj/Main.storyboard b/topon/template/PlayBTopOn/PlayBTopOn/Base.lproj/Main.storyboard new file mode 100644 index 0000000..25a7638 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/Base.lproj/Main.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/topon/template/PlayBTopOn/PlayBTopOn/Header/MyWallPaperHeader.h b/topon/template/PlayBTopOn/PlayBTopOn/Header/MyWallPaperHeader.h new file mode 100644 index 0000000..63e3167 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/Header/MyWallPaperHeader.h @@ -0,0 +1,15 @@ +// +// MyWallPaperHeader.h +// Mywallpaper +// +// Created by 忆海16 on 2024/8/1. +// + +#ifndef MyWallPaperHeader_h +#define MyWallPaperHeader_h + +#import "initializationTopOn.h" +#import "XUDPClient.h" + + +#endif /* MyWallPaperHeader_h */ diff --git a/topon/template/PlayBTopOn/PlayBTopOn/Header/MyWallpaperPCH.pch b/topon/template/PlayBTopOn/PlayBTopOn/Header/MyWallpaperPCH.pch new file mode 100644 index 0000000..eeea4e8 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/Header/MyWallpaperPCH.pch @@ -0,0 +1,14 @@ +// +// MyWallpaperPCH.pch +// Mywallpaper +// +// Created by 忆海16 on 2024/8/1. +// +#import "PlayBTopOn-Swift.h" +#ifndef MyWallpaperPCH_pch +#define MyWallpaperPCH_pch + +// Include any system framework and library headers here that should be included in all compilation units. +// You will also need to set the Prefix Header build setting of one or more of your targets to reference this file. + +#endif /* MyWallpaperPCH_pch */ diff --git a/topon/template/PlayBTopOn/PlayBTopOn/Info.plist b/topon/template/PlayBTopOn/PlayBTopOn/Info.plist new file mode 100644 index 0000000..7feb523 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/Info.plist @@ -0,0 +1,555 @@ + + + + + LSApplicationQueriesSchemes + + ctrip + pinduoduo + openapp.jdmobile + taobao + imeituan + iosamap + alipay + baiduboxapp + vipshop + tmall + meituanwaimai + kwai + snssdk1128 + eleme + qunariphone + diditaxi + lianjiabeike + xhsdiscover + dianping + lianjia + ksnebula + sinaweibo + fleamarket + 1641486558 + id785385147 + id959841854 + id959841453 + id959840394 + id959841113 + id387682726 + id959841443 + 1094591345 + id1094591345 + id454434967 + id594457652 + id1182474649 + id1617391485 + id1567026344 + suning + baiduhaokan + bdminivideo + baiduboxlite + baiduboxmission + zhihu + wireless1688 + iqiyi + weixin + qihooloan + weishi + travelguide + wbmain + taobaotravel + alipays + youku + openjdlite + cainiao + kaola + OneTravel + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + SKAdNetworkItems + + + SKAdNetworkIdentifier + kbd757ywx3.skadnetwork + + + SKAdNetworkIdentifier + mls7yz5dvl.skadnetwork + + + SKAdNetworkIdentifier + 4fzdc2evr5.skadnetwork + + + SKAdNetworkIdentifier + 4pfyvq9l8r.skadnetwork + + + SKAdNetworkIdentifier + ydx93a7ass.skadnetwork + + + SKAdNetworkIdentifier + cg4yq2srnc.skadnetwork + + + SKAdNetworkIdentifier + p78axxw29g.skadnetwork + + + SKAdNetworkIdentifier + 737z793b9f.skadnetwork + + + SKAdNetworkIdentifier + v72qych5uu.skadnetwork + + + SKAdNetworkIdentifier + 6xzpu9s2p8.skadnetwork + + + SKAdNetworkIdentifier + ludvb6z3bs.skadnetwork + + + SKAdNetworkIdentifier + mlmmfzh3r3.skadnetwork + + + SKAdNetworkIdentifier + c6k4g5qg8m.skadnetwork + + + SKAdNetworkIdentifier + wg4vff78zm.skadnetwork + + + SKAdNetworkIdentifier + 523jb4fst2.skadnetwork + + + SKAdNetworkIdentifier + ggvn48r87g.skadnetwork + + + SKAdNetworkIdentifier + 22mmun2rn5.skadnetwork + + + SKAdNetworkIdentifier + 3sh42y64q3.skadnetwork + + + SKAdNetworkIdentifier + f38h382jlk.skadnetwork + + + SKAdNetworkIdentifier + 24t9a8vw3c.skadnetwork + + + SKAdNetworkIdentifier + hs6bdukanm.skadnetwork + + + SKAdNetworkIdentifier + prcb7njmu6.skadnetwork + + + SKAdNetworkIdentifier + m8dbw4sv7c.skadnetwork + + + SKAdNetworkIdentifier + 9nlqeag3gk.skadnetwork + + + SKAdNetworkIdentifier + cj5566h2ga.skadnetwork + + + SKAdNetworkIdentifier + cstr6suwn9.skadnetwork + + + SKAdNetworkIdentifier + w9q455wk68.skadnetwork + + + SKAdNetworkIdentifier + wzmmz9fp6w.skadnetwork + + + SKAdNetworkIdentifier + yclnxrl5pm.skadnetwork + + + SKAdNetworkIdentifier + 4468km3ulz.skadnetwork + + + SKAdNetworkIdentifier + t38b2kh725.skadnetwork + + + SKAdNetworkIdentifier + k674qkevps.skadnetwork + + + SKAdNetworkIdentifier + 7ug5zh24hu.skadnetwork + + + SKAdNetworkIdentifier + 5lm9lj6jb7.skadnetwork + + + SKAdNetworkIdentifier + 9rd848q2bz.skadnetwork + + + SKAdNetworkIdentifier + 7rz58n8ntl.skadnetwork + + + SKAdNetworkIdentifier + 4w7y6s5ca2.skadnetwork + + + SKAdNetworkIdentifier + feyaarzu9v.skadnetwork + + + SKAdNetworkIdentifier + ejvt5qm6ak.skadnetwork + + + SKAdNetworkIdentifier + 9t245vhmpl.skadnetwork + + + SKAdNetworkIdentifier + n9x2a789qt.skadnetwork + + + SKAdNetworkIdentifier + 44jx6755aq.skadnetwork + + + SKAdNetworkIdentifier + zmvfpc5aq8.skadnetwork + + + SKAdNetworkIdentifier + tl55sbb4fm.skadnetwork + + + SKAdNetworkIdentifier + 2u9pt9hc89.skadnetwork + + + SKAdNetworkIdentifier + 5a6flpkh64.skadnetwork + + + SKAdNetworkIdentifier + 8s468mfl3y.skadnetwork + + + SKAdNetworkIdentifier + glqzh8vgby.skadnetwork + + + SKAdNetworkIdentifier + av6w8kgt66.skadnetwork + + + SKAdNetworkIdentifier + klf5c3l5u5.skadnetwork + + + SKAdNetworkIdentifier + dzg6xy7pwj.skadnetwork + + + SKAdNetworkIdentifier + y45688jllp.skadnetwork + + + SKAdNetworkIdentifier + hdw39hrw9y.skadnetwork + + + SKAdNetworkIdentifier + ppxm28t8ap.skadnetwork + + + SKAdNetworkIdentifier + 424m5254lk.skadnetwork + + + SKAdNetworkIdentifier + 5l3tpt7t6e.skadnetwork + + + SKAdNetworkIdentifier + uw77j35x4d.skadnetwork + + + SKAdNetworkIdentifier + 4dzt52r2t5.skadnetwork + + + SKAdNetworkIdentifier + mtkv5xtk9e.skadnetwork + + + SKAdNetworkIdentifier + gta9lk7p23.skadnetwork + + + SKAdNetworkIdentifier + 5tjdwbrq8w.skadnetwork + + + SKAdNetworkIdentifier + 3rd42ekr43.skadnetwork + + + SKAdNetworkIdentifier + g28c52eehv.skadnetwork + + + SKAdNetworkIdentifier + su67r6k2v3.skadnetwork + + + SKAdNetworkIdentifier + rx5hdcabgc.skadnetwork + + + SKAdNetworkIdentifier + 2fnua5tdw4.skadnetwork + + + SKAdNetworkIdentifier + 32z4fx6l9h.skadnetwork + + + SKAdNetworkIdentifier + xy9t38ct57.skadnetwork + + + SKAdNetworkIdentifier + 54nzkqm89y.skadnetwork + + + SKAdNetworkIdentifier + 9b89h5y424.skadnetwork + + + SKAdNetworkIdentifier + pwa73g5rt2.skadnetwork + + + SKAdNetworkIdentifier + 79pbpufp6p.skadnetwork + + + SKAdNetworkIdentifier + kbmxgpxpgc.skadnetwork + + + SKAdNetworkIdentifier + 275upjj5gd.skadnetwork + + + SKAdNetworkIdentifier + rvh3l7un93.skadnetwork + + + SKAdNetworkIdentifier + qqp299437r.skadnetwork + + + SKAdNetworkIdentifier + 294l99pt4k.skadnetwork + + + SKAdNetworkIdentifier + 74b6s63p6l.skadnetwork + + + SKAdNetworkIdentifier + 44n7hlldy6.skadnetwork + + + SKAdNetworkIdentifier + 6p4ks3rnbw.skadnetwork + + + SKAdNetworkIdentifier + f73kdq92p3.skadnetwork + + + SKAdNetworkIdentifier + e5fvkxwrpn.skadnetwork + + + SKAdNetworkIdentifier + 97r2b46745.skadnetwork + + + SKAdNetworkIdentifier + 3qcr597p9d.skadnetwork + + + SKAdNetworkIdentifier + 578prtvx9j.skadnetwork + + + SKAdNetworkIdentifier + n6fk4nfna4.skadnetwork + + + SKAdNetworkIdentifier + b9bk5wbcq9.skadnetwork + + + SKAdNetworkIdentifier + 84993kbrcf.skadnetwork + + + SKAdNetworkIdentifier + 24zw6aqk47.skadnetwork + + + SKAdNetworkIdentifier + pwdxu55a5a.skadnetwork + + + SKAdNetworkIdentifier + cs644xg564.skadnetwork + + + SKAdNetworkIdentifier + 6964rsfnh4.skadnetwork + + + SKAdNetworkIdentifier + 9vvzujtq5s.skadnetwork + + + SKAdNetworkIdentifier + a7xqa6mtl2.skadnetwork + + + SKAdNetworkIdentifier + r45fhb6rf7.skadnetwork + + + SKAdNetworkIdentifier + c3frkrj4fj.skadnetwork + + + SKAdNetworkIdentifier + 6g9af3uyq4.skadnetwork + + + SKAdNetworkIdentifier + u679fj5vs4.skadnetwork + + + SKAdNetworkIdentifier + g2y4y55b64.skadnetwork + + + SKAdNetworkIdentifier + zq492l623r.skadnetwork + + + SKAdNetworkIdentifier + a8cz6cu7e5.skadnetwork + + + SKAdNetworkIdentifier + s39g8k73mm.skadnetwork + + + SKAdNetworkIdentifier + dbu4b84rxf.skadnetwork + + + SKAdNetworkIdentifier + mj797d8u6f.skadnetwork + + + SKAdNetworkIdentifier + v9wttpbfk9.skadnetwork + + + SKAdNetworkIdentifier + ns5j362hk7.skadnetwork + + + SKAdNetworkIdentifier + mqn7fxpca7.skadnetwork + + + SKAdNetworkIdentifier + 252b5q8x7y.skadnetwork + + + SKAdNetworkIdentifier + 3qy4746246.skadnetwork + + + SKAdNetworkIdentifier + 6yxyv74ff7.skadnetwork + + + SKAdNetworkIdentifier + 7fmhfwg9en.skadnetwork + + + SKAdNetworkIdentifier + cwn433xbcr.skadnetwork + + + SKAdNetworkIdentifier + f7s53z58qe.skadnetwork + + + SKAdNetworkIdentifier + vhf287vqwu.skadnetwork + + + SKAdNetworkIdentifier + x44k69ngh6.skadnetwork + + + SKAdNetworkIdentifier + mp6xlyr22a.skadnetwork + + + SKAdNetworkIdentifier + 7953jerfzd.skadnetwork + + + SKAdNetworkIdentifier + qu637u8glc.skadnetwork + + + SKAdNetworkIdentifier + 9yg77x724h.skadnetwork + + + SKAdNetworkIdentifier + n66cz3y3bx.skadnetwork + + + + diff --git a/topon/template/PlayBTopOn/PlayBTopOn/ViewController.swift b/topon/template/PlayBTopOn/PlayBTopOn/ViewController.swift new file mode 100644 index 0000000..cd3bbe9 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/ViewController.swift @@ -0,0 +1,19 @@ +// +// ViewController.swift +// PlayBTopOn +// +// Created by 忆海16 on 2024/11/15. +// + +import UIKit + +class ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + } + + +} + diff --git a/topon/template/PlayBTopOn/PlayBTopOn/playB/CocoaAsyncSocket.h b/topon/template/PlayBTopOn/PlayBTopOn/playB/CocoaAsyncSocket.h new file mode 100755 index 0000000..b7ac3b3 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/playB/CocoaAsyncSocket.h @@ -0,0 +1,19 @@ +// +// CocoaAsyncSocket.h +// CocoaAsyncSocket +// +// Created by Derek Clarkson on 10/08/2015. +// CocoaAsyncSocket project is in the public domain. +// + +#import + +//! Project version number for CocoaAsyncSocket. +FOUNDATION_EXPORT double cocoaAsyncSocketVersionNumber; + +//! Project version string for CocoaAsyncSocket. +FOUNDATION_EXPORT const unsigned char cocoaAsyncSocketVersionString[]; + + +#import "GCD/GCDAsyncUdpSocket.h" + diff --git a/topon/template/PlayBTopOn/PlayBTopOn/playB/GCD/GCDAsyncUdpSocket.h b/topon/template/PlayBTopOn/PlayBTopOn/playB/GCD/GCDAsyncUdpSocket.h new file mode 100755 index 0000000..af327e0 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/playB/GCD/GCDAsyncUdpSocket.h @@ -0,0 +1,1036 @@ +// +// GCDAsyncUdpSocket +// +// This class is in the public domain. +// Originally created by Robbie Hanson of Deusty LLC. +// Updated and maintained by Deusty LLC and the Apple development community. +// +// https://github.com/robbiehanson/CocoaAsyncSocket +// + +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN +extern NSString *const GCDAsyncUdpSocketException; +extern NSString *const GCDAsyncUdpSocketErrorDomain; + +extern NSString *const GCDAsyncUdpSocketQueueName; +extern NSString *const GCDAsyncUdpSocketThreadName; + +typedef NS_ERROR_ENUM(GCDAsyncUdpSocketErrorDomain, GCDAsyncUdpSocketError) { + GCDAsyncUdpSocketNoError = 0, // Never used + GCDAsyncUdpSocketBadConfigError, // Invalid configuration + GCDAsyncUdpSocketBadParamError, // Invalid parameter was passed + GCDAsyncUdpSocketSendTimeoutError, // A send operation timed out + GCDAsyncUdpSocketClosedError, // The socket was closed + GCDAsyncUdpSocketOtherError, // Description provided in userInfo +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@class GCDAsyncUdpSocket; + +@protocol GCDAsyncUdpSocketDelegate +@optional + +/** + * By design, UDP is a connectionless protocol, and connecting is not needed. + * However, you may optionally choose to connect to a particular host for reasons + * outlined in the documentation for the various connect methods listed above. + * + * This method is called if one of the connect methods are invoked, and the connection is successful. +**/ +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address; + +/** + * By design, UDP is a connectionless protocol, and connecting is not needed. + * However, you may optionally choose to connect to a particular host for reasons + * outlined in the documentation for the various connect methods listed above. + * + * This method is called if one of the connect methods are invoked, and the connection fails. + * This may happen, for example, if a domain name is given for the host and the domain name is unable to be resolved. +**/ +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError * _Nullable)error; + +/** + * Called when the datagram with the given tag has been sent. +**/ +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag; + +/** + * Called if an error occurs while trying to send a datagram. + * This could be due to a timeout, or something more serious such as the data being too large to fit in a sigle packet. +**/ +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError * _Nullable)error; + +/** + * Called when the socket has received the requested datagram. +**/ +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data + fromAddress:(NSData *)address + withFilterContext:(nullable id)filterContext; + +/** + * Called when the socket is closed. +**/ +- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError * _Nullable)error; + +@end + +/** + * You may optionally set a receive filter for the socket. + * A filter can provide several useful features: + * + * 1. Many times udp packets need to be parsed. + * Since the filter can run in its own independent queue, you can parallelize this parsing quite easily. + * The end result is a parallel socket io, datagram parsing, and packet processing. + * + * 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited. + * The filter can prevent such packets from arriving at the delegate. + * And because the filter can run in its own independent queue, this doesn't slow down the delegate. + * + * - Since the udp protocol does not guarantee delivery, udp packets may be lost. + * Many protocols built atop udp thus provide various resend/re-request algorithms. + * This sometimes results in duplicate packets arriving. + * A filter may allow you to architect the duplicate detection code to run in parallel to normal processing. + * + * - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive. + * Such packets need to be ignored. + * + * 3. Sometimes traffic shapers are needed to simulate real world environments. + * A filter allows you to write custom code to simulate such environments. + * The ability to code this yourself is especially helpful when your simulated environment + * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), + * or the system tools to handle this aren't available (e.g. on a mobile device). + * + * @param data - The packet that was received. + * @param address - The address the data was received from. + * See utilities section for methods to extract info from address. + * @param context - Out parameter you may optionally set, which will then be passed to the delegate method. + * For example, filter block can parse the data and then, + * pass the parsed data to the delegate. + * + * @returns - YES if the received packet should be passed onto the delegate. + * NO if the received packet should be discarded, and not reported to the delegete. + * + * Example: + * + * GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) { + * + * MyProtocolMessage *msg = [MyProtocol parseMessage:data]; + * + * *context = response; + * return (response != nil); + * }; + * [udpSocket setReceiveFilter:filter withQueue:myParsingQueue]; + * +**/ +typedef BOOL (^GCDAsyncUdpSocketReceiveFilterBlock)(NSData *data, NSData *address, id __nullable * __nonnull context); + +/** + * You may optionally set a send filter for the socket. + * A filter can provide several interesting possibilities: + * + * 1. Optional caching of resolved addresses for domain names. + * The cache could later be consulted, resulting in fewer system calls to getaddrinfo. + * + * 2. Reusable modules of code for bandwidth monitoring. + * + * 3. Sometimes traffic shapers are needed to simulate real world environments. + * A filter allows you to write custom code to simulate such environments. + * The ability to code this yourself is especially helpful when your simulated environment + * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), + * or the system tools to handle this aren't available (e.g. on a mobile device). + * + * @param data - The packet that was received. + * @param address - The address the data was received from. + * See utilities section for methods to extract info from address. + * @param tag - The tag that was passed in the send method. + * + * @returns - YES if the packet should actually be sent over the socket. + * NO if the packet should be silently dropped (not sent over the socket). + * + * Regardless of the return value, the delegate will be informed that the packet was successfully sent. + * +**/ +typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, long tag); + + +@interface GCDAsyncUdpSocket : NSObject + +/** + * GCDAsyncUdpSocket uses the standard delegate paradigm, + * but executes all delegate callbacks on a given delegate dispatch queue. + * This allows for maximum concurrency, while at the same time providing easy thread safety. + * + * You MUST set a delegate AND delegate dispatch queue before attempting to + * use the socket, or you will get an error. + * + * The socket queue is optional. + * If you pass NULL, GCDAsyncSocket will automatically create its own socket queue. + * If you choose to provide a socket queue, the socket queue must not be a concurrent queue, + * then please see the discussion for the method markSocketQueueTargetQueue. + * + * The delegate queue and socket queue can optionally be the same. +**/ +- (instancetype)init; +- (instancetype)initWithSocketQueue:(nullable dispatch_queue_t)sq; +- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq; +- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq NS_DESIGNATED_INITIALIZER; + +#pragma mark Configuration + +- (nullable id)delegate; +- (void)setDelegate:(nullable id)delegate; +- (void)synchronouslySetDelegate:(nullable id)delegate; + +- (nullable dispatch_queue_t)delegateQueue; +- (void)setDelegateQueue:(nullable dispatch_queue_t)delegateQueue; +- (void)synchronouslySetDelegateQueue:(nullable dispatch_queue_t)delegateQueue; + +- (void)getDelegate:(id __nullable * __nullable)delegatePtr delegateQueue:(dispatch_queue_t __nullable * __nullable)delegateQueuePtr; +- (void)setDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; +- (void)synchronouslySetDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; + +/** + * By default, both IPv4 and IPv6 are enabled. + * + * This means GCDAsyncUdpSocket automatically supports both protocols, + * and can send to IPv4 or IPv6 addresses, + * as well as receive over IPv4 and IPv6. + * + * For operations that require DNS resolution, GCDAsyncUdpSocket supports both IPv4 and IPv6. + * If a DNS lookup returns only IPv4 results, GCDAsyncUdpSocket will automatically use IPv4. + * If a DNS lookup returns only IPv6 results, GCDAsyncUdpSocket will automatically use IPv6. + * If a DNS lookup returns both IPv4 and IPv6 results, then the protocol used depends on the configured preference. + * If IPv4 is preferred, then IPv4 is used. + * If IPv6 is preferred, then IPv6 is used. + * If neutral, then the first IP version in the resolved array will be used. + * + * Starting with Mac OS X 10.7 Lion and iOS 5, the default IP preference is neutral. + * On prior systems the default IP preference is IPv4. + **/ +- (BOOL)isIPv4Enabled; +- (void)setIPv4Enabled:(BOOL)flag; + +- (BOOL)isIPv6Enabled; +- (void)setIPv6Enabled:(BOOL)flag; + +- (BOOL)isIPv4Preferred; +- (BOOL)isIPv6Preferred; +- (BOOL)isIPVersionNeutral; + +- (void)setPreferIPv4; +- (void)setPreferIPv6; +- (void)setIPVersionNeutral; + +/** + * Gets/Sets the maximum size of the buffer that will be allocated for receive operations. + * The default maximum size is 65535 bytes. + * + * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535. + * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295. + * + * Since the OS/GCD notifies us of the size of each received UDP packet, + * the actual allocated buffer size for each packet is exact. + * And in practice the size of UDP packets is generally much smaller than the max. + * Indeed most protocols will send and receive packets of only a few bytes, + * or will set a limit on the size of packets to prevent fragmentation in the IP layer. + * + * If you set the buffer size too small, the sockets API in the OS will silently discard + * any extra data, and you will not be notified of the error. +**/ +- (uint16_t)maxReceiveIPv4BufferSize; +- (void)setMaxReceiveIPv4BufferSize:(uint16_t)max; + +- (uint32_t)maxReceiveIPv6BufferSize; +- (void)setMaxReceiveIPv6BufferSize:(uint32_t)max; + +/** + * Gets/Sets the maximum size of the buffer that will be allocated for send operations. + * The default maximum size is 65535 bytes. + * + * Given that a typical link MTU is 1500 bytes, a large UDP datagram will have to be + * fragmented, and that’s both expensive and risky (if one fragment goes missing, the + * entire datagram is lost). You are much better off sending a large number of smaller + * UDP datagrams, preferably using a path MTU algorithm to avoid fragmentation. + * + * You must set it before the sockt is created otherwise it won't work. + * + **/ +- (uint16_t)maxSendBufferSize; +- (void)setMaxSendBufferSize:(uint16_t)max; + +/** + * User data allows you to associate arbitrary information with the socket. + * This data is not used internally in any way. +**/ +- (nullable id)userData; +- (void)setUserData:(nullable id)arbitraryUserData; + +#pragma mark Diagnostics + +/** + * Returns the local address info for the socket. + * + * The localAddress method returns a sockaddr structure wrapped in a NSData object. + * The localHost method returns the human readable IP address as a string. + * + * Note: Address info may not be available until after the socket has been binded, connected + * or until after data has been sent. +**/ +- (nullable NSData *)localAddress; +- (nullable NSString *)localHost; +- (uint16_t)localPort; + +- (nullable NSData *)localAddress_IPv4; +- (nullable NSString *)localHost_IPv4; +- (uint16_t)localPort_IPv4; + +- (nullable NSData *)localAddress_IPv6; +- (nullable NSString *)localHost_IPv6; +- (uint16_t)localPort_IPv6; + +/** + * Returns the remote address info for the socket. + * + * The connectedAddress method returns a sockaddr structure wrapped in a NSData object. + * The connectedHost method returns the human readable IP address as a string. + * + * Note: Since UDP is connectionless by design, connected address info + * will not be available unless the socket is explicitly connected to a remote host/port. + * If the socket is not connected, these methods will return nil / 0. +**/ +- (nullable NSData *)connectedAddress; +- (nullable NSString *)connectedHost; +- (uint16_t)connectedPort; + +/** + * Returns whether or not this socket has been connected to a single host. + * By design, UDP is a connectionless protocol, and connecting is not needed. + * If connected, the socket will only be able to send/receive data to/from the connected host. +**/ +- (BOOL)isConnected; + +/** + * Returns whether or not this socket has been closed. + * The only way a socket can be closed is if you explicitly call one of the close methods. +**/ +- (BOOL)isClosed; + +/** + * Returns whether or not this socket is IPv4. + * + * By default this will be true, unless: + * - IPv4 is disabled (via setIPv4Enabled:) + * - The socket is explicitly bound to an IPv6 address + * - The socket is connected to an IPv6 address +**/ +- (BOOL)isIPv4; + +/** + * Returns whether or not this socket is IPv6. + * + * By default this will be true, unless: + * - IPv6 is disabled (via setIPv6Enabled:) + * - The socket is explicitly bound to an IPv4 address + * _ The socket is connected to an IPv4 address + * + * This method will also return false on platforms that do not support IPv6. + * Note: The iPhone does not currently support IPv6. +**/ +- (BOOL)isIPv6; + +#pragma mark Binding + +/** + * Binds the UDP socket to the given port. + * Binding should be done for server sockets that receive data prior to sending it. + * Client sockets can skip binding, + * as the OS will automatically assign the socket an available port when it starts sending data. + * + * You may optionally pass a port number of zero to immediately bind the socket, + * yet still allow the OS to automatically assign an available port. + * + * You cannot bind a socket after its been connected. + * You can only bind a socket once. + * You can still connect a socket (if desired) after binding. + * + * On success, returns YES. + * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr. +**/ +- (BOOL)bindToPort:(uint16_t)port error:(NSError **)errPtr; + +/** + * Binds the UDP socket to the given port and optional interface. + * Binding should be done for server sockets that receive data prior to sending it. + * Client sockets can skip binding, + * as the OS will automatically assign the socket an available port when it starts sending data. + * + * You may optionally pass a port number of zero to immediately bind the socket, + * yet still allow the OS to automatically assign an available port. + * + * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). + * You may also use the special strings "localhost" or "loopback" to specify that + * the socket only accept packets from the local machine. + * + * You cannot bind a socket after its been connected. + * You can only bind a socket once. + * You can still connect a socket (if desired) after binding. + * + * On success, returns YES. + * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr. +**/ +- (BOOL)bindToPort:(uint16_t)port interface:(nullable NSString *)interface error:(NSError **)errPtr; + +/** + * Binds the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object. + * + * If you have an existing struct sockaddr you can convert it to a NSData object like so: + * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; + * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; + * + * Binding should be done for server sockets that receive data prior to sending it. + * Client sockets can skip binding, + * as the OS will automatically assign the socket an available port when it starts sending data. + * + * You cannot bind a socket after its been connected. + * You can only bind a socket once. + * You can still connect a socket (if desired) after binding. + * + * On success, returns YES. + * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr. +**/ +- (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr; + +#pragma mark Connecting + +/** + * Connects the UDP socket to the given host and port. + * By design, UDP is a connectionless protocol, and connecting is not needed. + * + * Choosing to connect to a specific host/port has the following effect: + * - You will only be able to send data to the connected host/port. + * - You will only be able to receive data from the connected host/port. + * - You will receive ICMP messages that come from the connected host/port, such as "connection refused". + * + * The actual process of connecting a UDP socket does not result in any communication on the socket. + * It simply changes the internal state of the socket. + * + * You cannot bind a socket after it has been connected. + * You can only connect a socket once. + * + * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2"). + * + * This method is asynchronous as it requires a DNS lookup to resolve the given host name. + * If an obvious error is detected, this method immediately returns NO and sets errPtr. + * If you don't care about the error, you can pass nil for errPtr. + * Otherwise, this method returns YES and begins the asynchronous connection process. + * The result of the asynchronous connection process will be reported via the delegate methods. + **/ +- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr; + +/** + * Connects the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object. + * + * If you have an existing struct sockaddr you can convert it to a NSData object like so: + * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; + * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; + * + * By design, UDP is a connectionless protocol, and connecting is not needed. + * + * Choosing to connect to a specific address has the following effect: + * - You will only be able to send data to the connected address. + * - You will only be able to receive data from the connected address. + * - You will receive ICMP messages that come from the connected address, such as "connection refused". + * + * Connecting a UDP socket does not result in any communication on the socket. + * It simply changes the internal state of the socket. + * + * You cannot bind a socket after its been connected. + * You can only connect a socket once. + * + * On success, returns YES. + * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. + * + * Note: Unlike the connectToHost:onPort:error: method, this method does not require a DNS lookup. + * Thus when this method returns, the connection has either failed or fully completed. + * In other words, this method is synchronous, unlike the asynchronous connectToHost::: method. + * However, for compatibility and simplification of delegate code, if this method returns YES + * then the corresponding delegate method (udpSocket:didConnectToHost:port:) is still invoked. +**/ +- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; + +#pragma mark Multicast + +/** + * Join multicast group. + * Group should be an IP address (eg @"225.228.0.1"). + * + * On success, returns YES. + * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. +**/ +- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr; + +/** + * Join multicast group. + * Group should be an IP address (eg @"225.228.0.1"). + * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). + * + * On success, returns YES. + * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. +**/ +- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr; + +- (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr; +- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr; + +/** + * Send multicast on a specified interface. + * For IPv4, interface should be the the IP address of the interface (eg @"192.168.10.1"). + * For IPv6, interface should be the a network interface name (eg @"en0"). + * + * On success, returns YES. + * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. +**/ + +- (BOOL)sendIPv4MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr; +- (BOOL)sendIPv6MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr; + +#pragma mark Reuse Port + +/** + * By default, only one socket can be bound to a given IP address + port at a time. + * To enable multiple processes to simultaneously bind to the same address+port, + * you need to enable this functionality in the socket. All processes that wish to + * use the address+port simultaneously must all enable reuse port on the socket + * bound to that port. + **/ +- (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr; + +#pragma mark Broadcast + +/** + * By default, the underlying socket in the OS will not allow you to send broadcast messages. + * In order to send broadcast messages, you need to enable this functionality in the socket. + * + * A broadcast is a UDP message to addresses like "192.168.255.255" or "255.255.255.255" that is + * delivered to every host on the network. + * The reason this is generally disabled by default (by the OS) is to prevent + * accidental broadcast messages from flooding the network. +**/ +- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr; + +#pragma mark Sending + +/** + * Asynchronously sends the given data, with the given timeout and tag. + * + * This method may only be used with a connected socket. + * Recall that connecting is optional for a UDP socket. + * For connected sockets, data can only be sent to the connected address. + * For non-connected sockets, the remote destination is specified for each packet. + * For more information about optionally connecting udp sockets, see the documentation for the connect methods above. + * + * @param data + * The data to send. + * If data is nil or zero-length, this method does nothing. + * If passing NSMutableData, please read the thread-safety notice below. + * + * @param timeout + * The timeout for the send opeartion. + * If the timeout value is negative, the send operation will not use a timeout. + * + * @param tag + * The tag is for your convenience. + * It is not sent or received over the socket in any manner what-so-ever. + * It is reported back as a parameter in the udpSocket:didSendDataWithTag: + * or udpSocket:didNotSendDataWithTag:dueToError: methods. + * You can use it as an array index, state id, type constant, etc. + * + * + * Thread-Safety Note: + * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while + * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method + * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying + * that this particular send operation has completed. + * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data. + * It simply retains it for performance reasons. + * Often times, if NSMutableData is passed, it is because a request/response was built up in memory. + * Copying this data adds an unwanted/unneeded overhead. + * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket + * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time + * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method. +**/ +- (void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; + +/** + * Asynchronously sends the given data, with the given timeout and tag, to the given host and port. + * + * This method cannot be used with a connected socket. + * Recall that connecting is optional for a UDP socket. + * For connected sockets, data can only be sent to the connected address. + * For non-connected sockets, the remote destination is specified for each packet. + * For more information about optionally connecting udp sockets, see the documentation for the connect methods above. + * + * @param data + * The data to send. + * If data is nil or zero-length, this method does nothing. + * If passing NSMutableData, please read the thread-safety notice below. + * + * @param host + * The destination to send the udp packet to. + * May be specified as a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2"). + * You may also use the convenience strings of "loopback" or "localhost". + * + * @param port + * The port of the host to send to. + * + * @param timeout + * The timeout for the send opeartion. + * If the timeout value is negative, the send operation will not use a timeout. + * + * @param tag + * The tag is for your convenience. + * It is not sent or received over the socket in any manner what-so-ever. + * It is reported back as a parameter in the udpSocket:didSendDataWithTag: + * or udpSocket:didNotSendDataWithTag:dueToError: methods. + * You can use it as an array index, state id, type constant, etc. + * + * + * Thread-Safety Note: + * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while + * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method + * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying + * that this particular send operation has completed. + * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data. + * It simply retains it for performance reasons. + * Often times, if NSMutableData is passed, it is because a request/response was built up in memory. + * Copying this data adds an unwanted/unneeded overhead. + * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket + * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time + * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method. +**/ +- (void)sendData:(NSData *)data + toHost:(NSString *)host + port:(uint16_t)port + withTimeout:(NSTimeInterval)timeout + tag:(long)tag; + +/** + * Asynchronously sends the given data, with the given timeout and tag, to the given address. + * + * This method cannot be used with a connected socket. + * Recall that connecting is optional for a UDP socket. + * For connected sockets, data can only be sent to the connected address. + * For non-connected sockets, the remote destination is specified for each packet. + * For more information about optionally connecting udp sockets, see the documentation for the connect methods above. + * + * @param data + * The data to send. + * If data is nil or zero-length, this method does nothing. + * If passing NSMutableData, please read the thread-safety notice below. + * + * @param remoteAddr + * The address to send the data to (specified as a sockaddr structure wrapped in a NSData object). + * + * @param timeout + * The timeout for the send opeartion. + * If the timeout value is negative, the send operation will not use a timeout. + * + * @param tag + * The tag is for your convenience. + * It is not sent or received over the socket in any manner what-so-ever. + * It is reported back as a parameter in the udpSocket:didSendDataWithTag: + * or udpSocket:didNotSendDataWithTag:dueToError: methods. + * You can use it as an array index, state id, type constant, etc. + * + * + * Thread-Safety Note: + * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while + * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method + * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying + * that this particular send operation has completed. + * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data. + * It simply retains it for performance reasons. + * Often times, if NSMutableData is passed, it is because a request/response was built up in memory. + * Copying this data adds an unwanted/unneeded overhead. + * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket + * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time + * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method. +**/ +- (void)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag; + +/** + * You may optionally set a send filter for the socket. + * A filter can provide several interesting possibilities: + * + * 1. Optional caching of resolved addresses for domain names. + * The cache could later be consulted, resulting in fewer system calls to getaddrinfo. + * + * 2. Reusable modules of code for bandwidth monitoring. + * + * 3. Sometimes traffic shapers are needed to simulate real world environments. + * A filter allows you to write custom code to simulate such environments. + * The ability to code this yourself is especially helpful when your simulated environment + * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), + * or the system tools to handle this aren't available (e.g. on a mobile device). + * + * For more information about GCDAsyncUdpSocketSendFilterBlock, see the documentation for its typedef. + * To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue. + * + * Note: This method invokes setSendFilter:withQueue:isAsynchronous: (documented below), + * passing YES for the isAsynchronous parameter. +**/ +- (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue; + +/** + * The receive filter can be run via dispatch_async or dispatch_sync. + * Most typical situations call for asynchronous operation. + * + * However, there are a few situations in which synchronous operation is preferred. + * Such is the case when the filter is extremely minimal and fast. + * This is because dispatch_sync is faster than dispatch_async. + * + * If you choose synchronous operation, be aware of possible deadlock conditions. + * Since the socket queue is executing your block via dispatch_sync, + * then you cannot perform any tasks which may invoke dispatch_sync on the socket queue. + * For example, you can't query properties on the socket. +**/ +- (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock + withQueue:(nullable dispatch_queue_t)filterQueue + isAsynchronous:(BOOL)isAsynchronous; + +#pragma mark Receiving + +/** + * There are two modes of operation for receiving packets: one-at-a-time & continuous. + * + * In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet. + * Receiving packets one-at-a-time may be better suited for implementing certain state machine code, + * where your state machine may not always be ready to process incoming packets. + * + * In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received. + * Receiving packets continuously is better suited to real-time streaming applications. + * + * You may switch back and forth between one-at-a-time mode and continuous mode. + * If the socket is currently in continuous mode, calling this method will switch it to one-at-a-time mode. + * + * When a packet is received (and not filtered by the optional receive filter), + * the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked. + * + * If the socket is able to begin receiving packets, this method returns YES. + * Otherwise it returns NO, and sets the errPtr with appropriate error information. + * + * An example error: + * You created a udp socket to act as a server, and immediately called receive. + * You forgot to first bind the socket to a port number, and received a error with a message like: + * "Must bind socket before you can receive data." +**/ +- (BOOL)receiveOnce:(NSError **)errPtr; + +/** + * There are two modes of operation for receiving packets: one-at-a-time & continuous. + * + * In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet. + * Receiving packets one-at-a-time may be better suited for implementing certain state machine code, + * where your state machine may not always be ready to process incoming packets. + * + * In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received. + * Receiving packets continuously is better suited to real-time streaming applications. + * + * You may switch back and forth between one-at-a-time mode and continuous mode. + * If the socket is currently in one-at-a-time mode, calling this method will switch it to continuous mode. + * + * For every received packet (not filtered by the optional receive filter), + * the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked. + * + * If the socket is able to begin receiving packets, this method returns YES. + * Otherwise it returns NO, and sets the errPtr with appropriate error information. + * + * An example error: + * You created a udp socket to act as a server, and immediately called receive. + * You forgot to first bind the socket to a port number, and received a error with a message like: + * "Must bind socket before you can receive data." +**/ +- (BOOL)beginReceiving:(NSError **)errPtr; + +/** + * If the socket is currently receiving (beginReceiving has been called), this method pauses the receiving. + * That is, it won't read any more packets from the underlying OS socket until beginReceiving is called again. + * + * Important Note: + * GCDAsyncUdpSocket may be running in parallel with your code. + * That is, your delegate is likely running on a separate thread/dispatch_queue. + * When you invoke this method, GCDAsyncUdpSocket may have already dispatched delegate methods to be invoked. + * Thus, if those delegate methods have already been dispatch_async'd, + * your didReceive delegate method may still be invoked after this method has been called. + * You should be aware of this, and program defensively. +**/ +- (void)pauseReceiving; + +/** + * You may optionally set a receive filter for the socket. + * This receive filter may be set to run in its own queue (independent of delegate queue). + * + * A filter can provide several useful features. + * + * 1. Many times udp packets need to be parsed. + * Since the filter can run in its own independent queue, you can parallelize this parsing quite easily. + * The end result is a parallel socket io, datagram parsing, and packet processing. + * + * 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited. + * The filter can prevent such packets from arriving at the delegate. + * And because the filter can run in its own independent queue, this doesn't slow down the delegate. + * + * - Since the udp protocol does not guarantee delivery, udp packets may be lost. + * Many protocols built atop udp thus provide various resend/re-request algorithms. + * This sometimes results in duplicate packets arriving. + * A filter may allow you to architect the duplicate detection code to run in parallel to normal processing. + * + * - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive. + * Such packets need to be ignored. + * + * 3. Sometimes traffic shapers are needed to simulate real world environments. + * A filter allows you to write custom code to simulate such environments. + * The ability to code this yourself is especially helpful when your simulated environment + * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), + * or the system tools to handle this aren't available (e.g. on a mobile device). + * + * Example: + * + * GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) { + * + * MyProtocolMessage *msg = [MyProtocol parseMessage:data]; + * + * *context = response; + * return (response != nil); + * }; + * [udpSocket setReceiveFilter:filter withQueue:myParsingQueue]; + * + * For more information about GCDAsyncUdpSocketReceiveFilterBlock, see the documentation for its typedef. + * To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue. + * + * Note: This method invokes setReceiveFilter:withQueue:isAsynchronous: (documented below), + * passing YES for the isAsynchronous parameter. +**/ +- (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue; + +/** + * The receive filter can be run via dispatch_async or dispatch_sync. + * Most typical situations call for asynchronous operation. + * + * However, there are a few situations in which synchronous operation is preferred. + * Such is the case when the filter is extremely minimal and fast. + * This is because dispatch_sync is faster than dispatch_async. + * + * If you choose synchronous operation, be aware of possible deadlock conditions. + * Since the socket queue is executing your block via dispatch_sync, + * then you cannot perform any tasks which may invoke dispatch_sync on the socket queue. + * For example, you can't query properties on the socket. +**/ +- (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock + withQueue:(nullable dispatch_queue_t)filterQueue + isAsynchronous:(BOOL)isAsynchronous; + +#pragma mark Closing + +/** + * Immediately closes the underlying socket. + * Any pending send operations are discarded. + * + * The GCDAsyncUdpSocket instance may optionally be used again. + * (it will setup/configure/use another unnderlying BSD socket). +**/ +- (void)close; + +/** + * Closes the underlying socket after all pending send operations have been sent. + * + * The GCDAsyncUdpSocket instance may optionally be used again. + * (it will setup/configure/use another unnderlying BSD socket). +**/ +- (void)closeAfterSending; + +#pragma mark Advanced +/** + * GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue. + * In most cases, the instance creates this queue itself. + * However, to allow for maximum flexibility, the internal queue may be passed in the init method. + * This allows for some advanced options such as controlling socket priority via target queues. + * However, when one begins to use target queues like this, they open the door to some specific deadlock issues. + * + * For example, imagine there are 2 queues: + * dispatch_queue_t socketQueue; + * dispatch_queue_t socketTargetQueue; + * + * If you do this (pseudo-code): + * socketQueue.targetQueue = socketTargetQueue; + * + * Then all socketQueue operations will actually get run on the given socketTargetQueue. + * This is fine and works great in most situations. + * But if you run code directly from within the socketTargetQueue that accesses the socket, + * you could potentially get deadlock. Imagine the following code: + * + * - (BOOL)socketHasSomething + * { + * __block BOOL result = NO; + * dispatch_block_t block = ^{ + * result = [self someInternalMethodToBeRunOnlyOnSocketQueue]; + * } + * if (is_executing_on_queue(socketQueue)) + * block(); + * else + * dispatch_sync(socketQueue, block); + * + * return result; + * } + * + * What happens if you call this method from the socketTargetQueue? The result is deadlock. + * This is because the GCD API offers no mechanism to discover a queue's targetQueue. + * Thus we have no idea if our socketQueue is configured with a targetQueue. + * If we had this information, we could easily avoid deadlock. + * But, since these API's are missing or unfeasible, you'll have to explicitly set it. + * + * IF you pass a socketQueue via the init method, + * AND you've configured the passed socketQueue with a targetQueue, + * THEN you should pass the end queue in the target hierarchy. + * + * For example, consider the following queue hierarchy: + * socketQueue -> ipQueue -> moduleQueue + * + * This example demonstrates priority shaping within some server. + * All incoming client connections from the same IP address are executed on the same target queue. + * And all connections for a particular module are executed on the same target queue. + * Thus, the priority of all networking for the entire module can be changed on the fly. + * Additionally, networking traffic from a single IP cannot monopolize the module. + * + * Here's how you would accomplish something like that: + * - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock + * { + * dispatch_queue_t socketQueue = dispatch_queue_create("", NULL); + * dispatch_queue_t ipQueue = [self ipQueueForAddress:address]; + * + * dispatch_set_target_queue(socketQueue, ipQueue); + * dispatch_set_target_queue(iqQueue, moduleQueue); + * + * return socketQueue; + * } + * - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket + * { + * [clientConnections addObject:newSocket]; + * [newSocket markSocketQueueTargetQueue:moduleQueue]; + * } + * + * Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue. + * This is often NOT the case, as such queues are used solely for execution shaping. + **/ +- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue; +- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue; + +/** + * It's not thread-safe to access certain variables from outside the socket's internal queue. + * + * For example, the socket file descriptor. + * File descriptors are simply integers which reference an index in the per-process file table. + * However, when one requests a new file descriptor (by opening a file or socket), + * the file descriptor returned is guaranteed to be the lowest numbered unused descriptor. + * So if we're not careful, the following could be possible: + * + * - Thread A invokes a method which returns the socket's file descriptor. + * - The socket is closed via the socket's internal queue on thread B. + * - Thread C opens a file, and subsequently receives the file descriptor that was previously the socket's FD. + * - Thread A is now accessing/altering the file instead of the socket. + * + * In addition to this, other variables are not actually objects, + * and thus cannot be retained/released or even autoreleased. + * An example is the sslContext, of type SSLContextRef, which is actually a malloc'd struct. + * + * Although there are internal variables that make it difficult to maintain thread-safety, + * it is important to provide access to these variables + * to ensure this class can be used in a wide array of environments. + * This method helps to accomplish this by invoking the current block on the socket's internal queue. + * The methods below can be invoked from within the block to access + * those generally thread-unsafe internal variables in a thread-safe manner. + * The given block will be invoked synchronously on the socket's internal queue. + * + * If you save references to any protected variables and use them outside the block, you do so at your own peril. +**/ +- (void)performBlock:(dispatch_block_t)block; + +/** + * These methods are only available from within the context of a performBlock: invocation. + * See the documentation for the performBlock: method above. + * + * Provides access to the socket's file descriptor(s). + * If the socket isn't connected, or explicity bound to a particular interface, + * it might actually have multiple internal socket file descriptors - one for IPv4 and one for IPv6. +**/ +- (int)socketFD; +- (int)socket4FD; +- (int)socket6FD; + +#if TARGET_OS_IPHONE + +/** + * These methods are only available from within the context of a performBlock: invocation. + * See the documentation for the performBlock: method above. + * + * Returns (creating if necessary) a CFReadStream/CFWriteStream for the internal socket. + * + * Generally GCDAsyncUdpSocket doesn't use CFStream. (It uses the faster GCD API's.) + * However, if you need one for any reason, + * these methods are a convenient way to get access to a safe instance of one. +**/ +- (nullable CFReadStreamRef)readStream; +- (nullable CFWriteStreamRef)writeStream; + +/** + * This method is only available from within the context of a performBlock: invocation. + * See the documentation for the performBlock: method above. + * + * Configures the socket to allow it to operate when the iOS application has been backgrounded. + * In other words, this method creates a read & write stream, and invokes: + * + * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); + * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); + * + * Returns YES if successful, NO otherwise. + * + * Example usage: + * + * [asyncUdpSocket performBlock:^{ + * [asyncUdpSocket enableBackgroundingOnSocket]; + * }]; + * + * + * NOTE : Apple doesn't currently support backgrounding UDP sockets. (Only TCP for now). +**/ +//- (BOOL)enableBackgroundingOnSockets; + +#endif + +#pragma mark Utilities + +/** + * Extracting host/port/family information from raw address data. +**/ + ++ (nullable NSString *)hostFromAddress:(NSData *)address; ++ (uint16_t)portFromAddress:(NSData *)address; ++ (int)familyFromAddress:(NSData *)address; + ++ (BOOL)isIPv4Address:(NSData *)address; ++ (BOOL)isIPv6Address:(NSData *)address; + ++ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr fromAddress:(NSData *)address; ++ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr family:(int * __nullable)afPtr fromAddress:(NSData *)address; + +@end + +NS_ASSUME_NONNULL_END diff --git a/topon/template/PlayBTopOn/PlayBTopOn/playB/GCD/GCDAsyncUdpSocket.m b/topon/template/PlayBTopOn/PlayBTopOn/playB/GCD/GCDAsyncUdpSocket.m new file mode 100755 index 0000000..af0cbf2 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/playB/GCD/GCDAsyncUdpSocket.m @@ -0,0 +1,5632 @@ +// +// GCDAsyncUdpSocket +// +// This class is in the public domain. +// Originally created by Robbie Hanson of Deusty LLC. +// Updated and maintained by Deusty LLC and the Apple development community. +// +// https://github.com/robbiehanson/CocoaAsyncSocket +// + +#import "GCDAsyncUdpSocket.h" + +#if ! __has_feature(objc_arc) +#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +// For more information see: https://github.com/robbiehanson/CocoaAsyncSocket/wiki/ARC +#endif + +#if TARGET_OS_IPHONE + #import + #import +#endif + +#import +#import +#import +#import +#import +#import +#import + + +#if 0 + +// Logging Enabled - See log level below + +// Logging uses the CocoaLumberjack framework (which is also GCD based). +// https://github.com/robbiehanson/CocoaLumberjack +// +// It allows us to do a lot of logging without significantly slowing down the code. +#import "DDLog.h" + +#define LogAsync NO +#define LogContext 65535 + +#define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__) +#define LogC(flg, frmt, ...) LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__) + +#define LogError(frmt, ...) LogObjc(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) +#define LogWarn(frmt, ...) LogObjc(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) +#define LogInfo(frmt, ...) LogObjc(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) +#define LogVerbose(frmt, ...) LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) + +#define LogCError(frmt, ...) LogC(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) +#define LogCWarn(frmt, ...) LogC(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) +#define LogCInfo(frmt, ...) LogC(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) +#define LogCVerbose(frmt, ...) LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) + +#define LogTrace() LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD) +#define LogCTrace() LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__) + +// Log levels : off, error, warn, info, verbose +static const int logLevel = LOG_LEVEL_VERBOSE; + +#else + +// Logging Disabled + +#define LogError(frmt, ...) {} +#define LogWarn(frmt, ...) {} +#define LogInfo(frmt, ...) {} +#define LogVerbose(frmt, ...) {} + +#define LogCError(frmt, ...) {} +#define LogCWarn(frmt, ...) {} +#define LogCInfo(frmt, ...) {} +#define LogCVerbose(frmt, ...) {} + +#define LogTrace() {} +#define LogCTrace(frmt, ...) {} + +#endif + +/** + * Seeing a return statements within an inner block + * can sometimes be mistaken for a return point of the enclosing method. + * This makes inline blocks a bit easier to read. +**/ +#define return_from_block return + +/** + * A socket file descriptor is really just an integer. + * It represents the index of the socket within the kernel. + * This makes invalid file descriptor comparisons easier to read. +**/ +#define SOCKET_NULL -1 + +/** + * Just to type less code. +**/ +#define AutoreleasedBlock(block) ^{ @autoreleasepool { block(); }} + + +@class GCDAsyncUdpSendPacket; + +NSString *const GCDAsyncUdpSocketException = @"GCDAsyncUdpSocketException"; +NSString *const GCDAsyncUdpSocketErrorDomain = @"GCDAsyncUdpSocketErrorDomain"; + +NSString *const GCDAsyncUdpSocketQueueName = @"GCDAsyncUdpSocket"; +NSString *const GCDAsyncUdpSocketThreadName = @"GCDAsyncUdpSocket-CFStream"; + +enum GCDAsyncUdpSocketFlags +{ + kDidCreateSockets = 1 << 0, // If set, the sockets have been created. + kDidBind = 1 << 1, // If set, bind has been called. + kConnecting = 1 << 2, // If set, a connection attempt is in progress. + kDidConnect = 1 << 3, // If set, socket is connected. + kReceiveOnce = 1 << 4, // If set, one-at-a-time receive is enabled + kReceiveContinuous = 1 << 5, // If set, continuous receive is enabled + kIPv4Deactivated = 1 << 6, // If set, socket4 was closed due to bind or connect on IPv6. + kIPv6Deactivated = 1 << 7, // If set, socket6 was closed due to bind or connect on IPv4. + kSend4SourceSuspended = 1 << 8, // If set, send4Source is suspended. + kSend6SourceSuspended = 1 << 9, // If set, send6Source is suspended. + kReceive4SourceSuspended = 1 << 10, // If set, receive4Source is suspended. + kReceive6SourceSuspended = 1 << 11, // If set, receive6Source is suspended. + kSock4CanAcceptBytes = 1 << 12, // If set, we know socket4 can accept bytes. If unset, it's unknown. + kSock6CanAcceptBytes = 1 << 13, // If set, we know socket6 can accept bytes. If unset, it's unknown. + kForbidSendReceive = 1 << 14, // If set, no new send or receive operations are allowed to be queued. + kCloseAfterSends = 1 << 15, // If set, close as soon as no more sends are queued. + kFlipFlop = 1 << 16, // Used to alternate between IPv4 and IPv6 sockets. +#if TARGET_OS_IPHONE + kAddedStreamListener = 1 << 17, // If set, CFStreams have been added to listener thread +#endif +}; + +enum GCDAsyncUdpSocketConfig +{ + kIPv4Disabled = 1 << 0, // If set, IPv4 is disabled + kIPv6Disabled = 1 << 1, // If set, IPv6 is disabled + kPreferIPv4 = 1 << 2, // If set, IPv4 is preferred over IPv6 + kPreferIPv6 = 1 << 3, // If set, IPv6 is preferred over IPv4 +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface GCDAsyncUdpSocket () +{ +#if __has_feature(objc_arc_weak) + __weak id delegate; +#else + __unsafe_unretained id delegate; +#endif + dispatch_queue_t delegateQueue; + + GCDAsyncUdpSocketReceiveFilterBlock receiveFilterBlock; + dispatch_queue_t receiveFilterQueue; + BOOL receiveFilterAsync; + + GCDAsyncUdpSocketSendFilterBlock sendFilterBlock; + dispatch_queue_t sendFilterQueue; + BOOL sendFilterAsync; + + uint32_t flags; + uint16_t config; + + uint16_t max4ReceiveSize; + uint32_t max6ReceiveSize; + + uint16_t maxSendSize; + + int socket4FD; + int socket6FD; + + dispatch_queue_t socketQueue; + + dispatch_source_t send4Source; + dispatch_source_t send6Source; + dispatch_source_t receive4Source; + dispatch_source_t receive6Source; + dispatch_source_t sendTimer; + + GCDAsyncUdpSendPacket *currentSend; + NSMutableArray *sendQueue; + + unsigned long socket4FDBytesAvailable; + unsigned long socket6FDBytesAvailable; + + uint32_t pendingFilterOperations; + + NSData *cachedLocalAddress4; + NSString *cachedLocalHost4; + uint16_t cachedLocalPort4; + + NSData *cachedLocalAddress6; + NSString *cachedLocalHost6; + uint16_t cachedLocalPort6; + + NSData *cachedConnectedAddress; + NSString *cachedConnectedHost; + uint16_t cachedConnectedPort; + int cachedConnectedFamily; + + void *IsOnSocketQueueOrTargetQueueKey; + +#if TARGET_OS_IPHONE + CFStreamClientContext streamContext; + CFReadStreamRef readStream4; + CFReadStreamRef readStream6; + CFWriteStreamRef writeStream4; + CFWriteStreamRef writeStream6; +#endif + + id userData; +} + +- (void)resumeSend4Source; +- (void)resumeSend6Source; +- (void)resumeReceive4Source; +- (void)resumeReceive6Source; +- (void)closeSockets; + +- (void)maybeConnect; +- (BOOL)connectWithAddress4:(NSData *)address4 error:(NSError **)errPtr; +- (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr; + +- (void)maybeDequeueSend; +- (void)doPreSend; +- (void)doSend; +- (void)endCurrentSend; +- (void)setupSendTimerWithTimeout:(NSTimeInterval)timeout; + +- (void)doReceive; +- (void)doReceiveEOF; + +- (void)closeWithError:(NSError *)error; + +- (BOOL)performMulticastRequest:(int)requestType forGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr; + +#if TARGET_OS_IPHONE +- (BOOL)createReadAndWriteStreams:(NSError **)errPtr; +- (BOOL)registerForStreamCallbacks:(NSError **)errPtr; +- (BOOL)addStreamsToRunLoop:(NSError **)errPtr; +- (BOOL)openStreams:(NSError **)errPtr; +- (void)removeStreamsFromRunLoop; +- (void)closeReadAndWriteStreams; +#endif + ++ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4; ++ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6; ++ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4; ++ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6; + +#if TARGET_OS_IPHONE +// Forward declaration ++ (void)listenerThread:(id)unused; +#endif + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * The GCDAsyncUdpSendPacket encompasses the instructions for a single send/write. +**/ +@interface GCDAsyncUdpSendPacket : NSObject { +@public + NSData *buffer; + NSTimeInterval timeout; + long tag; + + BOOL resolveInProgress; + BOOL filterInProgress; + + NSArray *resolvedAddresses; + NSError *resolveError; + + NSData *address; + int addressFamily; +} + +- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i NS_DESIGNATED_INITIALIZER; + +@end + +@implementation GCDAsyncUdpSendPacket + +// Cover the superclass' designated initializer +- (instancetype)init NS_UNAVAILABLE +{ + NSAssert(0, @"Use the designated initializer"); + return nil; +} + +- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i +{ + if ((self = [super init])) + { + buffer = d; + timeout = t; + tag = i; + + resolveInProgress = NO; + } + return self; +} + + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface GCDAsyncUdpSpecialPacket : NSObject { +@public +// uint8_t type; + + BOOL resolveInProgress; + + NSArray *addresses; + NSError *error; +} + +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +@end + +@implementation GCDAsyncUdpSpecialPacket + +- (instancetype)init +{ + self = [super init]; + return self; +} + + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation GCDAsyncUdpSocket + +- (instancetype)init +{ + LogTrace(); + + return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL]; +} + +- (instancetype)initWithSocketQueue:(dispatch_queue_t)sq +{ + LogTrace(); + + return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq]; +} + +- (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq +{ + LogTrace(); + + return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL]; +} + +- (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq +{ + LogTrace(); + + if ((self = [super init])) + { + delegate = aDelegate; + + if (dq) + { + delegateQueue = dq; + #if !OS_OBJECT_USE_OBJC + dispatch_retain(delegateQueue); + #endif + } + + max4ReceiveSize = 65535; + max6ReceiveSize = 65535; + + maxSendSize = 65535; + + socket4FD = SOCKET_NULL; + socket6FD = SOCKET_NULL; + + if (sq) + { + NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), + @"The given socketQueue parameter must not be a concurrent queue."); + NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), + @"The given socketQueue parameter must not be a concurrent queue."); + NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), + @"The given socketQueue parameter must not be a concurrent queue."); + + socketQueue = sq; + #if !OS_OBJECT_USE_OBJC + dispatch_retain(socketQueue); + #endif + } + else + { + socketQueue = dispatch_queue_create([GCDAsyncUdpSocketQueueName UTF8String], NULL); + } + + // The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter. + // From the documentation: + // + // > Keys are only compared as pointers and are never dereferenced. + // > Thus, you can use a pointer to a static variable for a specific subsystem or + // > any other value that allows you to identify the value uniquely. + // + // We're just going to use the memory address of an ivar. + // Specifically an ivar that is explicitly named for our purpose to make the code more readable. + // + // However, it feels tedious (and less readable) to include the "&" all the time: + // dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey) + // + // So we're going to make it so it doesn't matter if we use the '&' or not, + // by assigning the value of the ivar to the address of the ivar. + // Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey; + + IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey; + + void *nonNullUnusedPointer = (__bridge void *)self; + dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL); + + currentSend = nil; + sendQueue = [[NSMutableArray alloc] initWithCapacity:5]; + + #if TARGET_OS_IPHONE + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(applicationWillEnterForeground:) + name:UIApplicationWillEnterForegroundNotification + object:nil]; + #endif + } + return self; +} + +- (void)dealloc +{ + LogInfo(@"%@ - %@ (start)", THIS_METHOD, self); + +#if TARGET_OS_IPHONE + [[NSNotificationCenter defaultCenter] removeObserver:self]; +#endif + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + [self closeWithError:nil]; + } + else + { + dispatch_sync(socketQueue, ^{ + [self closeWithError:nil]; + }); + } + + delegate = nil; + #if !OS_OBJECT_USE_OBJC + if (delegateQueue) dispatch_release(delegateQueue); + #endif + delegateQueue = NULL; + + #if !OS_OBJECT_USE_OBJC + if (socketQueue) dispatch_release(socketQueue); + #endif + socketQueue = NULL; + + LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Configuration +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (id)delegate +{ + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return delegate; + } + else + { + __block id result = nil; + + dispatch_sync(socketQueue, ^{ + result = self->delegate; + }); + + return result; + } +} + +- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously +{ + dispatch_block_t block = ^{ + self->delegate = newDelegate; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { + block(); + } + else { + if (synchronously) + dispatch_sync(socketQueue, block); + else + dispatch_async(socketQueue, block); + } +} + +- (void)setDelegate:(id)newDelegate +{ + [self setDelegate:newDelegate synchronously:NO]; +} + +- (void)synchronouslySetDelegate:(id)newDelegate +{ + [self setDelegate:newDelegate synchronously:YES]; +} + +- (dispatch_queue_t)delegateQueue +{ + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return delegateQueue; + } + else + { + __block dispatch_queue_t result = NULL; + + dispatch_sync(socketQueue, ^{ + result = self->delegateQueue; + }); + + return result; + } +} + +- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously +{ + dispatch_block_t block = ^{ + + #if !OS_OBJECT_USE_OBJC + if (self->delegateQueue) dispatch_release(self->delegateQueue); + if (newDelegateQueue) dispatch_retain(newDelegateQueue); + #endif + + self->delegateQueue = newDelegateQueue; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { + block(); + } + else { + if (synchronously) + dispatch_sync(socketQueue, block); + else + dispatch_async(socketQueue, block); + } +} + +- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue +{ + [self setDelegateQueue:newDelegateQueue synchronously:NO]; +} + +- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue +{ + [self setDelegateQueue:newDelegateQueue synchronously:YES]; +} + +- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr +{ + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + if (delegatePtr) *delegatePtr = delegate; + if (delegateQueuePtr) *delegateQueuePtr = delegateQueue; + } + else + { + __block id dPtr = NULL; + __block dispatch_queue_t dqPtr = NULL; + + dispatch_sync(socketQueue, ^{ + dPtr = self->delegate; + dqPtr = self->delegateQueue; + }); + + if (delegatePtr) *delegatePtr = dPtr; + if (delegateQueuePtr) *delegateQueuePtr = dqPtr; + } +} + +- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously +{ + dispatch_block_t block = ^{ + + self->delegate = newDelegate; + + #if !OS_OBJECT_USE_OBJC + if (self->delegateQueue) dispatch_release(self->delegateQueue); + if (newDelegateQueue) dispatch_retain(newDelegateQueue); + #endif + + self->delegateQueue = newDelegateQueue; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { + block(); + } + else { + if (synchronously) + dispatch_sync(socketQueue, block); + else + dispatch_async(socketQueue, block); + } +} + +- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue +{ + [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO]; +} + +- (void)synchronouslySetDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue +{ + [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES]; +} + +- (BOOL)isIPv4Enabled +{ + // Note: YES means kIPv4Disabled is OFF + + __block BOOL result = NO; + + dispatch_block_t block = ^{ + + result = ((self->config & kIPv4Disabled) == 0); + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (void)setIPv4Enabled:(BOOL)flag +{ + // Note: YES means kIPv4Disabled is OFF + + dispatch_block_t block = ^{ + + LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO")); + + if (flag) + self->config &= ~kIPv4Disabled; + else + self->config |= kIPv4Disabled; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (BOOL)isIPv6Enabled +{ + // Note: YES means kIPv6Disabled is OFF + + __block BOOL result = NO; + + dispatch_block_t block = ^{ + + result = ((self->config & kIPv6Disabled) == 0); + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (void)setIPv6Enabled:(BOOL)flag +{ + // Note: YES means kIPv6Disabled is OFF + + dispatch_block_t block = ^{ + + LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO")); + + if (flag) + self->config &= ~kIPv6Disabled; + else + self->config |= kIPv6Disabled; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (BOOL)isIPv4Preferred +{ + __block BOOL result = NO; + + dispatch_block_t block = ^{ + result = (self->config & kPreferIPv4) ? YES : NO; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (BOOL)isIPv6Preferred +{ + __block BOOL result = NO; + + dispatch_block_t block = ^{ + result = (self->config & kPreferIPv6) ? YES : NO; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (BOOL)isIPVersionNeutral +{ + __block BOOL result = NO; + + dispatch_block_t block = ^{ + result = (self->config & (kPreferIPv4 | kPreferIPv6)) == 0; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (void)setPreferIPv4 +{ + dispatch_block_t block = ^{ + + LogTrace(); + + self->config |= kPreferIPv4; + self->config &= ~kPreferIPv6; + + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (void)setPreferIPv6 +{ + dispatch_block_t block = ^{ + + LogTrace(); + + self->config &= ~kPreferIPv4; + self->config |= kPreferIPv6; + + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (void)setIPVersionNeutral +{ + dispatch_block_t block = ^{ + + LogTrace(); + + self->config &= ~kPreferIPv4; + self->config &= ~kPreferIPv6; + + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (uint16_t)maxReceiveIPv4BufferSize +{ + __block uint16_t result = 0; + + dispatch_block_t block = ^{ + + result = self->max4ReceiveSize; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (void)setMaxReceiveIPv4BufferSize:(uint16_t)max +{ + dispatch_block_t block = ^{ + + LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); + + self->max4ReceiveSize = max; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (uint32_t)maxReceiveIPv6BufferSize +{ + __block uint32_t result = 0; + + dispatch_block_t block = ^{ + + result = self->max6ReceiveSize; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (void)setMaxReceiveIPv6BufferSize:(uint32_t)max +{ + dispatch_block_t block = ^{ + + LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); + + self->max6ReceiveSize = max; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (void)setMaxSendBufferSize:(uint16_t)max +{ + dispatch_block_t block = ^{ + + LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); + + self->maxSendSize = max; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (uint16_t)maxSendBufferSize +{ + __block uint16_t result = 0; + + dispatch_block_t block = ^{ + + result = self->maxSendSize; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (id)userData +{ + __block id result = nil; + + dispatch_block_t block = ^{ + + result = self->userData; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (void)setUserData:(id)arbitraryUserData +{ + dispatch_block_t block = ^{ + + if (self->userData != arbitraryUserData) + { + self->userData = arbitraryUserData; + } + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Delegate Helpers +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)notifyDidConnectToAddress:(NSData *)anAddress +{ + LogTrace(); + + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didConnectToAddress:)]) + { + NSData *address = [anAddress copy]; // In case param is NSMutableData + + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate udpSocket:self didConnectToAddress:address]; + }}); + } +} + +- (void)notifyDidNotConnect:(NSError *)error +{ + LogTrace(); + + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotConnect:)]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate udpSocket:self didNotConnect:error]; + }}); + } +} + +- (void)notifyDidSendDataWithTag:(long)tag +{ + LogTrace(); + + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didSendDataWithTag:)]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate udpSocket:self didSendDataWithTag:tag]; + }}); + } +} + +- (void)notifyDidNotSendDataWithTag:(long)tag dueToError:(NSError *)error +{ + LogTrace(); + + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotSendDataWithTag:dueToError:)]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate udpSocket:self didNotSendDataWithTag:tag dueToError:error]; + }}); + } +} + +- (void)notifyDidReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)context +{ + LogTrace(); + + SEL selector = @selector(udpSocket:didReceiveData:fromAddress:withFilterContext:); + + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:selector]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate udpSocket:self didReceiveData:data fromAddress:address withFilterContext:context]; + }}); + } +} + +- (void)notifyDidCloseWithError:(NSError *)error +{ + LogTrace(); + + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocketDidClose:withError:)]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate udpSocketDidClose:self withError:error]; + }}); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Errors +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (NSError *)badConfigError:(NSString *)errMsg +{ + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + + return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain + code:GCDAsyncUdpSocketBadConfigError + userInfo:userInfo]; +} + +- (NSError *)badParamError:(NSString *)errMsg +{ + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + + return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain + code:GCDAsyncUdpSocketBadParamError + userInfo:userInfo]; +} + +- (NSError *)gaiError:(int)gai_error +{ + NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + + return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo]; +} + +- (NSError *)errnoErrorWithReason:(NSString *)reason +{ + NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)]; + NSDictionary *userInfo; + + if (reason) + userInfo = @{NSLocalizedDescriptionKey : errMsg, + NSLocalizedFailureReasonErrorKey : reason}; + else + userInfo = @{NSLocalizedDescriptionKey : errMsg}; + + return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; +} + +- (NSError *)errnoError +{ + return [self errnoErrorWithReason:nil]; +} + +/** + * Returns a standard send timeout error. +**/ +- (NSError *)sendTimeoutError +{ + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketSendTimeoutError", + @"GCDAsyncUdpSocket", [NSBundle mainBundle], + @"Send operation timed out", nil); + + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + + return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain + code:GCDAsyncUdpSocketSendTimeoutError + userInfo:userInfo]; +} + +- (NSError *)socketClosedError +{ + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketClosedError", + @"GCDAsyncUdpSocket", [NSBundle mainBundle], + @"Socket closed", nil); + + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + + return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain code:GCDAsyncUdpSocketClosedError userInfo:userInfo]; +} + +- (NSError *)otherError:(NSString *)errMsg +{ + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + + return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain + code:GCDAsyncUdpSocketOtherError + userInfo:userInfo]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Utilities +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)preOp:(NSError **)errPtr +{ + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + if (delegate == nil) // Must have delegate set + { + if (errPtr) + { + NSString *msg = @"Attempting to use socket without a delegate. Set a delegate first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + if (delegateQueue == NULL) // Must have delegate queue set + { + if (errPtr) + { + NSString *msg = @"Attempting to use socket without a delegate queue. Set a delegate queue first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + return YES; +} + +/** + * This method executes on a global concurrent queue. + * When complete, it executes the given completion block on the socketQueue. +**/ +- (void)asyncResolveHost:(NSString *)aHost + port:(uint16_t)port + withCompletionBlock:(void (^)(NSArray *addresses, NSError *error))completionBlock +{ + LogTrace(); + + // Check parameter(s) + + if (aHost == nil) + { + NSString *msg = @"The host param is nil. Should be domain name or IP address string."; + NSError *error = [self badParamError:msg]; + + // We should still use dispatch_async since this method is expected to be asynchronous + + dispatch_async(socketQueue, ^{ @autoreleasepool { + + completionBlock(nil, error); + }}); + + return; + } + + // It's possible that the given aHost parameter is actually a NSMutableString. + // So we want to copy it now, within this block that will be executed synchronously. + // This way the asynchronous lookup block below doesn't have to worry about it changing. + + NSString *host = [aHost copy]; + + + dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool { + + NSMutableArray *addresses = [NSMutableArray arrayWithCapacity:2]; + NSError *error = nil; + + if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"]) + { + // Use LOOPBACK address + struct sockaddr_in sockaddr4; + memset(&sockaddr4, 0, sizeof(sockaddr4)); + + sockaddr4.sin_len = sizeof(struct sockaddr_in); + sockaddr4.sin_family = AF_INET; + sockaddr4.sin_port = htons(port); + sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + struct sockaddr_in6 sockaddr6; + memset(&sockaddr6, 0, sizeof(sockaddr6)); + + sockaddr6.sin6_len = sizeof(struct sockaddr_in6); + sockaddr6.sin6_family = AF_INET6; + sockaddr6.sin6_port = htons(port); + sockaddr6.sin6_addr = in6addr_loopback; + + // Wrap the native address structures and add to list + [addresses addObject:[NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]]; + [addresses addObject:[NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]]; + } + else + { + NSString *portStr = [NSString stringWithFormat:@"%hu", port]; + + struct addrinfo hints, *res, *res0; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + + int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0); + + if (gai_error) + { + error = [self gaiError:gai_error]; + } + else + { + for(res = res0; res; res = res->ai_next) + { + if (res->ai_family == AF_INET) + { + // Found IPv4 address + // Wrap the native address structure and add to list + + [addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]]; + } + else if (res->ai_family == AF_INET6) + { + + // Fixes connection issues with IPv6, it is the same solution for udp socket. + // https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158 + struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)(void *)res->ai_addr; + in_port_t *portPtr = &sockaddr->sin6_port; + if ((portPtr != NULL) && (*portPtr == 0)) { + *portPtr = htons(port); + } + + // Found IPv6 address + // Wrap the native address structure and add to list + [addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]]; + } + } + freeaddrinfo(res0); + + if ([addresses count] == 0) + { + error = [self gaiError:EAI_FAIL]; + } + } + } + + dispatch_async(self->socketQueue, ^{ @autoreleasepool { + + completionBlock(addresses, error); + }}); + + }}); +} + +/** + * This method picks an address from the given list of addresses. + * The address picked depends upon which protocols are disabled, deactived, & preferred. + * + * Returns the address family (AF_INET or AF_INET6) of the picked address, + * or AF_UNSPEC and the corresponding error is there's a problem. +**/ +- (int)getAddress:(NSData **)addressPtr error:(NSError **)errorPtr fromAddresses:(NSArray *)addresses +{ + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert([addresses count] > 0, @"Expected at least one address"); + + int resultAF = AF_UNSPEC; + NSData *resultAddress = nil; + NSError *resultError = nil; + + // Check for problems + + BOOL resolvedIPv4Address = NO; + BOOL resolvedIPv6Address = NO; + + for (NSData *address in addresses) + { + switch ([[self class] familyFromAddress:address]) + { + case AF_INET : resolvedIPv4Address = YES; break; + case AF_INET6 : resolvedIPv6Address = YES; break; + + default : NSAssert(NO, @"Addresses array contains invalid address"); + } + } + + BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; + + if (isIPv4Disabled && !resolvedIPv6Address) + { + NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address(es)."; + resultError = [self otherError:msg]; + + if (addressPtr) *addressPtr = resultAddress; + if (errorPtr) *errorPtr = resultError; + + return resultAF; + } + + if (isIPv6Disabled && !resolvedIPv4Address) + { + NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address(es)."; + resultError = [self otherError:msg]; + + if (addressPtr) *addressPtr = resultAddress; + if (errorPtr) *errorPtr = resultError; + + return resultAF; + } + + BOOL isIPv4Deactivated = (flags & kIPv4Deactivated) ? YES : NO; + BOOL isIPv6Deactivated = (flags & kIPv6Deactivated) ? YES : NO; + + if (isIPv4Deactivated && !resolvedIPv6Address) + { + NSString *msg = @"IPv4 has been deactivated due to bind/connect, and DNS lookup found no IPv6 address(es)."; + resultError = [self otherError:msg]; + + if (addressPtr) *addressPtr = resultAddress; + if (errorPtr) *errorPtr = resultError; + + return resultAF; + } + + if (isIPv6Deactivated && !resolvedIPv4Address) + { + NSString *msg = @"IPv6 has been deactivated due to bind/connect, and DNS lookup found no IPv4 address(es)."; + resultError = [self otherError:msg]; + + if (addressPtr) *addressPtr = resultAddress; + if (errorPtr) *errorPtr = resultError; + + return resultAF; + } + + // Extract first IPv4 and IPv6 address in list + + BOOL ipv4WasFirstInList = YES; + NSData *address4 = nil; + NSData *address6 = nil; + + for (NSData *address in addresses) + { + int af = [[self class] familyFromAddress:address]; + + if (af == AF_INET) + { + if (address4 == nil) + { + address4 = address; + + if (address6) + break; + else + ipv4WasFirstInList = YES; + } + } + else // af == AF_INET6 + { + if (address6 == nil) + { + address6 = address; + + if (address4) + break; + else + ipv4WasFirstInList = NO; + } + } + } + + // Determine socket type + + BOOL preferIPv4 = (config & kPreferIPv4) ? YES : NO; + BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO; + + BOOL useIPv4 = ((preferIPv4 && address4) || (address6 == nil)); + BOOL useIPv6 = ((preferIPv6 && address6) || (address4 == nil)); + + NSAssert(!(preferIPv4 && preferIPv6), @"Invalid config state"); + NSAssert(!(useIPv4 && useIPv6), @"Invalid logic"); + + if (useIPv4 || (!useIPv6 && ipv4WasFirstInList)) + { + resultAF = AF_INET; + resultAddress = address4; + } + else + { + resultAF = AF_INET6; + resultAddress = address6; + } + + if (addressPtr) *addressPtr = resultAddress; + if (errorPtr) *errorPtr = resultError; + + return resultAF; +} + +/** + * Finds the address(es) of an interface description. + * An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34). +**/ +- (void)convertIntefaceDescription:(NSString *)interfaceDescription + port:(uint16_t)port + intoAddress4:(NSData **)interfaceAddr4Ptr + address6:(NSData **)interfaceAddr6Ptr +{ + NSData *addr4 = nil; + NSData *addr6 = nil; + + if (interfaceDescription == nil) + { + // ANY address + + struct sockaddr_in sockaddr4; + memset(&sockaddr4, 0, sizeof(sockaddr4)); + + sockaddr4.sin_len = sizeof(sockaddr4); + sockaddr4.sin_family = AF_INET; + sockaddr4.sin_port = htons(port); + sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY); + + struct sockaddr_in6 sockaddr6; + memset(&sockaddr6, 0, sizeof(sockaddr6)); + + sockaddr6.sin6_len = sizeof(sockaddr6); + sockaddr6.sin6_family = AF_INET6; + sockaddr6.sin6_port = htons(port); + sockaddr6.sin6_addr = in6addr_any; + + addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; + addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; + } + else if ([interfaceDescription isEqualToString:@"localhost"] || + [interfaceDescription isEqualToString:@"loopback"]) + { + // LOOPBACK address + + struct sockaddr_in sockaddr4; + memset(&sockaddr4, 0, sizeof(sockaddr4)); + + sockaddr4.sin_len = sizeof(struct sockaddr_in); + sockaddr4.sin_family = AF_INET; + sockaddr4.sin_port = htons(port); + sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + struct sockaddr_in6 sockaddr6; + memset(&sockaddr6, 0, sizeof(sockaddr6)); + + sockaddr6.sin6_len = sizeof(struct sockaddr_in6); + sockaddr6.sin6_family = AF_INET6; + sockaddr6.sin6_port = htons(port); + sockaddr6.sin6_addr = in6addr_loopback; + + addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; + addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; + } + else + { + const char *iface = [interfaceDescription UTF8String]; + + struct ifaddrs *addrs; + const struct ifaddrs *cursor; + + if ((getifaddrs(&addrs) == 0)) + { + cursor = addrs; + while (cursor != NULL) + { + if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET)) + { + // IPv4 + + struct sockaddr_in *addr = (struct sockaddr_in *)(void *)cursor->ifa_addr; + + if (strcmp(cursor->ifa_name, iface) == 0) + { + // Name match + + struct sockaddr_in nativeAddr4 = *addr; + nativeAddr4.sin_port = htons(port); + + addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; + } + else + { + char ip[INET_ADDRSTRLEN]; + + const char *conversion; + conversion = inet_ntop(AF_INET, &addr->sin_addr, ip, sizeof(ip)); + + if ((conversion != NULL) && (strcmp(ip, iface) == 0)) + { + // IP match + + struct sockaddr_in nativeAddr4 = *addr; + nativeAddr4.sin_port = htons(port); + + addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; + } + } + } + else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6)) + { + // IPv6 + + const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)cursor->ifa_addr; + + if (strcmp(cursor->ifa_name, iface) == 0) + { + // Name match + + struct sockaddr_in6 nativeAddr6 = *addr; + nativeAddr6.sin6_port = htons(port); + + addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; + } + else + { + char ip[INET6_ADDRSTRLEN]; + + const char *conversion; + conversion = inet_ntop(AF_INET6, &addr->sin6_addr, ip, sizeof(ip)); + + if ((conversion != NULL) && (strcmp(ip, iface) == 0)) + { + // IP match + + struct sockaddr_in6 nativeAddr6 = *addr; + nativeAddr6.sin6_port = htons(port); + + addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; + } + } + } + + cursor = cursor->ifa_next; + } + + freeifaddrs(addrs); + } + } + + if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4; + if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6; +} + +/** + * Converts a numeric hostname into its corresponding address. + * The hostname is expected to be an IPv4 or IPv6 address represented as a human-readable string. (e.g. 192.168.4.34) +**/ +- (void)convertNumericHost:(NSString *)numericHost + port:(uint16_t)port + intoAddress4:(NSData **)addr4Ptr + address6:(NSData **)addr6Ptr +{ + NSData *addr4 = nil; + NSData *addr6 = nil; + + if (numericHost) + { + NSString *portStr = [NSString stringWithFormat:@"%hu", port]; + + struct addrinfo hints, *res, *res0; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + hints.ai_flags = AI_NUMERICHOST; // No name resolution should be attempted + + if (getaddrinfo([numericHost UTF8String], [portStr UTF8String], &hints, &res0) == 0) + { + for (res = res0; res; res = res->ai_next) + { + if ((addr4 == nil) && (res->ai_family == AF_INET)) + { + // Found IPv4 address + // Wrap the native address structure + addr4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; + } + else if ((addr6 == nil) && (res->ai_family == AF_INET6)) + { + // Found IPv6 address + // Wrap the native address structure + addr6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; + } + } + freeaddrinfo(res0); + } + } + + if (addr4Ptr) *addr4Ptr = addr4; + if (addr6Ptr) *addr6Ptr = addr6; +} + +- (BOOL)isConnectedToAddress4:(NSData *)someAddr4 +{ + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(flags & kDidConnect, @"Not connected"); + NSAssert(cachedConnectedAddress, @"Expected cached connected address"); + + if (cachedConnectedFamily != AF_INET) + { + return NO; + } + + const struct sockaddr_in *sSockaddr4 = (const struct sockaddr_in *)[someAddr4 bytes]; + const struct sockaddr_in *cSockaddr4 = (const struct sockaddr_in *)[cachedConnectedAddress bytes]; + + if (memcmp(&sSockaddr4->sin_addr, &cSockaddr4->sin_addr, sizeof(struct in_addr)) != 0) + { + return NO; + } + if (memcmp(&sSockaddr4->sin_port, &cSockaddr4->sin_port, sizeof(in_port_t)) != 0) + { + return NO; + } + + return YES; +} + +- (BOOL)isConnectedToAddress6:(NSData *)someAddr6 +{ + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(flags & kDidConnect, @"Not connected"); + NSAssert(cachedConnectedAddress, @"Expected cached connected address"); + + if (cachedConnectedFamily != AF_INET6) + { + return NO; + } + + const struct sockaddr_in6 *sSockaddr6 = (const struct sockaddr_in6 *)[someAddr6 bytes]; + const struct sockaddr_in6 *cSockaddr6 = (const struct sockaddr_in6 *)[cachedConnectedAddress bytes]; + + if (memcmp(&sSockaddr6->sin6_addr, &cSockaddr6->sin6_addr, sizeof(struct in6_addr)) != 0) + { + return NO; + } + if (memcmp(&sSockaddr6->sin6_port, &cSockaddr6->sin6_port, sizeof(in_port_t)) != 0) + { + return NO; + } + + return YES; +} + +- (unsigned int)indexOfInterfaceAddr4:(NSData *)interfaceAddr4 +{ + if (interfaceAddr4 == nil) + return 0; + if ([interfaceAddr4 length] != sizeof(struct sockaddr_in)) + return 0; + + int result = 0; + const struct sockaddr_in *ifaceAddr = (const struct sockaddr_in *)[interfaceAddr4 bytes]; + + struct ifaddrs *addrs; + const struct ifaddrs *cursor; + + if ((getifaddrs(&addrs) == 0)) + { + cursor = addrs; + while (cursor != NULL) + { + if (cursor->ifa_addr->sa_family == AF_INET) + { + // IPv4 + + const struct sockaddr_in *addr = (const struct sockaddr_in *)(const void *)cursor->ifa_addr; + + if (memcmp(&addr->sin_addr, &ifaceAddr->sin_addr, sizeof(struct in_addr)) == 0) + { + result = if_nametoindex(cursor->ifa_name); + break; + } + } + + cursor = cursor->ifa_next; + } + + freeifaddrs(addrs); + } + + return result; +} + +- (unsigned int)indexOfInterfaceAddr6:(NSData *)interfaceAddr6 +{ + if (interfaceAddr6 == nil) + return 0; + if ([interfaceAddr6 length] != sizeof(struct sockaddr_in6)) + return 0; + + int result = 0; + const struct sockaddr_in6 *ifaceAddr = (const struct sockaddr_in6 *)[interfaceAddr6 bytes]; + + struct ifaddrs *addrs; + const struct ifaddrs *cursor; + + if ((getifaddrs(&addrs) == 0)) + { + cursor = addrs; + while (cursor != NULL) + { + if (cursor->ifa_addr->sa_family == AF_INET6) + { + // IPv6 + + const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)cursor->ifa_addr; + + if (memcmp(&addr->sin6_addr, &ifaceAddr->sin6_addr, sizeof(struct in6_addr)) == 0) + { + result = if_nametoindex(cursor->ifa_name); + break; + } + } + + cursor = cursor->ifa_next; + } + + freeifaddrs(addrs); + } + + return result; +} + +- (void)setupSendAndReceiveSourcesForSocket4 +{ + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + send4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket4FD, 0, socketQueue); + receive4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue); + + // Setup event handlers + + dispatch_source_set_event_handler(send4Source, ^{ @autoreleasepool { + + LogVerbose(@"send4EventBlock"); + LogVerbose(@"dispatch_source_get_data(send4Source) = %lu", dispatch_source_get_data(send4Source)); + + self->flags |= kSock4CanAcceptBytes; + + // If we're ready to send data, do so immediately. + // Otherwise pause the send source or it will continue to fire over and over again. + + if (self->currentSend == nil) + { + LogVerbose(@"Nothing to send"); + [self suspendSend4Source]; + } + else if (self->currentSend->resolveInProgress) + { + LogVerbose(@"currentSend - waiting for address resolve"); + [self suspendSend4Source]; + } + else if (self->currentSend->filterInProgress) + { + LogVerbose(@"currentSend - waiting on sendFilter"); + [self suspendSend4Source]; + } + else + { + [self doSend]; + } + + }}); + + dispatch_source_set_event_handler(receive4Source, ^{ @autoreleasepool { + + LogVerbose(@"receive4EventBlock"); + + self->socket4FDBytesAvailable = dispatch_source_get_data(self->receive4Source); + LogVerbose(@"socket4FDBytesAvailable: %lu", socket4FDBytesAvailable); + + if (self->socket4FDBytesAvailable > 0) + [self doReceive]; + else + [self doReceiveEOF]; + + }}); + + // Setup cancel handlers + + __block int socketFDRefCount = 2; + + int theSocketFD = socket4FD; + + #if !OS_OBJECT_USE_OBJC + dispatch_source_t theSendSource = send4Source; + dispatch_source_t theReceiveSource = receive4Source; + #endif + + dispatch_source_set_cancel_handler(send4Source, ^{ + + LogVerbose(@"send4CancelBlock"); + + #if !OS_OBJECT_USE_OBJC + LogVerbose(@"dispatch_release(send4Source)"); + dispatch_release(theSendSource); + #endif + + if (--socketFDRefCount == 0) + { + LogVerbose(@"close(socket4FD)"); + close(theSocketFD); + } + }); + + dispatch_source_set_cancel_handler(receive4Source, ^{ + + LogVerbose(@"receive4CancelBlock"); + + #if !OS_OBJECT_USE_OBJC + LogVerbose(@"dispatch_release(receive4Source)"); + dispatch_release(theReceiveSource); + #endif + + if (--socketFDRefCount == 0) + { + LogVerbose(@"close(socket4FD)"); + close(theSocketFD); + } + }); + + // We will not be able to receive until the socket is bound to a port, + // either explicitly via bind, or implicitly by connect or by sending data. + // + // But we should be able to send immediately. + + socket4FDBytesAvailable = 0; + flags |= kSock4CanAcceptBytes; + + flags |= kSend4SourceSuspended; + flags |= kReceive4SourceSuspended; +} + +- (void)setupSendAndReceiveSourcesForSocket6 +{ + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + send6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket6FD, 0, socketQueue); + receive6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket6FD, 0, socketQueue); + + // Setup event handlers + + dispatch_source_set_event_handler(send6Source, ^{ @autoreleasepool { + + LogVerbose(@"send6EventBlock"); + LogVerbose(@"dispatch_source_get_data(send6Source) = %lu", dispatch_source_get_data(send6Source)); + + self->flags |= kSock6CanAcceptBytes; + + // If we're ready to send data, do so immediately. + // Otherwise pause the send source or it will continue to fire over and over again. + + if (self->currentSend == nil) + { + LogVerbose(@"Nothing to send"); + [self suspendSend6Source]; + } + else if (self->currentSend->resolveInProgress) + { + LogVerbose(@"currentSend - waiting for address resolve"); + [self suspendSend6Source]; + } + else if (self->currentSend->filterInProgress) + { + LogVerbose(@"currentSend - waiting on sendFilter"); + [self suspendSend6Source]; + } + else + { + [self doSend]; + } + + }}); + + dispatch_source_set_event_handler(receive6Source, ^{ @autoreleasepool { + + LogVerbose(@"receive6EventBlock"); + + self->socket6FDBytesAvailable = dispatch_source_get_data(self->receive6Source); + LogVerbose(@"socket6FDBytesAvailable: %lu", socket6FDBytesAvailable); + + if (self->socket6FDBytesAvailable > 0) + [self doReceive]; + else + [self doReceiveEOF]; + + }}); + + // Setup cancel handlers + + __block int socketFDRefCount = 2; + + int theSocketFD = socket6FD; + + #if !OS_OBJECT_USE_OBJC + dispatch_source_t theSendSource = send6Source; + dispatch_source_t theReceiveSource = receive6Source; + #endif + + dispatch_source_set_cancel_handler(send6Source, ^{ + + LogVerbose(@"send6CancelBlock"); + + #if !OS_OBJECT_USE_OBJC + LogVerbose(@"dispatch_release(send6Source)"); + dispatch_release(theSendSource); + #endif + + if (--socketFDRefCount == 0) + { + LogVerbose(@"close(socket6FD)"); + close(theSocketFD); + } + }); + + dispatch_source_set_cancel_handler(receive6Source, ^{ + + LogVerbose(@"receive6CancelBlock"); + + #if !OS_OBJECT_USE_OBJC + LogVerbose(@"dispatch_release(receive6Source)"); + dispatch_release(theReceiveSource); + #endif + + if (--socketFDRefCount == 0) + { + LogVerbose(@"close(socket6FD)"); + close(theSocketFD); + } + }); + + // We will not be able to receive until the socket is bound to a port, + // either explicitly via bind, or implicitly by connect or by sending data. + // + // But we should be able to send immediately. + + socket6FDBytesAvailable = 0; + flags |= kSock6CanAcceptBytes; + + flags |= kSend6SourceSuspended; + flags |= kReceive6SourceSuspended; +} + +- (BOOL)createSocket4:(BOOL)useIPv4 socket6:(BOOL)useIPv6 error:(NSError * __autoreleasing *)errPtr +{ + LogTrace(); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(((flags & kDidCreateSockets) == 0), @"Sockets have already been created"); + + // CreateSocket Block + // This block will be invoked below. + + int(^createSocket)(int) = ^int (int domain) { + + int socketFD = socket(domain, SOCK_DGRAM, 0); + + if (socketFD == SOCKET_NULL) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error in socket() function"]; + + return SOCKET_NULL; + } + + int status; + + // Set socket options + + status = fcntl(socketFD, F_SETFL, O_NONBLOCK); + if (status == -1) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error enabling non-blocking IO on socket (fcntl)"]; + + close(socketFD); + return SOCKET_NULL; + } + + int reuseaddr = 1; + status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)); + if (status == -1) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error enabling address reuse (setsockopt)"]; + + close(socketFD); + return SOCKET_NULL; + } + + int nosigpipe = 1; + status = setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); + if (status == -1) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error disabling sigpipe (setsockopt)"]; + + close(socketFD); + return SOCKET_NULL; + } + + /** + * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535. + * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295. + * + * The default maximum size of the UDP buffer in iOS is 9216 bytes. + * + * This is the reason of #222(GCD does not necessarily return the size of an entire UDP packet) and + * #535(GCDAsyncUDPSocket can not send data when data is greater than 9K) + * + * + * Enlarge the maximum size of UDP packet. + * I can not ensure the protocol type now so that the max size is set to 65535 :) + **/ + + status = setsockopt(socketFD, SOL_SOCKET, SO_SNDBUF, (const char*)&self->maxSendSize, sizeof(int)); + if (status == -1) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error setting send buffer size (setsockopt)"]; + close(socketFD); + return SOCKET_NULL; + } + + status = setsockopt(socketFD, SOL_SOCKET, SO_RCVBUF, (const char*)&self->maxSendSize, sizeof(int)); + if (status == -1) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error setting receive buffer size (setsockopt)"]; + close(socketFD); + return SOCKET_NULL; + } + + + return socketFD; + }; + + // Create sockets depending upon given configuration. + + if (useIPv4) + { + LogVerbose(@"Creating IPv4 socket"); + + socket4FD = createSocket(AF_INET); + if (socket4FD == SOCKET_NULL) + { + // errPtr set in local createSocket() block + return NO; + } + } + + if (useIPv6) + { + LogVerbose(@"Creating IPv6 socket"); + + socket6FD = createSocket(AF_INET6); + if (socket6FD == SOCKET_NULL) + { + // errPtr set in local createSocket() block + + if (socket4FD != SOCKET_NULL) + { + close(socket4FD); + socket4FD = SOCKET_NULL; + } + + return NO; + } + } + + // Setup send and receive sources + + if (useIPv4) + [self setupSendAndReceiveSourcesForSocket4]; + if (useIPv6) + [self setupSendAndReceiveSourcesForSocket6]; + + flags |= kDidCreateSockets; + return YES; +} + +- (BOOL)createSockets:(NSError **)errPtr +{ + LogTrace(); + + BOOL useIPv4 = [self isIPv4Enabled]; + BOOL useIPv6 = [self isIPv6Enabled]; + + return [self createSocket4:useIPv4 socket6:useIPv6 error:errPtr]; +} + +- (void)suspendSend4Source +{ + if (send4Source && !(flags & kSend4SourceSuspended)) + { + LogVerbose(@"dispatch_suspend(send4Source)"); + + dispatch_suspend(send4Source); + flags |= kSend4SourceSuspended; + } +} + +- (void)suspendSend6Source +{ + if (send6Source && !(flags & kSend6SourceSuspended)) + { + LogVerbose(@"dispatch_suspend(send6Source)"); + + dispatch_suspend(send6Source); + flags |= kSend6SourceSuspended; + } +} + +- (void)resumeSend4Source +{ + if (send4Source && (flags & kSend4SourceSuspended)) + { + LogVerbose(@"dispatch_resume(send4Source)"); + + dispatch_resume(send4Source); + flags &= ~kSend4SourceSuspended; + } +} + +- (void)resumeSend6Source +{ + if (send6Source && (flags & kSend6SourceSuspended)) + { + LogVerbose(@"dispatch_resume(send6Source)"); + + dispatch_resume(send6Source); + flags &= ~kSend6SourceSuspended; + } +} + +- (void)suspendReceive4Source +{ + if (receive4Source && !(flags & kReceive4SourceSuspended)) + { + LogVerbose(@"dispatch_suspend(receive4Source)"); + + dispatch_suspend(receive4Source); + flags |= kReceive4SourceSuspended; + } +} + +- (void)suspendReceive6Source +{ + if (receive6Source && !(flags & kReceive6SourceSuspended)) + { + LogVerbose(@"dispatch_suspend(receive6Source)"); + + dispatch_suspend(receive6Source); + flags |= kReceive6SourceSuspended; + } +} + +- (void)resumeReceive4Source +{ + if (receive4Source && (flags & kReceive4SourceSuspended)) + { + LogVerbose(@"dispatch_resume(receive4Source)"); + + dispatch_resume(receive4Source); + flags &= ~kReceive4SourceSuspended; + } +} + +- (void)resumeReceive6Source +{ + if (receive6Source && (flags & kReceive6SourceSuspended)) + { + LogVerbose(@"dispatch_resume(receive6Source)"); + + dispatch_resume(receive6Source); + flags &= ~kReceive6SourceSuspended; + } +} + +- (void)closeSocket4 +{ + if (socket4FD != SOCKET_NULL) + { + LogVerbose(@"dispatch_source_cancel(send4Source)"); + dispatch_source_cancel(send4Source); + + LogVerbose(@"dispatch_source_cancel(receive4Source)"); + dispatch_source_cancel(receive4Source); + + // For some crazy reason (in my opinion), cancelling a dispatch source doesn't + // invoke the cancel handler if the dispatch source is paused. + // So we have to unpause the source if needed. + // This allows the cancel handler to be run, which in turn releases the source and closes the socket. + + [self resumeSend4Source]; + [self resumeReceive4Source]; + + // The sockets will be closed by the cancel handlers of the corresponding source + + send4Source = NULL; + receive4Source = NULL; + + socket4FD = SOCKET_NULL; + + // Clear socket states + + socket4FDBytesAvailable = 0; + flags &= ~kSock4CanAcceptBytes; + + // Clear cached info + + cachedLocalAddress4 = nil; + cachedLocalHost4 = nil; + cachedLocalPort4 = 0; + } +} + +- (void)closeSocket6 +{ + if (socket6FD != SOCKET_NULL) + { + LogVerbose(@"dispatch_source_cancel(send6Source)"); + dispatch_source_cancel(send6Source); + + LogVerbose(@"dispatch_source_cancel(receive6Source)"); + dispatch_source_cancel(receive6Source); + + // For some crazy reason (in my opinion), cancelling a dispatch source doesn't + // invoke the cancel handler if the dispatch source is paused. + // So we have to unpause the source if needed. + // This allows the cancel handler to be run, which in turn releases the source and closes the socket. + + [self resumeSend6Source]; + [self resumeReceive6Source]; + + send6Source = NULL; + receive6Source = NULL; + + // The sockets will be closed by the cancel handlers of the corresponding source + + socket6FD = SOCKET_NULL; + + // Clear socket states + + socket6FDBytesAvailable = 0; + flags &= ~kSock6CanAcceptBytes; + + // Clear cached info + + cachedLocalAddress6 = nil; + cachedLocalHost6 = nil; + cachedLocalPort6 = 0; + } +} + +- (void)closeSockets +{ + [self closeSocket4]; + [self closeSocket6]; + + flags &= ~kDidCreateSockets; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Diagnostics +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)getLocalAddress:(NSData **)dataPtr + host:(NSString **)hostPtr + port:(uint16_t *)portPtr + forSocket:(int)socketFD + withFamily:(int)socketFamily +{ + + NSData *data = nil; + NSString *host = nil; + uint16_t port = 0; + + if (socketFamily == AF_INET) + { + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); + + if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) + { + data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len]; + host = [[self class] hostFromSockaddr4:&sockaddr4]; + port = [[self class] portFromSockaddr4:&sockaddr4]; + } + else + { + LogWarn(@"Error in getsockname: %@", [self errnoError]); + } + } + else if (socketFamily == AF_INET6) + { + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); + + if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) + { + data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len]; + host = [[self class] hostFromSockaddr6:&sockaddr6]; + port = [[self class] portFromSockaddr6:&sockaddr6]; + } + else + { + LogWarn(@"Error in getsockname: %@", [self errnoError]); + } + } + + if (dataPtr) *dataPtr = data; + if (hostPtr) *hostPtr = host; + if (portPtr) *portPtr = port; + + return (data != nil); +} + +- (void)maybeUpdateCachedLocalAddress4Info +{ + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + if ( cachedLocalAddress4 || ((flags & kDidBind) == 0) || (socket4FD == SOCKET_NULL) ) + { + return; + } + + NSData *address = nil; + NSString *host = nil; + uint16_t port = 0; + + if ([self getLocalAddress:&address host:&host port:&port forSocket:socket4FD withFamily:AF_INET]) + { + + cachedLocalAddress4 = address; + cachedLocalHost4 = host; + cachedLocalPort4 = port; + } +} + +- (void)maybeUpdateCachedLocalAddress6Info +{ + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + if ( cachedLocalAddress6 || ((flags & kDidBind) == 0) || (socket6FD == SOCKET_NULL) ) + { + return; + } + + NSData *address = nil; + NSString *host = nil; + uint16_t port = 0; + + if ([self getLocalAddress:&address host:&host port:&port forSocket:socket6FD withFamily:AF_INET6]) + { + + cachedLocalAddress6 = address; + cachedLocalHost6 = host; + cachedLocalPort6 = port; + } +} + +- (NSData *)localAddress +{ + __block NSData *result = nil; + + dispatch_block_t block = ^{ + + if (self->socket4FD != SOCKET_NULL) + { + [self maybeUpdateCachedLocalAddress4Info]; + result = self->cachedLocalAddress4; + } + else + { + [self maybeUpdateCachedLocalAddress6Info]; + result = self->cachedLocalAddress6; + } + + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (NSString *)localHost +{ + __block NSString *result = nil; + + dispatch_block_t block = ^{ + + if (self->socket4FD != SOCKET_NULL) + { + [self maybeUpdateCachedLocalAddress4Info]; + result = self->cachedLocalHost4; + } + else + { + [self maybeUpdateCachedLocalAddress6Info]; + result = self->cachedLocalHost6; + } + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (uint16_t)localPort +{ + __block uint16_t result = 0; + + dispatch_block_t block = ^{ + + if (self->socket4FD != SOCKET_NULL) + { + [self maybeUpdateCachedLocalAddress4Info]; + result = self->cachedLocalPort4; + } + else + { + [self maybeUpdateCachedLocalAddress6Info]; + result = self->cachedLocalPort6; + } + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (NSData *)localAddress_IPv4 +{ + __block NSData *result = nil; + + dispatch_block_t block = ^{ + + [self maybeUpdateCachedLocalAddress4Info]; + result = self->cachedLocalAddress4; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (NSString *)localHost_IPv4 +{ + __block NSString *result = nil; + + dispatch_block_t block = ^{ + + [self maybeUpdateCachedLocalAddress4Info]; + result = self->cachedLocalHost4; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (uint16_t)localPort_IPv4 +{ + __block uint16_t result = 0; + + dispatch_block_t block = ^{ + + [self maybeUpdateCachedLocalAddress4Info]; + result = self->cachedLocalPort4; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (NSData *)localAddress_IPv6 +{ + __block NSData *result = nil; + + dispatch_block_t block = ^{ + + [self maybeUpdateCachedLocalAddress6Info]; + result = self->cachedLocalAddress6; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (NSString *)localHost_IPv6 +{ + __block NSString *result = nil; + + dispatch_block_t block = ^{ + + [self maybeUpdateCachedLocalAddress6Info]; + result = self->cachedLocalHost6; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (uint16_t)localPort_IPv6 +{ + __block uint16_t result = 0; + + dispatch_block_t block = ^{ + + [self maybeUpdateCachedLocalAddress6Info]; + result = self->cachedLocalPort6; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (void)maybeUpdateCachedConnectedAddressInfo +{ + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + if (cachedConnectedAddress || (flags & kDidConnect) == 0) + { + return; + } + + NSData *data = nil; + NSString *host = nil; + uint16_t port = 0; + int family = AF_UNSPEC; + + if (socket4FD != SOCKET_NULL) + { + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); + + if (getpeername(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) + { + data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len]; + host = [[self class] hostFromSockaddr4:&sockaddr4]; + port = [[self class] portFromSockaddr4:&sockaddr4]; + family = AF_INET; + } + else + { + LogWarn(@"Error in getpeername: %@", [self errnoError]); + } + } + else if (socket6FD != SOCKET_NULL) + { + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); + + if (getpeername(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) + { + data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len]; + host = [[self class] hostFromSockaddr6:&sockaddr6]; + port = [[self class] portFromSockaddr6:&sockaddr6]; + family = AF_INET6; + } + else + { + LogWarn(@"Error in getpeername: %@", [self errnoError]); + } + } + + + cachedConnectedAddress = data; + cachedConnectedHost = host; + cachedConnectedPort = port; + cachedConnectedFamily = family; +} + +- (NSData *)connectedAddress +{ + __block NSData *result = nil; + + dispatch_block_t block = ^{ + + [self maybeUpdateCachedConnectedAddressInfo]; + result = self->cachedConnectedAddress; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (NSString *)connectedHost +{ + __block NSString *result = nil; + + dispatch_block_t block = ^{ + + [self maybeUpdateCachedConnectedAddressInfo]; + result = self->cachedConnectedHost; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (uint16_t)connectedPort +{ + __block uint16_t result = 0; + + dispatch_block_t block = ^{ + + [self maybeUpdateCachedConnectedAddressInfo]; + result = self->cachedConnectedPort; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); + + return result; +} + +- (BOOL)isConnected +{ + __block BOOL result = NO; + + dispatch_block_t block = ^{ + result = (self->flags & kDidConnect) ? YES : NO; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (BOOL)isClosed +{ + __block BOOL result = YES; + + dispatch_block_t block = ^{ + + result = (self->flags & kDidCreateSockets) ? NO : YES; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (BOOL)isIPv4 +{ + __block BOOL result = NO; + + dispatch_block_t block = ^{ + + if (self->flags & kDidCreateSockets) + { + result = (self->socket4FD != SOCKET_NULL); + } + else + { + result = [self isIPv4Enabled]; + } + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +- (BOOL)isIPv6 +{ + __block BOOL result = NO; + + dispatch_block_t block = ^{ + + if (self->flags & kDidCreateSockets) + { + result = (self->socket6FD != SOCKET_NULL); + } + else + { + result = [self isIPv6Enabled]; + } + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Binding +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * This method runs through the various checks required prior to a bind attempt. + * It is shared between the various bind methods. +**/ +- (BOOL)preBind:(NSError **)errPtr +{ + if (![self preOp:errPtr]) + { + return NO; + } + + if (flags & kDidBind) + { + if (errPtr) + { + NSString *msg = @"Cannot bind a socket more than once."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + if ((flags & kConnecting) || (flags & kDidConnect)) + { + if (errPtr) + { + NSString *msg = @"Cannot bind after connecting. If needed, bind first, then connect."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; + + if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled + { + if (errPtr) + { + NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + return YES; +} + +- (BOOL)bindToPort:(uint16_t)port error:(NSError **)errPtr +{ + return [self bindToPort:port interface:nil error:errPtr]; +} + +- (BOOL)bindToPort:(uint16_t)port interface:(NSString *)interface error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + // Run through sanity checks + + if (![self preBind:&err]) + { + return_from_block; + } + + // Check the given interface + + NSData *interface4 = nil; + NSData *interface6 = nil; + + [self convertIntefaceDescription:interface port:port intoAddress4:&interface4 address6:&interface6]; + + if ((interface4 == nil) && (interface6 == nil)) + { + NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; + err = [self badParamError:msg]; + + return_from_block; + } + + BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO; + + if (isIPv4Disabled && (interface6 == nil)) + { + NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6."; + err = [self badParamError:msg]; + + return_from_block; + } + + if (isIPv6Disabled && (interface4 == nil)) + { + NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4."; + err = [self badParamError:msg]; + + return_from_block; + } + + // Determine protocol(s) + + BOOL useIPv4 = !isIPv4Disabled && (interface4 != nil); + BOOL useIPv6 = !isIPv6Disabled && (interface6 != nil); + + // Create the socket(s) if needed + + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err]) + { + return_from_block; + } + } + + // Bind the socket(s) + + LogVerbose(@"Binding socket to port(%hu) interface(%@)", port, interface); + + if (useIPv4) + { + int status = bind(self->socket4FD, (const struct sockaddr *)[interface4 bytes], (socklen_t)[interface4 length]); + if (status == -1) + { + [self closeSockets]; + + NSString *reason = @"Error in bind() function"; + err = [self errnoErrorWithReason:reason]; + + return_from_block; + } + } + + if (useIPv6) + { + int status = bind(self->socket6FD, (const struct sockaddr *)[interface6 bytes], (socklen_t)[interface6 length]); + if (status == -1) + { + [self closeSockets]; + + NSString *reason = @"Error in bind() function"; + err = [self errnoErrorWithReason:reason]; + + return_from_block; + } + } + + // Update flags + + self->flags |= kDidBind; + + if (!useIPv4) self->flags |= kIPv4Deactivated; + if (!useIPv6) self->flags |= kIPv6Deactivated; + + result = YES; + + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (err) + LogError(@"Error binding to port/interface: %@", err); + + if (errPtr) + *errPtr = err; + + return result; +} + +- (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + // Run through sanity checks + + if (![self preBind:&err]) + { + return_from_block; + } + + // Check the given address + + int addressFamily = [[self class] familyFromAddress:localAddr]; + + if (addressFamily == AF_UNSPEC) + { + NSString *msg = @"A valid IPv4 or IPv6 address was not given"; + err = [self badParamError:msg]; + + return_from_block; + } + + NSData *localAddr4 = (addressFamily == AF_INET) ? localAddr : nil; + NSData *localAddr6 = (addressFamily == AF_INET6) ? localAddr : nil; + + BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO; + + if (isIPv4Disabled && localAddr4) + { + NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed."; + err = [self badParamError:msg]; + + return_from_block; + } + + if (isIPv6Disabled && localAddr6) + { + NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed."; + err = [self badParamError:msg]; + + return_from_block; + } + + // Determine protocol(s) + + BOOL useIPv4 = !isIPv4Disabled && (localAddr4 != nil); + BOOL useIPv6 = !isIPv6Disabled && (localAddr6 != nil); + + // Create the socket(s) if needed + + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err]) + { + return_from_block; + } + } + + // Bind the socket(s) + + if (useIPv4) + { + LogVerbose(@"Binding socket to address(%@:%hu)", + [[self class] hostFromAddress:localAddr4], + [[self class] portFromAddress:localAddr4]); + + int status = bind(self->socket4FD, (const struct sockaddr *)[localAddr4 bytes], (socklen_t)[localAddr4 length]); + if (status == -1) + { + [self closeSockets]; + + NSString *reason = @"Error in bind() function"; + err = [self errnoErrorWithReason:reason]; + + return_from_block; + } + } + else + { + LogVerbose(@"Binding socket to address(%@:%hu)", + [[self class] hostFromAddress:localAddr6], + [[self class] portFromAddress:localAddr6]); + + int status = bind(self->socket6FD, (const struct sockaddr *)[localAddr6 bytes], (socklen_t)[localAddr6 length]); + if (status == -1) + { + [self closeSockets]; + + NSString *reason = @"Error in bind() function"; + err = [self errnoErrorWithReason:reason]; + + return_from_block; + } + } + + // Update flags + + self->flags |= kDidBind; + + if (!useIPv4) self->flags |= kIPv4Deactivated; + if (!useIPv6) self->flags |= kIPv6Deactivated; + + result = YES; + + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (err) + LogError(@"Error binding to address: %@", err); + + if (errPtr) + *errPtr = err; + + return result; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Connecting +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * This method runs through the various checks required prior to a connect attempt. + * It is shared between the various connect methods. +**/ +- (BOOL)preConnect:(NSError **)errPtr +{ + if (![self preOp:errPtr]) + { + return NO; + } + + if ((flags & kConnecting) || (flags & kDidConnect)) + { + if (errPtr) + { + NSString *msg = @"Cannot connect a socket more than once."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; + + if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled + { + if (errPtr) + { + NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + return YES; +} + +- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + // Run through sanity checks. + + if (![self preConnect:&err]) + { + return_from_block; + } + + // Check parameter(s) + + if (host == nil) + { + NSString *msg = @"The host param is nil. Should be domain name or IP address string."; + err = [self badParamError:msg]; + + return_from_block; + } + + // Create the socket(s) if needed + + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } + + // Create special connect packet + + GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init]; + packet->resolveInProgress = YES; + + // Start asynchronous DNS resolve for host:port on background queue + + LogVerbose(@"Dispatching DNS resolve for connect..."); + + [self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, NSError *error) { + + // The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue, + // and immediately returns. Once the async resolve task completes, + // this block is executed on our socketQueue. + + packet->resolveInProgress = NO; + + packet->addresses = addresses; + packet->error = error; + + [self maybeConnect]; + }]; + + // Updates flags, add connect packet to send queue, and pump send queue + + self->flags |= kConnecting; + + [self->sendQueue addObject:packet]; + [self maybeDequeueSend]; + + result = YES; + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (err) + LogError(@"Error connecting to host/port: %@", err); + + if (errPtr) + *errPtr = err; + + return result; +} + +- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + // Run through sanity checks. + + if (![self preConnect:&err]) + { + return_from_block; + } + + // Check parameter(s) + + if (remoteAddr == nil) + { + NSString *msg = @"The address param is nil. Should be a valid address."; + err = [self badParamError:msg]; + + return_from_block; + } + + // Create the socket(s) if needed + + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } + + // The remoteAddr parameter could be of type NSMutableData. + // So we copy it to be safe. + + NSData *address = [remoteAddr copy]; + NSArray *addresses = [NSArray arrayWithObject:address]; + + GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init]; + packet->addresses = addresses; + + // Updates flags, add connect packet to send queue, and pump send queue + + self->flags |= kConnecting; + + [self->sendQueue addObject:packet]; + [self maybeDequeueSend]; + + result = YES; + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (err) + LogError(@"Error connecting to address: %@", err); + + if (errPtr) + *errPtr = err; + + return result; +} + +- (void)maybeConnect +{ + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + + BOOL sendQueueReady = [currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]]; + + if (sendQueueReady) + { + GCDAsyncUdpSpecialPacket *connectPacket = (GCDAsyncUdpSpecialPacket *)currentSend; + + if (connectPacket->resolveInProgress) + { + LogVerbose(@"Waiting for DNS resolve..."); + } + else + { + if (connectPacket->error) + { + [self notifyDidNotConnect:connectPacket->error]; + } + else + { + NSData *address = nil; + NSError *error = nil; + + int addressFamily = [self getAddress:&address error:&error fromAddresses:connectPacket->addresses]; + + // Perform connect + + BOOL result = NO; + + switch (addressFamily) + { + case AF_INET : result = [self connectWithAddress4:address error:&error]; break; + case AF_INET6 : result = [self connectWithAddress6:address error:&error]; break; + } + + if (result) + { + flags |= kDidBind; + flags |= kDidConnect; + + cachedConnectedAddress = address; + cachedConnectedHost = [[self class] hostFromAddress:address]; + cachedConnectedPort = [[self class] portFromAddress:address]; + cachedConnectedFamily = addressFamily; + + [self notifyDidConnectToAddress:address]; + } + else + { + [self notifyDidNotConnect:error]; + } + } + + flags &= ~kConnecting; + + [self endCurrentSend]; + [self maybeDequeueSend]; + } + } +} + +- (BOOL)connectWithAddress4:(NSData *)address4 error:(NSError **)errPtr +{ + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + int status = connect(socket4FD, (const struct sockaddr *)[address4 bytes], (socklen_t)[address4 length]); + if (status != 0) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error in connect() function"]; + + return NO; + } + + [self closeSocket6]; + flags |= kIPv6Deactivated; + + return YES; +} + +- (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr +{ + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + int status = connect(socket6FD, (const struct sockaddr *)[address6 bytes], (socklen_t)[address6 length]); + if (status != 0) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error in connect() function"]; + + return NO; + } + + [self closeSocket4]; + flags |= kIPv4Deactivated; + + return YES; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Multicast +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)preJoin:(NSError **)errPtr +{ + if (![self preOp:errPtr]) + { + return NO; + } + + if (!(flags & kDidBind)) + { + if (errPtr) + { + NSString *msg = @"Must bind a socket before joining a multicast group."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + if ((flags & kConnecting) || (flags & kDidConnect)) + { + if (errPtr) + { + NSString *msg = @"Cannot join a multicast group if connected."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + return YES; +} + +- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr +{ + return [self joinMulticastGroup:group onInterface:nil error:errPtr]; +} + +- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr +{ + // IP_ADD_MEMBERSHIP == IPV6_JOIN_GROUP + return [self performMulticastRequest:IP_ADD_MEMBERSHIP forGroup:group onInterface:interface error:errPtr]; +} + +- (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr +{ + return [self leaveMulticastGroup:group onInterface:nil error:errPtr]; +} + +- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr +{ + // IP_DROP_MEMBERSHIP == IPV6_LEAVE_GROUP + return [self performMulticastRequest:IP_DROP_MEMBERSHIP forGroup:group onInterface:interface error:errPtr]; +} + +- (BOOL)performMulticastRequest:(int)requestType + forGroup:(NSString *)group + onInterface:(NSString *)interface + error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + // Run through sanity checks + + if (![self preJoin:&err]) + { + return_from_block; + } + + // Convert group to address + + NSData *groupAddr4 = nil; + NSData *groupAddr6 = nil; + + [self convertNumericHost:group port:0 intoAddress4:&groupAddr4 address6:&groupAddr6]; + + if ((groupAddr4 == nil) && (groupAddr6 == nil)) + { + NSString *msg = @"Unknown group. Specify valid group IP address."; + err = [self badParamError:msg]; + + return_from_block; + } + + // Convert interface to address + + NSData *interfaceAddr4 = nil; + NSData *interfaceAddr6 = nil; + + [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6]; + + if ((interfaceAddr4 == nil) && (interfaceAddr6 == nil)) + { + NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; + err = [self badParamError:msg]; + + return_from_block; + } + + // Perform join + + if ((self->socket4FD != SOCKET_NULL) && groupAddr4 && interfaceAddr4) + { + const struct sockaddr_in *nativeGroup = (const struct sockaddr_in *)[groupAddr4 bytes]; + const struct sockaddr_in *nativeIface = (const struct sockaddr_in *)[interfaceAddr4 bytes]; + + struct ip_mreq imreq; + imreq.imr_multiaddr = nativeGroup->sin_addr; + imreq.imr_interface = nativeIface->sin_addr; + + int status = setsockopt(self->socket4FD, IPPROTO_IP, requestType, (const void *)&imreq, sizeof(imreq)); + if (status != 0) + { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + + return_from_block; + } + + // Using IPv4 only + [self closeSocket6]; + + result = YES; + } + else if ((self->socket6FD != SOCKET_NULL) && groupAddr6 && interfaceAddr6) + { + const struct sockaddr_in6 *nativeGroup = (const struct sockaddr_in6 *)[groupAddr6 bytes]; + + struct ipv6_mreq imreq; + imreq.ipv6mr_multiaddr = nativeGroup->sin6_addr; + imreq.ipv6mr_interface = [self indexOfInterfaceAddr6:interfaceAddr6]; + + int status = setsockopt(self->socket6FD, IPPROTO_IPV6, requestType, (const void *)&imreq, sizeof(imreq)); + if (status != 0) + { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + + return_from_block; + } + + // Using IPv6 only + [self closeSocket4]; + + result = YES; + } + else + { + NSString *msg = @"Socket, group, and interface do not have matching IP versions"; + err = [self badParamError:msg]; + + return_from_block; + } + + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (errPtr) + *errPtr = err; + + return result; +} + +- (BOOL)sendIPv4MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + if (![self preOp:&err]) + { + return_from_block; + } + + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } + + // Convert interface to address + + NSData *interfaceAddr4 = nil; + NSData *interfaceAddr6 = nil; + + [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6]; + + if (interfaceAddr4 == nil) + { + NSString *msg = @"Unknown interface. Specify valid interface by IP address."; + err = [self badParamError:msg]; + return_from_block; + } + + if (self->socket4FD != SOCKET_NULL) { + const struct sockaddr_in *nativeIface = (struct sockaddr_in *)[interfaceAddr4 bytes]; + struct in_addr interface_addr = nativeIface->sin_addr; + int status = setsockopt(self->socket4FD, IPPROTO_IP, IP_MULTICAST_IF, &interface_addr, sizeof(interface_addr)); + if (status != 0) { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + return_from_block; + } + result = YES; + } + + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (errPtr) + *errPtr = err; + + return result; +} + +- (BOOL)sendIPv6MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + if (![self preOp:&err]) + { + return_from_block; + } + + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } + + // Convert interface to address + + NSData *interfaceAddr4 = nil; + NSData *interfaceAddr6 = nil; + + [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6]; + + if (interfaceAddr6 == nil) + { + NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\")."; + err = [self badParamError:msg]; + return_from_block; + } + + if ((self->socket6FD != SOCKET_NULL)) { + uint32_t scope_id = [self indexOfInterfaceAddr6:interfaceAddr6]; + int status = setsockopt(self->socket6FD, IPPROTO_IPV6, IPV6_MULTICAST_IF, &scope_id, sizeof(scope_id)); + if (status != 0) { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + return_from_block; + } + result = YES; + } + + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (errPtr) + *errPtr = err; + + return result; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Reuse port +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + if (![self preOp:&err]) + { + return_from_block; + } + + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } + + int value = flag ? 1 : 0; + if (self->socket4FD != SOCKET_NULL) + { + int error = setsockopt(self->socket4FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value)); + + if (error) + { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + + return_from_block; + } + result = YES; + } + + if (self->socket6FD != SOCKET_NULL) + { + int error = setsockopt(self->socket6FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value)); + + if (error) + { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + + return_from_block; + } + result = YES; + } + + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (errPtr) + *errPtr = err; + + return result; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Broadcast +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + if (![self preOp:&err]) + { + return_from_block; + } + + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } + + if (self->socket4FD != SOCKET_NULL) + { + int value = flag ? 1 : 0; + int error = setsockopt(self->socket4FD, SOL_SOCKET, SO_BROADCAST, (const void *)&value, sizeof(value)); + + if (error) + { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + + return_from_block; + } + result = YES; + } + + // IPv6 does not implement broadcast, the ability to send a packet to all hosts on the attached link. + // The same effect can be achieved by sending a packet to the link-local all hosts multicast group. + + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (errPtr) + *errPtr = err; + + return result; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Sending +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)sendData:(NSData *)data withTag:(long)tag +{ + [self sendData:data withTimeout:-1.0 tag:tag]; +} + +- (void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag +{ + LogTrace(); + + if ([data length] == 0) + { + LogWarn(@"Ignoring attempt to send nil/empty data."); + return; + } + + + + GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; + + dispatch_async(socketQueue, ^{ @autoreleasepool { + + [self->sendQueue addObject:packet]; + [self maybeDequeueSend]; + }}); + +} + +- (void)sendData:(NSData *)data + toHost:(NSString *)host + port:(uint16_t)port + withTimeout:(NSTimeInterval)timeout + tag:(long)tag +{ + LogTrace(); + + if ([data length] == 0) + { + LogWarn(@"Ignoring attempt to send nil/empty data."); + return; + } + + GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; + packet->resolveInProgress = YES; + + [self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, NSError *error) { + + // The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue, + // and immediately returns. Once the async resolve task completes, + // this block is executed on our socketQueue. + + packet->resolveInProgress = NO; + + packet->resolvedAddresses = addresses; + packet->resolveError = error; + + if (packet == self->currentSend) + { + LogVerbose(@"currentSend - address resolved"); + [self doPreSend]; + } + }]; + + dispatch_async(socketQueue, ^{ @autoreleasepool { + + [self->sendQueue addObject:packet]; + [self maybeDequeueSend]; + + }}); + +} + +- (void)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag +{ + LogTrace(); + + if ([data length] == 0) + { + LogWarn(@"Ignoring attempt to send nil/empty data."); + return; + } + + GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; + packet->addressFamily = [GCDAsyncUdpSocket familyFromAddress:remoteAddr]; + packet->address = remoteAddr; + + dispatch_async(socketQueue, ^{ @autoreleasepool { + + [self->sendQueue addObject:packet]; + [self maybeDequeueSend]; + }}); +} + +- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue +{ + [self setSendFilter:filterBlock withQueue:filterQueue isAsynchronous:YES]; +} + +- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock + withQueue:(dispatch_queue_t)filterQueue + isAsynchronous:(BOOL)isAsynchronous +{ + GCDAsyncUdpSocketSendFilterBlock newFilterBlock = NULL; + dispatch_queue_t newFilterQueue = NULL; + + if (filterBlock) + { + NSAssert(filterQueue, @"Must provide a dispatch_queue in which to run the filter block."); + + newFilterBlock = [filterBlock copy]; + newFilterQueue = filterQueue; + #if !OS_OBJECT_USE_OBJC + dispatch_retain(newFilterQueue); + #endif + } + + dispatch_block_t block = ^{ + + #if !OS_OBJECT_USE_OBJC + if (self->sendFilterQueue) dispatch_release(self->sendFilterQueue); + #endif + + self->sendFilterBlock = newFilterBlock; + self->sendFilterQueue = newFilterQueue; + self->sendFilterAsync = isAsynchronous; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (void)maybeDequeueSend +{ + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + // If we don't have a send operation already in progress + if (currentSend == nil) + { + // Create the sockets if needed + if ((flags & kDidCreateSockets) == 0) + { + NSError *err = nil; + if (![self createSockets:&err]) + { + [self closeWithError:err]; + return; + } + } + + while ([sendQueue count] > 0) + { + // Dequeue the next object in the queue + currentSend = [sendQueue objectAtIndex:0]; + [sendQueue removeObjectAtIndex:0]; + + if ([currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]]) + { + [self maybeConnect]; + + return; // The maybeConnect method, if it connects, will invoke this method again + } + else if (currentSend->resolveError) + { + // Notify delegate + [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:currentSend->resolveError]; + + // Clear currentSend + currentSend = nil; + + continue; + } + else + { + // Start preprocessing checks on the send packet + [self doPreSend]; + + break; + } + } + + if ((currentSend == nil) && (flags & kCloseAfterSends)) + { + [self closeWithError:nil]; + } + } +} + +/** + * This method is called after a sendPacket has been dequeued. + * It performs various preprocessing checks on the packet, + * and queries the sendFilter (if set) to determine if the packet can be sent. + * + * If the packet passes all checks, it will be passed on to the doSend method. +**/ +- (void)doPreSend +{ + LogTrace(); + + // + // 1. Check for problems with send packet + // + + BOOL waitingForResolve = NO; + NSError *error = nil; + + if (flags & kDidConnect) + { + // Connected socket + + if (currentSend->resolveInProgress || currentSend->resolvedAddresses || currentSend->resolveError) + { + NSString *msg = @"Cannot specify destination of packet for connected socket"; + error = [self badConfigError:msg]; + } + else + { + currentSend->address = cachedConnectedAddress; + currentSend->addressFamily = cachedConnectedFamily; + } + } + else + { + // Non-Connected socket + + if (currentSend->resolveInProgress) + { + // We're waiting for the packet's destination to be resolved. + waitingForResolve = YES; + } + else if (currentSend->resolveError) + { + error = currentSend->resolveError; + } + else if (currentSend->address == nil) + { + if (currentSend->resolvedAddresses == nil) + { + NSString *msg = @"You must specify destination of packet for a non-connected socket"; + error = [self badConfigError:msg]; + } + else + { + // Pick the proper address to use (out of possibly several resolved addresses) + + NSData *address = nil; + int addressFamily = AF_UNSPEC; + + addressFamily = [self getAddress:&address error:&error fromAddresses:currentSend->resolvedAddresses]; + + currentSend->address = address; + currentSend->addressFamily = addressFamily; + } + } + } + + if (waitingForResolve) + { + // We're waiting for the packet's destination to be resolved. + + LogVerbose(@"currentSend - waiting for address resolve"); + + if (flags & kSock4CanAcceptBytes) { + [self suspendSend4Source]; + } + if (flags & kSock6CanAcceptBytes) { + [self suspendSend6Source]; + } + + return; + } + + if (error) + { + // Unable to send packet due to some error. + // Notify delegate and move on. + + [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:error]; + [self endCurrentSend]; + [self maybeDequeueSend]; + + return; + } + + // + // 2. Query sendFilter (if applicable) + // + + if (sendFilterBlock && sendFilterQueue) + { + // Query sendFilter + + if (sendFilterAsync) + { + // Scenario 1 of 3 - Need to asynchronously query sendFilter + + currentSend->filterInProgress = YES; + GCDAsyncUdpSendPacket *sendPacket = currentSend; + + dispatch_async(sendFilterQueue, ^{ @autoreleasepool { + + BOOL allowed = self->sendFilterBlock(sendPacket->buffer, sendPacket->address, sendPacket->tag); + + dispatch_async(self->socketQueue, ^{ @autoreleasepool { + + sendPacket->filterInProgress = NO; + if (sendPacket == self->currentSend) + { + if (allowed) + { + [self doSend]; + } + else + { + LogVerbose(@"currentSend - silently dropped by sendFilter"); + + [self notifyDidSendDataWithTag:self->currentSend->tag]; + [self endCurrentSend]; + [self maybeDequeueSend]; + } + } + }}); + }}); + } + else + { + // Scenario 2 of 3 - Need to synchronously query sendFilter + + __block BOOL allowed = YES; + + dispatch_sync(sendFilterQueue, ^{ @autoreleasepool { + + allowed = self->sendFilterBlock(self->currentSend->buffer, self->currentSend->address, self->currentSend->tag); + }}); + + if (allowed) + { + [self doSend]; + } + else + { + LogVerbose(@"currentSend - silently dropped by sendFilter"); + + [self notifyDidSendDataWithTag:currentSend->tag]; + [self endCurrentSend]; + [self maybeDequeueSend]; + } + } + } + else // if (!sendFilterBlock || !sendFilterQueue) + { + // Scenario 3 of 3 - No sendFilter. Just go straight into sending. + + [self doSend]; + } +} + +/** + * This method performs the actual sending of data in the currentSend packet. + * It should only be called if the +**/ +- (void)doSend +{ + LogTrace(); + + NSAssert(currentSend != nil, @"Invalid logic"); + + // Perform the actual send + + ssize_t result = 0; + + if (flags & kDidConnect) + { + // Connected socket + + const void *buffer = [currentSend->buffer bytes]; + size_t length = (size_t)[currentSend->buffer length]; + + if (currentSend->addressFamily == AF_INET) + { + result = send(socket4FD, buffer, length, 0); + LogVerbose(@"send(socket4FD) = %d", result); + } + else + { + result = send(socket6FD, buffer, length, 0); + LogVerbose(@"send(socket6FD) = %d", result); + } + } + else + { + // Non-Connected socket + + const void *buffer = [currentSend->buffer bytes]; + size_t length = (size_t)[currentSend->buffer length]; + + const void *dst = [currentSend->address bytes]; + socklen_t dstSize = (socklen_t)[currentSend->address length]; + + if (currentSend->addressFamily == AF_INET) + { + result = sendto(socket4FD, buffer, length, 0, dst, dstSize); + LogVerbose(@"sendto(socket4FD) = %d", result); + } + else + { + result = sendto(socket6FD, buffer, length, 0, dst, dstSize); + LogVerbose(@"sendto(socket6FD) = %d", result); + } + } + + // If the socket wasn't bound before, it is now + + if ((flags & kDidBind) == 0) + { + flags |= kDidBind; + } + + // Check the results. + // + // From the send() & sendto() manpage: + // + // Upon successful completion, the number of bytes which were sent is returned. + // Otherwise, -1 is returned and the global variable errno is set to indicate the error. + + BOOL waitingForSocket = NO; + NSError *socketError = nil; + + if (result == 0) + { + waitingForSocket = YES; + } + else if (result < 0) + { + if (errno == EAGAIN) + waitingForSocket = YES; + else + socketError = [self errnoErrorWithReason:@"Error in send() function."]; + } + + if (waitingForSocket) + { + // Not enough room in the underlying OS socket send buffer. + // Wait for a notification of available space. + + LogVerbose(@"currentSend - waiting for socket"); + + if (!(flags & kSock4CanAcceptBytes)) { + [self resumeSend4Source]; + } + if (!(flags & kSock6CanAcceptBytes)) { + [self resumeSend6Source]; + } + + if ((sendTimer == NULL) && (currentSend->timeout >= 0.0)) + { + // Unable to send packet right away. + // Start timer to timeout the send operation. + + [self setupSendTimerWithTimeout:currentSend->timeout]; + } + } + else if (socketError) + { + [self closeWithError:socketError]; + } + else // done + { + [self notifyDidSendDataWithTag:currentSend->tag]; + [self endCurrentSend]; + [self maybeDequeueSend]; + } +} + +/** + * Releases all resources associated with the currentSend. +**/ +- (void)endCurrentSend +{ + if (sendTimer) + { + dispatch_source_cancel(sendTimer); + #if !OS_OBJECT_USE_OBJC + dispatch_release(sendTimer); + #endif + sendTimer = NULL; + } + + currentSend = nil; +} + +/** + * Performs the operations to timeout the current send operation, and move on. +**/ +- (void)doSendTimeout +{ + LogTrace(); + + [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:[self sendTimeoutError]]; + [self endCurrentSend]; + [self maybeDequeueSend]; +} + +/** + * Sets up a timer that fires to timeout the current send operation. + * This method should only be called once per send packet. +**/ +- (void)setupSendTimerWithTimeout:(NSTimeInterval)timeout +{ + NSAssert(sendTimer == NULL, @"Invalid logic"); + NSAssert(timeout >= 0.0, @"Invalid logic"); + + LogTrace(); + + sendTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); + + dispatch_source_set_event_handler(sendTimer, ^{ @autoreleasepool { + + [self doSendTimeout]; + }}); + + dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)); + + dispatch_source_set_timer(sendTimer, tt, DISPATCH_TIME_FOREVER, 0); + dispatch_resume(sendTimer); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Receiving +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)receiveOnce:(NSError **)errPtr +{ + LogTrace(); + + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ + + if ((self->flags & kReceiveOnce) == 0) + { + if ((self->flags & kDidCreateSockets) == 0) + { + NSString *msg = @"Must bind socket before you can receive data. " + @"You can do this explicitly via bind, or implicitly via connect or by sending data."; + + err = [self badConfigError:msg]; + return_from_block; + } + + self->flags |= kReceiveOnce; // Enable + self->flags &= ~kReceiveContinuous; // Disable + + dispatch_async(self->socketQueue, ^{ @autoreleasepool { + + [self doReceive]; + }}); + } + + result = YES; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (err) + LogError(@"Error in beginReceiving: %@", err); + + if (errPtr) + *errPtr = err; + + return result; +} + +- (BOOL)beginReceiving:(NSError **)errPtr +{ + LogTrace(); + + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ + + if ((self->flags & kReceiveContinuous) == 0) + { + if ((self->flags & kDidCreateSockets) == 0) + { + NSString *msg = @"Must bind socket before you can receive data. " + @"You can do this explicitly via bind, or implicitly via connect or by sending data."; + + err = [self badConfigError:msg]; + return_from_block; + } + + self->flags |= kReceiveContinuous; // Enable + self->flags &= ~kReceiveOnce; // Disable + + dispatch_async(self->socketQueue, ^{ @autoreleasepool { + + [self doReceive]; + }}); + } + + result = YES; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (err) + LogError(@"Error in beginReceiving: %@", err); + + if (errPtr) + *errPtr = err; + + return result; +} + +- (void)pauseReceiving +{ + LogTrace(); + + dispatch_block_t block = ^{ + + self->flags &= ~kReceiveOnce; // Disable + self->flags &= ~kReceiveContinuous; // Disable + + if (self->socket4FDBytesAvailable > 0) { + [self suspendReceive4Source]; + } + if (self->socket6FDBytesAvailable > 0) { + [self suspendReceive6Source]; + } + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue +{ + [self setReceiveFilter:filterBlock withQueue:filterQueue isAsynchronous:YES]; +} + +- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock + withQueue:(dispatch_queue_t)filterQueue + isAsynchronous:(BOOL)isAsynchronous +{ + GCDAsyncUdpSocketReceiveFilterBlock newFilterBlock = NULL; + dispatch_queue_t newFilterQueue = NULL; + + if (filterBlock) + { + NSAssert(filterQueue, @"Must provide a dispatch_queue in which to run the filter block."); + + newFilterBlock = [filterBlock copy]; + newFilterQueue = filterQueue; + #if !OS_OBJECT_USE_OBJC + dispatch_retain(newFilterQueue); + #endif + } + + dispatch_block_t block = ^{ + + #if !OS_OBJECT_USE_OBJC + if (self->receiveFilterQueue) dispatch_release(self->receiveFilterQueue); + #endif + + self->receiveFilterBlock = newFilterBlock; + self->receiveFilterQueue = newFilterQueue; + self->receiveFilterAsync = isAsynchronous; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (void)doReceive +{ + LogTrace(); + + if ((flags & (kReceiveOnce | kReceiveContinuous)) == 0) + { + LogVerbose(@"Receiving is paused..."); + + if (socket4FDBytesAvailable > 0) { + [self suspendReceive4Source]; + } + if (socket6FDBytesAvailable > 0) { + [self suspendReceive6Source]; + } + + return; + } + + if ((flags & kReceiveOnce) && (pendingFilterOperations > 0)) + { + LogVerbose(@"Receiving is temporarily paused (pending filter operations)..."); + + if (socket4FDBytesAvailable > 0) { + [self suspendReceive4Source]; + } + if (socket6FDBytesAvailable > 0) { + [self suspendReceive6Source]; + } + + return; + } + + if ((socket4FDBytesAvailable == 0) && (socket6FDBytesAvailable == 0)) + { + LogVerbose(@"No data available to receive..."); + + if (socket4FDBytesAvailable == 0) { + [self resumeReceive4Source]; + } + if (socket6FDBytesAvailable == 0) { + [self resumeReceive6Source]; + } + + return; + } + + // Figure out if we should receive on socket4 or socket6 + + BOOL doReceive4; + + if (flags & kDidConnect) + { + // Connected socket + + doReceive4 = (socket4FD != SOCKET_NULL); + } + else + { + // Non-Connected socket + + if (socket4FDBytesAvailable > 0) + { + if (socket6FDBytesAvailable > 0) + { + // Bytes available on socket4 & socket6 + + doReceive4 = (flags & kFlipFlop) ? YES : NO; + + flags ^= kFlipFlop; // flags = flags xor kFlipFlop; (toggle flip flop bit) + } + else { + // Bytes available on socket4, but not socket6 + doReceive4 = YES; + } + } + else { + // Bytes available on socket6, but not socket4 + doReceive4 = NO; + } + } + + // Perform socket IO + + ssize_t result = 0; + + NSData *data = nil; + NSData *addr4 = nil; + NSData *addr6 = nil; + + if (doReceive4) + { + NSAssert(socket4FDBytesAvailable > 0, @"Invalid logic"); + LogVerbose(@"Receiving on IPv4"); + + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); + + // #222: GCD does not necessarily return the size of an entire UDP packet + // from dispatch_source_get_data(), so we must use the maximum packet size. + size_t bufSize = max4ReceiveSize; + void *buf = malloc(bufSize); + + result = recvfrom(socket4FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr4, &sockaddr4len); + LogVerbose(@"recvfrom(socket4FD) = %i", (int)result); + + if (result > 0) + { + if ((size_t)result >= socket4FDBytesAvailable) + socket4FDBytesAvailable = 0; + else + socket4FDBytesAvailable -= result; + + if ((size_t)result != bufSize) { + buf = realloc(buf, result); + } + + data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES]; + addr4 = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len]; + } + else + { + LogVerbose(@"recvfrom(socket4FD) = %@", [self errnoError]); + socket4FDBytesAvailable = 0; + free(buf); + } + } + else + { + NSAssert(socket6FDBytesAvailable > 0, @"Invalid logic"); + LogVerbose(@"Receiving on IPv6"); + + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); + + // #222: GCD does not necessarily return the size of an entire UDP packet + // from dispatch_source_get_data(), so we must use the maximum packet size. + size_t bufSize = max6ReceiveSize; + void *buf = malloc(bufSize); + + result = recvfrom(socket6FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr6, &sockaddr6len); + LogVerbose(@"recvfrom(socket6FD) -> %i", (int)result); + + if (result > 0) + { + if ((size_t)result >= socket6FDBytesAvailable) + socket6FDBytesAvailable = 0; + else + socket6FDBytesAvailable -= result; + + if ((size_t)result != bufSize) { + buf = realloc(buf, result); + } + + data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES]; + addr6 = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len]; + } + else + { + LogVerbose(@"recvfrom(socket6FD) = %@", [self errnoError]); + socket6FDBytesAvailable = 0; + free(buf); + } + } + + + BOOL waitingForSocket = NO; + BOOL notifiedDelegate = NO; + BOOL ignored = NO; + + NSError *socketError = nil; + + if (result == 0) + { + waitingForSocket = YES; + } + else if (result < 0) + { + if (errno == EAGAIN) + waitingForSocket = YES; + else + socketError = [self errnoErrorWithReason:@"Error in recvfrom() function"]; + } + else + { + if (flags & kDidConnect) + { + if (addr4 && ![self isConnectedToAddress4:addr4]) + ignored = YES; + if (addr6 && ![self isConnectedToAddress6:addr6]) + ignored = YES; + } + + NSData *addr = (addr4 != nil) ? addr4 : addr6; + + if (!ignored) + { + if (receiveFilterBlock && receiveFilterQueue) + { + // Run data through filter, and if approved, notify delegate + + __block id filterContext = nil; + __block BOOL allowed = NO; + + if (receiveFilterAsync) + { + pendingFilterOperations++; + dispatch_async(receiveFilterQueue, ^{ @autoreleasepool { + + allowed = self->receiveFilterBlock(data, addr, &filterContext); + + // Transition back to socketQueue to get the current delegate / delegateQueue + dispatch_async(self->socketQueue, ^{ @autoreleasepool { + + self->pendingFilterOperations--; + + if (allowed) + { + [self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext]; + } + else + { + LogVerbose(@"received packet silently dropped by receiveFilter"); + } + + if (self->flags & kReceiveOnce) + { + if (allowed) + { + // The delegate has been notified, + // so our receive once operation has completed. + self->flags &= ~kReceiveOnce; + } + else if (self->pendingFilterOperations == 0) + { + // All pending filter operations have completed, + // and none were allowed through. + // Our receive once operation hasn't completed yet. + [self doReceive]; + } + } + }}); + }}); + } + else // if (!receiveFilterAsync) + { + dispatch_sync(receiveFilterQueue, ^{ @autoreleasepool { + + allowed = self->receiveFilterBlock(data, addr, &filterContext); + }}); + + if (allowed) + { + [self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext]; + notifiedDelegate = YES; + } + else + { + LogVerbose(@"received packet silently dropped by receiveFilter"); + ignored = YES; + } + } + } + else // if (!receiveFilterBlock || !receiveFilterQueue) + { + [self notifyDidReceiveData:data fromAddress:addr withFilterContext:nil]; + notifiedDelegate = YES; + } + } + } + + if (waitingForSocket) + { + // Wait for a notification of available data. + + if (socket4FDBytesAvailable == 0) { + [self resumeReceive4Source]; + } + if (socket6FDBytesAvailable == 0) { + [self resumeReceive6Source]; + } + } + else if (socketError) + { + [self closeWithError:socketError]; + } + else + { + if (flags & kReceiveContinuous) + { + // Continuous receive mode + [self doReceive]; + } + else + { + // One-at-a-time receive mode + if (notifiedDelegate) + { + // The delegate has been notified (no set filter). + // So our receive once operation has completed. + flags &= ~kReceiveOnce; + } + else if (ignored) + { + [self doReceive]; + } + else + { + // Waiting on asynchronous receive filter... + } + } + } +} + +- (void)doReceiveEOF +{ + LogTrace(); + + [self closeWithError:[self socketClosedError]]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Closing +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)closeWithError:(NSError *)error +{ + LogVerbose(@"closeWithError: %@", error); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + if (currentSend) [self endCurrentSend]; + + [sendQueue removeAllObjects]; + + // If a socket has been created, we should notify the delegate. + BOOL shouldCallDelegate = (flags & kDidCreateSockets) ? YES : NO; + + // Close all sockets, send/receive sources, cfstreams, etc +#if TARGET_OS_IPHONE + [self removeStreamsFromRunLoop]; + [self closeReadAndWriteStreams]; +#endif + [self closeSockets]; + + // Clear all flags (config remains as is) + flags = 0; + + if (shouldCallDelegate) + { + [self notifyDidCloseWithError:error]; + } +} + +- (void)close +{ + LogTrace(); + + dispatch_block_t block = ^{ @autoreleasepool { + + [self closeWithError:nil]; + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); +} + +- (void)closeAfterSending +{ + LogTrace(); + + dispatch_block_t block = ^{ @autoreleasepool { + + self->flags |= kCloseAfterSends; + + if (self->currentSend == nil && [self->sendQueue count] == 0) + { + [self closeWithError:nil]; + } + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark CFStream +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#if TARGET_OS_IPHONE + +static NSThread *listenerThread; + ++ (void)ignore:(id)_ +{} + ++ (void)startListenerThreadIfNeeded +{ + static dispatch_once_t predicate; + dispatch_once(&predicate, ^{ + + listenerThread = [[NSThread alloc] initWithTarget:self + selector:@selector(listenerThread:) + object:nil]; + [listenerThread start]; + }); +} + ++ (void)listenerThread:(id)unused +{ + @autoreleasepool { + + [[NSThread currentThread] setName:GCDAsyncUdpSocketThreadName]; + + LogInfo(@"ListenerThread: Started"); + + // We can't run the run loop unless it has an associated input source or a timer. + // So we'll just create a timer that will never fire - unless the server runs for a decades. + [NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow] + target:self + selector:@selector(ignore:) + userInfo:nil + repeats:YES]; + + [[NSRunLoop currentRunLoop] run]; + + LogInfo(@"ListenerThread: Stopped"); + } +} + ++ (void)addStreamListener:(GCDAsyncUdpSocket *)asyncUdpSocket +{ + LogTrace(); + NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread"); + + CFRunLoopRef runLoop = CFRunLoopGetCurrent(); + + if (asyncUdpSocket->readStream4) + CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream4, runLoop, kCFRunLoopDefaultMode); + + if (asyncUdpSocket->readStream6) + CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream6, runLoop, kCFRunLoopDefaultMode); + + if (asyncUdpSocket->writeStream4) + CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream4, runLoop, kCFRunLoopDefaultMode); + + if (asyncUdpSocket->writeStream6) + CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream6, runLoop, kCFRunLoopDefaultMode); +} + ++ (void)removeStreamListener:(GCDAsyncUdpSocket *)asyncUdpSocket +{ + LogTrace(); + NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread"); + + CFRunLoopRef runLoop = CFRunLoopGetCurrent(); + + if (asyncUdpSocket->readStream4) + CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream4, runLoop, kCFRunLoopDefaultMode); + + if (asyncUdpSocket->readStream6) + CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream6, runLoop, kCFRunLoopDefaultMode); + + if (asyncUdpSocket->writeStream4) + CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream4, runLoop, kCFRunLoopDefaultMode); + + if (asyncUdpSocket->writeStream6) + CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream6, runLoop, kCFRunLoopDefaultMode); +} + +static void CFReadStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void *pInfo) +{ + @autoreleasepool { + GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo; + + switch(type) + { + case kCFStreamEventOpenCompleted: + { + LogCVerbose(@"CFReadStreamCallback - Open"); + break; + } + case kCFStreamEventHasBytesAvailable: + { + LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable"); + break; + } + case kCFStreamEventErrorOccurred: + case kCFStreamEventEndEncountered: + { + NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream); + if (error == nil && type == kCFStreamEventEndEncountered) + { + error = [asyncUdpSocket socketClosedError]; + } + + dispatch_async(asyncUdpSocket->socketQueue, ^{ @autoreleasepool { + + LogCVerbose(@"CFReadStreamCallback - %@", + (type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered"); + + if (stream != asyncUdpSocket->readStream4 && + stream != asyncUdpSocket->readStream6 ) + { + LogCVerbose(@"CFReadStreamCallback - Ignored"); + return_from_block; + } + + [asyncUdpSocket closeWithError:error]; + + }}); + + break; + } + default: + { + LogCError(@"CFReadStreamCallback - UnknownType: %i", (int)type); + } + } + } +} + +static void CFWriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void *pInfo) +{ + @autoreleasepool { + GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo; + + switch(type) + { + case kCFStreamEventOpenCompleted: + { + LogCVerbose(@"CFWriteStreamCallback - Open"); + break; + } + case kCFStreamEventCanAcceptBytes: + { + LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes"); + break; + } + case kCFStreamEventErrorOccurred: + case kCFStreamEventEndEncountered: + { + NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream); + if (error == nil && type == kCFStreamEventEndEncountered) + { + error = [asyncUdpSocket socketClosedError]; + } + + dispatch_async(asyncUdpSocket->socketQueue, ^{ @autoreleasepool { + + LogCVerbose(@"CFWriteStreamCallback - %@", + (type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered"); + + if (stream != asyncUdpSocket->writeStream4 && + stream != asyncUdpSocket->writeStream6 ) + { + LogCVerbose(@"CFWriteStreamCallback - Ignored"); + return_from_block; + } + + [asyncUdpSocket closeWithError:error]; + + }}); + + break; + } + default: + { + LogCError(@"CFWriteStreamCallback - UnknownType: %i", (int)type); + } + } + } +} + +- (BOOL)createReadAndWriteStreams:(NSError **)errPtr +{ + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + NSError *err = nil; + + if (readStream4 || writeStream4 || readStream6 || writeStream6) + { + // Streams already created + return YES; + } + + if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL) + { + err = [self otherError:@"Cannot create streams without a file descriptor"]; + goto Failed; + } + + // Create streams + + LogVerbose(@"Creating read and write stream(s)..."); + + if (socket4FD != SOCKET_NULL) + { + CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socket4FD, &readStream4, &writeStream4); + if (!readStream4 || !writeStream4) + { + err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv4]"]; + goto Failed; + } + } + + if (socket6FD != SOCKET_NULL) + { + CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socket6FD, &readStream6, &writeStream6); + if (!readStream6 || !writeStream6) + { + err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv6]"]; + goto Failed; + } + } + + // Ensure the CFStream's don't close our underlying socket + + CFReadStreamSetProperty(readStream4, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); + CFWriteStreamSetProperty(writeStream4, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); + + CFReadStreamSetProperty(readStream6, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); + CFWriteStreamSetProperty(writeStream6, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); + + return YES; + +Failed: + if (readStream4) + { + CFReadStreamClose(readStream4); + CFRelease(readStream4); + readStream4 = NULL; + } + if (writeStream4) + { + CFWriteStreamClose(writeStream4); + CFRelease(writeStream4); + writeStream4 = NULL; + } + if (readStream6) + { + CFReadStreamClose(readStream6); + CFRelease(readStream6); + readStream6 = NULL; + } + if (writeStream6) + { + CFWriteStreamClose(writeStream6); + CFRelease(writeStream6); + writeStream6 = NULL; + } + + if (errPtr) + *errPtr = err; + + return NO; +} + +- (BOOL)registerForStreamCallbacks:(NSError **)errPtr +{ + LogTrace(); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null"); + + NSError *err = nil; + + streamContext.version = 0; + streamContext.info = (__bridge void *)self; + streamContext.retain = nil; + streamContext.release = nil; + streamContext.copyDescription = nil; + + CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; + CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; + +// readStreamEvents |= (kCFStreamEventOpenCompleted | kCFStreamEventHasBytesAvailable); +// writeStreamEvents |= (kCFStreamEventOpenCompleted | kCFStreamEventCanAcceptBytes); + + if (socket4FD != SOCKET_NULL) + { + if (readStream4 == NULL || writeStream4 == NULL) + { + err = [self otherError:@"Read/Write stream4 is null"]; + goto Failed; + } + + BOOL r1 = CFReadStreamSetClient(readStream4, readStreamEvents, &CFReadStreamCallback, &streamContext); + BOOL r2 = CFWriteStreamSetClient(writeStream4, writeStreamEvents, &CFWriteStreamCallback, &streamContext); + + if (!r1 || !r2) + { + err = [self otherError:@"Error in CFStreamSetClient(), [IPv4]"]; + goto Failed; + } + } + + if (socket6FD != SOCKET_NULL) + { + if (readStream6 == NULL || writeStream6 == NULL) + { + err = [self otherError:@"Read/Write stream6 is null"]; + goto Failed; + } + + BOOL r1 = CFReadStreamSetClient(readStream6, readStreamEvents, &CFReadStreamCallback, &streamContext); + BOOL r2 = CFWriteStreamSetClient(writeStream6, writeStreamEvents, &CFWriteStreamCallback, &streamContext); + + if (!r1 || !r2) + { + err = [self otherError:@"Error in CFStreamSetClient() [IPv6]"]; + goto Failed; + } + } + + return YES; + +Failed: + if (readStream4) { + CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL); + } + if (writeStream4) { + CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL); + } + if (readStream6) { + CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL); + } + if (writeStream6) { + CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL); + } + + if (errPtr) *errPtr = err; + return NO; +} + +- (BOOL)addStreamsToRunLoop:(NSError **)errPtr +{ + LogTrace(); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null"); + + if (!(flags & kAddedStreamListener)) + { + [[self class] startListenerThreadIfNeeded]; + [[self class] performSelector:@selector(addStreamListener:) + onThread:listenerThread + withObject:self + waitUntilDone:YES]; + + flags |= kAddedStreamListener; + } + + return YES; +} + +- (BOOL)openStreams:(NSError **)errPtr +{ + LogTrace(); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null"); + + NSError *err = nil; + + if (socket4FD != SOCKET_NULL) + { + BOOL r1 = CFReadStreamOpen(readStream4); + BOOL r2 = CFWriteStreamOpen(writeStream4); + + if (!r1 || !r2) + { + err = [self otherError:@"Error in CFStreamOpen() [IPv4]"]; + goto Failed; + } + } + + if (socket6FD != SOCKET_NULL) + { + BOOL r1 = CFReadStreamOpen(readStream6); + BOOL r2 = CFWriteStreamOpen(writeStream6); + + if (!r1 || !r2) + { + err = [self otherError:@"Error in CFStreamOpen() [IPv6]"]; + goto Failed; + } + } + + return YES; + +Failed: + if (errPtr) *errPtr = err; + return NO; +} + +- (void)removeStreamsFromRunLoop +{ + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + if (flags & kAddedStreamListener) + { + [[self class] performSelector:@selector(removeStreamListener:) + onThread:listenerThread + withObject:self + waitUntilDone:YES]; + + flags &= ~kAddedStreamListener; + } +} + +- (void)closeReadAndWriteStreams +{ + LogTrace(); + + if (readStream4) + { + CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL); + CFReadStreamClose(readStream4); + CFRelease(readStream4); + readStream4 = NULL; + } + if (writeStream4) + { + CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL); + CFWriteStreamClose(writeStream4); + CFRelease(writeStream4); + writeStream4 = NULL; + } + if (readStream6) + { + CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL); + CFReadStreamClose(readStream6); + CFRelease(readStream6); + readStream6 = NULL; + } + if (writeStream6) + { + CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL); + CFWriteStreamClose(writeStream6); + CFRelease(writeStream6); + writeStream6 = NULL; + } +} + +#endif + +#if TARGET_OS_IPHONE +- (void)applicationWillEnterForeground:(NSNotification *)notification +{ + LogTrace(); + + // If the application was backgrounded, then iOS may have shut down our sockets. + // So we take a quick look to see if any of them received an EOF. + + dispatch_block_t block = ^{ @autoreleasepool { + + [self resumeReceive4Source]; + [self resumeReceive6Source]; + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Advanced +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * See header file for big discussion of this method. + **/ +- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketNewTargetQueue +{ + void *nonNullUnusedPointer = (__bridge void *)self; + dispatch_queue_set_specific(socketNewTargetQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL); +} + +/** + * See header file for big discussion of this method. + **/ +- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketOldTargetQueue +{ + dispatch_queue_set_specific(socketOldTargetQueue, IsOnSocketQueueOrTargetQueueKey, NULL, NULL); +} + +- (void)performBlock:(dispatch_block_t)block +{ + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); +} + +- (int)socketFD +{ + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", + THIS_FILE, THIS_METHOD); + return SOCKET_NULL; + } + + if (socket4FD != SOCKET_NULL) + return socket4FD; + else + return socket6FD; +} + +- (int)socket4FD +{ + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", + THIS_FILE, THIS_METHOD); + return SOCKET_NULL; + } + + return socket4FD; +} + +- (int)socket6FD +{ + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", + THIS_FILE, THIS_METHOD); + return SOCKET_NULL; + } + + return socket6FD; +} + +#if TARGET_OS_IPHONE + +- (CFReadStreamRef)readStream +{ + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", + THIS_FILE, THIS_METHOD); + return NULL; + } + + NSError *err = nil; + if (![self createReadAndWriteStreams:&err]) + { + LogError(@"Error creating CFStream(s): %@", err); + return NULL; + } + + // Todo... + + if (readStream4) + return readStream4; + else + return readStream6; +} + +- (CFWriteStreamRef)writeStream +{ + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", + THIS_FILE, THIS_METHOD); + return NULL; + } + + NSError *err = nil; + if (![self createReadAndWriteStreams:&err]) + { + LogError(@"Error creating CFStream(s): %@", err); + return NULL; + } + + if (writeStream4) + return writeStream4; + else + return writeStream6; +} + +- (BOOL)enableBackgroundingOnSockets +{ + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", + THIS_FILE, THIS_METHOD); + return NO; + } + + // Why is this commented out? + // See comments below. + +// NSError *err = nil; +// if (![self createReadAndWriteStreams:&err]) +// { +// LogError(@"Error creating CFStream(s): %@", err); +// return NO; +// } +// +// LogVerbose(@"Enabling backgrouding on socket"); +// +// BOOL r1, r2; +// +// if (readStream4 && writeStream4) +// { +// r1 = CFReadStreamSetProperty(readStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); +// r2 = CFWriteStreamSetProperty(writeStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); +// +// if (!r1 || !r2) +// { +// LogError(@"Error setting voip type (IPv4)"); +// return NO; +// } +// } +// +// if (readStream6 && writeStream6) +// { +// r1 = CFReadStreamSetProperty(readStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); +// r2 = CFWriteStreamSetProperty(writeStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); +// +// if (!r1 || !r2) +// { +// LogError(@"Error setting voip type (IPv6)"); +// return NO; +// } +// } +// +// return YES; + + // The above code will actually appear to work. + // The methods will return YES, and everything will appear fine. + // + // One tiny problem: the sockets will still get closed when the app gets backgrounded. + // + // Apple does not officially support backgrounding UDP sockets. + + return NO; +} + +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Class Methods +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ++ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 +{ + char addrBuf[INET_ADDRSTRLEN]; + + if (inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) + { + addrBuf[0] = '\0'; + } + + return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; +} + ++ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 +{ + char addrBuf[INET6_ADDRSTRLEN]; + + if (inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) + { + addrBuf[0] = '\0'; + } + + return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; +} + ++ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 +{ + return ntohs(pSockaddr4->sin_port); +} + ++ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 +{ + return ntohs(pSockaddr6->sin6_port); +} + ++ (NSString *)hostFromAddress:(NSData *)address +{ + NSString *host = nil; + [self getHost:&host port:NULL family:NULL fromAddress:address]; + + return host; +} + ++ (uint16_t)portFromAddress:(NSData *)address +{ + uint16_t port = 0; + [self getHost:NULL port:&port family:NULL fromAddress:address]; + + return port; +} + ++ (int)familyFromAddress:(NSData *)address +{ + int af = AF_UNSPEC; + [self getHost:NULL port:NULL family:&af fromAddress:address]; + + return af; +} + ++ (BOOL)isIPv4Address:(NSData *)address +{ + int af = AF_UNSPEC; + [self getHost:NULL port:NULL family:&af fromAddress:address]; + + return (af == AF_INET); +} + ++ (BOOL)isIPv6Address:(NSData *)address +{ + int af = AF_UNSPEC; + [self getHost:NULL port:NULL family:&af fromAddress:address]; + + return (af == AF_INET6); +} + ++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address +{ + return [self getHost:hostPtr port:portPtr family:NULL fromAddress:address]; +} + ++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(int *)afPtr fromAddress:(NSData *)address +{ + if ([address length] >= sizeof(struct sockaddr)) + { + const struct sockaddr *addrX = (const struct sockaddr *)[address bytes]; + + if (addrX->sa_family == AF_INET) + { + if ([address length] >= sizeof(struct sockaddr_in)) + { + const struct sockaddr_in *addr4 = (const struct sockaddr_in *)(const void *)addrX; + + if (hostPtr) *hostPtr = [self hostFromSockaddr4:addr4]; + if (portPtr) *portPtr = [self portFromSockaddr4:addr4]; + if (afPtr) *afPtr = AF_INET; + + return YES; + } + } + else if (addrX->sa_family == AF_INET6) + { + if ([address length] >= sizeof(struct sockaddr_in6)) + { + const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)(const void *)addrX; + + if (hostPtr) *hostPtr = [self hostFromSockaddr6:addr6]; + if (portPtr) *portPtr = [self portFromSockaddr6:addr6]; + if (afPtr) *afPtr = AF_INET6; + + return YES; + } + } + } + + if (hostPtr) *hostPtr = nil; + if (portPtr) *portPtr = 0; + if (afPtr) *afPtr = AF_UNSPEC; + + return NO; +} + +@end diff --git a/topon/template/PlayBTopOn/PlayBTopOn/playB/XUDPClient.h b/topon/template/PlayBTopOn/PlayBTopOn/playB/XUDPClient.h new file mode 100644 index 0000000..b2499a0 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/playB/XUDPClient.h @@ -0,0 +1,27 @@ +// +// XUDPClient.h +// xcmd +// +// Created by mac on 2025/2/17. +// + +#ifndef XUDPClient_h +#define XUDPClient_h + +#import + +#import "CocoaAsyncSocket.h" + +typedef void (^SendCallback) (NSString *msg); + +@interface XUDPClient : NSObject + +@property (nonatomic, copy) SendCallback hintBlock; + + ++(instancetype)sharedInstance; +- (void) onShow: (NSDictionary *)data; +- (void) onEnd: (NSDictionary *)data; +@end + +#endif /* XUDPClient_h */ diff --git a/topon/template/PlayBTopOn/PlayBTopOn/playB/XUDPClient.m b/topon/template/PlayBTopOn/PlayBTopOn/playB/XUDPClient.m new file mode 100644 index 0000000..bab68ae --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/playB/XUDPClient.m @@ -0,0 +1,143 @@ +// +// XUDPClient.m +// xcmd +// +// Created by mac on 2025/2/17. +// + +#import + +#import "XUDPClient.h" + +#define HOST @"127.0.0.1" +#define PORT 6001 + + +@interface XUDPClient() { +@private + GCDAsyncUdpSocket *_udpSocket; +} + +@end + + +@implementation XUDPClient ++(instancetype)sharedInstance +{ + static XUDPClient* _sharedInstance = nil; + static dispatch_once_t oncePredicate; + dispatch_once (&oncePredicate, ^{ + _sharedInstance = [[XUDPClient alloc] init]; + }); + return _sharedInstance; +} +-(instancetype)init { + if (self = [super init]) { + [self start]; + return self; + } + return nil; +} + +- (void) start +{ + if (!_udpSocket) + { + _udpSocket=nil; + } + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + _udpSocket = [[GCDAsyncUdpSocket alloc]initWithDelegate:self delegateQueue:queue]; + NSError *error = nil; + if (![_udpSocket bindToPort:0 error:&error]) + { + NSLog(@"Error binding: %@", error); + return; + } + if (![_udpSocket beginReceiving:&error]) + { + NSLog(@"Error receiving: %@", error); + return; + } +} +- (void) close +{ + if(_udpSocket) { + [_udpSocket closeAfterSending]; + } +} +- (NSString *)dic2Json: (NSDictionary *)dict { + NSError *error; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict + options:NSJSONWritingPrettyPrinted + error:&error]; + if (error) { + NSLog(@"dic2json err:%@", error); + } + return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; +} + +- (void) onShow: (NSDictionary *)data { + NSDictionary *rq = @{ + @"url": @"/adtask/show", + @"body": data + }; + [self send:[self dic2Json: rq]]; +} + +- (void) onEnd: (NSDictionary *)data { + NSDictionary *rq = @{ + @"url": @"/adtask/end", + @"body": data + }; + [self send:[self dic2Json: rq]]; +} + +- (void) send: (NSString*) msg { + NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding]; + [_udpSocket sendData:data toHost:HOST port:PORT withTimeout:-1 tag:300]; +} +-(void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address +{ + NSError *error = nil; + NSLog(@"Message didConnectToAddress: %@",[[NSString alloc]initWithData:address encoding:NSUTF8StringEncoding]); + [_udpSocket beginReceiving:&error]; +} + +-(void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError *)error +{ + NSLog(@"Message didNotConnect: %@",error); +} + +-(void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error +{ + NSLog(@"Message didNotSendDataWithTag: %@",error); +} + +- (NSDictionary *) json2dic: (NSString *) jsstr { + NSError *jsonError; + NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:[jsstr dataUsingEncoding:NSUTF8StringEncoding] + options:NSJSONReadingMutableContainers + error:&jsonError]; + if (jsonError) { + NSLog(@"json2dic error: %@", jsonError); + } + return dic; +} + +-(void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)filterContext +{ + NSString *revDada =[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; + NSLog(@"Message didReceiveData :%@", revDada); + if(self.hintBlock) { + self.hintBlock(revDada); + } + +} + +-(void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag +{ + NSLog(@"Message 发送成功"); +} + + +@end diff --git a/topon/template/PlayBTopOn/PlayBTopOn/playB/YL_NetWorkManager.swift b/topon/template/PlayBTopOn/PlayBTopOn/playB/YL_NetWorkManager.swift new file mode 100644 index 0000000..a7d0fa5 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/playB/YL_NetWorkManager.swift @@ -0,0 +1,1362 @@ +// +// YL_NetWorkManager.swift +// wallpaper_project +// Created by 忆海16 on 2024/9/29. +// + +import Foundation +import UIKit +import AdSupport +import CoreTelephony +import Network +import SystemConfiguration +import AVFoundation +import OpenGLES +import AppTrackingTransparency +import CommonCrypto + + +/// <#Description#> +class YL_NetWorkManager{ + static var kBaseUrl = YL_NetWorkManager.bFaceLocalUrl() + + static var kURL_AD_Start = "/top_selection/save_app_start_log" + static var kURL_AD_Load = "/top_selection/save_ad_load_log" + static var kURL_AD_Show = "/top_selection/save_ad_show_log" + static var kURL_save_logs = "/ios/top_selection/save_iphone_logs" + + var ipLoadTime: DispatchTime? + + static var kLocalBaseUrl = BbbAdManager.config.adbrush_local_url + + + static var kLocalUrlStr: (String) -> String { + return { path in + return "\(self.kLocalBaseUrl)/\(path)" + } + } + + + static func bFaceLocalUrl() -> String { + + return BbbAdManager.config.adbrush_base_url + + } + + + + typealias LuxNetManagerCallback = (Error?, Bool, [String: Any]?) -> Void + + + + static func uploadData(mdic: [String: Any], urlPath: String, callback: @escaping (Error?, Bool, [String: Any]?) -> Void) { + guard let url = URL(string: urlPath) else { + print("Invalid URL") + return + } + + print("---------mdic \(mdic)") + + print("---------urlPath\(urlPath)") + + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + do { + let data = try JSONSerialization.data(withJSONObject: mdic, options: []) + request.httpBody = data + } catch { + print("Error serializing JSON: \(error.localizedDescription)") + + + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: [ + "text":"Error serializing JSON: \(error.localizedDescription)" + ]) + } + + return + } + + let task = URLSession.shared.dataTask(with: request) { (data, response, error) in + if let error = error { + callback(error, false, nil) + print("Network error: \(error.localizedDescription)") + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: [ + "text":"Network error: \(error.localizedDescription)" + ]) + } + } else { + guard let data = data else { + callback(nil, false, nil) + print("No data received") + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: [ + "text":"No data received" + ]) + } + return + } + + do { + if let result = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { + print("Server response: \(result)") + callback(nil, true, result) + + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: [ + "text":"Server response: \(result)" + ]) + } + + } + } catch { + callback(error, false, nil) + + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: [ + "text":"Error parsing response JSON: \(error.localizedDescription)" + ]) + } + + + + print("Error parsing response JSON: \(error.localizedDescription)") + } + } + } + + task.resume() + } + + + // 请求远程IP + func requestRemoteIp() { + ipLoadTime = DispatchTime.now() + + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "获取ip中..."]) + + guard let url = URL(string: "https://openapi.lux-ad.com/app/common/getIPInfo") else { return } + let request = URLRequest(url: url) + let task = URLSession.shared.dataTask(with: request) { data, response, error in + if let error = error { + print("解析ip失败....") + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "解析ip失败...."]) + } + return + } + + guard let data = data, + let result = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], + let ip = result["data"] as? [String: Any], + let ipAddress = ip["ip"] as? String, + let isoCode = ip["isoCode"] as? String else { + print("解析ip失败....") + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "解析ip失败...."]) + } + return + } + + if !ipAddress.isEmpty { + UserDefaults.standard.set(ipAddress, forKey: "kIP_key") + UserDefaults.standard.set(isoCode, forKey: "kIP_key_isoCode") + UserDefaults.standard.synchronize() + + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "当前ip: \(ipAddress) \n国家: \(isoCode) "]) + } + + if let startTime = self.ipLoadTime { + let loadDuration = self.IpcalculateElapsedTime(since: startTime) + BbbAdManager.config.ipTime = loadDuration + } + + } else { + print("解析ip失败....") + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "解析ip失败...."]) + } + } + } + task.resume() + } + + + // 辅助函数以毫秒为单位计算经过的时间 + private func IpcalculateElapsedTime(since startTime: DispatchTime) -> Int { + let endTime = DispatchTime.now() + let nanoseconds = endTime.uptimeNanoseconds - startTime.uptimeNanoseconds + return Int(nanoseconds / 1_000_000) // 转换为毫秒 + } + + + // DeviceId + + static func getDeviceId() -> String { + var adbrushDeviceId = UserDefaults.standard.dictionary(forKey: "bfaceDictKey")?["adbrush_deviceid"] as? String + if adbrushDeviceId == nil { + adbrushDeviceId = UIDevice.current.identifierForVendor?.uuidString + var bfaceDict = UserDefaults.standard.dictionary(forKey: "bfaceDictKey") ?? [:] + bfaceDict["adbrush_deviceid"] = adbrushDeviceId + UserDefaults.standard.set(bfaceDict, forKey: "bfaceDictKey") + UserDefaults.standard.synchronize() + } + + return adbrushDeviceId ?? "" + } + + + // 本地IP + static func getLocalIp() -> String { + var address = "an error occurred when obtaining ip address" + var interfaces: UnsafeMutablePointer? = nil + + if getifaddrs(&interfaces) == 0 { + var tempAddr = interfaces + + while tempAddr != nil { + let addrFamily = tempAddr!.pointee.ifa_addr.pointee.sa_family + let name = String(cString: tempAddr!.pointee.ifa_name) + + if addrFamily == AF_INET && name == "en0" { + var ipAddress = sockaddr_in() + memcpy(&ipAddress, tempAddr!.pointee.ifa_addr, MemoryLayout.size) + address = String(cString: inet_ntoa(ipAddress.sin_addr)) + } + tempAddr = tempAddr!.pointee.ifa_next + } + freeifaddrs(interfaces) + } + return address + } + + + // 包名 + static func getPackageName() -> String { + return Bundle.main.bundleIdentifier ?? "" + } + + // 版本 + static func getappVersion() -> String { + + return Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "" + + } + + // 手机型号 + static func getiphone() -> String { + + return UIDevice.current.localizedModel + + } + + // 系统版本 + static func getosVersion() -> String { + + return UIDevice.current.systemVersion + + } + + // 电池电量 + static func getBatteryLevel() -> Float? { + return UIDevice.current.batteryLevel + } + + // 是否在充电 + static func isDeviceCharging() -> Bool { + return UIDevice.current.batteryState == .charging || UIDevice.current.batteryState == .full + } + + // 获取deviceId + static func getPhoneDeviceId() -> String? { + return UIDevice.current.identifierForVendor?.uuidString + } + + // 当前的无线接入技术 + static func getCurrentRadioAccessTechnology() -> String? { + let networkInfo = CTTelephonyNetworkInfo() + let radioAccessTechnology = networkInfo.currentRadioAccessTechnology + return radioAccessTechnology + } + + + + // 时间 + static func getCurrentTime() -> String { + let date = Date() // 获取当前日期和时间 + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" // 设置日期格式 + formatter.locale = Locale(identifier: "en_US_POSIX") // 确保使用 ISO 8601 标准 + return formatter.string(from: date) + } + + // 字体 + static func getCurrentFont() -> String { + return "\(UIFont.systemFont(ofSize: UIFont.systemFontSize))" + } + + // 语言 + static func getlanguageCode() -> String { + return Locale.current.languageCode ?? "" + } + + + //GAID + + static func getGaid() -> String { + return ASIdentifierManager.shared().advertisingIdentifier.uuidString + } + + + // IDFA + static func getIdfa() -> String { + if ASIdentifierManager.shared().isAdvertisingTrackingEnabled { + let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString + return idfa + } else { + return "" + } + } + + // monotonicRawClockTimeMillis 单调时钟时间戳 + static func getMonotonicRawClockTimeMillis() -> UInt64 { + let time = mach_absolute_time() + let nanoseconds = Double(time) / Double(NSEC_PER_SEC) // Convert to nanoseconds + return UInt64(nanoseconds / 1000000) // Convert to milliseconds + } + + // 内核启动时间戳 + static func getKernelBootTimeMillis() -> Int64? { + // 定义变量以存储系统启动时间 + var bootTime = timeval() + var size = MemoryLayout.stride + + // 获取系统启动时间 + let mib: [Int32] = [CTL_KERN, KERN_BOOTTIME] + let result = sysctl(UnsafeMutablePointer(mutating: mib), 2, &bootTime, &size, nil, 0) + + guard result == 0 else { + print("获取系统启动时间失败") + return nil + } + + // 系统启动时间(秒 + 微秒) + let bootTimeInSeconds = Double(bootTime.tv_sec) + Double(bootTime.tv_usec) / 1_000_000 + + // 当前时间 + let currentTime = Date().timeIntervalSince1970 + + // 计算内核运行的时间(秒) + let kernelBootTimeInSeconds = currentTime - bootTimeInSeconds + + // 转换为毫秒 + return Int64(kernelBootTimeInSeconds * 1_000) + } + + + // 获取运营商信息 + static func getCarrierInfo() -> String { + var carrierInfo = "" + let networkInfo = CTTelephonyNetworkInfo() + let carrier = networkInfo.serviceSubscriberCellularProviders?.first?.value + if let carrier = carrier { + carrierInfo = "\(carrier.mobileCountryCode ?? "") \(carrier.mobileNetworkCode ?? "")" + } + return carrierInfo + } + + // 获取网络连接类型 + static func getNetworkType() -> String { + let monitor = NWPathMonitor() + let queue = DispatchQueue.global(qos: .background) + + var type:String = "" + + monitor.pathUpdateHandler = { path in + if path.status == .satisfied { + if path.usesInterfaceType(.wifi) { + type = "Wi-Fi" + } else if path.usesInterfaceType(.cellular) { + type = "Cellular" + } else if path.usesInterfaceType(.wiredEthernet) { + type = "Wired Ethernet" + } else if path.usesInterfaceType(.loopback) { + type = "Loopback" + } else { + type = "Other" + } + } else { + type = "Unavailable" + } + monitor.cancel() // 停止监控,释放资源 + } + + monitor.start(queue: queue) + return type + } + + // 是否连接VPN + static func isVPNOn() -> Bool { + var flag = false + let version = UIDevice.current.systemVersion + + // Check if the system version is >= 9.0 + if let versionDouble = Double(version), versionDouble >= 9.0 { + // For iOS 9.0 and above + if let dict = CFNetworkCopySystemProxySettings() as? Unmanaged { + let bridgedDict = dict.takeUnretainedValue() as? [String: Any] + + if let scopedSettings = bridgedDict?["__SCOPED__"] as? [String: Any] { + for key in scopedSettings.keys { + if key.contains("tap") || key.contains("tun") || key.contains("ipsec") || key.contains("ppp") { + flag = true + break + } + } + } + } + } else { + // For iOS versions below 9.0 + var interfaces: UnsafeMutablePointer? + let success = getifaddrs(&interfaces) + + if success == 0 { + var tempAddr = interfaces + while tempAddr != nil { + if let name = String(validatingUTF8: (tempAddr?.pointee.ifa_name)!) { + if name.contains("tap") || name.contains("tun") || name.contains("ipsec") || name.contains("ppp") { + flag = true + break + } + } + tempAddr = tempAddr?.pointee.ifa_next + } + } + + // Free memory + freeifaddrs(interfaces) + } + + return flag + } + + + + static func appId() -> String { + return Bundle.main.bundleIdentifier ?? "" + } + + + // 获取音量 + static func isDeviceMuted() -> Bool { + let audioSession = AVAudioSession.sharedInstance() + do { + try audioSession.setActive(true) + // 获取当前音量 + let currentVolume = audioSession.outputVolume + return currentVolume == 0 + } catch { + print("Error setting audio session active: \(error.localizedDescription)") + return false + } + } + + // 追踪状态 + static func getAdvertisingTrackingStatus() -> String { + if #available(iOS 14, *) { + switch ATTrackingManager.trackingAuthorizationStatus { + case .notDetermined: + return "notDetermined" + case .restricted: + return "restricted" + case .denied: + return "denied" + case .authorized: + return "authorized" + @unknown default: + return "unknown" + } + } else { + // iOS 14 以下版本 + let isTrackingEnabled = ASIdentifierManager.shared().isAdvertisingTrackingEnabled + return isTrackingEnabled ? "authorized" : "denied" + } + } + + // 是否启用追踪 + static func isAdvertisingTrackingEnabled() -> Bool { + if #available(iOS 14, *) { + switch ATTrackingManager.trackingAuthorizationStatus { + case .authorized: + return true // 用户允许广告追踪 + default: + return false // 用户未授权、受限制或拒绝 + } + } else { + // iOS 14 以下版本 + return ASIdentifierManager.shared().isAdvertisingTrackingEnabled + } + } + + // 获取设备总内存 + + static func getTotalMemoryInBytes() -> UInt64 { + return ProcessInfo.processInfo.physicalMemory + } + + static func getTotalMemoryInReadableFormat() -> String { + let totalMemoryBytes = getTotalMemoryInBytes() + let formatter = ByteCountFormatter() + formatter.allowedUnits = [.useGB] // 只显示为 GB + formatter.countStyle = .memory + return formatter.string(fromByteCount: Int64(totalMemoryBytes)) + } + + + // 是否是刘海屏 + static func isNotchScreen() -> Bool { + // 确保在主线程中访问 + if Thread.isMainThread { + guard let window = UIApplication.shared.windows.first else { return false } + let topSafeArea = window.safeAreaInsets.top + return topSafeArea > 20 + } else { + // 在非主线程中调用时,通过调度回主线程同步获取结果 + return DispatchQueue.main.sync { + guard let window = UIApplication.shared.windows.first else { return false } + let topSafeArea = window.safeAreaInsets.top + return topSafeArea > 20 + } + } + } + + + // 屏幕方向 + + static func getScreenOrientation() -> String { + let orientation = UIDevice.current.orientation + + switch orientation { + case .portrait: + return "portrait" + case .portraitUpsideDown: + return "portraitUpsideDown" + case .landscapeLeft: + return "landscapeLeft" + case .landscapeRight: + return "landscapeRight" + case .faceUp: + return "faceUp" + case .faceDown: + return "faceDown" + case .unknown: + return "unknown" + @unknown default: + return "unknown" + } + } + + // 获取 OpenGL版本 + static func getOpenGLVersion() -> String { + // 尝试初始化 OpenGL ES 3.0 上下文 + if let _ = EAGLContext(api: .openGLES3) { + return "openGLES3" + } + // 尝试初始化 OpenGL ES 2.0 上下文 + else if let _ = EAGLContext(api: .openGLES2) { + return "openGLES2" + } + // 如果上述都失败,则返回 OpenGL ES 1.0 + else if let _ = EAGLContext(api: .openGLES1) { + return "openGLES1" + } else { + return "NO OpenGL ES" + } + } + + // 获取时区偏移量 + static func getTimeZoneOffsetFromUS() -> Int { + // 获取当前设备的时区 + let currentTimeZone = TimeZone.current + + // 获取美国东部时间的时区(美区时区) + let usTimeZone = TimeZone(identifier: "America/New_York") + + // 获取两个时区的偏移量,单位为秒 + let currentOffset = currentTimeZone.secondsFromGMT() + let usOffset = usTimeZone?.secondsFromGMT() ?? 0 + + // 计算偏移差,并将结果转为分钟 + let offsetDifferenceInMinutes = (currentOffset - usOffset) / 60 + return offsetDifferenceInMinutes + } + + // 设备键盘类型 + static func getKeyboardLayoutType() -> [String] { + var keyboardLayouts: [String] = [] + + // 获取当前设备的语言环境 + let currentLanguage = Locale.current.languageCode ?? "" + + // 根据语言环境推测键盘布局类型 + switch currentLanguage { + case "en": + // 英语一般使用 QWERTY 布局 + keyboardLayouts.append("QWERTY") + case "fr": + // 法语一般使用 AZERTY 布局 + keyboardLayouts.append("AZERTY") + case "de": + // 德语一般使用 QWERTZ 布局 + keyboardLayouts.append("QWERTZ") + case "es": + // 西班牙语一般使用 QWERTY 布局 + keyboardLayouts.append("QWERTY") + case "it": + // 意大利语一般使用 QWERTY 布局 + keyboardLayouts.append("QWERTY") + case "ja": + // 日语一般使用日本输入法 + keyboardLayouts.append("Japanese") + case "zh": + // 中文一般使用拼音输入法 + keyboardLayouts.append("Pinyin") + default: + // 其他语言暂时返回 QWERTY + keyboardLayouts.append("QWERTY") + } + + return keyboardLayouts + } + + + // 设备型号修订版 + static func getDeviceHardwareIdentifier() -> String { + var size: Int = 0 // 使用 Int 类型而不是 UInt32 + sysctlbyname("hw.model", nil, &size, nil, 0) + + var model = [CChar](repeating: 0, count: size) + sysctlbyname("hw.model", &model, &size, nil, 0) + + let modelString = String(cString: model) + return modelString + } + + // static func mapHardwareIdentifierToModelNumber(_ identifier: String) -> String { + // switch identifier { + // // iPhone 系列 + // case "iPhone13,2": + // return "A2172" // iPhone 12 + // case "iPhone13,3": + // return "A2341" // iPhone 12 Pro + // case "iPhone13,4": + // return "A2342" // iPhone 12 Pro Max + // case "iPhone13,1": + // return "A2176" // iPhone 12 mini + // case "iPhone12,1": + // return "A2111" // iPhone 11 + // case "iPhone12,3": + // return "A2160" // iPhone 11 Pro + // case "iPhone12,5": + // return "A2161" // iPhone 11 Pro Max + // case "iPhone11,8": + // return "A1882" // iPhone XR + // case "iPhone11,2": + // return "A1920" // iPhone XS + // case "iPhone11,6": + // return "A1921" // iPhone XS Max + // case "iPhone10,3": + // return "A1865" // iPhone X + // case "iPhone10,6": + // return "A1901" // iPhone X + // case "iPhone9,1": + // return "A1660" // iPhone 7 + // case "iPhone9,2": + // return "A1661" // iPhone 7 Plus + // case "iPhone8,1": + // return "A1549" // iPhone 6s + // case "iPhone8,2": + // return "A1586" // iPhone 6s Plus + // case "iPhone7,1": + // return "A1522" // iPhone 6 Plus + // case "iPhone7,2": + // return "A1549" // iPhone 6 + // case "iPhone6,1": + // return "A1528" // iPhone 5s + // case "iPhone6,2": + // return "A1530" // iPhone 5s + // case "iPhone5,1": + // return "A1428" // iPhone 5 + // case "iPhone5,2": + // return "A1429" // iPhone 5 + // + // // iPad 系列 + // case "iPad8,1": + // return "A1893" // iPad Air 3 + // case "iPad8,2": + // return "A1894" // iPad Air 3 + // case "iPad8,3": + // return "A1954" // iPad Pro 10.5" + // case "iPad8,4": + // return "A1955" // iPad Pro 10.5" + // case "iPad8,5": + // return "A1701" // iPad Pro 12.9" (2nd generation) + // case "iPad8,6": + // return "A1709" // iPad Pro 12.9" (2nd generation) + // case "iPad8,7": + // return "A1673" // iPad Pro 12.9" (3rd generation) + // case "iPad8,8": + // return "A1674" // iPad Pro 12.9" (3rd generation) + // + // // iPad Mini 系列 + // case "iPad4,4": + // return "A1454" // iPad Mini 2 + // case "iPad4,5": + // return "A1455" // iPad Mini 2 + // case "iPad4,6": + // return "A1456" // iPad Mini 2 + // case "iPad5,1": + // return "A1822" // iPad Mini 4 + // case "iPad5,2": + // return "A1823" // iPad Mini 4 + // + // // iPad Pro 系列 + // case "iPad6,7": + // return "A1652" // iPad Pro 12.9" (1st generation) + // case "iPad6,8": + // return "A1670" // iPad Pro 12.9" (1st generation) + // case "iPad7,1": + // return "A1674" // iPad Pro 12.9" (2nd generation) + // case "iPad7,2": + // return "A1673" // iPad Pro 12.9" (2nd generation) + // + // // Apple Watch 系列 + // case "Watch1,1": + // return "A1554" // Apple Watch (1st generation) + // case "Watch1,2": + // return "A1553" // Apple Watch (1st generation) + // case "Watch2,6": + // return "A1792" // Apple Watch Series 1 + // case "Watch2,7": + // return "A1791" // Apple Watch Series 1 + // case "Watch3,1": + // return "A1860" // Apple Watch Series 3 + // case "Watch3,2": + // return "A1861" // Apple Watch Series 3 + // case "Watch3,3": + // return "A1889" // Apple Watch Series 3 + // case "Watch3,4": + // return "A1890" // Apple Watch Series 3 + // case "Watch4,1": + // return "A1975" // Apple Watch Series 4 + // case "Watch4,2": + // return "A1976" // Apple Watch Series 4 + // case "Watch4,3": + // return "A1977" // Apple Watch Series 4 + // case "Watch4,4": + // return "A1978" // Apple Watch Series 4 + // + // // Apple TV 系列 + // case "AppleTV5,3": + // return "A1469" // Apple TV (4th generation) + // case "AppleTV6,2": + // return "A1842" // Apple TV 4K (1st generation) + // case "AppleTV11,1": + // return "A2169" // Apple TV 4K (2nd generation) + // default: + // return "Unknown model" + // } + // } + + + +// static func uploadAD_Load(adid:String,ecpm:Double,network:String,countryCode:String,platformResponseTime:TimeInterval,dsp:String,loadTime:Int){ +// +// var mdic: [String: Any] = [:] +// +// mdic["deviceId"] = starManager.shared.adbrush_deviceid +// mdic["gaid"] = self.getGaid() +// mdic["localIp"] = starManager.shared.adbrush_localip +// +// // let remoteIp = UserDefaults.standard.string(forKey: "kIP_key") ?? "" +// mdic["remoteIp"] = starManager.shared.remouteIP +// +// mdic["packageName"] = appId() +// mdic["adPlatform"] = "MAX" +// mdic["countryCode"] = countryCode +// mdic["adId"] = adid +// mdic["platformResponseTime"] = platformResponseTime +// mdic["shelfNumber"] = "uploadAD_Load" +// mdic["ecpm"] = ecpm +// mdic["getIpResponseTime"] = starManager.shared.ipTime +// mdic["dsp"] = dsp +// mdic["dataId"] = starManager.shared.dataId +// mdic["linkId"] = starManager.shared.linkId +// mdic["loadTime"] = loadTime +// mdic["succeed"] = true +// mdic["network"] = network +// mdic["online"] = false +// mdic["washParam"] = starManager.shared.washParam +// +// // Log the JSON data to verify it's correctly formatted +// do { +// let jsonData = try JSONSerialization.data(withJSONObject: mdic, options: .prettyPrinted) +// let jsonString = String(data: jsonData, encoding: .utf8) ?? "" +// print("Uploading AD Show with data: \(jsonString)") +// +// DispatchQueue.main.sync { +// NotificationCenter.default.post(name: NSNotification.Name("adNetwork"), object: nil, userInfo: ["info": "uploadAD_Load:\n\(jsonString)"]) +// } +// +// +// YL_NetWorkManager.uploadData(mdic: mdic, urlPath: "\(kBaseUrl)\(kURL_AD_Load)") { err, state, result in +// if let error = err { +// print("Error: \(error.localizedDescription)") +// } else { +// print("Result: \(result ?? [:])") +// DispatchQueue.main.async { +// NotificationCenter.default.post(name: NSNotification.Name("adNetwork"), object: nil, userInfo: ["info": "AdID: \(adid) 广告加载成功, ecpm: \(ecpm) 加载时长: 0ms dsp: \( "MAX") network: \(network)"]) +// } +// +// } +// } +// +// +// } catch { +// print("Error serializing JSON: \(error.localizedDescription)") +// } +// +// +// } + + static func uploadAD_Load( + adid: String, + ecpm: Double, + network: String, + countryCode: String, + platformResponseTime: TimeInterval, + dsp: String, + loadTime: Int, + retryCount: Int = 3, // 默认重试3次, + errMsg:String = "" + ) { + var mdic: [String: Any] = [:] + + mdic["deviceId"] = BbbAdManager.config.adbrush_deviceid + mdic["gaid"] = self.getGaid() + mdic["localIp"] = BbbAdManager.config.adbrush_localip + mdic["remoteIp"] = BbbAdManager.config.remouteIP + mdic["packageName"] = appId() + mdic["adPlatform"] = "TopOn" + mdic["countryCode"] = countryCode + mdic["adId"] = adid + mdic["platformResponseTime"] = platformResponseTime + mdic["shelfNumber"] = "uploadAD_Load" + mdic["ecpm"] = ecpm + mdic["getIpResponseTime"] = BbbAdManager.config.ipTime + mdic["dsp"] = dsp + mdic["dataId"] = BbbAdManager.config.dataId + mdic["linkId"] = BbbAdManager.config.linkId + mdic["loadTime"] = loadTime + mdic["succeed"] = true + mdic["network"] = network + mdic["online"] = false + mdic["washParam"] = BbbAdManager.config.washParam + if !errMsg.isEmpty { + mdic["succeed"] = false + mdic["errorMsg"] = errMsg + } + + // Log the JSON data to verify it's correctly formatted + do { + let jsonData = try JSONSerialization.data(withJSONObject: mdic, options: .prettyPrinted) + let jsonString = String(data: jsonData, encoding: .utf8) ?? "" + NSLog("XS- Uploading AD Show with data: \(jsonString)") + + + + // 上传数据 + YL_NetWorkManager.uploadData(mdic: mdic, urlPath: "\(kBaseUrl)\(kURL_AD_Load)") { err, state, result in + if let error = err { + NSLog("XS- Error: \(error.localizedDescription)") + + if retryCount > 0 { + NSLog("XS- Retrying... Remaining attempts: \(retryCount - 1)") + // 延迟1秒后重试 + DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + 1.0) { + uploadAD_Load( + adid: adid, + ecpm: ecpm, + network: network, + countryCode: countryCode, + platformResponseTime: platformResponseTime, + dsp: dsp, + loadTime: loadTime, + retryCount: retryCount - 1 + ) + } + } else { + NSLog("XS- Failed to upload data after \(3 - retryCount) retries.") + } + } else { + NSLog("XS- Result: \(result ?? [:])") + } + } + } catch { + NSLog("XS- Error serializing JSON: \(error.localizedDescription)") + } + } + + + + static func uploadAD_Show(adid:String,ecpm:Double,network:String,countryCode:String,platformResponseTime:TimeInterval,dsp:String){ + + var mdic: [String: Any] = [:] + + mdic["deviceId"] = BbbAdManager.config.adbrush_deviceid + mdic["gaid"] = self.getGaid() + mdic["localIp"] = BbbAdManager.config.adbrush_localip + + // let remoteIp = UserDefaults.standard.string(forKey: "kIP_key") ?? "" + mdic["remoteIp"] = BbbAdManager.config.remouteIP + + mdic["packageName"] = appId() + mdic["adPlatform"] = "TopOn" + mdic["countryCode"] = countryCode + mdic["adId"] = adid + mdic["platformResponseTime"] = platformResponseTime + mdic["shelfNumber"] = "uploadAD_Load" + mdic["ecpm"] = ecpm + mdic["getIpResponseTime"] = BbbAdManager.config.ipTime + mdic["dsp"] = dsp + mdic["dataId"] = BbbAdManager.config.dataId + mdic["linkId"] = BbbAdManager.config.linkId + mdic["loadTime"] = 0 + mdic["succeed"] = true + mdic["network"] = network + mdic["online"] = false + + do { + // Log the JSON data to verify it's correctly formatted + let jsonData = try JSONSerialization.data(withJSONObject: mdic, options: .prettyPrinted) + let jsonString = String(data: jsonData, encoding: .utf8) ?? "" + NSLog("XS- Uploading AD Show with data: \(jsonString)") + YL_NetWorkManager.uploadData(mdic:mdic, urlPath: "\(kBaseUrl)\(kURL_AD_Show)") { (err, state, result) in + if let error = err { + NSLog("XS- Error uploading AD Show: \(error.localizedDescription)") + } else { + NSLog("XS- Upload AD Show response: \(result ?? [:])") + } + } + } catch { + NSLog("XS- Error serializing JSON: \(error.localizedDescription)") + } + + + } + + + + /* + + static func showAd(idfa: String, + adId: String, + ecpm: Double?, + ad: Bool) { + + var mdic = [String: Any]() + mdic["appid"] = appId() + mdic["idfa"] = getIdfa() + mdic["ecpm"] = ecpm ?? 0 + mdic["ad"] = ad + mdic["id"] = adId + + uploadData(mdic: mdic, urlPath: kLocalUrlStr("adtask/show")) { err, state, result in + guard let status = result?["status"] as? String, + let time = result!["close"] as? NSNumber else { + + return + } + + if status == "Success" && ad { + + closeAD.removeADVC(byDelayTime: time.intValue) + // closeWindows.removeADVCByDelayTime(time.intValue) + } + DispatchQueue.main.async { + + NotificationCenter.default.post(name: NSNotification.Name("adNetwork"), object: nil, userInfo: ["info": "showAd,已上传展示AD操作,\(time.intValue / 1000)秒后关闭"]) + } + + if ad { + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adNetwork"), object: nil, userInfo: [ + "info": "AdID:\(adId),广告开始展示,展示时长\(time)" + ]) + } + } + } + } + */ + static func convertStringToDictionary(text: String) -> [String:AnyObject]? { + if let data = text.data(using: String.Encoding.utf8) { + do { + return try JSONSerialization.jsonObject(with: data, options: [JSONSerialization.ReadingOptions.init(rawValue: 0)]) as? [String:AnyObject] + } catch let error as NSError { + print(error) + } + } + return nil + } + + static func showAd(adId: String, + ecpm: Double?, + ad: Bool, + callback: @escaping () -> Void) { + + var mdic = [String: Any]() + mdic["appid"] = appId() + mdic["idfa"] = getIdfa() + mdic["ecpm"] = ecpm ?? 0 + mdic["ad"] = ad + mdic["id"] = adId + + let client:XUDPClient = XUDPClient.sharedInstance() + client.hintBlock = { (t:String?) in + guard let jsonStr = t else { + return + } + let result = convertStringToDictionary(text: jsonStr) + guard let status = result?["status"] as? String, + let time = result!["close"] as? NSNumber else { + + return + } + + if status == "Success" && ad { + + initializationTopOn.removeADVC(byDelayTime: time.intValue, onclose:{ + callback(); + } ) + // closeWindows.removeADVCByDelayTime(time.intValue) + } + + } + + client.onShow(mdic); + } + static func loadend(max_ecpm:Double){ + var mdic = [String: Any]() + mdic["appid"] = appId() + mdic["idfa"] = getIdfa() + mdic["max_ecpm"] = max_ecpm + + let client:XUDPClient = XUDPClient.sharedInstance() + client.hintBlock = { (t:String?) in + guard let jsonStr = t else { + return + } + let result = convertStringToDictionary(text: jsonStr) + + // 解析返回的 JSON 数据 + if let resultDict = result, + let status = resultDict["status"] as? String, + let restart = resultDict["restart"] as? Bool { + + // 打印返回的状态 + print("状态: \(status)") + + if restart { + // 如果 restart 为 true,重新加载广告 + print("重新加载广告展示") + // BbbAdManager.config.isadload = 0 + // 调用广告加载的函数 + + + // starManager.shared.isadsureshow = true + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "重新加载广告展示"]) + } + + + } else { + // restart 为 false,不需要处理 + print("不需要重新加载广告,程序后续会被杀掉") + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "不需要重新加载广告,程序后续会被杀掉"]) + } + } + } else { + print("返回数据格式错误") + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "返回数据格式错误"]) + } + } + } + + client.onEnd(mdic); + } + + /* + static func loadend(max_ecpm:Double){ + var mdic = [String: Any]() + mdic["appid"] = appId() + mdic["idfa"] = getIdfa() + mdic["max_ecpm"] = max_ecpm + + uploadData(mdic: mdic, urlPath:kLocalUrlStr("adtask/end")) { err, state, result in + // 检查是否有错误 + guard err == nil else { + print("请求失败: \(String(describing: err))") + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "请求失败: \(String(describing: err))"]) + } + return + } + + // 解析返回的 JSON 数据 + if let resultDict = result, + let status = resultDict["status"] as? String, + let restart = resultDict["restart"] as? Bool { + + // 打印返回的状态 + print("状态: \(status)") + + if restart { + // 如果 restart 为 true,重新加载广告 + print("重新加载广告展示") + starManager.shared.isadload = 0 + // 调用广告加载的函数 + + for (index, adId) in starManager.shared.adids.enumerated() { + DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 2) { // 每隔 4 秒加载一个广告 + YL_AdManager.shared.addInterstitialAdItem(adId: adId) + } + } + starManager.shared.isadsureshow = true + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "重新加载广告展示"]) + } + + + } else { + // restart 为 false,不需要处理 + print("不需要重新加载广告,程序后续会被杀掉") + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "不需要重新加载广告,程序后续会被杀掉"]) + } + } + } else { + print("返回数据格式错误") + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "返回数据格式错误"]) + } + } + } + + } + + */ + + + static func saveIphonelogs() { + let processInfo = ProcessInfo.processInfo + let screenSize = UIScreen.main.bounds.size + let width = Int(screenSize.width) + let height = Int(screenSize.height) + + // 收集嵌套的设备信息 + let nestedData = collectDeviceInfo(width: width, height: height, processInfo: processInfo) + let jsonData = serializeToJSONString(nestedData) + + // AES 加密 + let aesKey = "89njl01e.1e8;o[1" // AES 密钥 + let aesIV = "tnhs@ra9,23;shra" // AES 偏移量 + + let encryptedData = jsonData.aesEncrypt(key: aesKey, iv: aesIV) + print("Encrypted text: \(encryptedData ?? "" )") + + if let decrypted = encryptedData?.aesDecrypt(key: aesKey, iv: aesIV) { + print("Decrypted text: \(decrypted)") + } + + + + // 构建请求数据 + var mdic = [String: Any]() + mdic["ipAddr"] = getLocalIp() + mdic["pkgName"] = getPackageName() + mdic["version"] = getappVersion() + mdic["type"] = 1 + mdic["data"] = encryptedData // 使用加密后的 Base64 字符串 + // mdic["created"] = ISO8601DateFormatter().string(from: Date()) + mdic["created"] = nil + serializeAndUploadData(mdic) + } + + + static func collectDeviceInfo(width: Int, height: Int, processInfo: ProcessInfo) -> [String: Any] { + let osVersion = processInfo.operatingSystemVersion + let osVersionString = "\(osVersion.majorVersion).\(osVersion.minorVersion).\(osVersion.patchVersion)" + + return [ + "deviceModel": getIpne.shard.getIPhoneModel().getName(), + "osVersion": getosVersion(), + "batteryLevel": getBatteryLevel() ?? 0.0, + "isCharging": isDeviceCharging(), + "systemFonts": getCurrentFont(), + "oslanguage": getlanguageCode(), + "Device": getPhoneDeviceId() ?? "", + "processID": processInfo.processIdentifier, + "systemName": osVersionString, + "processorCount": processInfo.processorCount, + "physicalMemory": Double(processInfo.physicalMemory) / 1024.0 / 1024.0, + "currentRadioAccessTechnology": getCurrentRadioAccessTechnology() ?? "", + "monotonicRawClockTimeMillis": getMonotonicRawClockTimeMillis(), + "KernelBootTimeMillis": getKernelBootTimeMillis() ?? 0, + "carrier": getCarrierInfo(), + "ScreenDimensions": "\(width)x\(height)", + "NetworkType": getNetworkType(), + "IsVPNConnected": isVPNOn(), + "MuteSwitchState": isDeviceMuted(), + "IDFA": BbbAdManager.config.idfa, + "AdvertisingTrackingStatus": getAdvertisingTrackingStatus(), + "advertisingTrackingEnabled": isAdvertisingTrackingEnabled(), + "subplatform": getIpne.shard.getIPhoneModel().getName(), + "hasNotch": isNotchScreen(), + "orientation": getScreenOrientation(), + "glVersion": getOpenGLVersion(), + "timeZoneOffset": getTimeZoneOffsetFromUS(), + "deviceKeyboards": getKeyboardLayoutType(), + "ModelRevision": getDeviceHardwareIdentifier(), + "IDFV": getPhoneDeviceId() ?? "", + ] + } + + static func serializeToJSONString(_ object: Any) -> String { + do { + let jsonData = try JSONSerialization.data(withJSONObject: object, options: []) + return String(data: jsonData, encoding: .utf8) ?? "{}" + } catch { + print("Error serializing object to JSON string: \(error.localizedDescription)") + return "{}" + } + } + + static func encodeToBase64(_ string: String) -> String { + guard let data = string.data(using: .utf8) else { return "" } + return data.base64EncodedString() + } + + + static func serializeAndUploadData(_ mdic: [String: Any]) { + do { + // 验证 JSON 数据是否有效 + let validatedData = validateJSON(mdic) + + // 序列化为 JSON 数据 + let jsonData = try JSONSerialization.data(withJSONObject: validatedData, options: .prettyPrinted) + let jsonString = String(data: jsonData, encoding: .utf8) ?? "" + print("save_iphone_logs with data: \(jsonString)") + + + + // 上传网络请求 + YL_NetWorkManager.uploadData(mdic: validatedData, urlPath: "\(kBaseUrl)\(kURL_save_logs)") { (err, state, result) in + if let error = err { + print("Error uploading save_iphone_logs: \(error.localizedDescription)") + } else { + print("save_iphone_logs response: \(result ?? [:])") + } + } + } catch { + print("Error serializing JSON save_iphone_logs: \(error.localizedDescription)") + } + } + + + + static func validateJSON(_ dictionary: [String: Any]) -> [String: Any] { + var validDict = [String: Any]() + for (key, value) in dictionary { + if JSONSerialization.isValidJSONObject([key: value]) { + validDict[key] = value + } else { + print("Invalid JSON value for key \(key): \(value)") + validDict[key] = "\(value)" // 将不支持的值转换为字符串 + } + } + return validDict + } + +} + + + +extension String { + func aesEncrypt(key: String, iv: String) -> String? { + guard let data = self.data(using: .utf8), + let encryptedData = data.aesEncrypt(key: key, iv: iv) else { return nil } + return encryptedData.base64EncodedString() + } + + func aesDecrypt(key: String, iv: String) -> String? { + guard let data = Data(base64Encoded: self), + let decryptedData = data.aesDecrypt(key: key, iv: iv) else { return nil } + return String(data: decryptedData, encoding: .utf8) + } +} + +extension Data { + func aesEncrypt(key: String, iv: String) -> Data? { + return self.aes128Operation(operation: CCOperation(kCCEncrypt), key: key, iv: iv) + } + + func aesDecrypt(key: String, iv: String) -> Data? { + return self.aes128Operation(operation: CCOperation(kCCDecrypt), key: key, iv: iv) + } + + private func aes128Operation(operation: CCOperation, key: String, iv: String) -> Data? { + guard key.count == kCCKeySizeAES128, iv.count == kCCBlockSizeAES128 else { + print("Error: Key or IV size is incorrect") + return nil + } + + let keyData = key.data(using: .utf8)! + let ivData = iv.data(using: .utf8)! + var outLength = Int(0) + var outBytes = [UInt8](repeating: 0, count: self.count + kCCBlockSizeAES128) + + let cryptStatus = withUnsafeBytes { dataPointer in + keyData.withUnsafeBytes { keyPointer in + ivData.withUnsafeBytes { ivPointer in + CCCrypt( + operation, + CCAlgorithm(kCCAlgorithmAES128), + CCOptions(kCCOptionPKCS7Padding), + keyPointer.baseAddress, kCCKeySizeAES128, + ivPointer.baseAddress, + dataPointer.baseAddress, self.count, + &outBytes, outBytes.count, + &outLength + ) + } + } + } + + guard cryptStatus == kCCSuccess else { + print("Error: AES operation failed with status \(cryptStatus)") + return nil + } + + return Data(bytes: outBytes, count: outLength) + } +} + + + + diff --git a/topon/template/PlayBTopOn/PlayBTopOn/playB/YL_PlayVC.swift b/topon/template/PlayBTopOn/PlayBTopOn/playB/YL_PlayVC.swift new file mode 100644 index 0000000..436d34e --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/playB/YL_PlayVC.swift @@ -0,0 +1,155 @@ +// +// YL_PlayVC.swift +// playbtest +// +// Created by 忆海16 on 2024/11/5. +// + +import UIKit +import AnyThinkInterstitial +class YL_PlayVC: UIViewController { + + @IBOutlet weak var textSDKView: UITextView! + @IBOutlet weak var bundleIdLab: UILabel! + @IBOutlet weak var deviceIdLab: UILabel! + @IBOutlet weak var ipLab: UILabel! + @IBOutlet weak var idfaLab: UILabel! + @IBOutlet weak var ad1Lab: UILabel! + @IBOutlet weak var ad2Lab: UILabel! + @IBOutlet weak var ad3Lab: UILabel! + + private var observation: NSKeyValueObservation? + + private var observationis: NSKeyValueObservation? + + var openADTimer:Timer? + let kOpenADPerSec: CGFloat = 0.1 // 假设的秒数 + let kOpenAdCTimeLength: CGFloat = 30 // 假设的超时时长 + + static var totalTimeC: CGFloat = 0.0 + var firstShow = true + + override func viewDidLoad() { + super.viewDidLoad() + + BbbAdManager.shared.loadAd(view:self) + let bundleId = Bundle.main.bundleIdentifier ?? "" + bundleIdLab.text = "Name:\(bundleId)" + let deviceId = BbbAdManager.config.adbrush_deviceid ?? "" + deviceIdLab.text = "DeviceID:\(deviceId)" + let locIp = BbbAdManager.config.adbrush_localip ?? "" + let remoteIp = BbbAdManager.config.remouteIP + ipLab.text = "LocIP:\(locIp),RemoteIp:\(remoteIp)" + + if #available(iOS 14, *) { + IDFA.shared.checkATT { idfa in + if let idfa = idfa { + print("IDFA: \(idfa)") + self.idfaLab.text = "IDFA:\(idfa)" + } else { + print("无法获取 IDFA") + } + } + } else { + IDFA.shared.getIDFAForOlderVersions { idfa in + if let idfa = idfa { + print("IDFA: \(idfa)") + self.idfaLab.text = "IDFA:\(idfa)" + } else { + print("无法获取 IDFA") + } + } + } + + BbbAdManager.shared.start() + + + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + if (!firstShow) { + return + } + firstShow = false + + ad1Lab.text = BbbAdManager.config.adids[0] + ad2Lab.text = BbbAdManager.config.adids[1] + ad3Lab.text = BbbAdManager.config.adids[2] + + + self.navigationController?.navigationBar.isHidden = true + + NotificationCenter.default.addObserver(self, selector: #selector(addTextToTextView), name: NSNotification.Name("adinfo"), object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(adStatusChange), name: NSNotification.Name("adStatus"), object: nil) + + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "adbrush_base_url:\(BbbAdManager.config.adbrush_base_url),adbrush_deviceid:\(String(describing: BbbAdManager.config.adbrush_deviceid)),adbrush_localip:\(String(describing: BbbAdManager.config.adbrush_localip)),adbrush_local_url:\(BbbAdManager.config.adbrush_local_url),adbrush_ecpm:\(BbbAdManager.config.adbrush_ecpm)"]) + } + + @objc func addTextToTextView(notification: Notification) { + if let newText = notification.userInfo?["text"] as? String { + // 正确的方式 + DispatchQueue.global().async { + // 后台线程处理数据 + + + DispatchQueue.main.async { + // 主线程更新UI + self.textSDKView.text.append("\(newText)\n\n") + } + } + + } + } + + + + + + + // 向文本末尾追加 100 行空白行 + func appendExtraLines1() { + let extraLines = String(repeating: "\n", count: 100) + let currentText = textSDKView.text ?? "" + + // 检查末尾是否已经有额外行,避免重复追加 + if !currentText.hasSuffix(extraLines) { + textSDKView.text = currentText.trimmingCharacters(in: .whitespacesAndNewlines) + extraLines + } + } + + + + + + + @objc func adStatusChange(notification: Notification) { + if let newText = notification.userInfo?["text"] as? String, let id = notification.userInfo?["id"] as? String { + var lab = ad1Lab + if id == BbbAdManager.config.adids[1] { + lab = ad2Lab + } else if id == BbbAdManager.config.adids[2] { + lab = ad3Lab + } + DispatchQueue.global().async { + // 后台线程处理数据 + + DispatchQueue.main.async { + // 主线程更新UI + lab?.text = "\(id):\(newText)" } + } + + } + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + +} + +extension Notification.Name { + static let adStatusChanged = Notification.Name("adStatusChanged") + +} diff --git a/topon/template/PlayBTopOn/PlayBTopOn/playB/YL_PlayVC.xib b/topon/template/PlayBTopOn/PlayBTopOn/playB/YL_PlayVC.xib new file mode 100644 index 0000000..b5f25b5 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/playB/YL_PlayVC.xib @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/topon/template/PlayBTopOn/PlayBTopOn/playB/bbbAdManager.swift b/topon/template/PlayBTopOn/PlayBTopOn/playB/bbbAdManager.swift new file mode 100644 index 0000000..d354319 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/playB/bbbAdManager.swift @@ -0,0 +1,549 @@ +// +// bbbAdManager.swift +// playbtest +// +// Created by mac on 2025/3/11. +// + +import Foundation +import AnyThinkInterstitial +import AnyThinkSDK + +class bConfig: NSObject { + var appId:String = "{appId}" + /// 广告Key + var adKey:String = "{adKey}" + + /// 广告数组 + var allAdIds:[String] = [{allAdIds}] + /// 广告数组 + var adids:[String] = [] + ///设备ID + var adbrush_deviceid:String? + ///最低ecpm + var adbrush_ecpm:Double = 0.0005 + /// 本地ip + var adbrush_localip:String? + /// A面load show + var adbrush_base_url:String = "http://192.168.9.11:8080" + /// 本地服务 + var adbrush_local_url:String = "http://127.0.0.1:6000" + + /// 远程ip + var remouteIP:String = "" + /// dataId + var dataId:String = "" + + ///IDFA + var idfa:String = "" + + + + /// + var washParam:Bool = false + /// + var linkId:String = "" + + ///load次数 + var loadcount:Int = 0 + ///load次数 + var loadcount1:Int = 0 + + var ipTime:Int = 0 + + override init() { + super.init() + + if self.allAdIds.count > 3 { + self.adids = Array(self.allAdIds.shuffled().prefix(3)) + } else { + self.adids = self.allAdIds + } + + } + + ///是否正在展示中 + @objc dynamic var isadsureshow:Bool = false + + + func getRandomString() -> String? { + return adids.randomElement() + } + // 判断当前是否为广告不量模式 + func isADSSMode() -> Bool { + // return true + return UserDefaults.standard.bool(forKey: "kLuxSSFaceKey") + } +} + +class AdItem :NSObject, ATInterstitialDelegate { + + + func didFinishLoadingAD(withPlacementID placementID: String!) { + BbbAdManager.config.loadcount1 += 1 + NSLog("XS- didFinishLoadingAD\(String(describing: placementID))") + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "加载广告1: \(String(describing: placementID)) 成功 - \(BbbAdManager.config.loadcount1)"]) + changeStatus(st: 2) + } + + + // var interstitialAd:MAInterstitialAd! + var interstitialAdID: String = "" + + var retryAttempt = 0.0 + + // 定义广告关闭后的操作闭包 + var _onAdClosed: (() -> Void)? + var onStatusChange:((_:String,_:Int, _:Double) -> Void)? + + var startLoadTime: DispatchTime? + + // 0: 初始,1:加载中,2:加载完成,3:展示中,4:关闭,5:加载失败,6:展示失败 + private(set) var status: Int = 0 + + private(set) var ecpm:Double = 0.0 + + + init(adID:String){ + + super.init() + self.interstitialAdID = adID + // loadInterstitialAd() + + } + func onAdClosed() { + if self._onAdClosed != nil { + self._onAdClosed!() + } + } + + func changeStatus(st:Int) { + self.status = st + onStatusChange?(self.interstitialAdID, st, self.ecpm) + } + + + func loadInterstitialAd(){ + NSLog("XS- placementIDLoad 1: ---- \(interstitialAdID)") + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text":"begin load:\(interstitialAdID)"]) + + startLoadTime = DispatchTime.now() + changeStatus(st: 1) + let extra: [String: Any] = [ + kATAdLoadingExtraMediaExtraKey: "custom_values" + ] + + ATAdManager.shared().loadAD(withPlacementID: interstitialAdID, extra: extra, delegate: self) + NSLog("XS- placementIDLoad 2: ---- \(interstitialAdID)") + + } + + + func showAd(viewController:UIViewController, onAdClosed: @escaping () -> Void) -> Bool{ + self._onAdClosed = onAdClosed + NSLog("XS- onAdClosed set: \(self._onAdClosed != nil)") + if ATAdManager.shared().interstitialReady(forPlacementID: interstitialAdID) { + ATAdManager.shared().showInterstitial(withPlacementID: interstitialAdID, in: viewController, delegate: self) + NSLog("XS- placementIDShow : ---- \(interstitialAdID)") + return true + } else { + NSLog("XS- interstitialAdID no redy 插页广告尚未准备好") +// retryLoadAdIfNecessary() + } + return false + } + + func to_network(_ network: Int) -> String { + switch network { + case 6: + return "Mintegral" + case 11: + return "Ironsource" + case 12: + return "UnityAds" + case 13: + return "Vungle" + case 50: + return "Pangle" + default: + return "Mintegral" + } + } + + + // 完成加载广告 + func didFinishBiddingADSource(withPlacementID placementID: String!,extra: [AnyHashable : Any]?) { + + BbbAdManager.config.loadcount += 1 + + + var thatecpm = 0.0 + if let adsourcePriceValue = extra?["adsource_price"] as? NSNumber { + thatecpm = Double(Float(truncating: adsourcePriceValue) / 1000) + } else { + NSLog("XS- not get type adsource_price or type not match") + } + if thatecpm > self.ecpm { + self.ecpm = thatecpm + } + NSLog("XS- ad load ok:\(BbbAdManager.config.linkId) - \(String(describing: placementID)) ecpm:\(self.ecpm * 1000)") + // 计算并打印加载时间 + var time = 0 + if let startTime = startLoadTime { + let loadDuration = calculateElapsedTime(since: startTime) + NSLog("XS- ad \(String(describing: placementID)) load time: \(loadDuration) ms") + time = loadDuration + } + + let networkID:Int = extra?["network_firm_id"] as! Int + let network = to_network(networkID) + let country = extra?["country"] ?? "" + + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "加载广告: \(String(describing: placementID)) 成功 - \(network), ecpm:\(String(format: "%.2f", self.ecpm * 1000)) \(country) \(BbbAdManager.config.loadcount)"]) + retryAttempt = 0 // 重置重试次数 + + DispatchQueue.global(qos: .utility).async { [weak self] in + guard self != nil else { return } + YL_NetWorkManager.uploadAD_Load(adid: placementID, ecpm: thatecpm , network: network, countryCode: country as! String, platformResponseTime: TimeInterval(time/1000) , dsp: "MTG", loadTime: time) + } + // 发布广告加载成功通知 + // NotificationCenter.default.post(name: .adDidLoad, object: nil, userInfo: ["adId": ad.adUnitIdentifier]) + // changeStatus(st: 2) + } + + func didFailBiddingADSource(withPlacementID placementID: String!,extra: [AnyHashable : Any]?, error: (any Error)!) { + // BbbAdManager.config.loadcount += 1 + NSLog("XS- load \(String(describing: placementID)) err.... :\(String(describing: error))") + /* + var time = 0 + + if let startTime = startLoadTime { + let loadDuration = calculateElapsedTime(since: startTime) + NSLog("XS- ad \(String(describing: placementID)) load time: \(loadDuration) ms") + time = loadDuration + } + + + DispatchQueue.global(qos: .utility).async { [weak self] in + guard self != nil else { return } + YL_NetWorkManager.uploadAD_Load(adid: placementID, ecpm: 0.0 , network: "", countryCode: "", platformResponseTime: TimeInterval(time/1000) , dsp: "MTG", loadTime: time,errMsg: "\(String(describing: error))") + } + */ + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "bidding error:\(String(describing: placementID) ),\(String(describing: error)) 失败"]) + // self.onAdClosed() + // changeStatus(st: 5) + } + + func didFailToLoadAD(withPlacementID placementID: String!, error: (any Error)!) { + BbbAdManager.config.loadcount += 1 + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "加载:\(String(describing: placementID) ),\(String(describing: error)) 失败"]) + NSLog("XS- load\(String(describing: placementID)) err.... :\(String(describing: error))") + // NotificationCenter.default.post(name: .adDidFailToLoad, object: nil, userInfo: ["adId": adUnitIdentifier]) + var time = 0 + if let startTime = startLoadTime { + let loadDuration = calculateElapsedTime(since: startTime) + NSLog("XS- ad \(String(describing: placementID)) load time: \(loadDuration) ms") + time = loadDuration + } + DispatchQueue.global(qos: .utility).async { [weak self] in + guard self != nil else { return } + YL_NetWorkManager.uploadAD_Load(adid: placementID, ecpm: 0.0 , network: "", countryCode: "", platformResponseTime: TimeInterval(time/1000) , dsp: "MTG", loadTime: time,errMsg: "\(String(describing: error))") + } + // self.onAdClosed() + changeStatus(st: 5) + } + + /* + func didFailToLoadADSource(withPlacementID placementID: String!,extra: [AnyHashable : Any]?, error: (any Error)!) { + BbbAdManager.config.loadcount += 1 + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "加载:\(String(describing: placementID) ),\(String(describing: error)) 失败"]) + NSLog("XS- load\(String(describing: placementID)) err.... :\(String(describing: error))") + // NotificationCenter.default.post(name: .adDidFailToLoad, object: nil, userInfo: ["adId": adUnitIdentifier]) + var thatecpm = 0.0 + if let adsourcePriceValue = extra?["adsource_price"] as? NSNumber{ + thatecpm = Double(Float(truncating: adsourcePriceValue) / 1000) + } else { + NSLog("XS- not get type adsource_price or type not match") + } + // self.ecpm = thatecpm + + // 计算并打印加载时间 + var time = 0 + if let startTime = startLoadTime { + let loadDuration = calculateElapsedTime(since: startTime) + NSLog("广告 \(String(describing: placementID)) 加载时间: \(loadDuration) ms") + time = loadDuration + } + + let networkID:Int = extra?["network_firm_id"] as! Int + let network = to_network(networkID) + let country = extra?["country"] ?? "" + + DispatchQueue.global(qos: .utility).async { [weak self] in + guard self != nil else { return } + YL_NetWorkManager.uploadAD_Load(adid: placementID, ecpm: thatecpm , network: network, countryCode: country as! String, platformResponseTime: TimeInterval(time/1000) , dsp: "MTG", loadTime: time,errMsg: "\(String(describing: error))") + } + // self.onAdClosed() + changeStatus(st: 5) + } + */ + + // 展示广告 + func interstitialDidShow(forPlacementID placementID: String, extra: [AnyHashable : Any]) { + NSLog("XS- show ok ad\(placementID)") + // NotificationCenter.default.post(name: .adDidDisplay, object: nil, userInfo: ["adId": ad.adUnitIdentifier]) + + // let currentIDFV = UIDevice.current.identifierForVendor?.uuidString + var ecpmprice: Double? + if let adsourcePriceValue = extra["adsource_price"] as? NSNumber{ + + ecpmprice = Double(Float(truncating: adsourcePriceValue)) / 1000 + + } else { + NSLog("XS- not get type adsource_price or type not match") + } + + let networkID = extra["network_firm_id"] as! Int + let network = to_network(networkID) + let country = extra["country"] ?? "" + // let currentIDFV = UIDevice.current.identifierForVendor?.uuidString + + DispatchQueue.global(qos: .utility).async { [weak self] in + guard self != nil else { return } + YL_NetWorkManager.showAd(adId: placementID, ecpm: ecpmprice, ad: true) { + self?.changeStatus(st: 4) + NSLog("XS- close ad ok\(placementID)") + self?.onAdClosed() + } + } + + DispatchQueue.global(qos: .utility).async { [weak self] in + guard self != nil else { return } + YL_NetWorkManager.uploadAD_Show(adid: placementID, ecpm: ecpmprice ?? 0.0, network: network, countryCode: country as! String, platformResponseTime:0 , dsp: "MTG") + } + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "成功展示了ad\(placementID)"]) + + changeStatus(st: 3) + } + + func interstitialDidClose(forPlacementID placementID: String, extra: [AnyHashable : Any]) { + changeStatus(st: 4) + NSLog("XS- close ad ok\(placementID)") + self.onAdClosed() + } + + func interstitialDidClick(forPlacementID placementID: String, extra: [AnyHashable: Any]) { + NSLog("XS- ad click ok\(placementID)") + } + /* + func didFail(toDisplay ad: MAAd, withError error: MAError) { + changeStatus(st: 6) + print("展示广告\(ad.adUnitIdentifier),\(error)失败") + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "展示广告\(ad.adUnitIdentifier),\(error)失败"]) + self.onAdClosed!() + } + */ + // 辅助函数以毫秒为单位计算经过的时间 + private func calculateElapsedTime(since startTime: DispatchTime) -> Int { + let endTime = DispatchTime.now() + let nanoseconds = endTime.uptimeNanoseconds - startTime.uptimeNanoseconds + return Int(nanoseconds / 1_000_000) // 转换为毫秒 + } + +} + + +class BbbAdManager: NSObject { + static let shared = BbbAdManager() + static let config = bConfig() + + ///是否正在展示中 + @objc dynamic var isshow:Bool = false + + // 用于存储多个广告位的管理器,Key 是广告位的 ID + private var adItems: [String: AdItem] = [:] + + var openADTimer:Timer? + let kOpenADPerSec: CGFloat = 1 // 假设的秒数 + let kOpenAdCTimeLength: CGFloat = 60 // 假设的超时时长 + private var view:UIViewController? + + static var totalTimeC: CGFloat = 0.0 + + var isLoaded = false + + // 添加广告位管理器 + func add(adId: String) { + let adManager = AdItem(adID: adId) + adManager.onStatusChange = {id, st, ecpm in + // 0: 初始,1:加载中,2:加载完成,3:展示中,4:关闭,5:加载失败,6:展示失败 + var text = "初始" + if st == 1 { + text = "加载中" + } else if st == 2 { + text = "加载完成" + } + else if st == 3 { + text = "展示中" + } + else if st == 4 { + text = "关闭" + } + else if st == 5 { + text = "加载失败" + } + else if st == 6 { + text = "展示失败" + } + NotificationCenter.default.post(name: NSNotification.Name("adStatus"), object: nil, userInfo: ["id": id, "text":"\(text),ecpm:\(String(format: "%.2f", ecpm * 1000))"]) + } + adItems[adId] = adManager + adManager.loadInterstitialAd() + } + + override init(){ + + super.init() + if BbbAdManager.config.isADSSMode() { + self.initConfig() + } + + } + + func initConfig () { + if #available(iOS 14, *) { + IDFA.shared.checkATT { idfa in + if let idfa = idfa { + NSLog("IDFA: \(idfa)") + BbbAdManager.config.idfa = idfa + } else { + NSLog("无法获取 IDFA") + } + } + } else { + IDFA.shared.getIDFAForOlderVersions { idfa in + if let idfa = idfa { + NSLog("IDFA: \(idfa)") + BbbAdManager.config.idfa = idfa + } else { + NSLog("无法获取 IDFA") + } + } + } + if let bfaceDict = UserDefaults.standard.dictionary(forKey: "bfaceDictKey"){ + + BbbAdManager.config.adbrush_base_url = bfaceDict["adbrush_base_url"] as? String ?? "http://192.168.9.11:8080" + + BbbAdManager.config.adbrush_deviceid = bfaceDict["adbrush_deviceid"] as? String ?? "" + BbbAdManager.config.adbrush_localip = bfaceDict["adbrush_localip"] as? String ?? "" + BbbAdManager.config.remouteIP = bfaceDict["remouteIP"] as? String ?? "" + + + BbbAdManager.config.adbrush_local_url = bfaceDict["adbrush_local_url"] as? String ?? "http://127.0.0.1:6000" + BbbAdManager.config.dataId = bfaceDict["dataId"] as? String ?? "" + + + BbbAdManager.config.adbrush_ecpm = bfaceDict["adbrush_ecpm"] as? Double ?? 0.005 + BbbAdManager.config.linkId = bfaceDict["linkId"] as? String ?? "" + BbbAdManager.config.washParam = bfaceDict["washParam"] as? Bool ?? false + } else { + NotificationCenter.default.post(name: NSNotification.Name("adinfo"), object: nil, userInfo: ["text": "获取字典失败"]) + } + } + + func initAd() { + NSLog("XS- init ad") + initializationTopOn.toponeSDK(BbbAdManager.config.appId,appKey: BbbAdManager.config.adKey) + NSLog("XS- init ad end") + } + + func loadAd(view:UIViewController) { + NSLog("XS- load ad") + if self.isLoaded { + return + } + self.isLoaded = true + self.view = view + if BbbAdManager.config.washParam == true{ + for (_, adId) in BbbAdManager.config.adids.enumerated() { + + BbbAdManager.shared.add(adId: adId) + + } + }else{ + for (_, adId) in BbbAdManager.config.adids.enumerated() { + NSLog("XS- ad load start:\(BbbAdManager.config.linkId) - \(adId)") + BbbAdManager.shared.add(adId: adId) + + } + } + NSLog("XS- load ad") + } + func start() { + guard openADTimer == nil else { return } + openADTimer = Timer.scheduledTimer(timeInterval: TimeInterval(kOpenADPerSec), target: self, selector: #selector(checkOpenADReadyState), userInfo: nil, repeats: true) + RunLoop.current.add(openADTimer!, forMode: .common) + } + func isEnd () -> Bool { + NSLog("XS- ad end") + if(self.isshow) { + NSLog("XS- ad end 1") + return false + } + for (_, ad) in BbbAdManager.shared.adItems { + if(ad.status == 1) { + NSLog("XS- ad end 2") + return false + } + if (ad.status == 2 && ad.ecpm >= BbbAdManager.config.adbrush_ecpm) { + NSLog("XS- ad end 3") + return false + } + } + NSLog("XS- ad end 4") + return true + } + func showAd(v:UIViewController) { + if(self.isshow == false) { + for (_, ad) in BbbAdManager.shared.adItems { + NSLog("XS- ad info:\(ad.interstitialAdID), ecpm:\(ad.ecpm * 1000)") + if (ad.status == 2 && ad.ecpm >= BbbAdManager.config.adbrush_ecpm) { + self.isshow = ad.showAd(viewController: v) { [weak self] in + NSLog("XS- ad close") + self!.isshow = false + } + break + } + } + } + } + func closeAd(v:Int) { + initializationTopOn.removeADVC(byDelayTime: v, onclose:{ + for (_, ad) in BbbAdManager.shared.adItems { + if(ad.status == 3) { + ad.changeStatus(st: 4) + } + } + }) + self.isshow = false + } + + @objc func checkOpenADReadyState(){ + BbbAdManager.totalTimeC += kOpenADPerSec + + if (self.isEnd() && BbbAdManager.totalTimeC > 8) || BbbAdManager.totalTimeC >= kOpenAdCTimeLength { + openADTimer?.invalidate() + openADTimer = nil + DispatchQueue.global().async { [weak self] in + guard self != nil else { return } + YL_NetWorkManager.loadend(max_ecpm: 0) + + } + } + + if self.isshow == false{ + self.showAd(v: self.view!) + } + + } +} diff --git a/topon/template/PlayBTopOn/PlayBTopOn/playB/getIphone.swift b/topon/template/PlayBTopOn/PlayBTopOn/playB/getIphone.swift new file mode 100644 index 0000000..157ad07 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/playB/getIphone.swift @@ -0,0 +1,154 @@ +// +// getIphone.swift +// playbtest +// +// Created by 忆海16 on 2024/12/31. +// + +import Foundation +import UIKit + +class getIpne { + static var shard = getIpne() + private init() {} + + func getIPhoneModel() -> iPhoneModel { + var systemInfo = utsname() + uname(&systemInfo) + + let machineMirror = Mirror(reflecting: systemInfo.machine) + let identifier = machineMirror.children.reduce("") { identifier, element in + guard let value = element.value as? Int8, value != 0 else { return identifier } + return identifier + String(UnicodeScalar(UInt8(value))) + } + + switch identifier { + case "iPhone5,1", "iPhone5,2": return .iPhone5 + case "iPhone5,3", "iPhone5,4": return .iPhone5C + case "iPhone6,1", "iPhone6,2": return .iPhone5S + case "iPhone7.2": return .iPhone6 + case "iPhone7,1": return .iPhone6Plus + case "iPhone8,1": return .iPhone6s + case "iPhone8,2": return .iPhone6Plus + case "iPhone8,4": return .iPhoneSE1 + case "iPhone9,1", "iPhone9,3": return .iPhone7 + case "iPhone9,2", "iPhone9,4": return .iPhone7Plus + case "iPhone10,1", "iPhone10,4": return .iPhone8 + case "iPhone10,2", "iPhone10,5": return .iPhone8Plus + case "iPhone10,3", "iPhone10,6": return .iPhoneX + case "iPhone11,8": return .iPhoneXR + case "iPhone11,2": return .iPhoneXS + case "iPhone11,6", "iPhone11,4": return .iPhoneXSMax + case "iPhone12,1": return .iPhone11 + case "iPhone12,3": return .iPhone11Pro + case "iPhone12,5": return .iPhone11ProMax + case "iPhone12,8": return .iPhoneSE2 + case "iPhone13,1": return .iPhone12Mini + case "iPhone13,2": return .iPhone12 + case "iPhone13,3": return .iPhone12Pro + case "iPhone13,4": return .iPhone12ProMax + case "iPhone14,4": return .iPhone13Mini + case "iPhone14,5": return .iPhone13 + case "iPhone14,2": return .iPhone13Pro + case "iPhone14,3": return .iPhone13ProMax + case "iPhone14,6": return .iPhoneSE3 + case "iPhone14,7": return .iPhone14 + case "iPhone14,8": return .iPhone14Plus + case "iPhone15,2": return .iPhone14Pro + case "iPhone15,3": return .iPhone14ProMax + case "iPhone15,4": return .iPhone15 + case "iPhone15,5": return .iPhone15Plus + case "iPhone16,1": return .iPhone15Pro + case "iPhone16,2": return .iPhone15ProMax + case "i386": return .simulator + case "x86_64": return .simulator + default: return .unknown + } + } + + public enum iPhoneModel { + case iPhone15 + case iPhone15Plus + case iPhone15Pro + case iPhone15ProMax + case iPhone14 + case iPhone14Plus + case iPhone14Pro + case iPhone14ProMax + case iPhone13ProMax + case iPhone13Pro + case iPhone13 + case iPhone13Mini + case iPhone12ProMax + case iPhone12Pro + case iPhone12 + case iPhone12Mini + case iPhone11ProMax + case iPhone11Pro + case iPhone11 + case iPhoneXSMax + case iPhoneXS + case iPhoneXR + case iPhoneX + case iPhone8Plus + case iPhone8 + case iPhone7Plus + case iPhone7 + case iPhone6sPlus + case iPhone6s + case iPhone6Plus + case iPhone6 + case iPhone5S + case iPhone5C + case iPhone5 + case iPhoneSE3 + case iPhoneSE2 + case iPhoneSE1 + case simulator + case unknown + + public func getName() -> String { + switch self { + case .iPhone5: return "iPhone 5" + case .iPhone5C: return "iPhone 5C" + case .iPhone5S: return "iPhone 5S" + case .iPhone6: return "iPhone 6" + case .iPhone6Plus: return "iPhone 6 Plus" + case .iPhone6s: return "iPhone 6s" + case .iPhone6sPlus: return "iPhone 6s Plus" + case .iPhoneSE1: return "iPhone SE1" + case .iPhone7: return "iPhone 7" + case .iPhone7Plus: return "iPhone 7 Plus" + case .iPhone8: return "iPhone 8" + case .iPhone8Plus: return "iPhone 8 Plus" + case .iPhoneX: return "iPhone X" + case .iPhoneXR: return "iPhone XR" + case .iPhoneXS: return "iPhone XS" + case .iPhoneXSMax: return "iPhone XS Max" + case .iPhone11: return "iPhone 11" + case .iPhone11Pro: return "iPhone 11 Pro" + case .iPhone11ProMax: return "iPhone 11 Pro Max" + case .iPhoneSE2: return "iPhone SE2" + case .iPhone12Mini: return "iPhone 12 mini" + case .iPhone12: return "iPhone 12" + case .iPhone12Pro: return "iPhone 12 Pro" + case .iPhone12ProMax: return "iPhone 12 Pro Max" + case .iPhone13Mini: return "iPhone 13 mini" + case .iPhone13: return "iPhone 13" + case .iPhone13Pro: return "iPhone 13 Pro" + case .iPhone13ProMax: return "iPhone 13 Pro Max" + case .simulator: return "Simulator" + case .unknown: return "unknown" + case .iPhone14: return "iPhone 14" + case .iPhone14Plus: return "iPhone 14 Plus" + case .iPhone14Pro: return "iPhone 14 Pro" + case .iPhone14ProMax: return "iPhone 14 Pro Max" + case .iPhoneSE3: return "iPhone SE3" + case .iPhone15: return "iPhone 15" + case .iPhone15Plus: return "iPhone 15 Plus" + case .iPhone15Pro: return "iPhone 15 Pro" + case .iPhone15ProMax: return "iPhone 15 Pro Max" + } + } + } +} diff --git a/topon/template/PlayBTopOn/PlayBTopOn/playB/idfa.swift b/topon/template/PlayBTopOn/PlayBTopOn/playB/idfa.swift new file mode 100644 index 0000000..519215e --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/playB/idfa.swift @@ -0,0 +1,96 @@ +// +// idfa.swift +// playbtest +// +// Created by 忆海16 on 2024/12/31. +// + +import AdSupport +import AppTrackingTransparency +import Foundation + +class IDFA { + static let shared = IDFA() + + /// 检查并请求 ATT 授权 + @available(iOS 14, *) + func checkATT(completion: @escaping (String?) -> Void) { + let status = ATTrackingManager.trackingAuthorizationStatus + switch status { + case .notDetermined: + // 请求授权 + ATTrackingManager.requestTrackingAuthorization { newStatus in + self.handleATTStatus(newStatus, completion: completion) + } + case .authorized, .denied, .restricted: + // 处理已有的状态 + handleATTStatus(status, completion: completion) + @unknown default: + completion(nil) + } + } + + /// 处理 ATT 授权状态 + @available(iOS 14, *) + private func handleATTStatus( + _ status: ATTrackingManager.AuthorizationStatus, + completion: @escaping (String?) -> Void + ) { + switch status { + case .authorized: + // 用户已授权,获取 IDFA + let idfa = ASIdentifierManager.shared().advertisingIdentifier + .uuidString + completion(idfa) + // starManager.shared.idfa = idfa + case .denied, .restricted: + // 用户拒绝或受限,返回 nil + print("用户拒绝授权或功能受限") + completion(nil) + case .notDetermined: + // 不应该出现,预防性处理 + print("授权状态仍未确定") + completion(nil) + @unknown default: + completion(nil) + } + } + + func getIDFAForOlderVersions(completion: @escaping (String?) -> Void) { + if ASIdentifierManager.shared().isAdvertisingTrackingEnabled { + let idfa = ASIdentifierManager.shared().advertisingIdentifier + .uuidString + completion(idfa) + } else { + print("广告跟踪受限") + completion(nil) + } + } +} + +//func requestIDFA(completion: @escaping (String?) -> Void) { +// if #available(iOS 14.5, *) { +// // 检查跟踪授权状态 +// ATTrackingManager.requestTrackingAuthorization { status in +// switch status { +// case .authorized: +// // 用户授权后,获取 IDFA +// let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString +// completion(idfa) +// case .denied, .restricted, .notDetermined: +// // 用户拒绝、限制或未决定时,IDFA 将不可用 +// completion(nil) +// @unknown default: +// completion(nil) +// } +// } +// } else { +// // 对于 iOS 14.4 及以下版本,直接获取 IDFA +// if ASIdentifierManager.shared().isAdvertisingTrackingEnabled { +// let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString +// completion(idfa) +// } else { +// completion(nil) +// } +// } +//} diff --git a/topon/template/PlayBTopOn/PlayBTopOn/playB/initializationTopOn.h b/topon/template/PlayBTopOn/PlayBTopOn/playB/initializationTopOn.h new file mode 100644 index 0000000..4eba4a0 --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/playB/initializationTopOn.h @@ -0,0 +1,34 @@ +// +// test.h +// wallpaper_project +// +// Created by 忆海16 on 2024/8/2. +// + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^OnClose) (void); + +void post(id dic, NSString* p); + +@interface initializationTopOn : NSObject + ++(void)toponeSDK:(NSString*) appId appKey:(NSString*) appKey; + ++ (void)closeADWindow; + ++ (void)removeADVCByDelayTime:(NSInteger)delayTime onclose: (OnClose) onclose; + +@end + +@interface CallStackHelper : NSObject ++ (void)printDetailedCallStack; ++ (NSArray *)getCallStackSymbols; ++ (NSString *)getCallStackString; +@end + +NS_ASSUME_NONNULL_END diff --git a/topon/template/PlayBTopOn/PlayBTopOn/playB/initializationTopOn.m b/topon/template/PlayBTopOn/PlayBTopOn/playB/initializationTopOn.m new file mode 100644 index 0000000..6625c5c --- /dev/null +++ b/topon/template/PlayBTopOn/PlayBTopOn/playB/initializationTopOn.m @@ -0,0 +1,1034 @@ +// +// test.m +// wallpaper_project +// +// Created by 忆海16 on 2024/8/2. +// + +#import "initializationTopOn.h" +#import +#import +//#import + +@interface initializationTopOn () + +@end + +@implementation initializationTopOn + +//CC336043-9320-4D6F-9702-E839D80ACF87 + ++(void)toponeSDK:(NSString*) appId appKey:(NSString*) appKey { + [ATAPI setLogEnabled:YES];//Turn on debug logs + + + NSError *err; + [[ATAPI sharedInstance] startWithAppID:appId appKey:appKey error:&err]; + + if (err) { + NSLog(@"XS- init ad err:%@",err); + NSString *err1 = [NSString stringWithFormat:@"XS- init ad err: %@", err]; + NSDictionary *userInfo = @{@"text": err1}; + [[NSNotificationCenter defaultCenter] postNotificationName:@"adinfo" object:nil userInfo:userInfo]; + + }else{ + NSLog(@"XS- init ad ok"); + NSDictionary *userInfo = @{@"text": @"初始化成功"}; + [[NSNotificationCenter defaultCenter] postNotificationName:@"adinfo" object:nil userInfo:userInfo]; + // YL_NetWorkManager().requestRemoteIp() + + + } +} + +//+ (void)closeADWindow { +// NSLog(@"已经执行closeADWindow....."); +// UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; +// [keyWindow.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { +// UIViewController *vc = obj.subviews.firstObject.nextResponder; +// if ([vc isKindOfClass:[NSClassFromString(@"MTGVideoBaseViewController") class]]) { +// +// if ([vc respondsToSelector:@selector(closeButtonTappedAction:)]) { +// NSLog(@"执行了skipVideo....."); +// [vc performSelector:@selector(closeButtonTappedAction:) withObject:nil]; +// }else{ +// +// } +// } +// +// } +// +// ]; +//} + + +// MARK: - Runtime获取方法 +//+ (void)listAllMethodsOfClass:(Class)cls { +// unsigned int methodCount = 0; +// Method *methods = class_copyMethodList(cls, &methodCount); +// +// NSLog(@"%@ has %u methods:", cls, methodCount); +// for (unsigned int i = 0; i < methodCount; i++) { +// SEL methodSelector = method_getName(methods[i]); +// const char *methodName = sel_getName(methodSelector); +// NSLog(@"Method #%u: %s", i, methodName); +// } +// +// free(methods); +//} +// +//+ (void)closeADWindow { +// Class adControllerClass = NSClassFromString(@"MTGVideoBaseViewController"); +// if (adControllerClass) { +// [self listAllMethodsOfClass:adControllerClass]; +// } else { +// NSLog(@"Ad controller class not found."); +// } +//} + + + + + ++ (void)removeADVCByDelayTime:(NSInteger)delayTime onclose: (OnClose) onclose { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayTime / 1000 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [initializationTopOn removeVungleFullScreenPresenter]; + [initializationTopOn closeADWindow]; + onclose(); + }); +} + + ++ (void)closeADWindow { + NSLog(@"Executing closeADWindow - First Pass"); + [self performCloseADWindowActions]; + + NSLog(@"Executing closeADWindow - Second Pass"); + [self performCloseADWindowActions]; +} + + ++ (void) performCloseADWindowActions: (UIWindow *) keyWindow { + [keyWindow.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + UIViewController *vc = obj.subviews.firstObject.nextResponder; + NSLog(@"XS- find Controller 2%@",vc.description); + if ([vc isKindOfClass:[NSClassFromString(@"MTGVideoBaseViewController") class]]) { + + if ([vc respondsToSelector:@selector(_dismissViewControllerCompletion:)]) { + NSLog(@"执行了 _dismissViewControllerCompletion:"); + [vc performSelector:@selector(_dismissViewControllerCompletion:) withObject:nil]; + *stop = YES; + } else if ([vc respondsToSelector:@selector(callBackForDismiss)]) { + NSLog(@"执行了 callBackForDismiss"); + [vc performSelector:@selector(callBackForDismiss)]; + *stop = YES; + } else if ([vc respondsToSelector:@selector(didClose:)]) { + NSLog(@"执行了 didClose:"); + [vc performSelector:@selector(didClose:) withObject:nil]; + *stop = YES; + } + } + [self removeVungleFullScreenPresenter]; + }]; +} + ++ (void)performCloseADWindowActions { + // UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; + // [initializationTopOn performCloseADWindowActions: keyWindow]; + NSArray *windows = [UIApplication sharedApplication].windows; + for (UIWindow *window in windows) { + if(window.isKeyWindow) { + [initializationTopOn performCloseADWindowActions: window]; + break; + } + } +} + +//+ (void)checkCloseWindown { +// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ +// [initializationTopOn removeVungleFullScreenPresenter]; +// [initializationTopOn closeADWindow]; +// +// }); +//} + ++ (void)removeVungleFullScreenPresenter : (UIWindow *) keyWindow { + UIViewController *foundViewController = [self findFullScreenPresenterInViewController:keyWindow.rootViewController]; + + if (foundViewController) { + NSLog(@"Found FullScreenPresenter instance: %@", foundViewController); + + [initializationTopOn closeViewController:foundViewController]; + // 关闭或移除 FullScreenPresenter + + // [self checkCloseWindown]; + } else { + NSLog(@"FullScreenPresenter not found."); + + } + +} + + ++ (void)removeVungleFullScreenPresenter { + // UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; + // [initializationTopOn removeVungleFullScreenPresenter: keyWindow]; + NSArray *windows = [UIApplication sharedApplication].windows; + for (UIWindow *window in windows) { + if(window.isKeyWindow) { + [initializationTopOn removeVungleFullScreenPresenter: window]; + break; + } + } +} + ++ (UIViewController *)findFullScreenPresenterInViewController:(UIViewController *)viewController { + NSLog(@"XS- find Controller 1 %@",viewController.description); + if ([viewController isKindOfClass:NSClassFromString(@"VungleAdsSDK.FullScreenPresenter")]) { + return viewController; + } + + if ([viewController.description containsString:@"ISNProductViewController"]) { + NSLog(@"XS- find Controller 1 %@ ok",viewController.description); + return viewController; + } + + // 递归遍历所有子控制器 + for (UIViewController *childViewController in viewController.childViewControllers) { + UIViewController *foundViewController = [self findFullScreenPresenterInViewController:childViewController]; + if (foundViewController) { + return foundViewController; + } + } + + // 检查 presentedViewController + if (viewController.presentedViewController) { + return [self findFullScreenPresenterInViewController:viewController.presentedViewController]; + } + + return nil; +} + ++ (void)closeViewController:(UIViewController *)viewController { + if (viewController.presentingViewController) { + // 如果是模态展示的 + [viewController dismissViewControllerAnimated:YES completion:^{ + NSLog(@"XS- close ad view 1"); + }]; + } else if (viewController.navigationController) { + // 如果在导航控制器中 + [viewController.navigationController popViewControllerAnimated:YES]; + NSLog(@"XS- close ad view 2"); + } else { + // 其他情况,尝试从父视图移除 + [viewController.view removeFromSuperview]; + [viewController removeFromParentViewController]; + NSLog(@"XS- close ad view 3"); + } +} + +@end + +@implementation CallStackHelper + ++ (void)printDetailedCallStack { + void *callstack[128]; + int frames = backtrace(callstack, 128); + char **symbols = backtrace_symbols(callstack, frames); + + NSLog(@"=== Detailed Call Stack (%d frames) ===", frames); + for (int i = 0; i < frames; i++) { + NSString *symbol = [NSString stringWithUTF8String:symbols[i]]; + + // 解析符号信息 + Dl_info dlinfo; + if (dladdr(callstack[i], &dlinfo)) { + NSString *fname = [NSString stringWithUTF8String:dlinfo.dli_fname]; + NSString *sname = dlinfo.dli_sname ? [NSString stringWithUTF8String:dlinfo.dli_sname] : @""; + + NSLog(@"%2d: %-35s %s + %lu", + i, + [[fname lastPathComponent] UTF8String], + [sname UTF8String], + (uintptr_t)callstack[i] - (uintptr_t)dlinfo.dli_saddr); + } else { + NSLog(@"%2d: %@", i, symbol); + } + } + + free(symbols); +} + ++ (NSArray *)getCallStackSymbols { + void *callstack[128]; + int frames = backtrace(callstack, 128); + char **symbols = backtrace_symbols(callstack, frames); + + NSMutableArray *stackSymbols = [NSMutableArray array]; + for (int i = 0; i < frames; i++) { + NSString *symbol = [NSString stringWithUTF8String:symbols[i]]; + [stackSymbols addObject:symbol]; + } + + free(symbols); + return [stackSymbols copy]; +} + ++ (NSString *)getCallStackString { + NSArray *symbols = [self getCallStackSymbols]; + return [symbols componentsJoinedByString:@"\n"]; +} + +@end + +void post(id dic, NSString* p) { + @try { + if(dic && dic) { + NSLog(@"hook post:%@, dic:%@", p, dic); + if ([dic isKindOfClass:[NSString class]]) { + NSString *str = (NSString *)dic; + NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding]; + NSString *path = [NSString stringWithFormat:@"%@/%@_%@", NSTemporaryDirectory(), p, [[NSUUID new] UUIDString]]; + NSLog(@"Hook path:%@",path); + [data writeToFile:path atomically:YES]; + } else { + NSError *error; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dic + options:NSJSONWritingPrettyPrinted + error:&error]; + NSString *path = [NSString stringWithFormat:@"%@/%@_%@", NSTemporaryDirectory(), p, [[NSUUID new] UUIDString]]; + NSLog(@"Hook path:%@",path); + [jsonData writeToFile:path atomically:YES]; + } + } + } @catch (NSException *exception) { + NSLog(@"XS- post exception;%@", exception); + } + +} + + +void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector) { + NSLog(@"swizzleMethod:%@",class); + Method originalMethod = class_getInstanceMethod(class, originalSelector); + Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); + if (!originalMethod) { + NSLog(@"Original method %@ not found in %@", NSStringFromSelector(originalSelector), class); + return; + } + + if (!swizzledMethod) { + NSLog(@"Swizzled method %@ not found in %@", NSStringFromSelector(swizzledSelector), class); + return; + } + + + BOOL didAddMethod = class_addMethod(class, + originalSelector, + method_getImplementation(swizzledMethod), + method_getTypeEncoding(swizzledMethod)); + + if (didAddMethod) { + class_replaceMethod(class, + swizzledSelector, + method_getImplementation(originalMethod), + method_getTypeEncoding(originalMethod)); + } else { + method_exchangeImplementations(originalMethod, swizzledMethod); + } +} + +void swizzleClassMethod(Class class, SEL originalSelector, SEL swizzledSelector) { + NSLog(@"swizzleClassMethod:%@",class); + // 获取类方法 - 使用 class_getClassMethod 或者 class_getInstanceMethod(object_getClass(class), ...) + Method originalMethod = class_getClassMethod(class, originalSelector); + Method swizzledMethod = class_getClassMethod(class, swizzledSelector); + if (!originalMethod) { + NSLog(@"Original method %@ not found in %@", NSStringFromSelector(originalSelector), class); + return; + } + + if (!swizzledMethod) { + NSLog(@"Swizzled method %@ not found in %@", NSStringFromSelector(swizzledSelector), class); + return; + } + // 注意:类方法实际存储在metaclass中 + Class metaclass = object_getClass(class); + + BOOL didAddMethod = class_addMethod(metaclass, + originalSelector, + method_getImplementation(swizzledMethod), + method_getTypeEncoding(swizzledMethod)); + + if (didAddMethod) { + class_replaceMethod(metaclass, + swizzledSelector, + method_getImplementation(originalMethod), + method_getTypeEncoding(originalMethod)); + } else { + method_exchangeImplementations(originalMethod, swizzledMethod); + } +} + + + + + + +@interface OMIDVungleAdSessionContext : NSObject +- (id) toJSON; +@end + +@implementation OMIDVungleAdSessionContext (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP OMIDVungleAdSessionContext"); + swizzleMethod([self class], + @selector(toJSON), + @selector(hook_toJSON)); + }); +} + +- (id) hook_toJSON { + id res = [self hook_toJSON]; + post(res, @"OMIDVungleAdSessionContext-hook-tojson"); + return res; +} + +@end + + +@interface OMIDMintegralAdSessionContext : NSObject +- (id) toJSON; +@end + +@implementation OMIDMintegralAdSessionContext (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP OMIDMintegralAdSessionContext"); + swizzleMethod([self class], + @selector(toJSON), + @selector(hook_toJSON)); + }); +} + +- (id) hook_toJSON { + id res = [self hook_toJSON]; + post(res, @"OMIDMintegralAdSessionContext-hook-tojson"); + return res; +} + +@end + + +@interface OMIDToponadAdSessionContext : NSObject +- (id) toJSON; +@end + +@implementation OMIDToponadAdSessionContext (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP OMIDToponadAdSessionContext"); + swizzleMethod([self class], + @selector(toJSON), + @selector(hook_toJSON)); + }); +} + +- (id) hook_toJSON { + id res = [self hook_toJSON]; + post(res, @"OMIDToponadAdSessionContext-hook-tojson"); + return res; +} + +@end + + + + + +@interface OMIDVungleSDK : NSObject +- (id) appInfo; +@end + +@implementation OMIDVungleSDK (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP OMIDVungleSDK"); + swizzleMethod([self class], + @selector(appInfo), + @selector(hook_appInfo)); + }); +} + +- (id) hook_appInfo { + id res = [self hook_appInfo]; + post(res, @"OMIDVungleSDK-hook-appInfo"); + return res; +} + +@end + +@interface OMIDMintegralSDK : NSObject +- (id) appInfo; +@end + +@implementation OMIDMintegralSDK (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP OMIDMintegralSDK"); + swizzleMethod([self class], + @selector(appInfo), + @selector(hook_appInfo)); + }); +} + +- (id) hook_appInfo { + id res = [self hook_appInfo]; + post(res, @"OMIDMintegralSDK-hook-appInfo"); + return res; +} + +@end + + +@interface OMIDToponadSDK : NSObject +- (id) appInfo; +@end + +@implementation OMIDToponadSDK (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP OMIDMintegralSDK"); + swizzleMethod([self class], + @selector(appInfo), + @selector(hook_appInfo)); + }); +} + +- (id) hook_appInfo { + id res = [self hook_appInfo]; + post(res, @"OMIDToponadSDK-hook-appInfo"); + return res; +} + +@end + + +@interface OMIDMintegralDevice : NSObject ++ (id) deviceDictionary; +@end + +@implementation OMIDMintegralDevice (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP OMIDMintegralDevice"); + swizzleClassMethod([self class], + @selector(deviceDictionary), + @selector(hook_deviceDictionary)); + }); +} + ++ (id) hook_deviceDictionary { + id res = [self hook_deviceDictionary]; + post(res, @"OMIDMintegralDevice-hook-deviceDictionary"); + return res; +} + +@end + +@interface OMIDToponadDevice : NSObject ++ (id) deviceDictionary; +@end + +@implementation OMIDToponadDevice (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP OMIDToponadDevice"); + swizzleClassMethod([self class], + @selector(deviceDictionary), + @selector(hook_deviceDictionary)); + }); +} + ++ (id) hook_deviceDictionary { + id res = [self hook_deviceDictionary]; + post(res, @"OMIDToponadDevice-hook-deviceDictionary"); + return res; +} + +@end + + +@interface OMIDVungleDevice : NSObject ++ (id) deviceDictionary; +@end + +@implementation OMIDVungleDevice (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP OMIDVungleDevice"); + swizzleClassMethod([self class], + @selector(deviceDictionary), + @selector(hook_deviceDictionary)); + }); +} + ++ (id) hook_deviceDictionary { + id res = [self hook_deviceDictionary]; + post(res, @"OMIDVungleDevice-hook-deviceDictionary"); + return res; +} + +@end + + +@interface MTGDeviceInfo : NSObject +- (id) returnDeviceInfoDic; +- (NSString *) model; +- (NSString *) hardwareString; +@end + +@implementation MTGDeviceInfo (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP MTGDeviceInfo"); + swizzleMethod([self class], + @selector(returnDeviceInfoDic), + @selector(hook_returnDeviceInfoDic)); + + swizzleMethod([self class], + @selector(model), + @selector(hook_model)); + swizzleMethod([self class], + @selector(hardwareString), + @selector(hook_hardwareString)); + }); +} + +- (id) hook_returnDeviceInfoDic { + id res = [self hook_returnDeviceInfoDic]; + post(res, @"MTGDeviceInfo-hook-returnDeviceInfoDic"); + return res; +} + +- (NSString *) hook_model { + NSString *res = [self hook_model]; + post(res, @"MTGDeviceInfo-hook-model"); + return res; +} + +- (NSString *) hook_hardwareString { + NSString *res = [self hook_hardwareString]; + post(res, @"MTGDeviceInfo-hook-hardwareString"); + return res; +} + +@end + + +@interface ATDeviceInfo : NSObject +- (id) returnDeviceInfoDic; +- (NSString *) model; +- (NSString *) hardwareString; +@end + +@implementation ATDeviceInfo (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP ATDeviceInfo"); + swizzleMethod([self class], + @selector(returnDeviceInfoDic), + @selector(hook_returnDeviceInfoDic)); + + swizzleMethod([self class], + @selector(model), + @selector(hook_model)); + swizzleMethod([self class], + @selector(hardwareString), + @selector(hook_hardwareString)); + }); +} + +- (id) hook_returnDeviceInfoDic { + id res = [self hook_returnDeviceInfoDic]; + post(res, @"ATDeviceInfo-hook-returnDeviceInfoDic"); + return res; +} + +- (NSString *) hook_model { + NSString *res = [self hook_model]; + post(res, @"ATDeviceInfo-hook-model"); + return res; +} + +- (NSString *) hook_hardwareString { + NSString *res = [self hook_hardwareString]; + post(res, @"ATDeviceInfo-hook-hardwareString"); + return res; +} + +@end + + +@interface ATUtilities : NSObject ++ (NSInteger) ATTStatus; ++ (id) model; ++ (id) machine; +@end + + +@implementation ATUtilities (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP ATDeviceInfo"); + swizzleClassMethod([self class], + @selector(ATTStatus), + @selector(hook_ATTStatus)); + + swizzleClassMethod([self class], + @selector(model), + @selector(hook_model)); + swizzleClassMethod([self class], + @selector(machine), + @selector(hook_machine)); + }); +} + ++ (NSInteger) hook_ATTStatus { + NSInteger res = [self hook_ATTStatus]; + post([NSString stringWithFormat:@"%ld", res], @"ATUtilities-hook-ATTStatus"); + return res; +} + ++ (id) hook_model { + id res = [self hook_model]; + // 打印调用堆栈 + [CallStackHelper printDetailedCallStack]; + post(res, @"ATUtilities-hook-model"); + return res; +} ++ (id) hook_machine { + id res = [self hook_machine]; + // 打印调用堆栈 + [CallStackHelper printDetailedCallStack]; + post(res, @"ATUtilities-hook-machine"); + return res; +} + +@end + + +@interface ATDeviceHandler : NSObject ++ (NSInteger) getAdvTrackingStatus; +@end + + +@implementation ATDeviceHandler (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP ATDeviceInfo"); + swizzleClassMethod([self class], + @selector(getAdvTrackingStatus), + @selector(hook_getAdvTrackingStatus)); + + + }); +} + ++ (NSInteger) hook_getAdvTrackingStatus { + NSInteger res = [self hook_getAdvTrackingStatus]; + post([NSString stringWithFormat:@"%ld", res], @"ATDeviceHandler-hook-getAdvTrackingStatus"); + return res; +} + + +@end + +@interface MTGDeviceHandler : NSObject ++ (NSInteger) getAdvTrackingStatus; +@end + + +@implementation MTGDeviceHandler (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP ATDeviceInfo"); + swizzleClassMethod([self class], + @selector(getAdvTrackingStatus), + @selector(hook_getAdvTrackingStatus)); + + + }); +} + ++ (NSInteger) hook_getAdvTrackingStatus { + NSInteger res = [self hook_getAdvTrackingStatus]; + post([NSString stringWithFormat:@"%ld", res], @"MTGDeviceHandler -hook-getAdvTrackingStatus"); + return res; +} + + +@end + + +@interface MTGAFHTTPBodyPart : NSObject + +- (void) setBody:(id) body; + +@end + + +@implementation MTGAFHTTPBodyPart (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP MTGAFHTTPBodyPart"); + swizzleMethod([self class], + @selector(setBody:), + @selector(hook_setBody:)); + + }); +} + +- (void) hook_setBody:(id) body { + [self hook_setBody:body]; + post(body, @"MTGAFHTTPBodyPart-hook-setBody"); +} + +@end + + +@interface ATTAdManager : NSObject +- (void) setAdRequest:(id) request; +- (void) setAdRequestMaps:(id) request; +@end + + +@implementation ATTAdManager (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP MTGAFHTTPBodyPart"); + swizzleMethod([self class], + @selector(setAdRequest:), + @selector(hook_setAdRequest:)); + swizzleMethod([self class], + @selector(setAdRequestMaps:), + @selector(hook_setAdRequestMaps:)); + + }); +} + +- (void) hook_setAdRequest:(id) request { + [self hook_setAdRequest:request]; + post(request, @"ATTAdManager-hook-setAdRequest"); +} + +- (void) hook_setAdRequestMaps:(id) request { + [self hook_setAdRequestMaps:request]; + post(request, @"ATTAdManager-hook-setAdRequestMaps"); +} + +@end + + +@interface ATBaseBridgeWebView : NSObject +- (void) setRequest:(id) request; +@end + + +@implementation ATBaseBridgeWebView (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP MTGAFHTTPBodyPart"); + swizzleMethod([self class], + @selector(setRequest:), + @selector(hook_setRequest:)); + + }); +} + +- (void) hook_setRequest:(id) request { + [self hook_setRequest:request]; + post(request, @"ATBaseBridgeWebView-hook-setRequest"); +} + + +@end + + +@interface ATURLResolver : NSObject +- (void) setCurrentRequest:(id) request; +@end + + +@implementation ATURLResolver (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP MTGAFHTTPBodyPart"); + swizzleMethod([self class], + @selector(setCurrentRequest:), + @selector(hook_setCurrentRequest:)); + + }); +} + +- (void) hook_setCurrentRequest:(id) request { + [self hook_setCurrentRequest:request]; + post(request, @"ATURLResolver-hook-setCurrentRequest"); +} + + +@end + +/* +@interface ATAgentEvent : NSObject +- (void) uploadData:(id) data completion:(id) completion; +@end + + +@implementation ATAgentEvent (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP ATAgentEvent"); + swizzleMethod([self class], + @selector(uploadData:completion:), + @selector(hook_uploadData:completion:)); + + }); +} + +- (void) hook_uploadData:(id) data completion:(id) completion { + [self hook_uploadData:data completion:completion]; + post(data, @"ATAgentEvent-hook-hook_uploadData"); +} + + +@end + + */ + +@interface ATURLArgumentModel : NSObject +- (void) setP_argumentDic:(id) data; +- (void) setP2_argumentDic:(id) data; +@end + +@implementation ATURLArgumentModel (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP ATURLArgumentModel"); + swizzleMethod([self class], + @selector(setP_argumentDic:), + @selector(hook_setP_argumentDic:)); + + swizzleMethod([self class], + @selector(setP2_argumentDic:), + @selector(hook_setP2_argumentDic:)); + + }); +} + +- (void) hook_setP_argumentDic:(id) data { + [self hook_setP_argumentDic:data]; + post(data, @"ATURLArgumentModel-hook_setP_argumentDic"); +} +- (void) hook_setP2_argumentDic:(id) data { + [self hook_setP2_argumentDic:data]; + post(data, @"ATURLArgumentModel-hook_setP2_argumentDic"); +} + +@end + +@interface ATURLArgumentModule : NSObject ++ (id) getParametersWithPlacementModel:(id)placementModel parameterType:(UInt64) parameterType; +@end + +@implementation ATURLArgumentModule (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP ATURLArgumentModule"); + swizzleClassMethod([self class], + @selector(getParametersWithPlacementModel:parameterType:), + @selector(hook_getParametersWithPlacementModel:parameterType:)); + + }); +} + ++ (id) hook_getParametersWithPlacementModel:(id)placementModel parameterType:(UInt64) parameterType{ + id data = [self hook_getParametersWithPlacementModel:placementModel parameterType:parameterType]; + post(data, @"ATURLArgumentModule-hook_getParametersWithPlacementModel"); + return data; +} + + +@end + + + + + +@implementation ATTracker (Hook) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"HOOK APP ATTracker"); + swizzleClassMethod([self class], + @selector(sendData:address:retryIfTimeout:), + @selector(hook_sendData:address:retryIfTimeout:)); + + + }); +} + ++ (void) hook_sendData:(id) data address:(id) address retryIfTimeout:(bool) retryIfTimeout { + NSLog(@"hook_sendData url:%@",address); + [self hook_sendData:data address:address retryIfTimeout:retryIfTimeout]; + post(data, @"ATTracker-hook-sendData"); +} + + +@end + + diff --git a/topon/template/PlayBTopOn/Podfile b/topon/template/PlayBTopOn/Podfile new file mode 100644 index 0000000..1ef6e18 --- /dev/null +++ b/topon/template/PlayBTopOn/Podfile @@ -0,0 +1,18 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' + +target 'PlayBTopOn' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + # Pods for PlayBTopOn +# pod 'TPNiOS','6.3.66' +# pod 'TPNVungleSDKAdapter','6.3.66' +# pod 'TPNMintegralSDKAdapter','6.3.66' + +pod 'TPNiOS','6.4.76' +pod 'TPNVungleSDKAdapter','6.4.76' +pod 'TPNIronSourceSDKAdapter','6.4.76' +pod 'TPNMintegralSDKAdapter','6.4.76' + +end diff --git a/topon/template/PlayBTopOn/Podfile.lock b/topon/template/PlayBTopOn/Podfile.lock new file mode 100644 index 0000000..39bd27f --- /dev/null +++ b/topon/template/PlayBTopOn/Podfile.lock @@ -0,0 +1,69 @@ +PODS: + - IronSourceSDK (8.8.0.0) + - MintegralAdSDK/All (7.7.7): + - MintegralAdSDK/BannerAd + - MintegralAdSDK/BidNativeAd + - MintegralAdSDK/InterstitialVideoAd + - MintegralAdSDK/NativeAd + - MintegralAdSDK/NativeAdvancedAd + - MintegralAdSDK/NewInterstitialAd + - MintegralAdSDK/RewardVideoAd + - MintegralAdSDK/SplashAd + - MintegralAdSDK/BannerAd (7.7.7): + - MintegralAdSDK/NativeAd + - MintegralAdSDK/BidNativeAd (7.7.7): + - MintegralAdSDK/NativeAd + - MintegralAdSDK/InterstitialVideoAd (7.7.7): + - MintegralAdSDK/NativeAd + - MintegralAdSDK/NativeAd (7.7.7) + - MintegralAdSDK/NativeAdvancedAd (7.7.7): + - MintegralAdSDK/NativeAd + - MintegralAdSDK/NewInterstitialAd (7.7.7): + - MintegralAdSDK/InterstitialVideoAd + - MintegralAdSDK/NativeAd + - MintegralAdSDK/RewardVideoAd (7.7.7): + - MintegralAdSDK/NativeAd + - MintegralAdSDK/SplashAd (7.7.7): + - MintegralAdSDK/NativeAd + - TPNiOS (6.4.76): + - TPNiOS/TPNSDK (= 6.4.76) + - TPNiOS/TPNSDK (6.4.76) + - TPNIronSourceSDKAdapter (6.4.76): + - IronSourceSDK (= 8.8.0.0) + - TPNiOS (= 6.4.76) + - TPNMintegralSDKAdapter (6.4.76): + - MintegralAdSDK/All (= 7.7.7) + - TPNiOS (= 6.4.76) + - TPNVungleSDKAdapter (6.4.76): + - TPNiOS (= 6.4.76) + - VungleAds (= 7.5.0) + - VungleAds (7.5.0) + +DEPENDENCIES: + - TPNiOS (= 6.4.76) + - TPNIronSourceSDKAdapter (= 6.4.76) + - TPNMintegralSDKAdapter (= 6.4.76) + - TPNVungleSDKAdapter (= 6.4.76) + +SPEC REPOS: + trunk: + - IronSourceSDK + - MintegralAdSDK + - TPNiOS + - TPNIronSourceSDKAdapter + - TPNMintegralSDKAdapter + - TPNVungleSDKAdapter + - VungleAds + +SPEC CHECKSUMS: + IronSourceSDK: ff0b14630899756847f7608a75182f10f022e5ef + MintegralAdSDK: 190c6cd3d83b31b51833e3341857c58dc430da2e + TPNiOS: 1b19f54b097912acf89dd41821605cc366432756 + TPNIronSourceSDKAdapter: 632739c889c7a52174c8e3f5e912b99a2db489c0 + TPNMintegralSDKAdapter: 67df075118c7268031c545b62e2216bba569cb22 + TPNVungleSDKAdapter: 15aa9b622b4ab59eb7a9f95d5c052d674483b585 + VungleAds: cb2aa4791ba4f341c6c026d44cc43eabe258890f + +PODFILE CHECKSUM: c9db2b56d9c1cce1499e833f0872906ef776ba87 + +COCOAPODS: 1.16.2 diff --git a/topon/template/embedded.mobileprovision b/topon/template/embedded.mobileprovision new file mode 100644 index 0000000..1583e4e Binary files /dev/null and b/topon/template/embedded.mobileprovision differ