From b7a58442d044ec5a138a1ccc0ff1931746767c96 Mon Sep 17 00:00:00 2001 From: Qusic Date: Tue, 15 May 2018 21:20:33 +0800 Subject: [PATCH] initial commit --- .gitignore | 4 + LICENSE | 20 + Makefile | 36 + config.cpp | 85 ++ config.hpp | 22 + control | 12 + include/launch.h | 406 ++++++ include/netdb_async.h | 267 ++++ include/xpc/activity.h | 429 ++++++ include/xpc/availability.h | 71 + include/xpc/base.h | 172 +++ include/xpc/connection.h | 714 ++++++++++ include/xpc/debug.h | 23 + include/xpc/endpoint.h | 22 + include/xpc/xpc.h | 2637 ++++++++++++++++++++++++++++++++++++ libc++/shared_mutex | 501 +++++++ libc++/shared_mutex.cpp | 117 ++ netcore.cpp | 161 +++ posix.cpp | 518 +++++++ res/Info.plist | 30 + res/add@2x.png | Bin 0 -> 3552 bytes res/add@3x.png | Bin 0 -> 5418 bytes res/config.js | 118 ++ res/instance@2x.png | Bin 0 -> 4314 bytes res/instance@3x.png | Bin 0 -> 6614 bytes res/proxy.js | 76 ++ res/skia@2x.png | Bin 0 -> 5141 bytes res/skia@3x.png | Bin 0 -> 8188 bytes res/test@2x.png | Bin 0 -> 4362 bytes res/test@3x.png | Bin 0 -> 7021 bytes res/twitter@2x.png | Bin 0 -> 4343 bytes res/twitter@3x.png | Bin 0 -> 6881 bytes res/view/highlight.css | 1 + res/view/highlight.js | 2 + res/view/index.html | 14 + res/view/style.css | 10 + res/view@2x.png | Bin 0 -> 4582 bytes res/view@3x.png | Bin 0 -> 7286 bytes scripts/postinst | 2 + scripts/prerm | 2 + skia.cpp | 233 ++++ skia.hpp | 82 ++ skia.plist | 14 + skiad.h | 245 ++++ skiad.mm | 146 ++ skiad.plist | 15 + skiapref.mm | 391 ++++++ skiapref.plist | 19 + 48 files changed, 7617 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 config.cpp create mode 100644 config.hpp create mode 100644 control create mode 100644 include/launch.h create mode 100644 include/netdb_async.h create mode 100644 include/xpc/activity.h create mode 100644 include/xpc/availability.h create mode 100644 include/xpc/base.h create mode 100644 include/xpc/connection.h create mode 100644 include/xpc/debug.h create mode 100644 include/xpc/endpoint.h create mode 100644 include/xpc/xpc.h create mode 100644 libc++/shared_mutex create mode 100644 libc++/shared_mutex.cpp create mode 100644 netcore.cpp create mode 100644 posix.cpp create mode 100644 res/Info.plist create mode 100644 res/add@2x.png create mode 100644 res/add@3x.png create mode 100644 res/config.js create mode 100644 res/instance@2x.png create mode 100644 res/instance@3x.png create mode 100644 res/proxy.js create mode 100644 res/skia@2x.png create mode 100644 res/skia@3x.png create mode 100644 res/test@2x.png create mode 100644 res/test@3x.png create mode 100644 res/twitter@2x.png create mode 100644 res/twitter@3x.png create mode 100644 res/view/highlight.css create mode 100644 res/view/highlight.js create mode 100644 res/view/index.html create mode 100644 res/view/style.css create mode 100644 res/view@2x.png create mode 100644 res/view@3x.png create mode 100644 scripts/postinst create mode 100644 scripts/prerm create mode 100644 skia.cpp create mode 100644 skia.hpp create mode 100644 skia.plist create mode 100644 skiad.h create mode 100644 skiad.mm create mode 100644 skiad.plist create mode 100644 skiapref.mm create mode 100644 skiapref.plist diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d637e70 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.theos +.DS_Store +*.swp +*.deb diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..23fa3e0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2018 Qusic + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..be8a0c3 --- /dev/null +++ b/Makefile @@ -0,0 +1,36 @@ +TWEAK_NAME = skia +TOOL_NAME = skiad +BUNDLE_NAME = skiapref + +skia_FILES = skia.cpp config.cpp posix.cpp netcore.cpp libc++/shared_mutex.cpp +skia_FRAMEWORKS = CoreFoundation CFNetwork JavaScriptCore +skia_LIBRARIES = substrate +skia_INSTALL_PATH = /Library/MobileSubstrate/DynamicLibraries + +skiad_FILES = skiad.mm +skiad_PRIVATE_FRAMEWORKS = AppSupport +skiad_INSTALL_PATH = /usr/libexec + +skiapref_FILES = skiapref.mm config.cpp +skiapref_RESOURCE_DIRS = res +skiapref_FRAMEWORKS = UIKit CoreGraphics CFNetwork JavaScriptCore +skiapref_PRIVATE_FRAMEWORKS = AppSupport Preferences +skiapref_LIBRARIES = substrate +skiapref_INSTALL_PATH = /Library/PreferenceBundles + +export TARGET = iphone:clang +export ARCHS = armv7 armv7s arm64 +export TARGET_IPHONEOS_DEPLOYMENT_VERSION = 7.0 +export ADDITIONAL_CFLAGS = -fvisibility=hidden -isystem include +export ADDITIONAL_CCFLAGS = -std=c++1y +export ADDITIONAL_OBJCFLAGS = -fobjc-arc + +include $(THEOS)/makefiles/common.mk +include $(THEOS_MAKE_PATH)/tweak.mk +include $(THEOS_MAKE_PATH)/tool.mk +include $(THEOS_MAKE_PATH)/bundle.mk + +internal-stage:: + $(ECHO_NOTHING)dir="$(THEOS_STAGING_DIR)/DEBIAN" && mkdir -p "$$dir" && cp scripts/postinst scripts/prerm "$$dir/"$(ECHO_END) + $(ECHO_NOTHING)dir="$(THEOS_STAGING_DIR)/Library/LaunchDaemons" && mkdir -p "$$dir" && cp $(TOOL_NAME).plist "$$dir/me.qusic.$(TOOL_NAME).plist"$(ECHO_END) + $(ECHO_NOTHING)dir="$(THEOS_STAGING_DIR)/Library/PreferenceLoader/Preferences" && mkdir -p "$$dir" && cp $(BUNDLE_NAME).plist "$$dir/$(TWEAK_NAME).plist"$(ECHO_END) diff --git a/config.cpp b/config.cpp new file mode 100644 index 0000000..bbe45b7 --- /dev/null +++ b/config.cpp @@ -0,0 +1,85 @@ +#include "config.hpp" +#include + +void config::create_context() { + script_context = JSGlobalContextCreate(NULL); + for (const auto &native_function : native_functions) { + JSStringRef function_name = JSStringCreateWithUTF8CString(native_function.first.c_str()); + JSObjectCallAsFunctionCallback function_callback = reinterpret_cast(MSFindSymbol(NULL, native_function.second.c_str())); + JSObjectRef function_object = JSObjectMakeFunctionWithCallback(script_context, function_name, function_callback); + JSObjectSetProperty(script_context, JSContextGetGlobalObject(script_context), function_name, function_object, 0, NULL); + JSStringRelease(function_name); + } + JSStringRef support_script = read_script("/Library/PreferenceBundles/skiapref.bundle/proxy.js"); + JSEvaluateScript(script_context, support_script, NULL, NULL, 0, NULL); + JSStringRelease(support_script); + JSStringRef config_script = read_script("/User/Library/Preferences/me.qusic.skia.js"); + JSEvaluateScript(script_context, config_script, NULL, NULL, 0, NULL); + JSStringRelease(config_script); +} + +void config::release_context() { + JSGlobalContextRelease(script_context); + script_context = NULL; +} + +JSStringRef config::read_script(const std::string &file) { + CFStringRef path = CFStringCreateWithCString(kCFAllocatorDefault, file.c_str(), CFStringGetSystemEncoding()); + CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path, kCFURLPOSIXPathStyle, FALSE); + CFRelease(path); + CFReadStreamRef stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url); + CFRelease(url); + if (stream == NULL) { + return JSStringCreateWithUTF8CString(""); + } + if (CFReadStreamOpen(stream) == FALSE) { + CFRelease(stream); + return JSStringCreateWithUTF8CString(""); + } + CFMutableDataRef data = CFDataCreateMutable(kCFAllocatorDefault, 0); + UInt8 buffer[1024 * 8]; + CFIndex read = 0; + while ((read = CFReadStreamRead(stream, buffer, sizeof(buffer))) > 0) { + CFDataAppendBytes(data, buffer, read); + } + CFReadStreamClose(stream); + CFRelease(stream); + if (read == -1) { + CFRelease(data); + return JSStringCreateWithUTF8CString(""); + } + CFStringRef string = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, CFDataGetBytePtr(data), CFDataGetLength(data), kCFStringEncodingUTF8, TRUE, kCFAllocatorNull); + JSStringRef script = JSStringCreateWithCFString(string); + CFRelease(string); + CFRelease(data); + return script; +} + +void config::execute(const std::function &code) { + code(script_context); +} + +std::string config::evaluate(const std::string &code) { + std::string result; + execute([&](JSGlobalContextRef context) { + JSStringRef script = JSStringCreateWithUTF8CString(code.c_str()); + JSValueRef exception = NULL; + JSValueRef value = JSEvaluateScript(context, script, NULL, NULL, 0, &exception); + JSStringRelease(script); + JSStringRef value_string = NULL; + if (exception) { + value_string = JSValueToStringCopy(context, exception, NULL); + } else { + if (JSValueIsObject(context, value)) { + value_string = JSValueCreateJSONString(context, value, 2, NULL) ?: JSValueToStringCopy(context, value, NULL); + } else { + value_string = JSValueToStringCopy(context, value, NULL); + } + } + char buffer[1024 * 8]; + JSStringGetUTF8CString(value_string, buffer, sizeof(buffer)); + JSStringRelease(value_string); + result = buffer; + }); + return result; +} diff --git a/config.hpp b/config.hpp new file mode 100644 index 0000000..9879ac8 --- /dev/null +++ b/config.hpp @@ -0,0 +1,22 @@ +#include +#include +#include +#include +#include + +class config { +private: + JSGlobalContextRef script_context; + const std::unordered_map native_functions = { + {"__skia_primaryAddresses", "__ZL39_JSPrimaryIpv4AddressesFunctionCallbackPK15OpaqueJSContextP13OpaqueJSValueS3_mPKPKS2_PS5_"}, + {"__skia_dnsResolve", "__ZL29_JSDnsResolveFunctionCallbackPK15OpaqueJSContextP13OpaqueJSValueS3_mPKPKS2_PS5_"}, + }; + void create_context(); + void release_context(); + JSStringRef read_script(const std::string &file); +public: + config() { create_context(); } + ~config() { release_context(); } + void execute(const std::function &code); + std::string evaluate(const std::string &code); +}; diff --git a/control b/control new file mode 100644 index 0000000..8f862de --- /dev/null +++ b/control @@ -0,0 +1,12 @@ +Package: me.qusic.skia +Name: skia +Version: 0.1 +Description: Transparent SOCKS Redirector +Section: Tweaks +Priority: optional +Depends: mobilesubstrate, preferenceloader, me.qusic.shadowsocks +Architecture: iphoneos-arm +Author: Qusic +Maintainer: Qusic +Sponsor: Qusic +Tag: purpose::extension diff --git a/include/launch.h b/include/launch.h new file mode 100644 index 0000000..35acd38 --- /dev/null +++ b/include/launch.h @@ -0,0 +1,406 @@ +#ifndef __XPC_LAUNCH_H__ +#define __XPC_LAUNCH_H__ + +/*! + * @header + * These interfaces were only ever documented for the purpose of allowing a + * launchd job to obtain file descriptors associated with the sockets it + * advertised in its launchd.plist(5). That functionality is now available in a + * much more straightforward fashion through the {@link launch_activate_socket} + * API. + * + * There are currently no replacements for other uses of the {@link launch_msg} + * API, including submitting, removing, starting, stopping and listing jobs. + */ + +#ifndef __XPC_INDIRECT__ +#define __XPC_INDIRECT__ +#endif // __XPC_INDIRECT__ + +#if XPC_BUILDING_LAUNCHD +// Temporary hack to resolve conflicting availability with launchd's existing +// internal headers. +#pragma GCC diagnostic ignored "-Wavailability" +#endif // XPC_BUILDING_LAUNCHD + +#include +#include + +#include +#include +#include +#include + +__BEGIN_DECLS + +#define LAUNCH_KEY_SUBMITJOB "SubmitJob" +#define LAUNCH_KEY_REMOVEJOB "RemoveJob" +#define LAUNCH_KEY_STARTJOB "StartJob" +#define LAUNCH_KEY_STOPJOB "StopJob" +#define LAUNCH_KEY_GETJOB "GetJob" +#define LAUNCH_KEY_GETJOBS "GetJobs" +#define LAUNCH_KEY_CHECKIN "CheckIn" + +#define LAUNCH_JOBKEY_LABEL "Label" +#define LAUNCH_JOBKEY_DISABLED "Disabled" +#define LAUNCH_JOBKEY_USERNAME "UserName" +#define LAUNCH_JOBKEY_GROUPNAME "GroupName" +#define LAUNCH_JOBKEY_TIMEOUT "TimeOut" +#define LAUNCH_JOBKEY_EXITTIMEOUT "ExitTimeOut" +#define LAUNCH_JOBKEY_INITGROUPS "InitGroups" +#define LAUNCH_JOBKEY_SOCKETS "Sockets" +#define LAUNCH_JOBKEY_MACHSERVICES "MachServices" +#define LAUNCH_JOBKEY_MACHSERVICELOOKUPPOLICIES "MachServiceLookupPolicies" +#define LAUNCH_JOBKEY_INETDCOMPATIBILITY "inetdCompatibility" +#define LAUNCH_JOBKEY_ENABLEGLOBBING "EnableGlobbing" +#define LAUNCH_JOBKEY_PROGRAMARGUMENTS "ProgramArguments" +#define LAUNCH_JOBKEY_PROGRAM "Program" +#define LAUNCH_JOBKEY_ONDEMAND "OnDemand" +#define LAUNCH_JOBKEY_KEEPALIVE "KeepAlive" +#define LAUNCH_JOBKEY_LIMITLOADTOHOSTS "LimitLoadToHosts" +#define LAUNCH_JOBKEY_LIMITLOADFROMHOSTS "LimitLoadFromHosts" +#define LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE "LimitLoadToSessionType" +#define LAUNCH_JOBKEY_LIMITLOADTOHARDWARE "LimitLoadToHardware" +#define LAUNCH_JOBKEY_LIMITLOADFROMHARDWARE "LimitLoadFromHardware" +#define LAUNCH_JOBKEY_RUNATLOAD "RunAtLoad" +#define LAUNCH_JOBKEY_ROOTDIRECTORY "RootDirectory" +#define LAUNCH_JOBKEY_WORKINGDIRECTORY "WorkingDirectory" +#define LAUNCH_JOBKEY_ENVIRONMENTVARIABLES "EnvironmentVariables" +#define LAUNCH_JOBKEY_USERENVIRONMENTVARIABLES "UserEnvironmentVariables" +#define LAUNCH_JOBKEY_UMASK "Umask" +#define LAUNCH_JOBKEY_NICE "Nice" +#define LAUNCH_JOBKEY_HOPEFULLYEXITSFIRST "HopefullyExitsFirst" +#define LAUNCH_JOBKEY_HOPEFULLYEXITSLAST "HopefullyExitsLast" +#define LAUNCH_JOBKEY_LOWPRIORITYIO "LowPriorityIO" +#define LAUNCH_JOBKEY_LOWPRIORITYBACKGROUNDIO "LowPriorityBackgroundIO" +#define LAUNCH_JOBKEY_SESSIONCREATE "SessionCreate" +#define LAUNCH_JOBKEY_STARTONMOUNT "StartOnMount" +#define LAUNCH_JOBKEY_SOFTRESOURCELIMITS "SoftResourceLimits" +#define LAUNCH_JOBKEY_HARDRESOURCELIMITS "HardResourceLimits" +#define LAUNCH_JOBKEY_STANDARDINPATH "StandardInPath" +#define LAUNCH_JOBKEY_STANDARDOUTPATH "StandardOutPath" +#define LAUNCH_JOBKEY_STANDARDERRORPATH "StandardErrorPath" +#define LAUNCH_JOBKEY_DEBUG "Debug" +#define LAUNCH_JOBKEY_WAITFORDEBUGGER "WaitForDebugger" +#define LAUNCH_JOBKEY_QUEUEDIRECTORIES "QueueDirectories" +#define LAUNCH_JOBKEY_WATCHPATHS "WatchPaths" +#define LAUNCH_JOBKEY_STARTINTERVAL "StartInterval" +#define LAUNCH_JOBKEY_STARTCALENDARINTERVAL "StartCalendarInterval" +#define LAUNCH_JOBKEY_BONJOURFDS "BonjourFDs" +#define LAUNCH_JOBKEY_LASTEXITSTATUS "LastExitStatus" +#define LAUNCH_JOBKEY_PID "PID" +#define LAUNCH_JOBKEY_THROTTLEINTERVAL "ThrottleInterval" +#define LAUNCH_JOBKEY_LAUNCHONLYONCE "LaunchOnlyOnce" +#define LAUNCH_JOBKEY_ABANDONPROCESSGROUP "AbandonProcessGroup" +#define LAUNCH_JOBKEY_IGNOREPROCESSGROUPATSHUTDOWN \ + "IgnoreProcessGroupAtShutdown" +#define LAUNCH_JOBKEY_LEGACYTIMERS "LegacyTimers" +#define LAUNCH_JOBKEY_ENABLEPRESSUREDEXIT "EnablePressuredExit" +#define LAUNCH_JOBKEY_DRAINMESSAGESONFAILEDINIT "DrainMessagesOnFailedInit" + +#define LAUNCH_JOBKEY_POLICIES "Policies" +#define LAUNCH_JOBKEY_ENABLETRANSACTIONS "EnableTransactions" + +#define LAUNCH_JOBPOLICY_DENYCREATINGOTHERJOBS "DenyCreatingOtherJobs" + +#define LAUNCH_JOBINETDCOMPATIBILITY_WAIT "Wait" +#define LAUNCH_JOBINETDCOMPATIBILITY_INSTANCES "Instances" + +#define LAUNCH_JOBKEY_MACH_RESETATCLOSE "ResetAtClose" +#define LAUNCH_JOBKEY_MACH_HIDEUNTILCHECKIN "HideUntilCheckIn" +#define LAUNCH_JOBKEY_MACH_DRAINMESSAGESONCRASH "DrainMessagesOnCrash" +#define LAUNCH_JOBKEY_MACH_PINGEVENTUPDATES "PingEventUpdates" + +#define LAUNCH_JOBKEY_KEEPALIVE_SUCCESSFULEXIT "SuccessfulExit" +#define LAUNCH_JOBKEY_KEEPALIVE_NETWORKSTATE "NetworkState" +#define LAUNCH_JOBKEY_KEEPALIVE_PATHSTATE "PathState" +#define LAUNCH_JOBKEY_KEEPALIVE_OTHERJOBACTIVE "OtherJobActive" +#define LAUNCH_JOBKEY_KEEPALIVE_OTHERJOBENABLED "OtherJobEnabled" +#define LAUNCH_JOBKEY_KEEPALIVE_AFTERINITIALDEMAND "AfterInitialDemand" +#define LAUNCH_JOBKEY_KEEPALIVE_CRASHED "Crashed" + +#define LAUNCH_JOBKEY_LAUNCHEVENTS "LaunchEvents" + +#define LAUNCH_JOBKEY_CAL_MINUTE "Minute" +#define LAUNCH_JOBKEY_CAL_HOUR "Hour" +#define LAUNCH_JOBKEY_CAL_DAY "Day" +#define LAUNCH_JOBKEY_CAL_WEEKDAY "Weekday" +#define LAUNCH_JOBKEY_CAL_MONTH "Month" + +#define LAUNCH_JOBKEY_RESOURCELIMIT_CORE "Core" +#define LAUNCH_JOBKEY_RESOURCELIMIT_CPU "CPU" +#define LAUNCH_JOBKEY_RESOURCELIMIT_DATA "Data" +#define LAUNCH_JOBKEY_RESOURCELIMIT_FSIZE "FileSize" +#define LAUNCH_JOBKEY_RESOURCELIMIT_MEMLOCK "MemoryLock" +#define LAUNCH_JOBKEY_RESOURCELIMIT_NOFILE "NumberOfFiles" +#define LAUNCH_JOBKEY_RESOURCELIMIT_NPROC "NumberOfProcesses" +#define LAUNCH_JOBKEY_RESOURCELIMIT_RSS "ResidentSetSize" +#define LAUNCH_JOBKEY_RESOURCELIMIT_STACK "Stack" + +#define LAUNCH_JOBKEY_DISABLED_MACHINETYPE "MachineType" +#define LAUNCH_JOBKEY_DISABLED_MODELNAME "ModelName" + +#define LAUNCH_JOBSOCKETKEY_TYPE "SockType" +#define LAUNCH_JOBSOCKETKEY_PASSIVE "SockPassive" +#define LAUNCH_JOBSOCKETKEY_BONJOUR "Bonjour" +#define LAUNCH_JOBSOCKETKEY_SECUREWITHKEY "SecureSocketWithKey" +#define LAUNCH_JOBSOCKETKEY_PATHNAME "SockPathName" +#define LAUNCH_JOBSOCKETKEY_PATHMODE "SockPathMode" +#define LAUNCH_JOBSOCKETKEY_PATHOWNER "SockPathOwner" +#define LAUNCH_JOBSOCKETKEY_PATHGROUP "SockPathGroup" +#define LAUNCH_JOBSOCKETKEY_NODENAME "SockNodeName" +#define LAUNCH_JOBSOCKETKEY_SERVICENAME "SockServiceName" +#define LAUNCH_JOBSOCKETKEY_FAMILY "SockFamily" +#define LAUNCH_JOBSOCKETKEY_PROTOCOL "SockProtocol" +#define LAUNCH_JOBSOCKETKEY_MULTICASTGROUP "MulticastGroup" + +#define LAUNCH_JOBKEY_PROCESSTYPE "ProcessType" +#define LAUNCH_KEY_PROCESSTYPE_APP "App" +#define LAUNCH_KEY_PROCESSTYPE_STANDARD "Standard" +#define LAUNCH_KEY_PROCESSTYPE_BACKGROUND "Background" +#define LAUNCH_KEY_PROCESSTYPE_INTERACTIVE "Interactive" +#define LAUNCH_KEY_PROCESSTYPE_ADAPTIVE "Adaptive" + +/*! + * @function launch_activate_socket + * + * @abstract + * Retrieves the file descriptors for sockets specified in the process' + * launchd.plist(5). + * + * @param name + * The name of the socket entry in the service's Sockets dictionary. + * + * @param fds + * On return, this parameter will be populated with an array of file + * descriptors. One socket can have many descriptors associated with it + * depending on the characteristics of the network interfaces on the system. + * The descriptors in this array are the results of calling getaddrinfo(3) with + * the parameters described in launchd.plist(5). + * + * The caller is responsible for calling free(3) on the returned pointer. + * + * @param cnt + * The number of file descriptor entries in the returned array. + * + * @result + * On success, zero is returned. Otherwise, an appropriate POSIX-domain is + * returned. Possible error codes are: + * + * ENOENT -> There was no socket of the specified name owned by the caller. + * ESRCH -> The caller is not a process managed by launchd. + * EALREADY -> The socket has already been activated by the caller. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 XPC_NONNULL2 XPC_NONNULL3 +int +launch_activate_socket(const char *name, int **fds, size_t *cnt); + +typedef struct _launch_data *launch_data_t; +typedef void (*launch_data_dict_iterator_t)(const launch_data_t lval, + const char *key, void *ctx); + +typedef enum { + LAUNCH_DATA_DICTIONARY = 1, + LAUNCH_DATA_ARRAY, + LAUNCH_DATA_FD, + LAUNCH_DATA_INTEGER, + LAUNCH_DATA_REAL, + LAUNCH_DATA_BOOL, + LAUNCH_DATA_STRING, + LAUNCH_DATA_OPAQUE, + LAUNCH_DATA_ERRNO, + LAUNCH_DATA_MACHPORT, +} launch_data_type_t; + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_MALLOC XPC_WARN_RESULT +launch_data_t +launch_data_alloc(launch_data_type_t type); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_MALLOC XPC_WARN_RESULT XPC_NONNULL1 +launch_data_t +launch_data_copy(launch_data_t ld); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +launch_data_type_t +launch_data_get_type(const launch_data_t ld); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_NONNULL1 +void +launch_data_free(launch_data_t ld); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 XPC_NONNULL3 +bool +launch_data_dict_insert(launch_data_t ldict, const launch_data_t lval, + const char *key); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 XPC_NONNULL2 +launch_data_t +launch_data_dict_lookup(const launch_data_t ldict, const char *key); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 +bool +launch_data_dict_remove(launch_data_t ldict, const char *key); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 +void +launch_data_dict_iterate(const launch_data_t ldict, + launch_data_dict_iterator_t iterator, void *ctx); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +size_t +launch_data_dict_get_count(const launch_data_t ldict); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_NONNULL1 +bool +launch_data_array_set_index(launch_data_t larray, const launch_data_t lval, + size_t idx); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +launch_data_t +launch_data_array_get_index(const launch_data_t larray, size_t idx); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +size_t +launch_data_array_get_count(const launch_data_t larray); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_MALLOC XPC_WARN_RESULT +launch_data_t +launch_data_new_fd(int fd); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_MALLOC XPC_WARN_RESULT +launch_data_t +launch_data_new_machport(mach_port_t val); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_MALLOC XPC_WARN_RESULT +launch_data_t +launch_data_new_integer(long long val); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_MALLOC XPC_WARN_RESULT +launch_data_t +launch_data_new_bool(bool val); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_MALLOC XPC_WARN_RESULT +launch_data_t +launch_data_new_real(double val); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_MALLOC XPC_WARN_RESULT +launch_data_t +launch_data_new_string(const char *val); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_MALLOC XPC_WARN_RESULT +launch_data_t +launch_data_new_opaque(const void *bytes, size_t sz); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_NONNULL1 +bool +launch_data_set_fd(launch_data_t ld, int fd); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_NONNULL1 +bool +launch_data_set_machport(launch_data_t ld, mach_port_t mp); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_NONNULL1 +bool +launch_data_set_integer(launch_data_t ld, long long val); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_NONNULL1 +bool +launch_data_set_bool(launch_data_t ld, bool val); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_NONNULL1 +bool +launch_data_set_real(launch_data_t ld, double val); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_NONNULL1 +bool +launch_data_set_string(launch_data_t ld, const char *val); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_NONNULL1 +bool +launch_data_set_opaque(launch_data_t ld, const void *bytes, size_t sz); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +int +launch_data_get_fd(const launch_data_t ld); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +mach_port_t +launch_data_get_machport(const launch_data_t ld); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +long long +launch_data_get_integer(const launch_data_t ld); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +bool +launch_data_get_bool(const launch_data_t ld); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +double +launch_data_get_real(const launch_data_t ld); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +const char * +launch_data_get_string(const launch_data_t ld); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +void * +launch_data_get_opaque(const launch_data_t ld); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +size_t +launch_data_get_opaque_size(const launch_data_t ld); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +int +launch_data_get_errno(const launch_data_t ld); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_WARN_RESULT +int +launch_get_fd(void); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4, __MAC_10_10, __IPHONE_2_0, __IPHONE_8_0) +XPC_EXPORT XPC_MALLOC XPC_WARN_RESULT XPC_NONNULL1 +launch_data_t +launch_msg(const launch_data_t request); + +__END_DECLS + +#endif // __XPC_LAUNCH_H__ diff --git a/include/netdb_async.h b/include/netdb_async.h new file mode 100644 index 0000000..6fd7bbf --- /dev/null +++ b/include/netdb_async.h @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2002-2009 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef _NETDB_ASYNC_H_ +#define _NETDB_ASYNC_H_ + +#include +#include +#include + +#define gethostbyname_async_handle_reply gethostbyname_async_handleReply +#define gethostbyaddr_async_handle_reply gethostbyaddr_async_handleReply +#define getipnodebyaddr_async_handle_reply getipnodebyaddr_async_handleReply +#define getipnodebyname_async_handle_reply getipnodebyname_async_handleReply + +/* SPI for parallel / fast getaddrinfo */ +#define AI_PARALLEL 0x00000008 +#define AI_SRV 0x01000000 + +__BEGIN_DECLS + + +/* + * getaddrinfo + */ +typedef void (*getaddrinfo_async_callback)(int32_t status, struct addrinfo *res, void *context); +int32_t getaddrinfo_async_start(mach_port_t *p, const char *nodename, const char *servname, const struct addrinfo *hints, getaddrinfo_async_callback callback, void *context); +int32_t getaddrinfo_async_send(mach_port_t *p, const char *nodename, const char *servname, const struct addrinfo *hints); +int32_t getaddrinfo_async_receive(mach_port_t p, struct addrinfo **res); +int32_t getaddrinfo_async_handle_reply(void *msg); +void getaddrinfo_async_cancel(mach_port_t p); + + +/* + * getnameinfo + */ +typedef void (*getnameinfo_async_callback)(int32_t status, char *host, char *serv, void *context); +int32_t getnameinfo_async_start(mach_port_t *p, const struct sockaddr *sa, size_t salen, int flags, getnameinfo_async_callback callback, void *context); +int32_t getnameinfo_async_send(mach_port_t *p, const struct sockaddr *sa, size_t salen, int flags); +int32_t getnameinfo_async_receive(mach_port_t p, char **host, char **serv); +int32_t getnameinfo_async_handle_reply(void *msg); +void getnameinfo_async_cancel(mach_port_t p); + +/* + * DNS + */ +typedef void (*dns_async_callback)(int32_t status, char *buf, uint32_t len, struct sockaddr *from, int fromlen, void *context); +int32_t dns_async_start(mach_port_t *p, const char *name, uint16_t dnsclass, uint16_t dnstype, uint32_t do_search, dns_async_callback callback, void *context); +int32_t dns_async_send(mach_port_t *p, const char *name, uint16_t dnsclass, uint16_t dnstype, uint32_t do_search); +int32_t dns_async_receive(mach_port_t p, char **buf, uint32_t *len, struct sockaddr **from, uint32_t *fromlen); +int32_t dns_async_handle_reply(void *msg); +void dns_async_cancel(mach_port_t p); + +/* + * Host lookup + */ + +/* + @typedef gethostbyaddr_async_callback + @discussion Type of the callback function used when a + gethostbyaddr_async_start() request is delivered. + @param hent The resolved host entry. + @param context The context provided when the request + was initiated. + */ +typedef void (*gethostbyaddr_async_callback)(struct hostent *hent, void *context); + +/*! + @function gethostbyaddr_async_start + @description Asynchronously resolves an Internet address + @param addr The address to be resolved. + @param len The length, in bytes, of the address. + @param type + @param callout The function to be called when the specified + address has been resolved. + @param context A user specified context which will be passed + to the callout function. + @result A mach reply port which will be sent a message when + the addr resolution has completed. This message + should be passed to the gethostbyaddr_async_handleReply + function. A NULL value indicates that no address + was specified or some other error occurred which + prevented the resolution from being started. + */ +mach_port_t +gethostbyaddr_async_start(const char *addr, int len, int type, gethostbyaddr_async_callback callout, void *context); + +/*! + @function gethostbyaddr_async_cancel + @description Cancel an asynchronous request currently in progress. + @param port The mach reply port associated with the request to be cancelled. + */ +void gethostbyaddr_async_cancel(mach_port_t port); + +/*! + @function gethostbyaddr_async_handleReply + @description This function should be called with the Mach message sent + to the port returned by the call to gethostbyaddr_async_start. + The reply message will be interpreted and will result in a + call to the specified callout function. + @param replyMsg The Mach message. + */ +void gethostbyaddr_async_handleReply(void *replyMsg); + +/* + @typedef gethostbyname_async_callback + @discussion Type of the callback function used when a + gethostbyname_async_start() request is delivered. + @param hent The resolved host entry. + @param context The context provided when the request was initiated. + */ +typedef void (*gethostbyname_async_callback)(struct hostent *hent, void *context); + +/*! + @function gethostbyname_async_start + @description Asynchronously resolves a hostname + @param name The hostname to be resolved. + @param callout The function to be called when the specified + hostname has been resolved. + @param context A user specified context which will be passed + to the callout function. + @result A mach reply port which will be sent a message when + the name resolution has completed. This message + should be passed to the gethostbyname_async_handleReply + function. A NULL value indicates that no hostname + was specified or some other error occurred which + prevented the resolution from being started. + */ +mach_port_t gethostbyname_async_start(const char *name, gethostbyname_async_callback callout, void *context); + +/*! + @function gethostbyname_async_cancel + @description Cancel an asynchronous request currently in progress. + @param port The mach reply port associated with the request to be cancelled. + */ +void gethostbyname_async_cancel(mach_port_t port); + +/*! + @function gethostbyname_async_handleReply + @description This function should be called with the Mach message sent + to the port returned by the call to gethostbyname_async_start. + The reply message will be interpreted and will result in a + call to the specified callout function. + @param replyMsg The Mach message. + */ +void gethostbyname_async_handleReply(void *replyMsg); + +/* + @typedef getipnodebyaddr_async_callback + @discussion Type of the callback function used when a + getipnodebyaddr_async_start() request is delivered. + @param hent The resolved host entry. If not NULL, the caller + must call freehostent() on the host entry. + @param error If error code if the resolved host entry is NULL + @param context The context provided when the request was initiated. + */ +typedef void (*getipnodebyaddr_async_callback)(struct hostent *hent, int error, void *context); + +/*! + @function getipnodebyaddr_async_start + @description Asynchronously resolves an Internet address + @param addr The address to be resolved. + @param len The length, in bytes, of the address. + @param af The address family + @param error + @param callout The function to be called when the specified + address has been resolved. + @param context A user specified context which will be passed + to the callout function. + @result A mach reply port which will be sent a message when + the addr resolution has completed. This message + should be passed to the getipnodebyaddr_async_handleReply + function. A NULL value indicates that no address + was specified or some other error occurred which + prevented the resolution from being started. + */ +mach_port_t getipnodebyaddr_async_start(const void *addr, size_t len, int af, int *error, getipnodebyaddr_async_callback callout, void *context); + +/*! + @function getipnodebyaddr_async_cancel + @description Cancel an asynchronous request currently in progress. + @param port The mach reply port associated with the request to be cancelled. + */ +void getipnodebyaddr_async_cancel(mach_port_t port); + +/*! + @function getipnodebyaddr_async_handleReply + @description This function should be called with the Mach message sent + to the port returned by the call to getipnodebyaddr_async_start. + The reply message will be interpreted and will result in a + call to the specified callout function. + @param replyMsg The Mach message. + */ +void getipnodebyaddr_async_handleReply(void *replyMsg); + + +/* + @typedef getipnodebyname_async_callback + @discussion Type of the callback function used when a + getipnodebyname_async_start() request is delivered. + @param hent The resolved host entry. If not NULL, the caller + must call freehostent() on the host entry. + @param error If error code if the resolved host entry is NULL + @param context The context provided when the request was initiated. + */ +typedef void (*getipnodebyname_async_callback)(struct hostent *hent, int error, void *context); + +/*! + @function getipnodebyname_async_start + @description Asynchronously resolves a hostname + @param name The hostname to be resolved. + @param af + @param flags + @param error + @param callout The function to be called when the specified + hostname has been resolved. + @param context A user specified context which will be passed + to the callout function. + @result A mach reply port which will be sent a message when + the name resolution has completed. This message + should be passed to the getipnodebyname_async_handleReply + function. A NULL value indicates that no hostname + was specified or some other error occurred which + prevented the resolution from being started. + */ +mach_port_t getipnodebyname_async_start(const char *name, int af, int flags, int *error, getipnodebyname_async_callback callout, void *context); + +/*! + @function getipnodebyname_async_cancel + @description Cancel an asynchronous request currently in progress. + @param port The mach reply port associated with the request to be cancelled. + */ +void getipnodebyname_async_cancel(mach_port_t port); + +/*! + @function getipnodebyname_async_handleReply + @description This function should be called with the Mach message sent + to the port returned by the call to getipnodebyname_async_start. + The reply message will be interpreted and will result in a + call to the specified callout function. + @param replyMsg The Mach message. + */ +void getipnodebyname_async_handleReply(void *replyMsg); + +__END_DECLS + +#endif /* !_NETDB_ASYNC_H_ */ diff --git a/include/xpc/activity.h b/include/xpc/activity.h new file mode 100644 index 0000000..4b08b11 --- /dev/null +++ b/include/xpc/activity.h @@ -0,0 +1,429 @@ +#ifndef __XPC_ACTIVITY_H__ +#define __XPC_ACTIVITY_H__ + +#ifndef __XPC_INDIRECT__ +#error "Please #include instead of this file directly." +// For HeaderDoc. +#include +#endif // __XPC_INDIRECT__ + +#ifdef __BLOCKS__ + +__BEGIN_DECLS + +/* + * The following are a collection of keys and values used to set an activity's + * execution criteria. + */ + +/*! + * @constant XPC_ACTIVITY_INTERVAL + * An integer property indicating the desired time interval (in seconds) of the + * activity. The activity will not be run more than once per time interval. + * Due to the nature of XPC Activity finding an opportune time to run + * the activity, any two occurrences may be more or less than 'interval' + * seconds apart, but on average will be 'interval' seconds apart. + * The presence of this key implies the following, unless overridden: + * - XPC_ACTIVITY_REPEATING with a value of true + * - XPC_ACTIVITY_DELAY with a value of half the 'interval' + * The delay enforces a minimum distance between any two occurrences. + * - XPC_ACTIVITY_GRACE_PERIOD with a value of half the 'interval'. + * The grace period is the amount of time allowed to pass after the end of + * the interval before more aggressive scheduling occurs. The grace period + * does not increase the size of the interval. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT +const char *XPC_ACTIVITY_INTERVAL; + +/*! + * @constant XPC_ACTIVITY_REPEATING + * A boolean property indicating whether this is a repeating activity. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT +const char *XPC_ACTIVITY_REPEATING; + +/*! + * @constant XPC_ACTIVITY_DELAY + * An integer property indicating the number of seconds to delay before + * beginning the activity. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT +const char *XPC_ACTIVITY_DELAY; + +/*! + * @constant XPC_ACTIVITY_GRACE_PERIOD + * An integer property indicating the number of seconds to allow as a grace + * period before the scheduling of the activity becomes more aggressive. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT +const char *XPC_ACTIVITY_GRACE_PERIOD; + + +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT +const int64_t XPC_ACTIVITY_INTERVAL_1_MIN; + +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT +const int64_t XPC_ACTIVITY_INTERVAL_5_MIN; + +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT +const int64_t XPC_ACTIVITY_INTERVAL_15_MIN; + +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT +const int64_t XPC_ACTIVITY_INTERVAL_30_MIN; + +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT +const int64_t XPC_ACTIVITY_INTERVAL_1_HOUR; + +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT +const int64_t XPC_ACTIVITY_INTERVAL_4_HOURS; + +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT +const int64_t XPC_ACTIVITY_INTERVAL_8_HOURS; + +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT +const int64_t XPC_ACTIVITY_INTERVAL_1_DAY; + +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT +const int64_t XPC_ACTIVITY_INTERVAL_7_DAYS; + +/*! + * @constant XPC_ACTIVITY_PRIORITY + * A string property indicating the priority of the activity. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT +const char *XPC_ACTIVITY_PRIORITY; + +/*! + * @constant XPC_ACTIVITY_PRIORITY_MAINTENANCE + * A string indicating activity is maintenance priority. + * Maintenance priority is intended for user-invisible maintenance tasks + * such as garbage collection or optimization. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT +const char *XPC_ACTIVITY_PRIORITY_MAINTENANCE; + +/*! + * @constant XPC_ACTIVITY_PRIORITY_UTILITY + * A string indicating activity is utility priority. + * Utility priority is intended for user-visible tasks such as fetching data + * from the network, copying files, or importing data. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT +const char *XPC_ACTIVITY_PRIORITY_UTILITY; + +/*! + * @constant XPC_ACTIVITY_ALLOW_BATTERY + * A Boolean value indicating whether the activity should be allowed to run + * while the computer is on battery power. The default value is false for + * maintenance priority activity and true for utility priority activity. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT +const char *XPC_ACTIVITY_ALLOW_BATTERY; + +/*! + * @constant XPC_ACTIVITY_REQUIRE_SCREEN_SLEEP + * A Boolean value indicating whether the activity should only be performed + * while the primary screen is in sleep mode. Defaults to false. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT +const char *XPC_ACTIVITY_REQUIRE_SCREEN_SLEEP; // bool + +/*! + * @constant XPC_ACTIVITY_REQUIRE_BATTERY_LEVEL + * An integer percentage of minimum battery charge required to allow the + * activity to run. A default minimum battery level is determined by the + * system. + */ +__OSX_AVAILABLE_BUT_DEPRECATED_MSG(__MAC_10_9, __MAC_10_9, __IPHONE_7_0, __IPHONE_7_0, + "REQUIRE_BATTERY_LEVEL is not implemented") +XPC_EXPORT +const char *XPC_ACTIVITY_REQUIRE_BATTERY_LEVEL; // int (%) + +/*! + * @constant XPC_ACTIVITY_REQUIRE_HDD_SPINNING + * A Boolean value indicating whether the activity should only be performed + * while the hard disk drive (HDD) is spinning. Computers with flash storage + * are considered to be equivalent to HDD spinning. Defaults to false. + */ +__OSX_AVAILABLE_BUT_DEPRECATED_MSG(__MAC_10_9, __MAC_10_9, __IPHONE_7_0, __IPHONE_7_0, + "REQUIRE_HDD_SPINNING is not implemented") +XPC_EXPORT +const char *XPC_ACTIVITY_REQUIRE_HDD_SPINNING; // bool + +/*! + * @define XPC_TYPE_ACTIVITY + * A type representing a connection to a named service. This connection is + * bidirectional and can be used to both send and receive messages. A + * connection carries the credentials of the remote service provider. + */ +#define XPC_TYPE_ACTIVITY (&_xpc_type_activity) +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT +XPC_TYPE(_xpc_type_activity); + +/*! + * @typedef xpc_activity_t + * + * @abstract + * An XPC activity object. + * + * @discussion + * This object represents a set of execution criteria and a current execution + * state for background activity on the system. Once an activity is registered, + * the system will evaluate its criteria to determine whether the activity is + * eligible to run under current system conditions. When an activity becomes + * eligible to run, its execution state will be updated and an invocation of + * its handler block will be made. + */ +XPC_DECL(xpc_activity); + +/*! + * @typedef xpc_activity_handler_t + * + * @abstract + * A block that is called when an XPC activity becomes eligible to run. + */ +typedef void (^xpc_activity_handler_t)(xpc_activity_t activity); + +/*! + * @constant XPC_ACTIVITY_CHECK_IN + * This constant may be passed to xpc_activity_register() as the criteria + * dictionary in order to check in with the system for previously registered + * activity using the same identifier (for example, an activity taken from a + * launchd property list). + */ +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT +const xpc_object_t XPC_ACTIVITY_CHECK_IN; + +/*! + * @function xpc_activity_register + * + * @abstract + * Registers an activity with the system. + * + * @discussion + * Registers a new activity with the system. The criteria of the activity are + * described by the dictionary passed to this function. If an activity with the + * same identifier already exists, the criteria provided override the existing + * criteria unless the special dictionary XPC_ACTIVITY_CHECK_IN is used. The + * XPC_ACTIVITY_CHECK_IN dictionary instructs the system to first look up an + * existing activity without modifying its criteria. Once the existing activity + * is found (or a new one is created with an empty set of criteria) the handler + * will be called with an activity object in the XPC_ACTIVITY_STATE_CHECK_IN + * state. + * + * @param identifier + * A unique identifier for the activity. Each application has its own namespace. + * The identifier should remain constant across registrations, relaunches of + * the application, and reboots. It should identify the kind of work being done, + * not a particular invocation of the work. + * + * @param criteria + * A dictionary of criteria for the activity. + * + * @param handler + * The handler block to be called when the activity changes state to one of the + * following states: + * - XPC_ACTIVITY_STATE_CHECK_IN (optional) + * - XPC_ACTIVITY_STATE_RUN + * + * The handler block is never invoked reentrantly. It will be invoked on a + * dispatch queue with an appropriate priority to perform the activity. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 XPC_NONNULL3 +void +xpc_activity_register(const char *identifier, xpc_object_t criteria, + xpc_activity_handler_t handler); + +/*! + * @function xpc_activity_copy_criteria + * + * @abstract + * Returns an XPC dictionary describing the execution criteria of an activity. + * This will return NULL in cases where the activity has already completed, e.g. + * when checking in to an event that finished and was not rescheduled. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT XPC_WARN_RESULT XPC_RETURNS_RETAINED +xpc_object_t +xpc_activity_copy_criteria(xpc_activity_t activity); + +/*! + * @function xpc_activity_set_criteria + * + * @abstract + * Modifies the execution criteria of an activity. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 +void +xpc_activity_set_criteria(xpc_activity_t activity, xpc_object_t criteria); + +/*! + * @enum xpc_activity_state_t + * An activity is defined to be in one of the following states. Applications + * may check the current state of the activity using xpc_activity_get_state() + * in the handler block provided to xpc_activity_register(). + * + * The application can modify the state of the activity by calling + * xpc_activity_set_state() with one of the following: + * - XPC_ACTIVITY_STATE_DEFER + * - XPC_ACTIVITY_STATE_CONTINUE + * - XPC_ACTIVITY_STATE_DONE + * + * @constant XPC_ACTIVITY_STATE_CHECK_IN + * An activity in this state has just completed a checkin with the system after + * XPC_ACTIVITY_CHECK_IN was provided as the criteria dictionary to + * xpc_activity_register. The state gives the application an opportunity to + * inspect and modify the activity's criteria. + * + * @constant XPC_ACTIVITY_STATE_WAIT + * An activity in this state is waiting for an opportunity to run. This value + * is never returned within the activity's handler block, as the block is + * invoked in response to XPC_ACTIVITY_STATE_CHECK_IN or XPC_ACTIVITY_STATE_RUN. + * + * Note: + * A launchd job may idle exit while an activity is in the wait state and be + * relaunched in response to the activity becoming runnable. The launchd job + * simply needs to re-register for the activity on its next launch by passing + * XPC_ACTIVITY_STATE_CHECK_IN to xpc_activity_register(). + * + * @constant XPC_ACTIVITY_STATE_RUN + * An activity in this state is eligible to run based on its criteria. + * + * @constant XPC_ACTIVITY_STATE_DEFER + * An application may pass this value to xpc_activity_set_state() to indicate + * that the activity should be deferred (placed back into the WAIT state) until + * a time when its criteria are met again. Deferring an activity does not reset + * any of its time-based criteria (in other words, it will remain past due). + * + * IMPORTANT: + * This should be done in response to observing xpc_activity_should_defer(). + * It should not be done unilaterally. If you determine that conditions are bad + * to do your activity's work for reasons you can't express in a criteria + * dictionary, you should set the activity's state to XPC_ACTIVITY_STATE_DONE. + * + * + * @constant XPC_ACTIVITY_STATE_CONTINUE + * An application may pass this value to xpc_activity_set_state() to indicate + * that the activity will continue its operation beyond the return of its + * handler block. This can be used to extend an activity to include asynchronous + * operations. The activity's handler block will not be invoked again until the + * state has been updated to either XPC_ACTIVITY_STATE_DEFER or, in the case + * of repeating activity, XPC_ACTIVITY_STATE_DONE. + * + * @constant XPC_ACTIVITY_STATE_DONE + * An application may pass this value to xpc_activity_set_state() to indicate + * that the activity has completed. For non-repeating activity, the resources + * associated with the activity will be automatically released upon return from + * the handler block. For repeating activity, timers present in the activity's + * criteria will be reset. + */ +enum { + XPC_ACTIVITY_STATE_CHECK_IN, + XPC_ACTIVITY_STATE_WAIT, + XPC_ACTIVITY_STATE_RUN, + XPC_ACTIVITY_STATE_DEFER, + XPC_ACTIVITY_STATE_CONTINUE, + XPC_ACTIVITY_STATE_DONE, +}; +typedef long xpc_activity_state_t; + +/*! + * @function xpc_activity_get_state + * + * @abstract + * Returns the current state of an activity. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +xpc_activity_state_t +xpc_activity_get_state(xpc_activity_t activity); + +/*! + * @function xpc_activity_set_state + * + * @abstract + * Updates the current state of an activity. + * + * @return + * Returns true if the state was successfully updated; otherwise, returns + * false if the requested state transition is not valid. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +bool +xpc_activity_set_state(xpc_activity_t activity, xpc_activity_state_t state); + +/*! + * @function xpc_activity_should_defer + * + * @abstract + * Test whether an activity should be deferred. + * + * @discussion + * This function may be used to test whether the criteria of a long-running + * activity are still satisfied. If not, the system indicates that the + * application should defer the activity. The application may acknowledge the + * deferral by calling xpc_activity_set_state() with XPC_ACTIVITY_STATE_DEFER. + * Once deferred, the system will place the activity back into the WAIT state + * and re-invoke the handler block at the earliest opportunity when the criteria + * are once again satisfied. + * + * @return + * Returns true if the activity should be deferred. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT +bool +xpc_activity_should_defer(xpc_activity_t activity); + +/*! + * @function xpc_activity_unregister + * + * @abstract + * Unregisters an activity found by its identifier. + * + * @discussion + * A dynamically registered activity will be deleted in response to this call. + * Statically registered activity (from a launchd property list) will be + * reverted to its original criteria if any modifications were made. + * + * Unregistering an activity has no effect on any outstanding xpc_activity_t + * objects or any currently executing xpc_activity_handler_t blocks; however, + * no new handler block invocations will be made after it is unregistered. + * + * @param identifier + * The identifier of the activity to unregister. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) +XPC_EXPORT XPC_NONNULL1 +void +xpc_activity_unregister(const char *identifier); + +__END_DECLS + +#endif // __BLOCKS__ + +#endif // __XPC_ACTIVITY_H__ + diff --git a/include/xpc/availability.h b/include/xpc/availability.h new file mode 100644 index 0000000..3665c99 --- /dev/null +++ b/include/xpc/availability.h @@ -0,0 +1,71 @@ +#ifndef __XPC_AVAILABILITY_H__ +#define __XPC_AVAILABILITY_H__ + +#include + +// Certain parts of the project use all the project's headers but have to build +// against newer OSX SDKs than ebuild uses -- liblaunch_host being the example. +// So we need to define these. +#ifndef __MAC_10_11 +#define __MAC_10_11 101100 +#define __AVAILABILITY_INTERNAL__MAC_10_11 \ + __attribute__((availability(macosx, introduced=10.11))) +#endif // __MAC_10_11 + +#ifndef __AVAILABILITY_INTERNAL__MAC_10_2_DEP__MAC_10_11 +#define __AVAILABILITY_INTERNAL__MAC_10_2_DEP__MAC_10_11 +#endif // __AVAILABILITY_INTERNAL__MAC_10_2_DEP__MAC_10_11 + +#ifndef __AVAILABILITY_INTERNAL__MAC_10_3_DEP__MAC_10_11 +#define __AVAILABILITY_INTERNAL__MAC_10_3_DEP__MAC_10_11 +#endif // __AVAILABILITY_INTERNAL__MAC_10_3_DEP__MAC_10_11 + +#ifndef __AVAILABILITY_INTERNAL__MAC_10_4_DEP__MAC_10_11 +#define __AVAILABILITY_INTERNAL__MAC_10_4_DEP__MAC_10_11 +#endif // __AVAILABILITY_INTERNAL__MAC_10_4_DEP__MAC_10_11 + +#ifndef __AVAILABILITY_INTERNAL__MAC_10_5_DEP__MAC_10_11 +#define __AVAILABILITY_INTERNAL__MAC_10_5_DEP__MAC_10_11 +#endif // __AVAILABILITY_INTERNAL__MAC_10_5_DEP__MAC_10_11 + +#ifndef __AVAILABILITY_INTERNAL__MAC_10_6_DEP__MAC_10_11 +#define __AVAILABILITY_INTERNAL__MAC_10_6_DEP__MAC_10_11 +#endif // __AVAILABILITY_INTERNAL__MAC_10_6_DEP__MAC_10_11 + +#ifndef __AVAILABILITY_INTERNAL__MAC_10_7_DEP__MAC_10_11 +#define __AVAILABILITY_INTERNAL__MAC_10_7_DEP__MAC_10_11 +#endif // __AVAILABILITY_INTERNAL__MAC_10_7_DEP__MAC_10_11 + +#ifndef __AVAILABILITY_INTERNAL__MAC_10_8_DEP__MAC_10_11 +#define __AVAILABILITY_INTERNAL__MAC_10_8_DEP__MAC_10_11 +#endif // __AVAILABILITY_INTERNAL__MAC_10_8_DEP__MAC_10_11 + +#ifndef __AVAILABILITY_INTERNAL__MAC_10_9_DEP__MAC_10_11 +#define __AVAILABILITY_INTERNAL__MAC_10_9_DEP__MAC_10_11 +#endif // __AVAILABILITY_INTERNAL__MAC_10_9_DEP__MAC_10_11 + +#ifndef __AVAILABILITY_INTERNAL__MAC_10_10_DEP__MAC_10_11 +#define __AVAILABILITY_INTERNAL__MAC_10_10_DEP__MAC_10_11 +#endif // __AVAILABILITY_INTERNAL__MAC_10_10_DEP__MAC_10_11 + +#ifndef __AVAILABILITY_INTERNAL__MAC_10_11_DEP__MAC_10_11 +#define __AVAILABILITY_INTERNAL__MAC_10_11_DEP__MAC_10_11 +#endif // __AVAILABILITY_INTERNAL__MAC_10_11_DEP__MAC_10_11 + +#if __has_include() +#include +#else // __has_include() +#ifndef IPHONE_SIMULATOR_HOST_MIN_VERSION_REQUIRED +#define IPHONE_SIMULATOR_HOST_MIN_VERSION_REQUIRED 999999 +#endif // IPHONE_SIMULATOR_HOST_MIN_VERSION_REQUIRED +#endif // __has_include() + +#ifndef __WATCHOS_UNAVAILABLE +#define __WATCHOS_UNAVAILABLE +#endif + +#ifndef __TVOS_UNAVAILABLE +#define __TVOS_UNAVAILABLE +#endif + +#endif // __XPC_AVAILABILITY_H__ diff --git a/include/xpc/base.h b/include/xpc/base.h new file mode 100644 index 0000000..cd4dc0f --- /dev/null +++ b/include/xpc/base.h @@ -0,0 +1,172 @@ +// Copyright (c) 2009-2011 Apple Inc. All rights reserved. + +#ifndef __XPC_BASE_H__ +#define __XPC_BASE_H__ + +#include + +__BEGIN_DECLS + +#if !defined(__has_include) +#define __has_include(x) 0 +#endif // !defined(__has_include) + +#if !defined(__has_attribute) +#define __has_attribute(x) 0 +#endif // !defined(__has_attribute) + +#if !defined(__has_feature) +#define __has_feature(x) 0 +#endif // !defined(__has_feature) + +#if !defined(__has_extension) +#define __has_extension(x) 0 +#endif // !defined(__has_extension) + +#if __has_include() +#include +#else // __has_include() +#include +#endif // __has_include() + +#if XPC_SERVICE_MAIN_IN_LIBXPC +#define XPC_HOSTING_OLD_MAIN 1 +#else // XPC_SERVICE_MAIN_IN_LIBXPC +#define XPC_HOSTING_OLD_MAIN 0 +#endif // XPC_SERVICE_MAIN_IN_LIBXPC + +#ifndef __XPC_INDIRECT__ +#error "Please #include instead of this file directly." +#endif // __XPC_INDIRECT__ + +#pragma mark Attribute Shims +#ifdef __GNUC__ +#define XPC_CONSTRUCTOR __attribute__((constructor)) +#define XPC_NORETURN __attribute__((__noreturn__)) +#define XPC_NOTHROW __attribute__((__nothrow__)) +#define XPC_NONNULL1 __attribute__((__nonnull__(1))) +#define XPC_NONNULL2 __attribute__((__nonnull__(2))) +#define XPC_NONNULL3 __attribute__((__nonnull__(3))) +#define XPC_NONNULL4 __attribute__((__nonnull__(4))) +#define XPC_NONNULL5 __attribute__((__nonnull__(5))) +#define XPC_NONNULL6 __attribute__((__nonnull__(6))) +#define XPC_NONNULL7 __attribute__((__nonnull__(7))) +#define XPC_NONNULL8 __attribute__((__nonnull__(8))) +#define XPC_NONNULL9 __attribute__((__nonnull__(9))) +#define XPC_NONNULL10 __attribute__((__nonnull__(10))) +#define XPC_NONNULL11 __attribute__((__nonnull__(11))) +#define XPC_NONNULL_ALL __attribute__((__nonnull__)) +#define XPC_SENTINEL __attribute__((__sentinel__)) +#define XPC_PURE __attribute__((__pure__)) +#define XPC_WARN_RESULT __attribute__((__warn_unused_result__)) +#define XPC_MALLOC __attribute__((__malloc__)) +#define XPC_UNUSED __attribute__((__unused__)) +#define XPC_USED __attribute__((__used__)) +#define XPC_PACKED __attribute__((__packed__)) +#define XPC_PRINTF(m, n) __attribute__((format(printf, m, n))) +#define XPC_INLINE static __inline__ __attribute__((__always_inline__)) +#define XPC_NOINLINE __attribute__((noinline)) +#define XPC_NOIMPL __attribute__((unavailable)) + +#if __has_extension(attribute_unavailable_with_message) +#define XPC_UNAVAILABLE(m) __attribute__((unavailable(m))) +#else // __has_extension(attribute_unavailable_with_message) +#define XPC_UNAVAILABLE(m) XPC_NOIMPL +#endif // __has_extension(attribute_unavailable_with_message) + +#define XPC_EXPORT extern __attribute__((visibility("default"))) +#define XPC_NOEXPORT __attribute__((visibility("hidden"))) +#define XPC_WEAKIMPORT extern __attribute__((weak_import)) +#define XPC_DEBUGGER_EXCL XPC_NOEXPORT XPC_USED +#define XPC_TRANSPARENT_UNION __attribute__((transparent_union)) +#if __clang__ +#define XPC_DEPRECATED(m) __attribute__((deprecated(m))) +#else // __clang__ +#define XPC_DEPRECATED(m) __attribute__((deprecated)) +#endif // __clang + +#if __has_feature(objc_arc) +#define XPC_GIVES_REFERENCE __strong +#define XPC_UNRETAINED __unsafe_unretained +#define XPC_BRIDGE(xo) ((__bridge void *)(xo)) +#define XPC_BRIDGEREF_BEGIN(xo) ((__bridge_retained void *)(xo)) +#define XPC_BRIDGEREF_BEGIN_WITH_REF(xo) ((__bridge void *)(xo)) +#define XPC_BRIDGEREF_MIDDLE(xo) ((__bridge id)(xo)) +#define XPC_BRIDGEREF_END(xo) ((__bridge_transfer id)(xo)) +#else // __has_feature(objc_arc) +#define XPC_GIVES_REFERENCE +#define XPC_UNRETAINED +#define XPC_BRIDGE(xo) +#define XPC_BRIDGEREF_BEGIN(xo) (xo) +#define XPC_BRIDGEREF_BEGIN_WITH_REF(xo) (xo) +#define XPC_BRIDGEREF_MIDDLE(xo) (xo) +#define XPC_BRIDGEREF_END(xo) (xo) +#endif // __has_feature(objc_arc) + +#define _xpc_unreachable() __builtin_unreachable() +#else // __GNUC__ +/*! @parseOnly */ +#define XPC_CONSTRUCTOR +/*! @parseOnly */ +#define XPC_NORETURN +/*! @parseOnly */ +#define XPC_NOTHROW +/*! @parseOnly */ +#define XPC_NONNULL1 +/*! @parseOnly */ +#define XPC_NONNULL2 +/*! @parseOnly */ +#define XPC_NONNULL3 +/*! @parseOnly */ +#define XPC_NONNULL4 +/*! @parseOnly */ +#define XPC_NONNULL5 +/*! @parseOnly */ +#define XPC_NONNULL6 +/*! @parseOnly */ +#define XPC_NONNULL7 +/*! @parseOnly */ +#define XPC_NONNULL8 +/*! @parseOnly */ +#define XPC_NONNULL9 +/*! @parseOnly */ +#define XPC_NONNULL10 +/*! @parseOnly */ +#define XPC_NONNULL11 +/*! @parseOnly */ +#define XPC_NONNULL(n) +/*! @parseOnly */ +#define XPC_NONNULL_ALL +/*! @parseOnly */ +#define XPC_SENTINEL +/*! @parseOnly */ +#define XPC_PURE +/*! @parseOnly */ +#define XPC_WARN_RESULT +/*! @parseOnly */ +#define XPC_MALLOC +/*! @parseOnly */ +#define XPC_UNUSED +/*! @parseOnly */ +#define XPC_PACKED +/*! @parseOnly */ +#define XPC_PRINTF(m, n) +/*! @parseOnly */ +#define XPC_INLINE static inline +/*! @parseOnly */ +#define XPC_NOINLINE +/*! @parseOnly */ +#define XPC_NOIMPL +/*! @parseOnly */ +#define XPC_EXPORT extern +/*! @parseOnly */ +#define XPC_WEAKIMPORT +/*! @parseOnly */ +#define XPC_DEPRECATED +/*! @parseOnly */ +#define XPC_UNAVAILABLE(m) +#endif // __GNUC__ + +__END_DECLS + +#endif // __XPC_BASE_H__ diff --git a/include/xpc/connection.h b/include/xpc/connection.h new file mode 100644 index 0000000..6e7f8ef --- /dev/null +++ b/include/xpc/connection.h @@ -0,0 +1,714 @@ +#ifndef __XPC_CONNECTION_H__ +#define __XPC_CONNECTION_H__ + +#ifndef __XPC_INDIRECT__ +#error "Please #include instead of this file directly." +// For HeaderDoc. +#include +#endif // __XPC_INDIRECT__ + +#ifndef __BLOCKS__ +#error "XPC connections require Blocks support." +#endif // __BLOCKS__ + +__BEGIN_DECLS + +/*! + * @constant XPC_ERROR_CONNECTION_INTERRUPTED + * Will be delivered to the connection's event handler if the remote service + * exited. The connection is still live even in this case, and resending a + * message will cause the service to be launched on-demand. This error serves + * as a client's indication that it should resynchronize any state that it had + * given the service. + * + * Any messages in the queue to be sent will be unwound and canceled when this + * error occurs. In the case where a message waiting to be sent has a reply + * handler, that handler will be invoked with this error. In the context of the + * reply handler, this error indicates that a reply to the message will never + * arrive. + * + * Messages that do not have reply handlers associated with them will be + * silently disposed of. This error will only be given to peer connections. + */ +#define XPC_ERROR_CONNECTION_INTERRUPTED \ + XPC_GLOBAL_OBJECT(_xpc_error_connection_interrupted) +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +const struct _xpc_dictionary_s _xpc_error_connection_interrupted; + +/*! + * @constant XPC_ERROR_CONNECTION_INVALID + * Will be delivered to the connection's event handler if the named service + * provided to xpc_connection_create() could not be found in the XPC service + * namespace. The connection is useless and should be disposed of. + * + * Any messages in the queue to be sent will be unwound and canceled when this + * error occurs, similarly to the behavior when XPC_ERROR_CONNECTION_INTERRUPTED + * occurs. The only difference is that the XPC_ERROR_CONNECTION_INVALID will be + * given to outstanding reply handlers and the connection's event handler. + * + * This error may be given to any type of connection. + */ +#define XPC_ERROR_CONNECTION_INVALID \ + XPC_GLOBAL_OBJECT(_xpc_error_connection_invalid) +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +const struct _xpc_dictionary_s _xpc_error_connection_invalid; + +/*! + * @constant XPC_ERROR_TERMINATION_IMMINENT + * This error will be delivered to a peer connection's event handler when the + * XPC runtime has determined that the program should exit and that all + * outstanding transactions must be wound down, and no new transactions can be + * opened. + * + * After this error has been delivered to the event handler, no more messages + * will be received by the connection. The runtime will still attempt to deliver + * outgoing messages, but this error should be treated as an indication that + * the program will exit very soon, and any outstanding business over the + * connection should be wrapped up as quickly as possible and the connection + * canceled shortly thereafter. + * + * This error will only be delivered to peer connections received through a + * listener or the xpc_main() event handler. + */ +#define XPC_ERROR_TERMINATION_IMMINENT \ + XPC_GLOBAL_OBJECT(_xpc_error_termination_imminent) +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +const struct _xpc_dictionary_s _xpc_error_termination_imminent; + +/*! + * @constant XPC_CONNECTION_MACH_SERVICE_LISTENER + * Passed to xpc_connection_create_mach_service(). This flag indicates that the + * caller is the listener for the named service. This flag may only be passed + * for services which are advertised in the process' launchd.plist(5). You may + * not use this flag to dynamically add services to the Mach bootstrap + * namespace. + */ +#define XPC_CONNECTION_MACH_SERVICE_LISTENER (1 << 0) + +/*! + * @constant XPC_CONNECTION_MACH_SERVICE_PRIVILEGED + * Passed to xpc_connection_create_mach_service(). This flag indicates that the + * job advertising the service name in its launchd.plist(5) should be in the + * privileged Mach bootstrap. This is typically accomplished by placing your + * launchd.plist(5) in /Library/LaunchDaemons. If specified alongside the + * XPC_CONNECTION_MACH_SERVICE_LISTENER flag, this flag is a no-op. + */ +#define XPC_CONNECTION_MACH_SERVICE_PRIVILEGED (1 << 1) + +/*! + * @typedef xpc_finalizer_f + * A function that is invoked when a connection is being torn down and its + * context needs to be freed. The sole argument is the value that was given to + * {@link xpc_connection_set_context} or NULL if no context has been set. It is + * not safe to reference the connection from within this function. + * + * @param value + * The context object that is to be disposed of. + */ +typedef void (*xpc_finalizer_t)(void *value); + +/*! + * @function xpc_connection_create + * Creates a new connection object. + * + * @param name + * If non-NULL, the name of the service with which to connect. The returned + * connection will be a peer. + * + * If NULL, an anonymous listener connection will be created. You can embed the + * ability to create new peer connections in an endpoint, which can be inserted + * into a message and sent to another process . + * + * @param targetq + * The GCD queue to which the event handler block will be submitted. This + * parameter may be NULL, in which case the connection's target queue will be + * libdispatch's default target queue, defined as DISPATCH_TARGET_QUEUE_DEFAULT. + * The target queue may be changed later with a call to + * xpc_connection_set_target_queue(). + * + * @result + * A new connection object. The caller is responsible for disposing of the + * returned object with {@link xpc_release} when it is no longer needed. + * + * @discussion + * This method will succeed even if the named service does not exist. This is + * because the XPC namespace is not queried for the service name until + * the first call to xpc_connection_resume(). + * + * XPC connections, like dispatch sources, are returned in a suspended state, so + * you must call {@link xpc_connection_resume()} in order to begin receiving + * events from the connection. Also like dispatch sources, connections must be + * resumed in order to be safely released. It is a programming error to release + * a suspended connection. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT +xpc_connection_t +xpc_connection_create(const char *name, dispatch_queue_t targetq); + +/*! + * @function xpc_connection_create_mach_service + * Creates a new connection object representing a Mach service. + * + * @param name + * The name of the remote service with which to connect. The service name must + * exist in a Mach bootstrap that is accessible to the process and be advertised + * in a launchd.plist. + * + * @param targetq + * The GCD queue to which the event handler block will be submitted. This + * parameter may be NULL, in which case the connection's target queue will be + * libdispatch's default target queue, defined as DISPATCH_TARGET_QUEUE_DEFAULT. + * The target queue may be changed later with a call to + * xpc_connection_set_target_queue(). + * + * @param flags + * Additional attributes with which to create the connection. + * + * @result + * A new connection object. + * + * @discussion + * If the XPC_CONNECTION_MACH_SERVICE_LISTENER flag is given to this method, + * then the connection returned will be a listener connection. Otherwise, a peer + * connection will be returned. See the documentation for + * {@link xpc_connection_set_event_handler()} for the semantics of listener + * connections versus peer connections. + * + * This method will succeed even if the named service does not exist. This is + * because the Mach namespace is not queried for the service name until the + * first call to {@link xpc_connection_resume()}. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT XPC_NONNULL1 +xpc_connection_t +xpc_connection_create_mach_service(const char *name, dispatch_queue_t targetq, + uint64_t flags); + +/*! + * @function xpc_connection_create_from_endpoint + * Creates a new connection from the given endpoint. + * + * @param endpoint + * The endpoint from which to create the new connection. + * + * @result + * A new peer connection to the listener represented by the given endpoint. + * + * The same responsibilities of setting an event handler and resuming the + * connection after calling xpc_connection_create() apply to the connection + * returned by this API. Since the connection yielded by this API is not + * associated with a name (and therefore is not rediscoverable), this connection + * will receive XPC_ERROR_CONNECTION_INVALID if the listening side crashes, + * exits or cancels the listener connection. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT XPC_NONNULL_ALL +xpc_connection_t +xpc_connection_create_from_endpoint(xpc_endpoint_t endpoint); + +/*! + * @function xpc_connection_set_target_queue + * Sets the target queue of the given connection. + * + * @param connection + * The connection object which is to be manipulated. + * + * @param targetq + * The GCD queue to which the event handler block will be submitted. This + * parameter may be NULL, in which case the connection's target queue will be + * libdispatch's default target queue, defined as DISPATCH_TARGET_QUEUE_DEFAULT. + * + * @discussion + * Setting the target queue is asynchronous and non-preemptive and therefore + * this method will not interrupt the execution of an already-running event + * handler block. Setting the target queue may be likened to issuing a barrier + * to the connection which does the actual work of changing the target queue. + * + * The XPC runtime guarantees this non-preemptiveness even for concurrent target + * queues. If the target queue is a concurrent queue, then XPC still guarantees + * that there will never be more than one invocation of the connection's event + * handler block executing concurrently. If you wish to process events + * concurrently, you can dispatch_async(3) to a concurrent queue from within + * the event handler. + * + * IMPORTANT: When called from within the event handler block, + * dispatch_get_current_queue(3) is NOT guaranteed to return a pointer to the + * queue set with this method. + * + * Despite this seeming inconsistency, the XPC runtime guarantees that, when the + * target queue is a serial queue, the event handler block will execute + * synchonously with respect to other blocks submitted to that same queue. When + * the target queue is a concurrent queue, the event handler block may run + * concurrently with other blocks submitted to that queue, but it will never run + * concurrently with other invocations of itself for the same connection, as + * discussed previously. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 +void +xpc_connection_set_target_queue(xpc_connection_t connection, + dispatch_queue_t targetq); + +/*! + * @function xpc_connection_set_event_handler + * Sets the event handler block for the connection. + * + * @param connection + * The connection object which is to be manipulated. + * + * @param handler + * The event handler block. + * + * @discussion + * Setting the event handler is asynchronous and non-preemptive, and therefore + * this method will not interrupt the execution of an already-running event + * handler block. If the event handler is executing at the time of this call, it + * will finish, and then the connection's event handler will be changed before + * the next invocation of the event handler. The XPC runtime guarantees this + * non-preemptiveness even for concurrent target queues. + * + * Connection event handlers are non-reentrant, so it is safe to call + * xpc_connection_set_event_handler() from within the event handler block. + * + * The event handler's execution should be treated as a barrier to all + * connection activity. When it is executing, the connection will not attempt to + * send or receive messages, including reply messages. Thus, it is not safe to + * call xpc_connection_send_message_with_reply_sync() on the connection from + * within the event handler. + * + * You do not hold a reference on the object received as the event handler's + * only argument. Regardless of the type of object received, it is safe to call + * xpc_retain() on the object to obtain a reference to it. + * + * A connection may receive different events depending upon whether it is a + * listener or not. Any connection may receive an error in its event handler. + * But while normal connections may receive messages in addition to errors, + * listener connections will receive connections and and not messages. + * + * Connections received by listeners are equivalent to those returned by + * xpc_connection_create() with a non-NULL name argument and a NULL targetq + * argument with the exception that you do not hold a reference on them. + * You must set an event handler and resume the connection. If you do not wish + * to accept the connection, you may simply call xpc_connection_cancel() on it + * and return. The runtime will dispose of it for you. + * + * If there is an error in the connection, this handler will be invoked with the + * error dictionary as its argument. This dictionary will be one of the well- + * known XPC_ERROR_* dictionaries. + * + * Regardless of the type of event, ownership of the event object is NOT + * implicitly transferred. Thus, the object will be released and deallocated at + * some point in the future after the event handler returns. If you wish the + * event's lifetime to persist, you must retain it with xpc_retain(). + * + * Connections received through the event handler will be released and + * deallocated after the connection has gone invalid and delivered that event to + * its event handler. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL_ALL +void +xpc_connection_set_event_handler(xpc_connection_t connection, + xpc_handler_t handler); + +/*! + * @function xpc_connection_suspend + * Suspends the connection so that the event handler block will not fire and + * that the connection will not attempt to send any messages it has in its + * queue. All calls to xpc_connection_suspend() must be balanced with calls to + * xpc_connection_resume() before releasing the last reference to the + * connection. + * + * @param connection + * The connection object which is to be manipulated. + * + * @discussion + * Suspension is asynchronous and non-preemptive, and therefore this method will + * not interrupt the execution of an already-running event handler block. If + * the event handler is executing at the time of this call, it will finish, and + * then the connection will be suspended before the next scheduled invocation + * of the event handler. The XPC runtime guarantees this non-preemptiveness even + * for concurrent target queues. + * + * Connection event handlers are non-reentrant, so it is safe to call + * xpc_connection_suspend() from within the event handler block. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL_ALL +void +xpc_connection_suspend(xpc_connection_t connection); + +/*! + * @function xpc_connection_resume + * Resumes the connection. Connections start in a suspended state, so you must + * call xpc_connection_resume() on a connection before it will send or receive + * any messages. + * + * @param connection + * The connection object which is to be manipulated. + * + * @discussion + * In order for a connection to become live, every call to + * xpc_connection_suspend() must be balanced with a call to + * xpc_connection_resume() after the initial call to xpc_connection_resume(). + * After the initial resume of the connection, calling xpc_connection_resume() + * more times than xpc_connection_suspend() has been called is considered an + * error. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL_ALL +void +xpc_connection_resume(xpc_connection_t connection); + +/*! + * @function xpc_connection_send_message + * Sends a message over the connection to the destination service. + * + * @param connection + * The connection over which the message shall be sent. + * + * @param message + * The message to send. This must be a dictionary object. This dictionary is + * logically copied by the connection, so it is safe to modify the dictionary + * after this call. + * + * @discussion + * Messages are delivered in FIFO order. This API is safe to call from multiple + * GCD queues. There is no indication that a message was delivered successfully. + * This is because even once the message has been successfully enqueued on the + * remote end, there are no guarantees about when the runtime will dequeue the + * message and invoke the other connection's event handler block. + * + * If this API is used to send a message that is in reply to another message, + * there is no guarantee of ordering between the invocations of the connection's + * event handler and the reply handler for that message, even if they are + * targeted to the same queue. + * + * After extensive study, we have found that clients who are interested in + * the state of the message on the server end are typically holding open + * transactions related to that message. And the only reliable way to track the + * lifetime of that transaction is at the protocol layer. So the server should + * send a reply message, which upon receiving, will cause the client to close + * its transaction. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL_ALL +void +xpc_connection_send_message(xpc_connection_t connection, xpc_object_t message); + +/*! + * @function xpc_connection_send_barrier + * Issues a barrier against the connection's message-send activity. + * + * @param connection + * The connection against which the barrier is to be issued. + * + * @param barrier + * The barrier block to issue. This barrier prevents concurrent message-send + * activity on the connection. No messages will be sent while the barrier block + * is executing. + * + * @discussion + * XPC guarantees that, even if the connection's target queue is a concurrent + * queue, there are no other messages being sent concurrently while the barrier + * block is executing. XPC does not guarantee that the reciept of messages + * (either through the connection's event handler or through reply handlers) + * will be suspended while the barrier is executing. + * + * A barrier is issued relative to the message-send queue. Thus, if you call + * xpc_connection_send_message() five times and then call + * xpc_connection_send_barrier(), the barrier will be invoked after the fifth + * message has been sent and its memory disposed of. You may safely cancel a + * connection from within a barrier block. + * + * If a barrier is issued after sending a message which expects a reply, the + * behavior is the same as described above. The receipt of a reply message will + * not influence when the barrier runs. + * + * A barrier block can be useful for throttling resource consumption on the + * connected side of a connection. For example, if your connection sends many + * large messages, you can use a barrier to limit the number of messages that + * are inflight at any given time. This can be particularly useful for messages + * that contain kernel resources (like file descriptors) which have a system- + * wide limit. + * + * If a barrier is issued on a canceled connection, it will be invoked + * immediately. If a connection has been canceled and still has outstanding + * barriers, those barriers will be invoked as part of the connection's + * unwinding process. + * + * It is important to note that a barrier block's execution order is not + * guaranteed with respect to other blocks that have been scheduled on the + * target queue of the connection. Or said differently, + * xpc_connection_send_barrier(3) is not equivalent to dispatch_async(3). + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL_ALL +void +xpc_connection_send_barrier(xpc_connection_t connection, + dispatch_block_t barrier); + +/*! + * @function xpc_connection_send_message_with_reply + * Sends a message over the connection to the destination service and associates + * a handler to be invoked when the remote service sends a reply message. + * + * @param connection + * The connection over which the message shall be sent. + * + * @param message + * The message to send. This must be a dictionary object. + * + * @param replyq + * The GCD queue to which the reply handler will be submitted. This may be a + * concurrent queue. + * + * @param handler + * The handler block to invoke when a reply to the message is received from + * the connection. If the remote service exits prematurely before the reply was + * received, the XPC_ERROR_CONNECTION_INTERRUPTED error will be returned. + * If the connection went invalid before the message could be sent, the + * XPC_ERROR_CONNECTION_INVALID error will be returned. + * + * @discussion + * If the given GCD queue is a concurrent queue, XPC cannot guarantee that there + * will not be multiple reply handlers being invoked concurrently. XPC does not + * guarantee any ordering for the invocation of reply handers. So if multiple + * messages are waiting for replies and the connection goes invalid, there is no + * guarantee that the reply handlers will be invoked in FIFO order. Similarly, + * XPC does not guarantee that reply handlers will not run concurrently with + * the connection's event handler in the case that the reply queue and the + * connection's target queue are the same concurrent queue. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 XPC_NONNULL4 +void +xpc_connection_send_message_with_reply(xpc_connection_t connection, + xpc_object_t message, dispatch_queue_t replyq, xpc_handler_t handler); + +/*! + * @function xpc_connection_send_message_with_reply_sync + * Sends a message over the connection and blocks the caller until a reply is + * received. + * + * @param connection + * The connection over which the message shall be sent. + * + * @param message + * The message to send. This must be a dictionary object. + * + * @result + * The message that the remote service sent in reply to the original message. + * If the remote service exits prematurely before the reply was received, the + * XPC_ERROR_CONNECTION_INTERRUPTED error will be returned. If the connection + * went invalid before the message could be sent, the + * XPC_ERROR_CONNECTION_INVALID error will be returned. + * + * You are responsible for releasing the returned object. + * + * @discussion + * This API is primarily for transitional purposes. Its implementation is + * conceptually equivalent to calling xpc_connection_send_message_with_reply() + * and then immediately blocking the calling thread on a semaphore and + * signaling the semaphore from the reply block. + * + * Be judicious about your use of this API. It can block indefinitely, so if you + * are using it to implement an API that can be called from the main thread, you + * may wish to consider allowing the API to take a queue and callback block so + * that results may be delivered asynchronously if possible. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL_ALL XPC_WARN_RESULT XPC_RETURNS_RETAINED +xpc_object_t +xpc_connection_send_message_with_reply_sync(xpc_connection_t connection, + xpc_object_t message); + +/*! + * @function xpc_connection_cancel + * Cancels the connection and ensures that its event handler will not fire + * again. After this call, any messages that have not yet been sent will be + * discarded, and the connection will be unwound. If there are messages that are + * awaiting replies, they will have their reply handlers invoked with the + * XPC_ERROR_CONNECTION_INVALID error. + * + * @param connection + * The connection object which is to be manipulated. + * + * @discussion + * Cancellation is asynchronous and non-preemptive and therefore this method + * will not interrupt the execution of an already-running event handler block. + * If the event handler is executing at the time of this call, it will finish, + * and then the connection will be canceled, causing a final invocation of the + * event handler to be scheduled with the XPC_ERROR_CONNECTION_INVALID error. + * After that invocation, there will be no further invocations of the event + * handler. + * + * The XPC runtime guarantees this non-preemptiveness even for concurrent target + * queues. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL_ALL +void +xpc_connection_cancel(xpc_connection_t connection); + +/*! + * @function xpc_connection_get_name + * Returns the name of the service with which the connections was created. + * + * @param connection + * The connection object which is to be examined. + * + * @result + * The name of the remote service. If you obtained the connection through an + * invocation of another connection's event handler, NULL is returned. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL_ALL XPC_WARN_RESULT +const char * +xpc_connection_get_name(xpc_connection_t connection); + +/*! + * @function xpc_connection_get_euid + * Returns the EUID of the remote peer. + * + * @param connection + * The connection object which is to be examined. + * + * @result + * The EUID of the remote peer at the time the connection was made. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL_ALL XPC_WARN_RESULT +uid_t +xpc_connection_get_euid(xpc_connection_t connection); + +/*! + * @function xpc_connection_get_egid + * Returns the EGID of the remote peer. + * + * @param connection + * The connection object which is to be examined. + * + * @result + * The EGID of the remote peer at the time the connection was made. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL_ALL XPC_WARN_RESULT +gid_t +xpc_connection_get_egid(xpc_connection_t connection); + +/*! + * @function xpc_connection_get_pid + * Returns the PID of the remote peer. + * + * @param connection + * The connection object which is to be examined. + * + * @result + * The PID of the remote peer. + * + * @discussion + * A given PID is not guaranteed to be unique across an entire boot cycle. + * Great care should be taken when dealing with this information, as it can go + * stale after the connection is established. OS X recycles PIDs, and therefore + * another process could spawn and claim the PID before a message is actually + * received from the connection. + * + * XPC will deliver an error to your event handler if the remote process goes + * away, but there are no guarantees as to the timing of this notification's + * delivery either at the kernel layer or at the XPC layer. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL_ALL XPC_WARN_RESULT +pid_t +xpc_connection_get_pid(xpc_connection_t connection); + +/*! + * @function xpc_connection_get_asid + * Returns the audit session identifier of the remote peer. + * + * @param connection + * The connection object which is to be examined. + * + * @result + * The audit session ID of the remote peer at the time the connection was made. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL_ALL XPC_WARN_RESULT +au_asid_t +xpc_connection_get_asid(xpc_connection_t connection); + +/*! + * @function xpc_connection_set_context + * Sets context on an connection. + * + * @param connection + * The connection which is to be manipulated. + * + * @param context + * The context to associate with the connection. + * + * @discussion + * If you must manage the memory of the context object, you must set a finalizer + * to dispose of it. If this method is called on a connection which already has + * context associated with it, the finalizer will NOT be invoked. The finalizer + * is only invoked when the connection is being deallocated. + * + * It is recommended that, instead of changing the actual context pointer + * associated with the object, you instead change the state of the context + * object itself. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 +void +xpc_connection_set_context(xpc_connection_t connection, void *context); + +/*! + * @function xpc_connection_get_context + * Returns the context associated with the connection. + * + * @param connection + * The connection which is to be examined. + * + * @result + * The context associated with the connection. NULL if there has been no context + * associated with the object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL_ALL XPC_WARN_RESULT +void * +xpc_connection_get_context(xpc_connection_t connection); + +/*! + * @function xpc_connection_set_finalizer_f + * Sets the finalizer for the given connection. + * + * @param connection + * The connection on which to set the finalizer. + * + * @param finalizer + * The function that will be invoked when the connection's retain count has + * dropped to zero and is being torn down. + * + * @discussion + * This method disposes of the context value associated with a connection, as + * set by {@link xpc_connection_set_context}. + * + * For many uses of context objects, this API allows for a convenient shorthand + * for freeing them. For example, for a context object allocated with malloc(3): + * + * xpc_connection_set_finalizer_f(object, free); + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 +void +xpc_connection_set_finalizer_f(xpc_connection_t connection, + xpc_finalizer_t finalizer); + +__END_DECLS + +#endif // __XPC_CONNECTION_H__ diff --git a/include/xpc/debug.h b/include/xpc/debug.h new file mode 100644 index 0000000..b50361c --- /dev/null +++ b/include/xpc/debug.h @@ -0,0 +1,23 @@ +#ifndef __XPC_DEBUG_H__ +#define __XPC_DEBUG_H__ + +/*! + * @function xpc_debugger_api_misuse_info + * Returns a pointer to a string describing the reason XPC aborted the calling + * process. On OS X, this will be the same string present in the "Application + * Specific Information" section of the crash report. + * + * @result + * A pointer to the human-readable string describing the reason the caller was + * aborted. If XPC was not responsible for the program's termination, NULL will + * be returned. + * + * @discussion + * This function is only callable from within a debugger. It is not meant to be + * called by the program directly. + */ +XPC_DEBUGGER_EXCL +const char * +xpc_debugger_api_misuse_info(void); + +#endif // __XPC_DEBUG_H__ diff --git a/include/xpc/endpoint.h b/include/xpc/endpoint.h new file mode 100644 index 0000000..b4bac81 --- /dev/null +++ b/include/xpc/endpoint.h @@ -0,0 +1,22 @@ +#ifndef __XPC_ENDPOINT_H__ +#define __XPC_ENDPOINT_H__ + +/*! + * @function xpc_endpoint_create + * Creates a new endpoint from a connection that is suitable for embedding into + * messages. + * + * @param connection + * Only connections obtained through calls to xpc_connection_create*() may be + * given to this API. Passing any other type of connection is not supported and + * will result in undefined behavior. + * + * @result + * A new endpoint object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT XPC_NONNULL1 +xpc_endpoint_t +xpc_endpoint_create(xpc_connection_t connection); + +#endif // __XPC_ENDPOINT_H__ diff --git a/include/xpc/xpc.h b/include/xpc/xpc.h new file mode 100644 index 0000000..8cb7c04 --- /dev/null +++ b/include/xpc/xpc.h @@ -0,0 +1,2637 @@ +// Copyright (c) 2009-2011 Apple Inc. All rights reserved. + +#ifndef __XPC_H__ +#define __XPC_H__ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +__BEGIN_DECLS + +#ifndef __OSX_AVAILABLE_STARTING +#define __OSX_AVAILABLE_STARTING(x, y) +#endif // __OSX_AVAILABLE_STARTING + +#ifndef __XPC_INDIRECT__ +#define __XPC_INDIRECT__ +#endif // __XPC_INDIRECT__ + +#include + +#define XPC_API_VERSION 20121012 + +/*! + * @typedef xpc_type_t + * A type that describes XPC object types. + */ +typedef const struct _xpc_type_s * xpc_type_t; +#ifndef __XPC_BUILDING_XPC__ +#define XPC_TYPE(type) const struct _xpc_type_s type +#endif // __XPC_BUILDING_XPC__ + +/*! + * @typedef xpc_object_t + * A type that can describe all XPC objects. Dictionaries, arrays, strings, etc. + * are all described by this type. + * + * XPC objects are created with a retain count of 1, and therefore it is the + * caller's responsibility to call xpc_release() on them when they are no longer + * needed. + */ + +#if OS_OBJECT_USE_OBJC +/* By default, XPC objects are declared as Objective-C types when building with + * an Objective-C compiler. This allows them to participate in ARC, in RR + * management by the Blocks runtime and in leaks checking by the static + * analyzer, and enables them to be added to Cocoa collections. + * + * See for details. + */ +OS_OBJECT_DECL(xpc_object); +#ifndef __XPC_PROJECT_BUILD__ +#define XPC_DECL(name) typedef xpc_object_t name##_t +#endif // __XPC_PROJECT_BUILD__ + +#define XPC_GLOBAL_OBJECT(object) ((OS_OBJECT_BRIDGE xpc_object_t)&(object)) +#define XPC_RETURNS_RETAINED OS_OBJECT_RETURNS_RETAINED +XPC_INLINE XPC_NONNULL_ALL +void +_xpc_object_validate(xpc_object_t object) { + void *isa = *(void * volatile *)(OS_OBJECT_BRIDGE void *)object; + (void)isa; +} +#else // OS_OBJECT_USE_OBJC +typedef void * xpc_object_t; +#define XPC_DECL(name) typedef struct _##name##_s * name##_t +#define XPC_GLOBAL_OBJECT(object) (&(object)) +#define XPC_RETURNS_RETAINED +#endif // OS_OBJECT_USE_OBJC + +/*! + * @typedef xpc_handler_t + * The type of block that is accepted by the XPC connection APIs. + * + * @param object + * An XPC object that is to be handled. If there was an error, this object will + * be equal to one of the well-known XPC_ERROR_* dictionaries and can be + * compared with the equality operator. + * + * @discussion + * You are not responsible for releasing the event object. + */ +#if __BLOCKS__ +typedef void (^xpc_handler_t)(xpc_object_t object); +#endif // __BLOCKS__ + +/*! + * @define XPC_TYPE_CONNECTION + * A type representing a connection to a named service. This connection is + * bidirectional and can be used to both send and receive messages. A + * connection carries the credentials of the remote service provider. + */ +#define XPC_TYPE_CONNECTION (&_xpc_type_connection) +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +XPC_TYPE(_xpc_type_connection); +XPC_DECL(xpc_connection); + +/*! + * @typedef xpc_connection_handler_t + * The type of the function that will be invoked for a bundled XPC service when + * there is a new connection on the service. + * + * @param connection + * A new connection that is equivalent to one received by a listener connection. + * See the documentation for {@link xpc_connection_set_event_handler} for the + * semantics associated with the received connection. + */ +typedef void (*xpc_connection_handler_t)(xpc_connection_t connection); + +/*! + * @define XPC_TYPE_ENDPOINT + * A type representing a connection in serialized form. Unlike a connection, an + * endpoint is an inert object that does not have any runtime activity + * associated with it. Thus, it is safe to pass an endpoint in a message. Upon + * receiving an endpoint, the recipient can use + * xpc_connection_create_from_endpoint() to create as many distinct connections + * as desired. + */ +#define XPC_TYPE_ENDPOINT (&_xpc_type_endpoint) +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +XPC_TYPE(_xpc_type_endpoint); +XPC_DECL(xpc_endpoint); + +/*! + * @define XPC_TYPE_NULL + * A type representing a null object. This type is useful for disambiguating + * an unset key in a dictionary and one which has been reserved but set empty. + * Also, this type is a way to represent a "null" value in dictionaries, which + * do not accept NULL. + */ +#define XPC_TYPE_NULL (&_xpc_type_null) +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +XPC_TYPE(_xpc_type_null); + +/*! + * @define XPC_TYPE_BOOL + * A type representing a Boolean value. + */ +#define XPC_TYPE_BOOL (&_xpc_type_bool) +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +XPC_TYPE(_xpc_type_bool); + +/*! + * @define XPC_BOOL_TRUE + * A constant representing a Boolean value of true. You may compare a Boolean + * object against this constant to determine its value. + */ +#define XPC_BOOL_TRUE XPC_GLOBAL_OBJECT(_xpc_bool_true) +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +const struct _xpc_bool_s _xpc_bool_true; + +/*! + * @define XPC_BOOL_FALSE + * A constant representing a Boolean value of false. You may compare a Boolean + * object against this constant to determine its value. + */ +#define XPC_BOOL_FALSE XPC_GLOBAL_OBJECT(_xpc_bool_false) +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +const struct _xpc_bool_s _xpc_bool_false; + +/*! + * @define XPC_TYPE_INT64 + * A type representing a signed, 64-bit integer value. + */ +#define XPC_TYPE_INT64 (&_xpc_type_int64) +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +XPC_TYPE(_xpc_type_int64); + +/*! + * @define XPC_TYPE_UINT64 + * A type representing an unsigned, 64-bit integer value. + */ +#define XPC_TYPE_UINT64 (&_xpc_type_uint64) +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +XPC_TYPE(_xpc_type_uint64); + +/*! + * @define XPC_TYPE_DOUBLE + * A type representing an IEEE-compliant, double-precision floating point value. + */ +#define XPC_TYPE_DOUBLE (&_xpc_type_double) +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +XPC_TYPE(_xpc_type_double); + +/*! + * @define XPC_TYPE_DATE +* A type representing a date interval. The interval is with respect to the + * Unix epoch. XPC dates are in Unix time and are thus unaware of local time + * or leap seconds. + */ +#define XPC_TYPE_DATE (&_xpc_type_date) +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +XPC_TYPE(_xpc_type_date); + +/*! + * @define XPC_TYPE_DATA + * A type representing a an arbitrary buffer of bytes. + */ +#define XPC_TYPE_DATA (&_xpc_type_data) +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +XPC_TYPE(_xpc_type_data); + +/*! + * @define XPC_TYPE_STRING + * A type representing a NUL-terminated C-string. + */ +#define XPC_TYPE_STRING (&_xpc_type_string) +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +XPC_TYPE(_xpc_type_string); + +/*! + * @define XPC_TYPE_UUID + * A type representing a Universally Unique Identifier as defined by uuid(3). + */ +#define XPC_TYPE_UUID (&_xpc_type_uuid) +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +XPC_TYPE(_xpc_type_uuid); + +/*! + * @define XPC_TYPE_FD + * A type representing a POSIX file descriptor. + */ +#define XPC_TYPE_FD (&_xpc_type_fd) +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +XPC_TYPE(_xpc_type_fd); + +/*! + * @define XPC_TYPE_SHMEM + * A type representing a region of shared memory. + */ +#define XPC_TYPE_SHMEM (&_xpc_type_shmem) +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +XPC_TYPE(_xpc_type_shmem); + +/*! + * @define XPC_TYPE_ARRAY + * A type representing an array of XPC objects. This array must be contiguous, + * i.e. it cannot contain NULL values. If you wish to indicate that a slot + * is empty, you can insert a null object. The array will grow as needed to + * accommodate more objects. + */ +#define XPC_TYPE_ARRAY (&_xpc_type_array) +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +XPC_TYPE(_xpc_type_array); + +/*! + * @define XPC_TYPE_DICTIONARY + * A type representing a dictionary of XPC objects, keyed off of C-strings. + * You may insert NULL values into this collection. The dictionary will grow + * as needed to accommodate more key/value pairs. + */ +#define XPC_TYPE_DICTIONARY (&_xpc_type_dictionary) +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +XPC_TYPE(_xpc_type_dictionary); + +/*! + * @define XPC_TYPE_ERROR + * A type representing an error object. Errors in XPC are dictionaries, but + * xpc_get_type() will return this type when given an error object. You + * cannot create an error object directly; XPC will only give them to handlers. + * These error objects have pointer values that are constant across the lifetime + * of your process and can be safely compared. + * + * These constants are enumerated in the header for the connection object. Error + * dictionaries may reserve keys so that they can be queried to obtain more + * detailed information about the error. Currently, the only reserved key is + * XPC_ERROR_KEY_DESCRIPTION. + */ +#define XPC_TYPE_ERROR (&_xpc_type_error) +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +XPC_TYPE(_xpc_type_error); + +/*! + * @define XPC_ERROR_KEY_DESCRIPTION + * In an error dictionary, querying for this key will return a string object + * that describes the error in a human-readable way. + */ +#define XPC_ERROR_KEY_DESCRIPTION _xpc_error_key_description +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +const char *const _xpc_error_key_description; + +/*! + * @define XPC_EVENT_KEY_NAME + * In an event dictionary, this querying for this key will return a string + * object that describes the event. + */ +#define XPC_EVENT_KEY_NAME _xpc_event_key_name +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +const char *const _xpc_event_key_name; + +#ifndef __XPC_BUILDING_XPC__ +#include +#include +#if __BLOCKS__ +#include +#include +#endif // __BLOCKS__ +#undef __XPC_INDIRECT__ +#include +#endif // __XPC_BUILDING_XPC__ + +#pragma mark XPC Object Protocol +/*! + * @function xpc_retain + * + * @abstract + * Increments the reference count of an object. + * + * @param object + * The object which is to be manipulated. + * + * @result + * The object which was given. + * + * @discussion + * Calls to xpc_retain() must be balanced with calls to xpc_release() + * to avoid leaking memory. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 +xpc_object_t +xpc_retain(xpc_object_t object); +#if OS_OBJECT_USE_OBJC_RETAIN_RELEASE +#undef xpc_retain +#define xpc_retain(object) ({ xpc_object_t _o = (object); \ + _xpc_object_validate(_o); [_o retain]; }) +#endif // OS_OBJECT_USE_OBJC_RETAIN_RELEASE + +/*! + * @function xpc_release + * + * @abstract + * Decrements the reference count of an object. + * + * @param object + * The object which is to be manipulated. + * + * @discussion + * The caller must take care to balance retains and releases. When creating or + * retaining XPC objects, the creator obtains a reference on the object. Thus, + * it is the caller's responsibility to call xpc_release() on those objects when + * they are no longer needed. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 +void +xpc_release(xpc_object_t object); +#if OS_OBJECT_USE_OBJC_RETAIN_RELEASE +#undef xpc_release +#define xpc_release(object) ({ xpc_object_t _o = (object); \ + _xpc_object_validate(_o); [_o release]; }) +#endif // OS_OBJECT_USE_OBJC_RETAIN_RELEASE + +/*! + * @function xpc_get_type + * + * @abstract + * Returns the type of an object. + * + * @param object + * The object to examine. + * + * @result + * An opaque pointer describing the type of the object. This pointer is suitable + * direct comparison to exported type constants with the equality operator. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL_ALL XPC_WARN_RESULT +xpc_type_t +xpc_get_type(xpc_object_t object); + +/*! + * @function xpc_copy + * + * @abstract + * Creates a copy of the object. + * + * @param object + * The object to copy. + * + * @result + * The new object. NULL if the object type does not support copying or if + * sufficient memory for the copy could not be allocated. Service objects do + * not support copying. + * + * @discussion + * When called on an array or dictionary, xpc_copy() will perform a deep copy. + * + * The object returned is not necessarily guaranteed to be a new object, and + * whether it is will depend on the implementation of the object being copied. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL_ALL XPC_WARN_RESULT XPC_RETURNS_RETAINED +xpc_object_t +xpc_copy(xpc_object_t object); + +/*! + * @function xpc_equal + * + * @abstract + * Compares two objects for equality. + * + * @param object1 + * The first object to compare. + * + * @param object2 + * The second object to compare. + * + * @result + * Returns true if the objects are equal, otherwise false. Two objects must be + * of the same type in order to be equal. + * + * For two arrays to be equal, they must contain the same values at the + * same indexes. For two dictionaries to be equal, they must contain the same + * values for the same keys. + * + * Two objects being equal implies that their hashes (as returned by xpc_hash()) + * are also equal. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 XPC_WARN_RESULT +bool +xpc_equal(xpc_object_t object1, xpc_object_t object2); + +/*! + * @function xpc_hash + * + * @abstract + * Calculates a hash value for the given object. + * + * @param object + * The object for which to calculate a hash value. This value may be modded + * with a table size for insertion into a dictionary-like data structure. + * + * @result + * The calculated hash value. + * + * @discussion + * Note that the computed hash values for any particular type and value of an + * object can change from across releases and platforms and should not be + * assumed to be constant across all time and space or stored persistently. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 XPC_WARN_RESULT +size_t +xpc_hash(xpc_object_t object); + +/*! + * @function xpc_copy_description + * + * @abstract + * Copies a debug string describing the object. + * + * @param object + * The object which is to be examined. + * + * @result + * A string describing object which contains information useful for debugging. + * This string should be disposed of with free(3) when done. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_WARN_RESULT XPC_NONNULL1 +char * +xpc_copy_description(xpc_object_t object); + +#pragma mark XPC Object Types +#pragma mark Null +/*! + * @function xpc_null_create + * + * @abstract + * Creates an XPC object representing the null object. + * + * @result + * A new null object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_RETURNS_RETAINED XPC_WARN_RESULT +xpc_object_t +xpc_null_create(void); + +#pragma mark Boolean +/*! + * @function xpc_bool_create + * + * @abstract + * Creates an XPC Boolean object. + * + * @param value + * The Boolean primitive value which is to be boxed. + * + * @result + * A new Boolean object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_RETURNS_RETAINED XPC_WARN_RESULT +xpc_object_t +xpc_bool_create(bool value); + +/*! + * @function xpc_bool_get_value + * + * @abstract + * Returns the underlying Boolean value from the object. + * + * @param xbool + * The Boolean object which is to be examined. + * + * @result + * The underlying Boolean value or false if the given object was not an XPC + * Boolean object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +bool +xpc_bool_get_value(xpc_object_t xbool); + +#pragma mark Signed Integer +/*! + * @function xpc_int64_create + * + * @abstract + * Creates an XPC signed integer object. + * + * @param value + * The signed integer value which is to be boxed. + * + * @result + * A new signed integer object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT +xpc_object_t +xpc_int64_create(int64_t value); + +/*! + * @function xpc_int64_get_value + * + * @abstract + * Returns the underlying signed 64-bit integer value from an object. + * + * @param xint + * The signed integer object which is to be examined. + * + * @result + * The underlying signed 64-bit value or 0 if the given object was not an XPC + * integer object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +int64_t +xpc_int64_get_value(xpc_object_t xint); + +#pragma mark Unsigned Integer +/*! + * @function xpc_uint64_create + * + * @abstract + * Creates an XPC unsigned integer object. + * + * @param value + * The unsigned integer value which is to be boxed. + * + * @result + * A new unsigned integer object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT +xpc_object_t +xpc_uint64_create(uint64_t value); + +/*! + * @function xpc_uint64_get_value + * + * @abstract + * Returns the underlying unsigned 64-bit integer value from an object. + * + * @param xuint + * The unsigned integer object which is to be examined. + * + * @result + * The underlying unsigned integer value or 0 if the given object was not an XPC + * unsigned integer object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +uint64_t +xpc_uint64_get_value(xpc_object_t xuint); + +#pragma mark Double +/*! + * @function xpc_double_create + * + * @abstract + * Creates an XPC double object. + * + * @param value + * The floating point quantity which is to be boxed. + * + * @result + * A new floating point object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT +xpc_object_t +xpc_double_create(double value); + +/*! + * @function xpc_double_get_value + * + * @abstract + * Returns the underlying double-precision floating point value from an object. + * + * @param xdouble + * The floating point object which is to be examined. + * + * @result + * The underlying floating point value or NAN if the given object was not an XPC + * floating point object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +double +xpc_double_get_value(xpc_object_t xdouble); + +#pragma mark Date +/*! + * @function xpc_date_create + * + * @abstract + * Creates an XPC date object. + * + * @param interval + * The date interval which is to be boxed. Negative values indicate the number + * of nanoseconds before the epoch. Positive values indicate the number of + * nanoseconds after the epoch. + * + * @result + * A new date object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT +xpc_object_t +xpc_date_create(int64_t interval); + +/*! + * @function xpc_date_create_from_current + * + * @abstract + * Creates an XPC date object representing the current date. + * + * @result + * A new date object representing the current date. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT +xpc_object_t +xpc_date_create_from_current(void); + +/*! + * @function xpc_date_get_value + * + * @abstract + * Returns the underlying date interval from an object. + * + * @param xdate + * The date object which is to be examined. + * + * @result + * The underlying date interval or 0 if the given object was not an XPC date + * object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +int64_t +xpc_date_get_value(xpc_object_t xdate); + +#pragma mark Data +/*! + * @function xpc_data_create + * + * @abstract + * Creates an XPC object representing buffer of bytes. + * + * @param bytes + * The buffer of bytes which is to be boxed. You may create an empty data object + * by passing NULL for this parameter and 0 for the length. Passing NULL with + * any other length will result in undefined behavior. + * + * @param length + * The number of bytes which are to be boxed. + * + * @result + * A new data object. + * + * @discussion + * This method will copy the buffer given into internal storage. After calling + * this method, it is safe to dispose of the given buffer. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT +xpc_object_t +xpc_data_create(const void *bytes, size_t length); + +/*! + * @function xpc_data_create_with_dispatch_data + * + * @abstract + * Creates an XPC object representing buffer of bytes described by the given GCD + * data object. + * + * @param ddata + * The GCD data object containing the bytes which are to be boxed. This object + * is retained by the data object. + * + * @result + * A new data object. + * + * @discussion + * The object returned by this method will refer to the buffer returned by + * dispatch_data_create_map(). The point where XPC will make the call to + * dispatch_data_create_map() is undefined. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT XPC_NONNULL1 +xpc_object_t +xpc_data_create_with_dispatch_data(dispatch_data_t ddata); + +/*! + * @function xpc_data_get_length + * + * @abstract + * Returns the length of the data encapsulated by an XPC data object. + * + * @param xdata + * The data object which is to be examined. + * + * @result + * The length of the underlying boxed data or 0 if the given object was not an + * XPC data object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +size_t +xpc_data_get_length(xpc_object_t xdata); + +/*! + * @function xpc_data_get_bytes_ptr + * + * @abstract + * Returns a pointer to the internal storage of a data object. + * + * @param xdata + * The data object which is to be examined. + * + * @result + * A pointer to the underlying boxed data or NULL if the given object was not an + * XPC data object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +const void * +xpc_data_get_bytes_ptr(xpc_object_t xdata); + +/*! + * @function xpc_data_get_bytes + * + * @abstract + * Copies the bytes stored in an data objects into the specified buffer. + * + * @param xdata + * The data object which is to be examined. + * + * @param buffer + * The buffer in which to copy the data object's bytes. + * + * @param off + * The offset at which to begin the copy. If this offset is greater than the + * length of the data element, nothing is copied. Pass 0 to start the copy + * at the beginning of the buffer. + * + * @param length + * The length of the destination buffer. + * + * @result + * The number of bytes that were copied into the buffer or 0 if the given object + * was not an XPC data object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 XPC_NONNULL2 +size_t +xpc_data_get_bytes(xpc_object_t xdata, + void *buffer, size_t off, size_t length); + +#pragma mark String +/*! + * @function xpc_string_create + * + * @abstract + * Creates an XPC object representing a NUL-terminated C-string. + * + * @param string + * The C-string which is to be boxed. + * + * @result + * A new string object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT XPC_NONNULL1 +xpc_object_t +xpc_string_create(const char *string); + +/*! + * @function xpc_string_create_with_format + * + * @abstract + * Creates an XPC object representing a C-string that is generated from the + * given format string and arguments. + * + * @param fmt + * The printf(3)-style format string from which to construct the final C-string + * to be boxed. + * + * @param ... + * The arguments which correspond to those specified in the format string. + * + * @result + * A new string object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT XPC_NONNULL1 +XPC_PRINTF(1, 2) +xpc_object_t +xpc_string_create_with_format(const char *fmt, ...); + +/*! + * @function xpc_string_create_with_format_and_arguments + * + * @abstract + * Creates an XPC object representing a C-string that is generated from the + * given format string and argument list pointer. + * + * @param fmt + * The printf(3)-style format string from which to construct the final C-string + * to be boxed. + * + * @param ap + * A pointer to the arguments which correspond to those specified in the format + * string. + * + * @result + * A new string object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT XPC_NONNULL1 +XPC_PRINTF(1, 0) +xpc_object_t +xpc_string_create_with_format_and_arguments(const char *fmt, va_list ap); + +/*! + * @function xpc_string_get_length + * + * @abstract + * Returns the length of the underlying string. + * + * @param xstring + * The string object which is to be examined. + * + * @result + * The length of the underlying string, not including the NUL-terminator, or 0 + * if the given object was not an XPC string object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL_ALL +size_t +xpc_string_get_length(xpc_object_t xstring); + +/*! + * @function xpc_string_get_string_ptr + * + * @abstract + * Returns a pointer to the internal storage of a string object. + * + * @param xstring + * The string object which is to be examined. + * + * @result + * A pointer to the string object's internal storage or NULL if the given object + * was not an XPC string object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +const char * +xpc_string_get_string_ptr(xpc_object_t xstring); + +#pragma mark UUID +/*! + * @function xpc_uuid_create + * + * @abstract + * Creates an XPC object representing a universally-unique identifier (UUID) as + * described by uuid(3). + * + * @param uuid + * The UUID which is to be boxed. + * + * @result + * A new UUID object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT +xpc_object_t +xpc_uuid_create(const uuid_t uuid); + +/*! + * @function xpc_uuid_get_bytes + * + * @abstract + * Returns a pointer to the the boxed UUID bytes in an XPC UUID object. + * + * @param xuuid + * The UUID object which is to be examined. + * + * @result + * The underlying uuid_t bytes or NULL if the given object was not + * an XPC UUID object. The returned pointer may be safely passed to the uuid(3) + * APIs. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 +const uint8_t * +xpc_uuid_get_bytes(xpc_object_t xuuid); + +#pragma mark File Descriptors +/*! + * @function xpc_fd_create + * + * @abstract + * Creates an XPC object representing a POSIX file descriptor. + * + * @param fd + * The file descriptor which is to be boxed. + * + * @result + * A new file descriptor object. NULL if sufficient memory could not be + * allocated or if the given file descriptor was not valid. + * + * @discussion + * This method performs the equivalent of a dup(2) on the descriptor, and thus + * it is safe to call close(2) on the descriptor after boxing it with a file + * descriptor object. + * + * IMPORTANT: Pointer equality is the ONLY valid test for equality between two + * file descriptor objects. There is no reliable way to determine whether two + * file descriptors refer to the same inode with the same capabilities, so two + * file descriptor objects created from the same underlying file descriptor + * number will not compare equally with xpc_equal(). This is also true of a + * file descriptor object created using xpc_copy() and the original. + * + * This also implies that two collections containing file descriptor objects + * cannot be equal unless the exact same object was inserted into both. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT +xpc_object_t +xpc_fd_create(int fd); + +/*! + * @function xpc_fd_dup + * + * @abstract + * Returns a file descriptor that is equivalent to the one boxed by the file + * file descriptor object. + * + * @param xfd + * The file descriptor object which is to be examined. + * + * @result + * A file descriptor that is equivalent to the one originally given to + * xpc_fd_create(). If the descriptor could not be created or if the given + * object was not an XPC file descriptor, -1 is returned. + * + * @discussion + * Multiple invocations of xpc_fd_dup() will not return the same file descriptor + * number, but they will return descriptors that are equivalent, as though they + * had been created by dup(2). + * + * The caller is responsible for calling close(2) on the returned descriptor. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +int +xpc_fd_dup(xpc_object_t xfd); + +#pragma mark Shared Memory +/*! + * @function xpc_shmem_create + * + * @abstract + * Creates an XPC object representing the given shared memory region. + * + * @param region + * A pointer to a region of shared memory, created through a call to mmap(2) + * with the MAP_SHARED flag, which is to be boxed. + * + * @param length + * The length of the region. + * + * @result + * A new shared memory object. + * + * @discussion + * Only memory regions whose exact characteristics are known to the caller + * should be boxed using this API. Memory returned from malloc(3) may not be + * safely shared on either OS X or iOS because the underlying virtual memory + * objects for malloc(3)ed allocations are owned by the malloc(3) subsystem and + * not the caller of malloc(3). + * + * If you wish to share a memory region that you receive from another subsystem, + * part of the interface contract with that other subsystem must include how to + * create the region of memory, or sharing it may be unsafe. + * + * Certain operations may internally fragment a region of memory in a way that + * would truncate the range detected by the shared memory object. vm_copy(), for + * example, may split the region into multiple parts to avoid copying certain + * page ranges. For this reason, it is recommended that you delay all VM + * operations until the shared memory object has been created so that the VM + * system knows that the entire range is intended for sharing. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT XPC_NONNULL1 +xpc_object_t +xpc_shmem_create(void *region, size_t length); + +/*! + * @function xpc_shmem_map + * + * @abstract + * Maps the region boxed by the XPC shared memory object into the caller's + * address space. + * + * @param xshmem + * The shared memory object to be examined. + * + * @param region + * On return, this will point to the region at which the shared memory was + * mapped. + * + * @result + * The length of the region that was mapped. If the mapping failed or if the + * given object was not an XPC shared memory object, 0 is returned. The length + * of the mapped region will always be an integral page size, even if the + * creator of the region specified a non-integral page size. + * + * @discussion + * The resulting region must be disposed of with munmap(2). + * + * It is the responsibility of the caller to manage protections on the new + * region accordingly. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL_ALL +size_t +xpc_shmem_map(xpc_object_t xshmem, void **region); + +#pragma mark Array +/*! + * @typedef xpc_array_applier_t + * A block to be invoked for every value in the array. + * + * @param index + * The current index in the iteration. + * + * @param value + * The current value in the iteration. + * + * @result + * A Boolean indicating whether iteration should continue. + */ +#ifdef __BLOCKS__ +typedef bool (^xpc_array_applier_t)(size_t index, xpc_object_t value); +#endif // __BLOCKS__ + +/*! + * @function xpc_array_create + * + * @abstract + * Creates an XPC object representing an array of XPC objects. + * + * @discussion + * This array must be contiguous and cannot contain any NULL values. If you + * wish to insert the equivalent of a NULL value, you may use the result of + * {@link xpc_null_create}. + * + * @param objects + * An array of XPC objects which is to be boxed. The order of this array is + * preserved in the object. If this array contains a NULL value, the behavior + * is undefined. This parameter may be NULL only if the count is 0. + * + * @param count + * The number of objects in the given array. If the number passed is less than + * the actual number of values in the array, only the specified number of items + * are inserted into the resulting array. If the number passed is more than + * the the actual number of values, the behavior is undefined. + * + * @result + * A new array object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT +xpc_object_t +xpc_array_create(const xpc_object_t *objects, size_t count); + +/*! + * @function xpc_array_set_value + * + * @abstract + * Inserts the specified object into the array at the specified index. + * + * @param xarray + * The array object which is to be manipulated. + * + * @param index + * The index at which to insert the value. This value must lie within the index + * space of the array (0 to N-1 inclusive, where N is the count of the array). + * If the index is outside that range, the behavior is undefined. + * + * @param value + * The object to insert. This value is retained by the array and cannot be + * NULL. If there is already a value at the specified index, it is released, + * and the new value is inserted in its place. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL3 +void +xpc_array_set_value(xpc_object_t xarray, size_t index, xpc_object_t value); + +/*! + * @function xpc_array_append_value + * + * @abstract + * Appends an object to an XPC array. + * + * @param xarray + * The array object which is to be manipulated. + * + * @param value + * The object to append. This object is retained by the array. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 +void +xpc_array_append_value(xpc_object_t xarray, xpc_object_t value); + +/*! + * @function xpc_array_get_count + * + * @abstract + * Returns the count of values currently in the array. + * + * @param xarray + * The array object which is to be examined. + * + * @result + * The count of values in the array or 0 if the given object was not an XPC + * array. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +size_t +xpc_array_get_count(xpc_object_t xarray); + +/*! + * @function xpc_array_get_value + * + * @abstract + * Returns the value at the specified index in the array. + * + * @param xarray + * The array object which is to be examined. + * + * @param index + * The index of the value to obtain. This value must lie within the range of + * indexes as specified in xpc_array_set_value(). + * + * @result + * The object at the specified index within the array or NULL if the given + * object was not an XPC array. + * + * @discussion + * This method does not grant the caller a reference to the underlying object, + * and thus the caller is not responsible for releasing the object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL_ALL +xpc_object_t +xpc_array_get_value(xpc_object_t xarray, size_t index); + +/*! + * @function xpc_array_apply + * + * @abstract + * Invokes the given block for every value in the array. + * + * @param xarray + * The array object which is to be examined. + * + * @param applier + * The block which this function applies to every element in the array. + * + * @result + * A Boolean indicating whether iteration of the array completed successfully. + * Iteration will only fail if the applier block returns false. + * + * @discussion + * You should not modify an array's contents during iteration. The array indexes + * are iterated in order. + */ +#ifdef __BLOCKS__ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL_ALL +bool +xpc_array_apply(xpc_object_t xarray, xpc_array_applier_t applier); +#endif // __BLOCKS__ + +#pragma mark Array Primitive Setters +/*! + * @define XPC_ARRAY_APPEND + * A constant that may be passed as the destination index to the class of + * primitive XPC array setters indicating that the given primitive should be + * appended to the array. + */ +#define XPC_ARRAY_APPEND ((size_t)(-1)) + +/*! + * @function xpc_array_set_bool + * + * @abstract + * Inserts a bool (primitive) value into an array. + * + * @param xarray + * The array object which is to be manipulated. + * + * @param index + * The index at which to insert the value. This value must lie within the index + * space of the array (0 to N-1 inclusive, where N is the count of the array) or + * be XPC_ARRAY_APPEND. If the index is outside that range, the behavior is + * undefined. + * + * @param value + * The bool value to insert. After calling this method, the XPC + * object corresponding to the primitive value inserted may be safely retrieved + * with {@link xpc_array_get_value()}. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 +void +xpc_array_set_bool(xpc_object_t xarray, size_t index, bool value); + +/*! + * @function xpc_array_set_int64 + * + * @abstract + * Inserts an int64_t (primitive) value into an array. + * + * @param xarray + * The array object which is to be manipulated. + * + * @param index + * The index at which to insert the value. This value must lie within the index + * space of the array (0 to N-1 inclusive, where N is the count of the array) or + * be XPC_ARRAY_APPEND. If the index is outside that range, the behavior is + * undefined. + * + * @param value + * The int64_t value to insert. After calling this method, the XPC + * object corresponding to the primitive value inserted may be safely retrieved + * with {@link xpc_array_get_value()}. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 +void +xpc_array_set_int64(xpc_object_t xarray, size_t index, int64_t value); + +/*! + * @function xpc_array_set_uint64 + * + * @abstract + * Inserts a uint64_t (primitive) value into an array. + * + * @param xarray + * The array object which is to be manipulated. + * + * @param index + * The index at which to insert the value. This value must lie within the index + * space of the array (0 to N-1 inclusive, where N is the count of the array) or + * be XPC_ARRAY_APPEND. If the index is outside that range, the behavior is + * undefined. + * + * @param value + * The uint64_t value to insert. After calling this method, the XPC + * object corresponding to the primitive value inserted may be safely retrieved + * with {@link xpc_array_get_value()}. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 +void +xpc_array_set_uint64(xpc_object_t xarray, size_t index, uint64_t value); + +/*! + * @function xpc_array_set_double + * + * @abstract + * Inserts a double (primitive) value into an array. + * + * @param xarray + * The array object which is to be manipulated. + * + * @param index + * The index at which to insert the value. This value must lie within the index + * space of the array (0 to N-1 inclusive, where N is the count of the array) or + * be XPC_ARRAY_APPEND. If the index is outside that range, the behavior is + * undefined. + * + * @param value + * The double value to insert. After calling this method, the XPC + * object corresponding to the primitive value inserted may be safely retrieved + * with {@link xpc_array_get_value()}. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 +void +xpc_array_set_double(xpc_object_t xarray, size_t index, double value); + +/*! + * @function xpc_array_set_date + * + * @abstract + * Inserts a date value into an array. + * + * @param xarray + * The array object which is to be manipulated. + * + * @param index + * The index at which to insert the value. This value must lie within the index + * space of the array (0 to N-1 inclusive, where N is the count of the array) or + * be XPC_ARRAY_APPEND. If the index is outside that range, the behavior is + * undefined. + * + * @param value + * The date value to insert, represented as an int64_t. After + * calling this method, the XPC object corresponding to the primitive value + * inserted may be safely retrieved with {@link xpc_array_get_value()}. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 +void +xpc_array_set_date(xpc_object_t xarray, size_t index, int64_t value); + +/*! + * @function xpc_array_set_data + * + * @abstract + * Inserts a raw data value into an array. + * + * @param xarray + * The array object which is to be manipulated. + * + * @param index + * The index at which to insert the value. This value must lie within the index + * space of the array (0 to N-1 inclusive, where N is the count of the array) or + * be XPC_ARRAY_APPEND. If the index is outside that range, the behavior is + * undefined. + * + * @param bytes + * The raw data to insert. After calling this method, the XPC object + * corresponding to the primitive value inserted may be safely retrieved with + * {@link xpc_array_get_value()}. + * + * @param length + * The length of the data. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL3 +void +xpc_array_set_data(xpc_object_t xarray, size_t index, const void *bytes, + size_t length); + +/*! + * @function xpc_array_set_string + * + * @abstract + * Inserts a C string into an array. + * + * @param xarray + * The array object which is to be manipulated. + * + * @param index + * The index at which to insert the value. This value must lie within the index + * space of the array (0 to N-1 inclusive, where N is the count of the array) or + * be XPC_ARRAY_APPEND. If the index is outside that range, the behavior is + * undefined. + * + * @param string + * The C string to insert. After calling this method, the XPC object + * corresponding to the primitive value inserted may be safely retrieved with + * {@link xpc_array_get_value()}. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL3 +void +xpc_array_set_string(xpc_object_t xarray, size_t index, const char *string); + +/*! + * @function xpc_array_set_uuid + * + * @abstract + * Inserts a uuid_t (primitive) value into an array. + * + * @param xarray + * The array object which is to be manipulated. + * + * @param index + * The index at which to insert the value. This value must lie within the index + * space of the array (0 to N-1 inclusive, where N is the count of the array) or + * be XPC_ARRAY_APPEND. If the index is outside that range, the behavior is + * undefined. + * + * @param uuid + * The UUID primitive to insert. After calling this method, the XPC object + * corresponding to the primitive value inserted may be safely retrieved with + * {@link xpc_array_get_value()}. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 +void +xpc_array_set_uuid(xpc_object_t xarray, size_t index, const uuid_t uuid); + +/*! + * @function xpc_array_set_fd + * + * @abstract + * Inserts a file descriptor into an array. + * + * @param xarray + * The array object which is to be manipulated. + * + * @param index + * The index at which to insert the value. This value must lie within the index + * space of the array (0 to N-1 inclusive, where N is the count of the array) or + * be XPC_ARRAY_APPEND. If the index is outside that range, the behavior is + * undefined. + * + * @param fd + * The file descriptor to insert. After calling this method, the XPC object + * corresponding to the primitive value inserted may be safely retrieved with + * {@link xpc_array_get_value()}. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 +void +xpc_array_set_fd(xpc_object_t xarray, size_t index, int fd); + +/*! + * @function xpc_array_set_connection + * + * @abstract + * Inserts a connection into an array. + * + * @param xarray + * The array object which is to be manipulated. + * + * @param index + * The index at which to insert the value. This value must lie within the index + * space of the array (0 to N-1 inclusive, where N is the count of the array) or + * be XPC_ARRAY_APPEND. If the index is outside that range, the behavior is + * undefined. + * + * @param connection + * The connection to insert. After calling this method, the XPC object + * corresponding to the primitive value inserted may be safely retrieved with + * {@link xpc_array_get_value()}. The connection is NOT retained by the array. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 +void +xpc_array_set_connection(xpc_object_t xarray, size_t index, + xpc_connection_t connection); + +#pragma mark Array Primitive Getters +/*! + * @function xpc_array_get_bool + * + * @abstract + * Gets a bool primitive value from an array directly. + * + * @param xarray + * The array which is to be examined. + * + * @param index + * The index of the value to obtain. This value must lie within the index space + * of the array (0 to N-1 inclusive, where N is the count of the array). If the + * index is outside that range, the behavior is undefined. + * + * @result + * The underlying bool value at the specified index. false if the + * value at the specified index is not a Boolean value. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +bool +xpc_array_get_bool(xpc_object_t xarray, size_t index); + +/*! + * @function xpc_array_get_int64 + * + * @abstract + * Gets an int64_t primitive value from an array directly. + * + * @param xarray + * The array which is to be examined. + * + * @param index + * The index of the value to obtain. This value must lie within the index space + * of the array (0 to N-1 inclusive, where N is the count of the array). If the + * index is outside that range, the behavior is undefined. + * + * @result + * The underlying int64_t value at the specified index. 0 if the + * value at the specified index is not a signed integer value. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +int64_t +xpc_array_get_int64(xpc_object_t xarray, size_t index); + +/*! + * @function xpc_array_get_uint64 + * + * @abstract + * Gets a uint64_t primitive value from an array directly. + * + * @param xarray + * The array which is to be examined. + * + * @param index + * The index of the value to obtain. This value must lie within the index space + * of the array (0 to N-1 inclusive, where N is the count of the array). If the + * index is outside that range, the behavior is undefined. + * + * @result + * The underlying uint64_t value at the specified index. 0 if the + * value at the specified index is not an unsigned integer value. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +uint64_t +xpc_array_get_uint64(xpc_object_t xarray, size_t index); + +/*! + * @function xpc_array_get_double + * + * @abstract + * Gets a double primitive value from an array directly. + * + * @param xarray + * The array which is to be examined. + * + * @param index + * The index of the value to obtain. This value must lie within the index space + * of the array (0 to N-1 inclusive, where N is the count of the array). If the + * index is outside that range, the behavior is undefined. + * + * @result + * The underlying double value at the specified index. NAN if the + * value at the specified index is not a floating point value. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +double +xpc_array_get_double(xpc_object_t xarray, size_t index); + +/*! + * @function xpc_array_get_date + * + * @abstract + * Gets a date interval from an array directly. + * + * @param xarray + * The array which is to be examined. + * + * @param index + * The index of the value to obtain. This value must lie within the index space + * of the array (0 to N-1 inclusive, where N is the count of the array). If the + * index is outside that range, the behavior is undefined. + * + * @result + * The underlying date interval at the specified index. 0 if the value at the + * specified index is not a date value. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +int64_t +xpc_array_get_date(xpc_object_t xarray, size_t index); + +/*! + * @function xpc_array_get_data + * + * @abstract + * Gets a pointer to the raw bytes of a data object from an array directly. + * + * @param xarray + * The array which is to be examined. + * + * @param index + * The index of the value to obtain. This value must lie within the index space + * of the array (0 to N-1 inclusive, where N is the count of the array). If the + * index is outside that range, the behavior is undefined. + * + * @param length + * Upon return output, will contain the length of the data corresponding to the + * specified key. + * + * @result + * The underlying bytes at the specified index. NULL if the value at the + * specified index is not a data value. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +const void * +xpc_array_get_data(xpc_object_t xarray, size_t index, size_t *length); + +/*! + * @function xpc_array_get_string + * + * @abstract + * Gets a C string value from an array directly. + * + * @param xarray + * The array which is to be examined. + * + * @param index + * The index of the value to obtain. This value must lie within the index space + * of the array (0 to N-1 inclusive, where N is the count of the array). If the + * index is outside that range, the behavior is undefined. + * + * @result + * The underlying C string at the specified index. NULL if the value at the + * specified index is not a C string value. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +const char * +xpc_array_get_string(xpc_object_t xarray, size_t index); + +/*! + * @function xpc_array_get_uuid + * + * @abstract + * Gets a uuid_t value from an array directly. + * + * @param xarray + * The array which is to be examined. + * + * @param index + * The index of the value to obtain. This value must lie within the index space + * of the array (0 to N-1 inclusive, where N is the count of the array). If the + * index is outside that range, the behavior is undefined. + * + * @result + * The underlying uuid_t value at the specified index. The null + * UUID if the value at the specified index is not a UUID value. The returned + * pointer may be safely passed to the uuid(3) APIs. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +const uint8_t * +xpc_array_get_uuid(xpc_object_t xarray, size_t index); + +/*! + * @function xpc_array_dup_fd + * + * @abstract + * Gets a file descriptor from an array directly. + * + * @param xarray + * The array which is to be examined. + * + * @param index + * The index of the value to obtain. This value must lie within the index space + * of the array (0 to N-1 inclusive, where N is the count of the array). If the + * index is outside that range, the behavior is undefined. + * + * @result + * A new file descriptor created from the value at the specified index. You are + * responsible for close(2)ing this descriptor. -1 if the value at the specified + * index is not a file descriptor value. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +int +xpc_array_dup_fd(xpc_object_t xarray, size_t index); + +/*! + * @function xpc_array_create_connection + * + * @abstract + * Creates a connection object from an array directly. + * + * @param xarray + * The array which is to be examined. + * + * @param index + * The index of the value to obtain. This value must lie within the index space + * of the array (0 to N-1 inclusive, where N is the count of the array). If the + * index is outside that range, the behavior is undefined. + * + * @result + * A new connection created from the value at the specified index. You are + * responsible for calling xpc_release() on the returned connection. NULL if the + * value at the specified index is not an endpoint containing a connection. Each + * call to this method for the same index in the same array will yield a + * different connection. See {@link xpc_connection_create_from_endpoint()} for + * discussion as to the responsibilities when dealing with the returned + * connection. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT XPC_NONNULL1 +xpc_connection_t +xpc_array_create_connection(xpc_object_t xarray, size_t index); + +/*! + * @function xpc_array_get_dictionary + * + * @abstract + * Returns the dictionary at the specified index in the array. + * + * @param xarray + * The array object which is to be examined. + * + * @param index + * The index of the value to obtain. This value must lie within the range of + * indexes as specified in xpc_array_set_value(). + * + * @result + * The object at the specified index within the array or NULL if the given + * object was not an XPC array or if the the value at the specified index was + * not a dictionary. + * + * @discussion + * This method does not grant the caller a reference to the underlying object, + * and thus the caller is not responsible for releasing the object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_11, __IPHONE_9_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL_ALL +xpc_object_t +xpc_array_get_dictionary(xpc_object_t self, size_t index); + +/*! + * @function xpc_array_get_array + * + * @abstract + * Returns the array at the specified index in the array. + * + * @param xarray + * The array object which is to be examined. + * + * @param index + * The index of the value to obtain. This value must lie within the range of + * indexes as specified in xpc_array_set_value(). + * + * @result + * The object at the specified index within the array or NULL if the given + * object was not an XPC array or if the the value at the specified index was + * not an array. + * + * @discussion + * This method does not grant the caller a reference to the underlying object, + * and thus the caller is not responsible for releasing the object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_11, __IPHONE_9_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL_ALL +xpc_object_t +xpc_array_get_array(xpc_object_t self, size_t index); + +#pragma mark Dictionary +/*! + * @typedef xpc_dictionary_applier_t + * A block to be invoked for every key/value pair in the dictionary. + * + * @param key + * The current key in the iteration. + * + * @param value + * The current value in the iteration. + * + * @result + * A Boolean indicating whether iteration should continue. + */ +#ifdef __BLOCKS__ +typedef bool (^xpc_dictionary_applier_t)(const char *key, xpc_object_t value); +#endif // __BLOCKS__ + +/*! + * @function xpc_dictionary_create + * + * @abstract + * Creates an XPC object representing a dictionary of XPC objects keyed to + * C-strings. + * + * @param keys + * An array of C-strings that are to be the keys for the values to be inserted. + * Each element of this array is copied into the dictionary's internal storage. + * If any element of this array is NULL, the behavior is undefined. + * + * @param values + * A C-array that is parallel to the array of keys, consisting of objects that + * are to be inserted. Each element in this array is retained. Elements in this + * array may be NULL. + * + * @param count + * The number of key/value pairs in the given arrays. If the count is less than + * the actual count of values, only that many key/value pairs will be inserted + * into the dictionary. + * + * If the count is more than the the actual count of key/value pairs, the + * behavior is undefined. If one array is NULL and the other is not, the + * behavior is undefined. If both arrays are NULL and the count is non-0, the + * behavior is undefined. + * + * @result + * The new dictionary object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT +xpc_object_t +xpc_dictionary_create(const char * const *keys, const xpc_object_t *values, + size_t count); + +/*! + * @function xpc_dictionary_create_reply + * + * @abstract + * Creates a dictionary that is in reply to the given dictionary. + * + * @param original + * The original dictionary that is to be replied to. + * + * @result + * The new dictionary object. NULL if the object was not a dictionary with a + * reply context. + * + * @discussion + * After completing successfully on a dictionary, this method may not be called + * again on that same dictionary. Attempts to do so will return NULL. + * + * When this dictionary is sent across the reply connection, the remote end's + * reply handler is invoked. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT XPC_NONNULL_ALL +xpc_object_t +xpc_dictionary_create_reply(xpc_object_t original); + +/*! + * @function xpc_dictionary_set_value + * + * @abstract + * Sets the value for the specified key to the specified object. + * + * @param xdict + * The dictionary object which is to be manipulated. + * + * @param key + * The key for which the value shall be set. + * + * @param value + * The object to insert. The object is retained by the dictionary. If there + * already exists a value for the specified key, the old value is released + * and overwritten by the new value. This parameter may be NULL, in which case + * the value corresponding to the specified key is deleted if present. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 +void +xpc_dictionary_set_value(xpc_object_t xdict, const char *key, + xpc_object_t value); + +/*! + * @function xpc_dictionary_get_value + * + * @abstract + * Returns the value for the specified key. + * + * @param xdict + * The dictionary object which is to be examined. + * + * @param key + * The key whose value is to be obtained. + * + * @result + * The object for the specified key within the dictionary. NULL if there is no + * value associated with the specified key or if the given object was not an + * XPC dictionary. + * + * @discussion + * This method does not grant the caller a reference to the underlying object, + * and thus the caller is not responsible for releasing the object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 XPC_NONNULL2 +xpc_object_t +xpc_dictionary_get_value(xpc_object_t xdict, const char *key); + +/*! + * @function xpc_dictionary_get_count + * + * @abstract + * Returns the number of values stored in the dictionary. + * + * @param xdict + * The dictionary object which is to be examined. + * + * @result + * The number of values stored in the dictionary or 0 if the given object was + * not an XPC dictionary. Calling xpc_dictionary_set_value() with a non-NULL + * value will increment the count. Calling xpc_dictionary_set_value() with a + * NULL value will decrement the count. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +size_t +xpc_dictionary_get_count(xpc_object_t xdict); + +/*! + * @function xpc_dictionary_apply + * + * @abstract + * Invokes the given block for every key/value pair in the dictionary. + * + * @param xdict + * The dictionary object which is to be examined. + * + * @param applier + * The block which this function applies to every key/value pair in the + * dictionary. + * + * @result + * A Boolean indicating whether iteration of the dictionary completed + * successfully. Iteration will only fail if the applier block returns false. + * + * @discussion + * You should not modify a dictionary's contents during iteration. There is no + * guaranteed order of iteration over dictionaries. + */ +#ifdef __BLOCKS__ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL_ALL +bool +xpc_dictionary_apply(xpc_object_t xdict, xpc_dictionary_applier_t applier); +#endif // __BLOCKS__ + +/*! + * @function xpc_dictionary_get_remote_connection + * + * @abstract + * Returns the connection from which the dictionary was received. + * + * @param xdict + * The dictionary object which is to be examined. + * + * @result + * If the dictionary was received by a connection event handler or a dictionary + * created through xpc_dictionary_create_reply(), a connection object over which + * a reply message can be sent is returned. For any other dictionary, NULL is + * returned. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL_ALL +xpc_connection_t +xpc_dictionary_get_remote_connection(xpc_object_t xdict); + +#pragma mark Dictionary Primitive Setters +/*! + * @function xpc_dictionary_set_bool + * + * @abstract + * Inserts a bool (primitive) value into a dictionary. + * + * @param xdict + * The dictionary which is to be manipulated. + * + * @param key + * The key for which the primitive value shall be set. + * + * @param value + * The bool value to insert. After calling this method, the XPC + * object corresponding to the primitive value inserted may be safely retrieved + * with {@link xpc_dictionary_get_value()}. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 +void +xpc_dictionary_set_bool(xpc_object_t xdict, const char *key, bool value); + +/*! + * @function xpc_dictionary_set_int64 + * + * @abstract + * Inserts an int64_t (primitive) value into a dictionary. + * + * @param xdict + * The dictionary which is to be manipulated. + * + * @param key + * The key for which the primitive value shall be set. + * + * @param value + * The int64_t value to insert. After calling this method, the XPC + * object corresponding to the primitive value inserted may be safely retrieved + * with {@link xpc_dictionary_get_value()}. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 +void +xpc_dictionary_set_int64(xpc_object_t xdict, const char *key, int64_t value); + +/*! + * @function xpc_dictionary_set_uint64 + * + * @abstract + * Inserts a uint64_t (primitive) value into a dictionary. + * + * @param xdict + * The dictionary which is to be manipulated. + * + * @param key + * The key for which the primitive value shall be set. + * + * @param value + * The uint64_t value to insert. After calling this method, the XPC + * object corresponding to the primitive value inserted may be safely retrieved + * with {@link xpc_dictionary_get_value()}. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 +void +xpc_dictionary_set_uint64(xpc_object_t xdict, const char *key, uint64_t value); + +/*! + * @function xpc_dictionary_set_double + * + * @abstract + * Inserts a double (primitive) value into a dictionary. + * + * @param xdict + * The dictionary which is to be manipulated. + * + * @param key + * The key for which the primitive value shall be set. + * + * @param value + * The double value to insert. After calling this method, the XPC + * object corresponding to the primitive value inserted may be safely retrieved + * with {@link xpc_dictionary_get_value()}. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 +void +xpc_dictionary_set_double(xpc_object_t xdict, const char *key, double value); + +/*! + * @function xpc_dictionary_set_date + * + * @abstract + * Inserts a date (primitive) value into a dictionary. + * + * @param xdict + * The dictionary which is to be manipulated. + * + * @param key + * The key for which the primitive value shall be set. + * + * @param value + * The date value to insert. After calling this method, the XPC object + * corresponding to the primitive value inserted may be safely retrieved with + * {@link xpc_dictionary_get_value()}. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 +void +xpc_dictionary_set_date(xpc_object_t xdict, const char *key, int64_t value); + +/*! + * @function xpc_dictionary_set_data + * + * @abstract + * Inserts a raw data value into a dictionary. + * + * @param xdict + * The dictionary which is to be manipulated. + * + * @param key + * The key for which the primitive value shall be set. + * + * @param bytes + * The bytes to insert. After calling this method, the XPC object corresponding + * to the primitive value inserted may be safely retrieved with + * {@link xpc_dictionary_get_value()}. + * + * @param length + * The length of the data. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 +void +xpc_dictionary_set_data(xpc_object_t xdict, const char *key, const void *bytes, + size_t length); + +/*! + * @function xpc_dictionary_set_string + * + * @abstract + * Inserts a C string value into a dictionary. + * + * @param xdict + * The dictionary which is to be manipulated. + * + * @param key + * The key for which the primitive value shall be set. + * + * @param string + * The C string to insert. After calling this method, the XPC object + * corresponding to the primitive value inserted may be safely retrieved with + * {@link xpc_dictionary_get_value()}. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 +void +xpc_dictionary_set_string(xpc_object_t xdict, const char *key, + const char *string); + +/*! + * @function xpc_dictionary_set_uuid + * + * @abstract + * Inserts a uuid (primitive) value into an array. + * + * @param xdict + * The dictionary which is to be manipulated. + * + * @param key + * The key for which the primitive value shall be set. + * + * @param uuid + * The uuid_t value to insert. After calling this method, the XPC + * object corresponding to the primitive value inserted may be safely retrieved + * with {@link xpc_dictionary_get_value()}. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 +void +xpc_dictionary_set_uuid(xpc_object_t xdict, const char *key, const uuid_t uuid); + +/*! + * @function xpc_dictionary_set_fd + * + * @abstract + * Inserts a file descriptor into a dictionary. + * + * @param xdict + * The dictionary which is to be manipulated. + * + * @param key + * The key for which the primitive value shall be set. + * + * @param fd + * The file descriptor to insert. After calling this method, the XPC object + * corresponding to the primitive value inserted may be safely retrieved + * with {@link xpc_dictionary_get_value()}. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 +void +xpc_dictionary_set_fd(xpc_object_t xdict, const char *key, int fd); + +/*! + * @function xpc_dictionary_set_connection + * + * @abstract + * Inserts a connection into a dictionary. + * + * @param xdict + * The dictionary which is to be manipulated. + * + * @param key + * The key for which the primitive value shall be set. + * + * @param connection + * The connection to insert. After calling this method, the XPC object + * corresponding to the primitive value inserted may be safely retrieved + * with {@link xpc_dictionary_get_value()}. The connection is NOT retained by + * the dictionary. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 +void +xpc_dictionary_set_connection(xpc_object_t xdict, const char *key, + xpc_connection_t connection); + +#pragma mark Dictionary Primitive Getters +/*! + * @function xpc_dictionary_get_bool + * + * @abstract + * Gets a bool primitive value from a dictionary directly. + * + * @param xdict + * The dictionary object which is to be examined. + * + * @param key + * The key whose value is to be obtained. + * + * @result + * The underlying bool value for the specified key. false if the + * the value for the specified key is not a Boolean value or if there is no + * value for the specified key. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL_ALL +bool +xpc_dictionary_get_bool(xpc_object_t xdict, const char *key); + +/*! + * @function xpc_dictionary_get_int64 + * + * @abstract + * Gets an int64 primitive value from a dictionary directly. + * + * @param xdict + * The dictionary object which is to be examined. + * + * @param key + * The key whose value is to be obtained. + * + * @result + * The underlying int64_t value for the specified key. 0 if the + * value for the specified key is not a signed integer value or if there is no + * value for the specified key. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL_ALL +int64_t +xpc_dictionary_get_int64(xpc_object_t xdict, const char *key); + +/*! + * @function xpc_dictionary_get_uint64 + * + * @abstract + * Gets a uint64 primitive value from a dictionary directly. + * + * @param xdict + * The dictionary object which is to be examined. + * + * @param key + * The key whose value is to be obtained. + * + * @result + * The underlying uint64_t value for the specified key. 0 if the + * value for the specified key is not an unsigned integer value or if there is + * no value for the specified key. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL_ALL +uint64_t +xpc_dictionary_get_uint64(xpc_object_t xdict, const char *key); + +/*! + * @function xpc_dictionary_get_double + * + * @abstract + * Gets a double primitive value from a dictionary directly. + * + * @param xdict + * The dictionary object which is to be examined. + * + * @param key + * The key whose value is to be obtained. + * + * @result + * The underlying double value for the specified key. NAN if the + * value for the specified key is not a floating point value or if there is no + * value for the specified key. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL_ALL +double +xpc_dictionary_get_double(xpc_object_t xdict, const char *key); + +/*! + * @function xpc_dictionary_get_date + * + * @abstract + * Gets a date value from a dictionary directly. + * + * @param xdict + * The dictionary object which is to be examined. + * + * @param key + * The key whose value is to be obtained. + * + * @result + * The underlying date interval for the specified key. 0 if the value for the + * specified key is not a date value or if there is no value for the specified + * key. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL_ALL +int64_t +xpc_dictionary_get_date(xpc_object_t xdict, const char *key); + +/*! + * @function xpc_dictionary_get_data + * + * @abstract + * Gets a raw data value from a dictionary directly. + * + * @param xdict + * The dictionary object which is to be examined. + * + * @param key + * The key whose value is to be obtained. + * + * @param length + * For the data type, the third parameter, upon output, will contain the length + * of the data corresponding to the specified key. May be NULL. + * + * @result + * The underlying raw data for the specified key. NULL if the value for the + * specified key is not a data value or if there is no value for the specified + * key. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 +const void * +xpc_dictionary_get_data(xpc_object_t xdict, const char *key, size_t *length); + +/*! + * @function xpc_dictionary_get_string + * + * @abstract + * Gets a C string value from a dictionary directly. + * + * @param xdict + * The dictionary object which is to be examined. + * + * @param key + * The key whose value is to be obtained. + * + * @result + * The underlying C string for the specified key. NULL if the value for the + * specified key is not a C string value or if there is no value for the + * specified key. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL_ALL +const char * +xpc_dictionary_get_string(xpc_object_t xdict, const char *key); + +/*! + * @function xpc_dictionary_get_uuid + * + * @abstract + * Gets a uuid value from a dictionary directly. + * + * @param xdict + * The dictionary object which is to be examined. + * + * @param key + * The key whose value is to be obtained. + * + * @result + * The underlying uuid_t value for the specified key. NULL is the + * value at the specified index is not a UUID value. The returned pointer may be + * safely passed to the uuid(3) APIs. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 XPC_NONNULL2 +const uint8_t * +xpc_dictionary_get_uuid(xpc_object_t xdict, const char *key); + +/*! + * @function xpc_dictionary_dup_fd + * + * @abstract + * Creates a file descriptor from a dictionary directly. + * + * @param xdict + * The dictionary object which is to be examined. + * + * @param key + * The key whose value is to be obtained. + * + * @result + * A new file descriptor created from the value for the specified key. You are + * responsible for close(2)ing this descriptor. -1 if the value for the + * specified key is not a file descriptor value or if there is no value for the + * specified key. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL_ALL +int +xpc_dictionary_dup_fd(xpc_object_t xdict, const char *key); + +/*! + * @function xpc_dictionary_create_connection + * + * @abstract + * Creates a connection from a dictionary directly. + * + * @param xdict + * The dictionary object which is to be examined. + * + * @param key + * The key whose value is to be obtained. + * + * @result + * A new connection created from the value for the specified key. You are + * responsible for calling xpc_release() on the returned connection. NULL if the + * value for the specified key is not an endpoint containing a connection or if + * there is no value for the specified key. Each call to this method for the + * same key in the same dictionary will yield a different connection. See + * {@link xpc_connection_create_from_endpoint()} for discussion as to the + * responsibilities when dealing with the returned connection. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT XPC_NONNULL_ALL +xpc_connection_t +xpc_dictionary_create_connection(xpc_object_t xdict, const char *key); + +/*! + * @function xpc_dictionary_get_dictionary + * + * @abstract + * Returns the dictionary value for the specified key. + * + * @param xdict + * The dictionary object which is to be examined. + * + * @param key + * The key whose value is to be obtained. + * + * @result + * The object for the specified key within the dictionary. NULL if there is no + * value associated with the specified key, if the given object was not an + * XPC dictionary, or if the object for the specified key is not a dictionary. + * + * @discussion + * This method does not grant the caller a reference to the underlying object, + * and thus the caller is not responsible for releasing the object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_11, __IPHONE_9_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL_ALL +xpc_object_t +xpc_dictionary_get_dictionary(xpc_object_t self, const char *key); + +/*! + * @function xpc_dictionary_get_array + * + * @abstract + * Returns the array value for the specified key. + * + * @param xdict + * The dictionary object which is to be examined. + * + * @param key + * The key whose value is to be obtained. + * + * @result + * The object for the specified key within the dictionary. NULL if there is no + * value associated with the specified key, if the given object was not an + * XPC dictionary, or if the object for the specified key is not an array. + * + * @discussion + * This method does not grant the caller a reference to the underlying object, + * and thus the caller is not responsible for releasing the object. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_11, __IPHONE_9_0) +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL_ALL +xpc_object_t +xpc_dictionary_get_array(xpc_object_t self, const char *key); + +#pragma mark Runtime +/*! + * @function xpc_main + * The springboard into the XPCService runtime. This function will set up your + * service bundle's listener connection and manage it automatically. After this + * initial setup, this function will, by default, call dispatch_main(). You may + * override this behavior by setting the RunLoopType key in your XPC service + * bundle's Info.plist under the XPCService dictionary. + * + * @param handler + * The handler with which to accept new connections. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NORETURN XPC_NONNULL1 +void +xpc_main(xpc_connection_handler_t handler); + +#if XPC_HOSTING_OLD_MAIN +typedef void (*xpc_service_event_handler_t)(xpc_connection_t, xpc_object_t); + +__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_7, __MAC_10_7, __IPHONE_5_0, __IPHONE_5_0) +XPC_EXPORT XPC_NORETURN XPC_NONNULL3 +void +xpc_service_main(int argc, const char *argv[], + xpc_service_event_handler_t handler); +#endif // XPC_HOSTING_OLD_MAIN + +#pragma mark Transactions +/*! + * @function xpc_transaction_begin + * Informs the XPC runtime that a transaction has begun and that the service + * should not exit due to inactivity. + * + * @discussion + * A service with no outstanding transactions may automatically exit due to + * inactivity as determined by the system. + * + * This function may be used to manually manage transactions in cases where + * their automatic management (as described below) does not meet the needs of an + * XPC service. This function also updates the transaction count used for sudden + * termination, i.e. vproc_transaction_begin(), and these two interfaces may be + * used in combination. + * + * The XPC runtime will automatically begin a transaction on behalf of a service + * when a new message is received. If no reply message is expected, the + * transaction is automatically ended when the connection event handler returns. + * If a reply message is created, the transaction will end when the reply + * message is sent or released. An XPC service may use xpc_transaction_begin() + * and xpc_transaction_end() to inform the XPC runtime about activity that + * occurs outside of this common pattern. + * + * When the XPC runtime has determined that the service should exit, the event + * handlers for all active peer connections will receive + * {@link XPC_ERROR_TERMINATION_IMMINENT} as an indication that they should + * unwind their existing transactions. After this error is delivered to a + * connection's event handler, no more messages will be delivered to the + * connection. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +void +xpc_transaction_begin(void); + +/*! + * @function xpc_transaction_end + * Informs the XPC runtime that a transaction has ended. + * + * @discussion + * As described in {@link xpc_transaction_begin()}, this API may be used + * interchangeably with vproc_transaction_end(). + * + * See the discussion for {@link xpc_transaction_begin()} for details regarding + * the XPC runtime's idle-exit policy. + */ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT +void +xpc_transaction_end(void); + +#pragma mark XPC Event Stream +/*! + * @function xpc_set_event_stream_handler + * Sets the event handler to invoke when streamed events are received. + * + * @param stream + * The name of the event stream for which this handler will be invoked. + * + * @param targetq + * The GCD queue to which the event handler block will be submitted. This + * parameter may be NULL, in which case the connection's target queue will be + * libdispatch's default target queue, defined as DISPATCH_TARGET_QUEUE_DEFAULT. + * + * @param handler + * The event handler block. The event which this block receives as its first + * parameter will always be a dictionary which contains the XPC_EVENT_KEY_NAME + * key. The value for this key will be a string whose value is the name assigned + * to the XPC event specified in the launchd.plist. Future keys may be added to + * this dictionary. + * + * @discussion + * Multiple calls to this function for the same event stream will result in + * undefined behavior. + */ +#if __BLOCKS__ +__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL3 +void +xpc_set_event_stream_handler(const char *stream, dispatch_queue_t targetq, + xpc_handler_t handler); +#endif // __BLOCKS__ + +__END_DECLS + +#endif // __XPC_H__ diff --git a/libc++/shared_mutex b/libc++/shared_mutex new file mode 100644 index 0000000..dcb9394 --- /dev/null +++ b/libc++/shared_mutex @@ -0,0 +1,501 @@ +// -*- C++ -*- +//===------------------------ shared_mutex --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_SHARED_MUTEX +#define _LIBCPP_SHARED_MUTEX + +/* + shared_mutex synopsis + +// C++1y + +namespace std +{ + +class shared_mutex // C++17 +{ +public: + shared_mutex(); + ~shared_mutex(); + + shared_mutex(const shared_mutex&) = delete; + shared_mutex& operator=(const shared_mutex&) = delete; + + // Exclusive ownership + void lock(); // blocking + bool try_lock(); + void unlock(); + + // Shared ownership + void lock_shared(); // blocking + bool try_lock_shared(); + void unlock_shared(); + + typedef implementation-defined native_handle_type; // See 30.2.3 + native_handle_type native_handle(); // See 30.2.3 +}; + +class shared_timed_mutex +{ +public: + shared_timed_mutex(); + ~shared_timed_mutex(); + + shared_timed_mutex(const shared_timed_mutex&) = delete; + shared_timed_mutex& operator=(const shared_timed_mutex&) = delete; + + // Exclusive ownership + void lock(); // blocking + bool try_lock(); + template + bool try_lock_for(const chrono::duration& rel_time); + template + bool try_lock_until(const chrono::time_point& abs_time); + void unlock(); + + // Shared ownership + void lock_shared(); // blocking + bool try_lock_shared(); + template + bool + try_lock_shared_for(const chrono::duration& rel_time); + template + bool + try_lock_shared_until(const chrono::time_point& abs_time); + void unlock_shared(); +}; + +template +class shared_lock +{ +public: + typedef Mutex mutex_type; + + // Shared locking + shared_lock() noexcept; + explicit shared_lock(mutex_type& m); // blocking + shared_lock(mutex_type& m, defer_lock_t) noexcept; + shared_lock(mutex_type& m, try_to_lock_t); + shared_lock(mutex_type& m, adopt_lock_t); + template + shared_lock(mutex_type& m, + const chrono::time_point& abs_time); + template + shared_lock(mutex_type& m, + const chrono::duration& rel_time); + ~shared_lock(); + + shared_lock(shared_lock const&) = delete; + shared_lock& operator=(shared_lock const&) = delete; + + shared_lock(shared_lock&& u) noexcept; + shared_lock& operator=(shared_lock&& u) noexcept; + + void lock(); // blocking + bool try_lock(); + template + bool try_lock_for(const chrono::duration& rel_time); + template + bool try_lock_until(const chrono::time_point& abs_time); + void unlock(); + + // Setters + void swap(shared_lock& u) noexcept; + mutex_type* release() noexcept; + + // Getters + bool owns_lock() const noexcept; + explicit operator bool () const noexcept; + mutex_type* mutex() const noexcept; +}; + +template + void swap(shared_lock& x, shared_lock& y) noexcept; + +} // std + +*/ + +#include <__config> + +#if _LIBCPP_STD_VER > 11 || defined(_LIBCPP_BUILDING_SHARED_MUTEX) + +#include <__mutex_base> + +#include <__undef_min_max> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +#pragma GCC system_header +#endif + +#ifdef _LIBCPP_HAS_NO_THREADS +#error is not supported on this single threaded system +#else // !_LIBCPP_HAS_NO_THREADS + +_LIBCPP_BEGIN_NAMESPACE_STD + +struct _LIBCPP_TYPE_VIS __shared_mutex_base +{ + mutex __mut_; + condition_variable __gate1_; + condition_variable __gate2_; + unsigned __state_; + + static const unsigned __write_entered_ = 1U << (sizeof(unsigned)*__CHAR_BIT__ - 1); + static const unsigned __n_readers_ = ~__write_entered_; + + __shared_mutex_base(); + _LIBCPP_INLINE_VISIBILITY ~__shared_mutex_base() = default; + + __shared_mutex_base(const __shared_mutex_base&) = delete; + __shared_mutex_base& operator=(const __shared_mutex_base&) = delete; + + // Exclusive ownership + void lock(); // blocking + bool try_lock(); + void unlock(); + + // Shared ownership + void lock_shared(); // blocking + bool try_lock_shared(); + void unlock_shared(); + +// typedef implementation-defined native_handle_type; // See 30.2.3 +// native_handle_type native_handle(); // See 30.2.3 +}; + + +#if _LIBCPP_STD_VER > 14 +class _LIBCPP_TYPE_VIS shared_mutex +{ + __shared_mutex_base __base; +public: + shared_mutex() : __base() {} + _LIBCPP_INLINE_VISIBILITY ~shared_mutex() = default; + + shared_mutex(const shared_mutex&) = delete; + shared_mutex& operator=(const shared_mutex&) = delete; + + // Exclusive ownership + _LIBCPP_INLINE_VISIBILITY void lock() { return __base.lock(); } + _LIBCPP_INLINE_VISIBILITY bool try_lock() { return __base.try_lock(); } + _LIBCPP_INLINE_VISIBILITY void unlock() { return __base.unlock(); } + + // Shared ownership + _LIBCPP_INLINE_VISIBILITY void lock_shared() { return __base.lock_shared(); } + _LIBCPP_INLINE_VISIBILITY bool try_lock_shared() { return __base.try_lock_shared(); } + _LIBCPP_INLINE_VISIBILITY void unlock_shared() { return __base.unlock_shared(); } + +// typedef __shared_mutex_base::native_handle_type native_handle_type; +// _LIBCPP_INLINE_VISIBILITY native_handle_type native_handle() { return __base::unlock_shared(); } +}; +#endif + + +class _LIBCPP_TYPE_VIS shared_timed_mutex +{ + __shared_mutex_base __base; +public: + shared_timed_mutex(); + _LIBCPP_INLINE_VISIBILITY ~shared_timed_mutex() = default; + + shared_timed_mutex(const shared_timed_mutex&) = delete; + shared_timed_mutex& operator=(const shared_timed_mutex&) = delete; + + // Exclusive ownership + void lock(); + bool try_lock(); + template + _LIBCPP_INLINE_VISIBILITY + bool + try_lock_for(const chrono::duration<_Rep, _Period>& __rel_time) + { + return try_lock_until(chrono::steady_clock::now() + __rel_time); + } + template + bool + try_lock_until(const chrono::time_point<_Clock, _Duration>& __abs_time); + void unlock(); + + // Shared ownership + void lock_shared(); + bool try_lock_shared(); + template + _LIBCPP_INLINE_VISIBILITY + bool + try_lock_shared_for(const chrono::duration<_Rep, _Period>& __rel_time) + { + return try_lock_shared_until(chrono::steady_clock::now() + __rel_time); + } + template + bool + try_lock_shared_until(const chrono::time_point<_Clock, _Duration>& __abs_time); + void unlock_shared(); +}; + +template +bool +shared_timed_mutex::try_lock_until( + const chrono::time_point<_Clock, _Duration>& __abs_time) +{ + unique_lock __lk(__base.__mut_); + if (__base.__state_ & __base.__write_entered_) + { + while (true) + { + cv_status __status = __base.__gate1_.wait_until(__lk, __abs_time); + if ((__base.__state_ & __base.__write_entered_) == 0) + break; + if (__status == cv_status::timeout) + return false; + } + } + __base.__state_ |= __base.__write_entered_; + if (__base.__state_ & __base.__n_readers_) + { + while (true) + { + cv_status __status = __base.__gate2_.wait_until(__lk, __abs_time); + if ((__base.__state_ & __base.__n_readers_) == 0) + break; + if (__status == cv_status::timeout) + { + __base.__state_ &= ~__base.__write_entered_; + __base.__gate1_.notify_all(); + return false; + } + } + } + return true; +} + +template +bool +shared_timed_mutex::try_lock_shared_until( + const chrono::time_point<_Clock, _Duration>& __abs_time) +{ + unique_lock __lk(__base.__mut_); + if ((__base.__state_ & __base.__write_entered_) || (__base.__state_ & __base.__n_readers_) == __base.__n_readers_) + { + while (true) + { + cv_status status = __base.__gate1_.wait_until(__lk, __abs_time); + if ((__base.__state_ & __base.__write_entered_) == 0 && + (__base.__state_ & __base.__n_readers_) < __base.__n_readers_) + break; + if (status == cv_status::timeout) + return false; + } + } + unsigned __num_readers = (__base.__state_ & __base.__n_readers_) + 1; + __base.__state_ &= ~__base.__n_readers_; + __base.__state_ |= __num_readers; + return true; +} + +template +class shared_lock +{ +public: + typedef _Mutex mutex_type; + +private: + mutex_type* __m_; + bool __owns_; + +public: + _LIBCPP_INLINE_VISIBILITY + shared_lock() _NOEXCEPT + : __m_(nullptr), + __owns_(false) + {} + + _LIBCPP_INLINE_VISIBILITY + explicit shared_lock(mutex_type& __m) + : __m_(&__m), + __owns_(true) + {__m_->lock_shared();} + + _LIBCPP_INLINE_VISIBILITY + shared_lock(mutex_type& __m, defer_lock_t) _NOEXCEPT + : __m_(&__m), + __owns_(false) + {} + + _LIBCPP_INLINE_VISIBILITY + shared_lock(mutex_type& __m, try_to_lock_t) + : __m_(&__m), + __owns_(__m.try_lock_shared()) + {} + + _LIBCPP_INLINE_VISIBILITY + shared_lock(mutex_type& __m, adopt_lock_t) + : __m_(&__m), + __owns_(true) + {} + + template + _LIBCPP_INLINE_VISIBILITY + shared_lock(mutex_type& __m, + const chrono::time_point<_Clock, _Duration>& __abs_time) + : __m_(&__m), + __owns_(__m.try_lock_shared_until(__abs_time)) + {} + + template + _LIBCPP_INLINE_VISIBILITY + shared_lock(mutex_type& __m, + const chrono::duration<_Rep, _Period>& __rel_time) + : __m_(&__m), + __owns_(__m.try_lock_shared_for(__rel_time)) + {} + + _LIBCPP_INLINE_VISIBILITY + ~shared_lock() + { + if (__owns_) + __m_->unlock_shared(); + } + + shared_lock(shared_lock const&) = delete; + shared_lock& operator=(shared_lock const&) = delete; + + _LIBCPP_INLINE_VISIBILITY + shared_lock(shared_lock&& __u) _NOEXCEPT + : __m_(__u.__m_), + __owns_(__u.__owns_) + { + __u.__m_ = nullptr; + __u.__owns_ = false; + } + + _LIBCPP_INLINE_VISIBILITY + shared_lock& operator=(shared_lock&& __u) _NOEXCEPT + { + if (__owns_) + __m_->unlock_shared(); + __m_ = nullptr; + __owns_ = false; + __m_ = __u.__m_; + __owns_ = __u.__owns_; + __u.__m_ = nullptr; + __u.__owns_ = false; + return *this; + } + + void lock(); + bool try_lock(); + template + bool try_lock_for(const chrono::duration& rel_time); + template + bool try_lock_until(const chrono::time_point& abs_time); + void unlock(); + + // Setters + _LIBCPP_INLINE_VISIBILITY + void swap(shared_lock& __u) _NOEXCEPT + { + _VSTD::swap(__m_, __u.__m_); + _VSTD::swap(__owns_, __u.__owns_); + } + + _LIBCPP_INLINE_VISIBILITY + mutex_type* release() _NOEXCEPT + { + mutex_type* __m = __m_; + __m_ = nullptr; + __owns_ = false; + return __m; + } + + // Getters + _LIBCPP_INLINE_VISIBILITY + bool owns_lock() const _NOEXCEPT {return __owns_;} + + _LIBCPP_INLINE_VISIBILITY + explicit operator bool () const _NOEXCEPT {return __owns_;} + + _LIBCPP_INLINE_VISIBILITY + mutex_type* mutex() const _NOEXCEPT {return __m_;} +}; + +template +void +shared_lock<_Mutex>::lock() +{ + if (__m_ == nullptr) + __throw_system_error(EPERM, "shared_lock::lock: references null mutex"); + if (__owns_) + __throw_system_error(EDEADLK, "shared_lock::lock: already locked"); + __m_->lock_shared(); + __owns_ = true; +} + +template +bool +shared_lock<_Mutex>::try_lock() +{ + if (__m_ == nullptr) + __throw_system_error(EPERM, "shared_lock::try_lock: references null mutex"); + if (__owns_) + __throw_system_error(EDEADLK, "shared_lock::try_lock: already locked"); + __owns_ = __m_->try_lock_shared(); + return __owns_; +} + +template +template +bool +shared_lock<_Mutex>::try_lock_for(const chrono::duration<_Rep, _Period>& __d) +{ + if (__m_ == nullptr) + __throw_system_error(EPERM, "shared_lock::try_lock_for: references null mutex"); + if (__owns_) + __throw_system_error(EDEADLK, "shared_lock::try_lock_for: already locked"); + __owns_ = __m_->try_lock_shared_for(__d); + return __owns_; +} + +template +template +bool +shared_lock<_Mutex>::try_lock_until(const chrono::time_point<_Clock, _Duration>& __t) +{ + if (__m_ == nullptr) + __throw_system_error(EPERM, "shared_lock::try_lock_until: references null mutex"); + if (__owns_) + __throw_system_error(EDEADLK, "shared_lock::try_lock_until: already locked"); + __owns_ = __m_->try_lock_shared_until(__t); + return __owns_; +} + +template +void +shared_lock<_Mutex>::unlock() +{ + if (!__owns_) + __throw_system_error(EPERM, "shared_lock::unlock: not locked"); + __m_->unlock_shared(); + __owns_ = false; +} + +template +inline _LIBCPP_INLINE_VISIBILITY +void +swap(shared_lock<_Mutex>& __x, shared_lock<_Mutex>& __y) _NOEXCEPT + {__x.swap(__y);} + +_LIBCPP_END_NAMESPACE_STD + +#endif // !_LIBCPP_HAS_NO_THREADS + +#endif // _LIBCPP_STD_VER > 11 + +#endif // _LIBCPP_SHARED_MUTEX diff --git a/libc++/shared_mutex.cpp b/libc++/shared_mutex.cpp new file mode 100644 index 0000000..874aceb --- /dev/null +++ b/libc++/shared_mutex.cpp @@ -0,0 +1,117 @@ +//===---------------------- shared_mutex.cpp ------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "__config" +#ifndef _LIBCPP_HAS_NO_THREADS + +#define _LIBCPP_BUILDING_SHARED_MUTEX +#include "shared_mutex" + +_LIBCPP_BEGIN_NAMESPACE_STD + +// Shared Mutex Base +__shared_mutex_base::__shared_mutex_base() + : __state_(0) +{ +} + +// Exclusive ownership + +void +__shared_mutex_base::lock() +{ + unique_lock lk(__mut_); + while (__state_ & __write_entered_) + __gate1_.wait(lk); + __state_ |= __write_entered_; + while (__state_ & __n_readers_) + __gate2_.wait(lk); +} + +bool +__shared_mutex_base::try_lock() +{ + unique_lock lk(__mut_); + if (__state_ == 0) + { + __state_ = __write_entered_; + return true; + } + return false; +} + +void +__shared_mutex_base::unlock() +{ + lock_guard _(__mut_); + __state_ = 0; + __gate1_.notify_all(); +} + +// Shared ownership + +void +__shared_mutex_base::lock_shared() +{ + unique_lock lk(__mut_); + while ((__state_ & __write_entered_) || (__state_ & __n_readers_) == __n_readers_) + __gate1_.wait(lk); + unsigned num_readers = (__state_ & __n_readers_) + 1; + __state_ &= ~__n_readers_; + __state_ |= num_readers; +} + +bool +__shared_mutex_base::try_lock_shared() +{ + unique_lock lk(__mut_); + unsigned num_readers = __state_ & __n_readers_; + if (!(__state_ & __write_entered_) && num_readers != __n_readers_) + { + ++num_readers; + __state_ &= ~__n_readers_; + __state_ |= num_readers; + return true; + } + return false; +} + +void +__shared_mutex_base::unlock_shared() +{ + lock_guard _(__mut_); + unsigned num_readers = (__state_ & __n_readers_) - 1; + __state_ &= ~__n_readers_; + __state_ |= num_readers; + if (__state_ & __write_entered_) + { + if (num_readers == 0) + __gate2_.notify_one(); + } + else + { + if (num_readers == __n_readers_ - 1) + __gate1_.notify_one(); + } +} + + +// Shared Timed Mutex +// These routines are here for ABI stability +shared_timed_mutex::shared_timed_mutex() : __base() {} +void shared_timed_mutex::lock() { return __base.lock(); } +bool shared_timed_mutex::try_lock() { return __base.try_lock(); } +void shared_timed_mutex::unlock() { return __base.unlock(); } +void shared_timed_mutex::lock_shared() { return __base.lock_shared(); } +bool shared_timed_mutex::try_lock_shared() { return __base.try_lock_shared(); } +void shared_timed_mutex::unlock_shared() { return __base.unlock_shared(); } + +_LIBCPP_END_NAMESPACE_STD + +#endif // !_LIBCPP_HAS_NO_THREADS diff --git a/netcore.cpp b/netcore.cpp new file mode 100644 index 0000000..ff510f6 --- /dev/null +++ b/netcore.cpp @@ -0,0 +1,161 @@ +#include "skia.hpp" +#include +#include + +typedef void * tcp_connection_t; +typedef void * nw_endpoint_t; +typedef void * nw_path_t; + +enum nw_endpoint_type { + nw_endpoint_type_invalid = 0, + nw_endpoint_type_address = 1, + nw_endpoint_type_hostname = 2, + nw_endpoint_type_bonjour = 3, + nw_endpoint_type_ledbelly = 4, +}; + +enum network_proxy_type { + network_proxy_type_direct = 1, + network_proxy_type_pac_script = 1001, + network_proxy_type_pac_url = 1002, + network_proxy_type_http = 2001, + network_proxy_type_https = 2002, + network_proxy_type_ftp = 2003, + network_proxy_type_gopher = 2004, + network_proxy_type_socks_v4 = 3001, + network_proxy_type_socks_v5 = 3002, +}; + +#define LazyFunction(type, name, args...) static type (* const name)(args) = reinterpret_cast(MSFindSymbol(NULL, "_" # name)) +LazyFunction(nw_endpoint_t, tcp_connection_get_first_endpoint, tcp_connection_t connection); +LazyFunction(nw_endpoint_type, nw_endpoint_get_type, nw_endpoint_t endpoint); +LazyFunction(const void *, nw_endpoint_get_address, nw_endpoint_t endpoint); +LazyFunction(int, nw_endpoint_get_address_family, nw_endpoint_t endpoint); +LazyFunction(const char *, nw_endpoint_get_hostname, nw_endpoint_t endpoint); +LazyFunction(in_port_t, nw_endpoint_get_port, nw_endpoint_t endpoint); +LazyFunction(bool, nw_endpoint_is_local_domain, nw_endpoint_t endpoint); +#undef LazyFunction + +static pthread_key_t proxy_key; + +static const socket_address *get_proxy() { + return reinterpret_cast(pthread_getspecific(proxy_key)); +} + +static void set_proxy(const socket_address *proxy) { + pthread_setspecific(proxy_key, proxy); +} + +static socket_address proxy_for_endpoint(nw_endpoint_t endpoint) { + socket_address proxy; + if (endpoint == NULL) { + return proxy; + } + nw_endpoint_type type = nw_endpoint_get_type(endpoint); + if (!((type == nw_endpoint_type_address || type == nw_endpoint_type_hostname) && !nw_endpoint_is_local_domain(endpoint))) { + return proxy; + } + std::string name; + in_port_t port = nw_endpoint_get_port(endpoint); + if (type == nw_endpoint_type_address) { + int af = nw_endpoint_get_address_family(endpoint); + if (!(af == AF_INET || af == AF_INET6)) { + return proxy; + } + bool ipv6 = af == AF_INET6; + size_t address_len = ipv6 ? sizeof(struct in6_addr) : sizeof(struct in_addr); + struct in6_addr address; + memcpy(&address, nw_endpoint_get_address(endpoint), address_len); + if (skia::instance().should_bypass(address, port, ipv6)) { + return proxy; + } + char buffer[INET6_ADDRSTRLEN]; + if (inet_ntop(af, &address, buffer, sizeof(buffer)) != NULL) { + name = buffer; + } + } else if (type == nw_endpoint_type_hostname) { + name = nw_endpoint_get_hostname(endpoint) ?: ""; + struct in6_addr buffer; + if (inet_aton(name.c_str(), reinterpret_cast(&buffer)) == 1) { + if (skia::instance().should_bypass(buffer, port, false)) { + return proxy; + } + } else { + if (skia::instance().should_bypass(name, "")) { + return proxy; + } + } + } + if (name.length() == 0) { + return proxy; + } + proxy = skia::instance().query_proxy(name, ntohs(port)); + if (proxy.addr == 0) { + log("direct connect: %s:%u...applied", name.c_str(), ntohs(port)); + } else { + log("proxied connect: %s:%u...%s:%u...applied", inet_ntoa(*reinterpret_cast(&proxy.addr)), ntohs(proxy.port), name.c_str(), ntohs(port)); + } + return proxy; +} + +FHFunction(void, tcp_connection_handle_path_changed, tcp_connection_t connection, xpc_object_t path_dictionary, xpc_object_t connected_path_dictionary) { + socket_address proxy = proxy_for_endpoint(tcp_connection_get_first_endpoint(connection)); + if (proxy.addr != 0) { + set_proxy(&proxy); + } + FHOriginal(tcp_connection_handle_path_changed)(connection, path_dictionary, connected_path_dictionary); + set_proxy(NULL); +} + +FHFunction(xpc_object_t, nw_path_copy_proxy_settings, nw_path_t path) { + const socket_address *proxy = get_proxy(); + if (proxy != NULL && proxy->addr != 0) { + xpc_object_t proxy_dictionary = xpc_dictionary_create(NULL, NULL, 0); + xpc_dictionary_set_int64(proxy_dictionary, "proxy_type", network_proxy_type_socks_v5); + xpc_dictionary_set_string(proxy_dictionary, "proxy_host", inet_ntoa(*reinterpret_cast(&proxy->addr))); + xpc_dictionary_set_int64(proxy_dictionary, "proxy_port", proxy->port); + xpc_object_t proxies_array = xpc_array_create(&proxy_dictionary, 1); + xpc_release(proxy_dictionary); + return proxies_array; + } else { + return FHOriginal(nw_path_copy_proxy_settings)(path); + } +} + +FHConstructor { + pthread_key_create(&proxy_key, NULL); + FHHook(tcp_connection_handle_path_changed); + FHHook(nw_path_copy_proxy_settings); +} + +#define DEBUG_NETCORE 1 + +#if DEBUG && DEBUG_NETCORE + +#include + +FHFunction(asl_object_t, asl_open, const char *ident, const char *facility, uint32_t opts) { + return FHOriginal(asl_open)(ident, facility, opts | ASL_OPT_STDERR); +} + +FHFunction(int, netcore_logging_at_level, int level) { + return 1; +} + +FHFunction(void, netcore_log_init_once) { + asl_object_t *logClient = reinterpret_cast(MSFindSymbol(NULL, "_gLogClient")); + int *logFilter = reinterpret_cast(MSFindSymbol(NULL, "_gLogFilter")); + int *logDatapath = reinterpret_cast(MSFindSymbol(NULL, "_gLogDatapath")); + FHOriginal(netcore_log_init_once); + asl_set_filter(*logClient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG)); + *logFilter = 7; + *logDatapath = 1; +} + +FHConstructor { + FHHook(asl_open); + FHHook(netcore_logging_at_level); + FHHook(netcore_log_init_once); +} + +#endif diff --git a/posix.cpp b/posix.cpp new file mode 100644 index 0000000..281029b --- /dev/null +++ b/posix.cpp @@ -0,0 +1,518 @@ +#include "skia.hpp" +#include +#include +#include +#include +#include +#include + +FHOriginalPrototype(int, connect)(int sock, const struct sockaddr *addr, socklen_t addr_len); +FHOriginalPrototype(struct hostent *, gethostbyname)(const char *name); +FHOriginalPrototype(struct hostent *, gethostbyaddr)(const void *addr, socklen_t len, int type); +FHOriginalPrototype(int, getaddrinfo)(const char *hostname, const char *servname, const struct addrinfo *hints, struct addrinfo **res); +FHOriginalPrototype(int, getnameinfo)(const struct sockaddr *sa, socklen_t salen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags); + +static void try_select(int sock, bool for_write) { + try { + fd_set sock_set; + struct timeval timeout; + timeout.tv_sec = 10; + timeout.tv_usec = 0; + while (true) { + FD_ZERO(&sock_set); + FD_SET(sock, &sock_set); + int result = select(sock + 1, for_write ? NULL : &sock_set, for_write ? &sock_set : NULL, NULL, &timeout); + if (result > 0) { + int error; + socklen_t error_len = sizeof(error); + getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &error_len); + if (error == 0) { + return; + } else { + throw std::runtime_error(strerror(error)); + } + } else if (result == -1 && errno == EINTR) { + continue; + } else { + throw std::runtime_error("timed out"); + } + } + } catch (const std::runtime_error &error) { + throw std::runtime_error(std::string("select: ") + error.what()); + } +} + +static void timed_connect(int sock, const struct sockaddr *addr, socklen_t addr_len) { + try { + class socket_nonblocker { + private: + int sock; + public: + socket_nonblocker(int sock): sock(sock) { + fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, NULL) | O_NONBLOCK); + } + ~socket_nonblocker() { + fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, NULL) & ~O_NONBLOCK); + } + }; + socket_nonblocker nonblocker(sock); + if (FHOriginal(connect)(sock, addr, addr_len) == -1 && errno == EINPROGRESS) { + try_select(sock, true); + return; + } else { + throw std::runtime_error(strerror(errno)); + } + } catch (const std::runtime_error &error) { + throw std::runtime_error(std::string("connect: ") + error.what()); + } +} + +static void send_bytes(int sock, const uint8_t *bytes, size_t len) { + try { + ssize_t current = 0; + size_t total = 0; + while (total < len) { + current = send(sock, bytes + total, len - total, 0); + if (current > 0) { + total += current; + } else if (current < 0) { + throw std::runtime_error(strerror(errno)); + } else { + throw std::runtime_error("closed"); + } + } + } catch (const std::runtime_error &error) { + throw std::runtime_error(std::string("send: ") + error.what()); + } +} + +static void recv_bytes(int sock, uint8_t *bytes, size_t len) { + try { + ssize_t current = 0; + size_t total = 0; + while (total < len) { + try_select(sock, false); + current = recv(sock, bytes + total, len - total, 0); + if (current > 0) { + total += current; + } else if (current < 0) { + throw std::runtime_error(strerror(errno)); + } else { + throw std::runtime_error("closed"); + } + } + } catch (const std::runtime_error &error) { + throw std::runtime_error(std::string("recv: ") + error.what()); + } +} + +static bool make_direct(int &sock, const struct in6_addr &target_addr, const in_port_t &target_port, bool ipv6) { + bool result = false; + std::string target_name; + std::string target_serv = std::to_string(ntohs(target_port)); + struct addrinfo *addr_info_list, hints; + hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_IP; + hints.ai_family = AF_UNSPEC; + if (!ipv6 && resolve_table::instance().is_resolved_addr(target_addr.__u6_addr.__u6_addr32[0])) { + target_name = resolve_table::instance().addr_to_name(reinterpret_cast(&target_addr)->s_addr); + } else { + char target_name_buffer[INET6_ADDRSTRLEN]; + target_name = inet_ntop(ipv6 ? AF_INET6 : AF_INET, &target_addr, target_name_buffer, sizeof(target_name_buffer)); + hints.ai_flags |= AI_NUMERICHOST; + hints.ai_family = ipv6 ? AF_INET6 : AF_INET; + } + std::string log_str = target_name + ":" + target_serv + "..."; + if (FHOriginal(getaddrinfo)(target_name.c_str(), target_serv.c_str(), NULL, &addr_info_list) == 0) { + for (struct addrinfo *addr_info = addr_info_list; addr_info != NULL; addr_info = addr_info->ai_next) { + sock = socket(addr_info->ai_family, hints.ai_socktype, 0); + if (sock == -1) { + err("direct connect failed: %s%s", log_str.c_str(), strerror(errno)); + continue; + } + try { + timed_connect(sock, addr_info->ai_addr, addr_info->ai_addrlen); + result = true; + log("direct connect: %s%s", log_str.c_str(), "ok"); + break; + } catch (const std::runtime_error &error) { + close(sock); + err("direct connect failed: %s%s", log_str.c_str(), error.what()); + continue; + } + } + freeaddrinfo(addr_info_list); + } else { + err("direct connect failed: %s%s", log_str.c_str(), strerror(errno)); + } + return result; +} + +static bool make_proxied(int &sock, const struct in6_addr &target_addr, const in_port_t &target_port, bool ipv6, const socket_address &proxy) { + try { + if (resolve_table::instance().is_resolved_addr(target_addr.__u6_addr.__u6_addr32[0]) && resolve_table::instance().addr_to_name(reinterpret_cast(&target_addr)->s_addr).length() == 0) { + throw std::runtime_error("invalid resolved address"); + } + + sock = socket(PF_INET, SOCK_STREAM, 0); + if (sock == -1) { + throw std::runtime_error(strerror(errno)); + } + + struct sockaddr_in proxy_addr; + memset(&proxy_addr, 0, sizeof(proxy_addr)); + proxy_addr.sin_len = sizeof(proxy_addr); + proxy_addr.sin_family = AF_INET; + proxy_addr.sin_addr.s_addr = proxy.addr; + proxy_addr.sin_port = proxy.port; + timed_connect(sock, reinterpret_cast(&proxy_addr), sizeof(proxy_addr)); + + uint8_t buffer[1024]; + // SOCK 5 negotiation + buffer[0] = 5; // version + buffer[1] = 1; // number of methods + buffer[2] = 0; // method #1: no authentication required + send_bytes(sock, buffer, 3); + recv_bytes(sock, buffer, 2); + if (buffer[0] != 5) { + throw std::runtime_error("invalid proxy"); + } + if (buffer[1] != 0) { + throw std::runtime_error("proxy authentication required"); + } + // SOCK5 request + buffer[0] = 5; // version + buffer[1] = 1; // command: connect + buffer[2] = 0; // reserved + std::string target_str; + if (ipv6) { + buffer[3] = 4; // address type = ipv6 + send_bytes(sock, buffer, 4); + send_bytes(sock, reinterpret_cast(&target_addr), sizeof(struct in6_addr)); + send_bytes(sock, reinterpret_cast(&target_port), sizeof(in_port_t)); + target_str = inet_ntop(AF_INET6, &target_addr, reinterpret_cast(buffer), sizeof(buffer)) + std::string(":") + std::to_string(ntohs(target_port)); + } else if (resolve_table::instance().is_resolved_addr(target_addr.__u6_addr.__u6_addr32[0])) { + buffer[3] = 3; // address type = name + send_bytes(sock, buffer, 4); + std::string target_name = resolve_table::instance().addr_to_name(reinterpret_cast(&target_addr)->s_addr); + buffer[0] = std::min(target_name.length(), static_cast(UINT8_MAX)); + send_bytes(sock, buffer, 1); + send_bytes(sock, reinterpret_cast(target_name.c_str()), buffer[0]); + send_bytes(sock, reinterpret_cast(&target_port), sizeof(in_port_t)); + target_str = target_name + ":" + std::to_string(ntohs(target_port)); + } else { + buffer[3] = 1; // address type = ipv4 + send_bytes(sock, buffer, 4); + send_bytes(sock, reinterpret_cast(&target_addr), sizeof(struct in_addr)); + send_bytes(sock, reinterpret_cast(&target_port), sizeof(in_port_t)); + target_str = inet_ntop(AF_INET, &target_addr, reinterpret_cast(buffer), sizeof(buffer)) + std::string(":") + std::to_string(ntohs(target_port)); + } + recv_bytes(sock, buffer, 4); + std::string log_str = std::string(inet_ntoa(*reinterpret_cast(&proxy.addr))) + ":" + std::to_string(ntohs(proxy.port)) + "..." + target_str + "..."; + if (buffer[0] != 5) { + throw std::runtime_error(log_str + "invalid proxy"); + } + switch (buffer[1]) { + case 0: break; + case 1: throw std::runtime_error(log_str + "general failure"); + case 2: throw std::runtime_error(log_str + "connection not allowed"); + case 3: throw std::runtime_error(log_str + "network unreachable"); + case 4: throw std::runtime_error(log_str + "host unreachable"); + case 5: throw std::runtime_error(log_str + "connection refused"); + case 6: throw std::runtime_error(log_str + "ttl expired"); + case 7: throw std::runtime_error(log_str + "command not supported"); + case 8: throw std::runtime_error(log_str + "address type not supported"); + default: throw std::runtime_error(log_str + "unknown error " + std::to_string(buffer[1])); + } + size_t remaining_len = 0; + switch (buffer[3]) { + case 1: + remaining_len = sizeof(struct in_addr) + sizeof(in_port_t); + break; + case 3: + recv_bytes(sock, buffer, 1); + remaining_len = buffer[0] + sizeof(in_port_t); + break; + case 4: + remaining_len = sizeof(struct in6_addr) + sizeof(in_port_t); + break; + default: + throw std::runtime_error(log_str + "replied address type not supported"); + } + recv_bytes(sock, buffer, remaining_len); + log("proxied connect: %s%s", log_str.c_str(), "ok"); + return true; + } catch (const std::runtime_error &error) { + close(sock); + err("proxied connect failed: %s", error.what()); + return false; + } +} + +FHReplacedPrototype(int, connect)(int sock, const struct sockaddr *addr, socklen_t addr_len) { + if (skia::instance().should_bypass(sock) || skia::instance().should_bypass(addr)) { + return FHOriginal(connect)(sock, addr, addr_len); + } + + struct in6_addr target_addr; + in_port_t target_port; + bool ipv6; + skia::instance().extract_target(addr, target_addr, target_port, ipv6); + debug({ + char buffer[INET6_ADDRSTRLEN]; + log("connect: %s:%u", inet_ntop(ipv6 ? AF_INET6 : AF_INET, &target_addr, buffer, sizeof(buffer)), ntohs(target_port)); + }); + + if (skia::instance().should_bypass(target_addr, target_port, ipv6)) { + return FHOriginal(connect)(sock, addr, addr_len); + } + + int new_sock; + socket_address proxy = skia::instance().query_proxy(target_addr, target_port, ipv6); + bool result = proxy.addr == 0 ? make_direct(new_sock, target_addr, target_port, ipv6) : make_proxied(new_sock, target_addr, target_port, ipv6, proxy); + if (result) { + bool nonblock = fcntl(sock, F_GETFL, NULL) & O_NONBLOCK; + dup2(new_sock, sock); + close(new_sock); + if (nonblock) { + fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, NULL) | O_NONBLOCK); + errno = EINPROGRESS; + return -1; + } else { + errno = 0; + return 0; + } + } else { + errno = ETIMEDOUT; + return -1; + } +} + +FHReplacedPrototype(struct hostent *, gethostbyname)(const char *name) { + if (skia::instance().should_bypass(name ?: "", "")) { + return FHOriginal(gethostbyname)(name); + } + + static struct hostent result; + static char result_name[NI_MAXHOST]; + static in_addr_t result_addr; + static char *result_list[2]; + strlcpy(result_name, name, sizeof(result_name)); + result_addr = resolve_table::instance().name_to_addr(result_name); + result_list[0] = reinterpret_cast(&result_addr); + result_list[1] = NULL; + result.h_addrtype = AF_INET; + result.h_length = sizeof(struct in_addr); + result.h_name = result_name; + result.h_addr_list = result_list + 0; + result.h_aliases = result_list + 1; + + debug({ + char buffer[INET6_ADDRSTRLEN]; + log("gethostbyname: %s -> %s", name, inet_ntop(AF_INET, &result.h_addr_list, buffer, sizeof(buffer))); + }); + return &result; +} + +FHReplacedPrototype(struct hostent *, gethostbyaddr)(const void *addr, socklen_t len, int type) { + if (addr == NULL) { + return FHOriginal(gethostbyaddr)(addr, len, type); + } + + if (!(len == sizeof(struct in_addr) && type == AF_INET && resolve_table::instance().is_resolved_addr(*reinterpret_cast(addr)))) { + return FHOriginal(gethostbyaddr)(addr, len, type); + } + + static struct hostent result; + static in_addr_t result_addr; + static char result_name[NI_MAXHOST]; + static char *result_list[2]; + result_addr = *reinterpret_cast(addr); + strlcpy(result_name, resolve_table::instance().addr_to_name(result_addr).c_str(), sizeof(result_name)); + result_list[0] = reinterpret_cast(&result_addr); + result_list[1] = NULL; + result.h_addrtype = AF_INET; + result.h_length = sizeof(struct in_addr); + result.h_name = result_name; + result.h_addr_list = result_list + 0; + result.h_aliases = result_list + 1; + + debug({ + char buffer[INET6_ADDRSTRLEN]; + log("gethostbyaddr: %s -> %s", inet_ntop(type, addr, buffer, sizeof(buffer)), result.h_name); + }); + return &result; +} + +static pthread_key_t resolve_key; + +static bool get_should_resolve() { + return pthread_getspecific(resolve_key) != NULL; +} + +static void set_should_resolve(bool should_resolve) { + pthread_setspecific(resolve_key, should_resolve ? &resolve_key : NULL); +} + +static bool getaddrinfo_test(const char *hostname, const char *servname, const struct addrinfo *hints) { + if (hints && (hints->ai_flags & AI_NUMERICHOST)) { + return true; + } + if (skia::instance().should_bypass(hostname ?: "", servname ?: "")) { + return true; + } + return get_should_resolve(); +} + +static int getaddrinfo_resolve(const char *hostname, const char *servname, const struct addrinfo *hints, struct addrinfo **res) { + struct addrinfo *result = reinterpret_cast(malloc(sizeof(struct addrinfo))); + struct sockaddr_in *result_addr = reinterpret_cast(malloc(sizeof(struct sockaddr_in))); + char *result_name = reinterpret_cast(calloc(NI_MAXHOST, sizeof(char))); + if (result == NULL || result_addr == NULL || result_name == NULL) { + free(result); + free(result_addr); + free(result_name); + return EAI_MEMORY; + } + memset(result_addr, 0, sizeof(struct sockaddr_in)); + result_addr->sin_len = sizeof(struct sockaddr_in); + result_addr->sin_family = AF_INET; + result_addr->sin_addr.s_addr = resolve_table::instance().name_to_addr(hostname); + result_addr->sin_port = 0; + if (servname) { + struct servent *serv = getservbyname(servname, NULL); + result_addr->sin_port = serv ? serv->s_port : htons(atoi(servname)); + } + strlcpy(result_name, hostname, NI_MAXHOST); + result->ai_flags = hints ? hints->ai_flags : AI_ADDRCONFIG; + result->ai_socktype = hints ? hints->ai_socktype : SOCK_STREAM; + result->ai_protocol = hints ? hints->ai_protocol : IPPROTO_IPV4; + result->ai_family = AF_INET; + result->ai_addrlen = sizeof(struct sockaddr_in); + result->ai_addr = reinterpret_cast(result_addr); + result->ai_canonname = result_name; + result->ai_next = NULL; + + debug({ + char buffer[INET6_ADDRSTRLEN]; + log("getaddrinfo: %s:%s -> %s:%u", hostname, servname, inet_ntop(AF_INET, &reinterpret_cast(result->ai_addr)->sin_addr, buffer, sizeof(buffer)), ntohs(reinterpret_cast(result->ai_addr)->sin_port)); + }); + *res = result; + return 0; +} + +FHReplacedPrototype(int, getaddrinfo)(const char *hostname, const char *servname, const struct addrinfo *hints, struct addrinfo **res) { + if (getaddrinfo_test(hostname, servname, hints)) { + return FHOriginal(getaddrinfo)(hostname, servname, hints, res); + } + return getaddrinfo_resolve(hostname, servname, hints, res); +} + +FHFunction(int, getaddrinfo_async_start, mach_port_t *p, const char *hostname, const char *servname, const struct addrinfo *hints, getaddrinfo_async_callback callback, void *context) { + if (getaddrinfo_test(hostname, servname, hints)) { + return FHOriginal(getaddrinfo_async_start)(p, hostname, servname, hints, callback, context); + } + int status = EAI_MEMORY; + if (mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, p) == 0) { + *p = MACH_PORT_NULL; + return status; + } + struct addrinfo *res = NULL; + if ((status = getaddrinfo_resolve(hostname, servname, hints, &res)) != 0) { + mach_port_destroy(mach_task_self(), *p); + *p = MACH_PORT_NULL; + return status; + } + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + callback(status, res, context); + }); + return status; +} + +static bool getnameinfo_test(const struct sockaddr *sa, socklen_t salen, int flags) { + if (sa == NULL) { + return true; + } + if (!(salen == sizeof(struct sockaddr_in) && sa->sa_family == AF_INET && resolve_table::instance().is_resolved_addr(reinterpret_cast(sa)->sin_addr.s_addr))) { + return true; + } + return get_should_resolve(); +} + +static int getnameinfo_resolve(const struct sockaddr *sa, socklen_t salen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags) { + const struct sockaddr_in *sa_in = reinterpret_cast(sa); + if (host != NULL && hostlen > 0) { + if (strlcpy(host, resolve_table::instance().addr_to_name(sa_in->sin_addr.s_addr).c_str(), hostlen) >= hostlen) { + return EAI_OVERFLOW; + } + } + if (serv != NULL && servlen > 0) { + struct servent *serv_ent = getservbyport(sa_in->sin_port, NULL); + if (serv_ent) { + if (strlcpy(serv, serv_ent->s_name, servlen) >= servlen) { + return EAI_OVERFLOW; + } + } else { + if (snprintf(serv, servlen, "%d", ntohs(sa_in->sin_port)) >= servlen) { + return EAI_OVERFLOW; + } + } + } + + debug({ + char buffer[INET6_ADDRSTRLEN]; + log("getnameinfo: %s:%u -> %s:%s", inet_ntop(sa->sa_family, &reinterpret_cast(sa)->sin_addr, buffer, sizeof(buffer)), ntohs(reinterpret_cast(sa)->sin_port), host, serv); + }); + return 0; +} + +FHReplacedPrototype(int, getnameinfo)(const struct sockaddr *sa, socklen_t salen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags) { + if (getnameinfo_test(sa, salen, flags)) { + return FHOriginal(getnameinfo)(sa, salen, host, hostlen, serv, servlen, flags); + } + return getnameinfo_resolve(sa, salen, host, hostlen, serv, servlen, flags); +} + +FHFunction(int, getnameinfo_async_start, mach_port_t *p, const struct sockaddr *sa, socklen_t salen, int flags, getnameinfo_async_callback callback, void *context) { + if (getnameinfo_test(sa, salen, flags)) { + return FHOriginal(getnameinfo_async_start)(p,sa, salen, flags, callback, context); + } + int status = EAI_MEMORY; + if (mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, p) == 0) { + *p = MACH_PORT_NULL; + return status; + } + char *host = reinterpret_cast(calloc(NI_MAXHOST, sizeof(char))); + char *serv = reinterpret_cast(calloc(NI_MAXSERV, sizeof(char))); + if (host == NULL || serv == NULL || (status = getnameinfo_resolve(sa, salen, host, sizeof(host), serv, sizeof(serv), flags)) != 0) { + mach_port_destroy(mach_task_self(), *p); + *p = MACH_PORT_NULL; + free(host); + free(serv); + return status; + } + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + callback(status, host, serv, context); + }); + return status; +} + +FHFunction(JSValueRef, _ZL29_JSDnsResolveFunctionCallbackPK15OpaqueJSContextP13OpaqueJSValueS3_mPKPKS2_PS5_, JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { + set_should_resolve(true); + JSValueRef value = FHOriginal(_ZL29_JSDnsResolveFunctionCallbackPK15OpaqueJSContextP13OpaqueJSValueS3_mPKPKS2_PS5_)(context, function, thisObject, argumentCount, arguments, exception); + set_should_resolve(false); + return value; +} + +FHConstructor { + pthread_key_create(&resolve_key, NULL); + FHHook(connect); + FHHook(gethostbyname); + FHHook(gethostbyaddr); + FHHook(getaddrinfo); + FHHook(getaddrinfo_async_start); + FHHook(getnameinfo); + FHHook(getnameinfo_async_start); + FHHook(_ZL29_JSDnsResolveFunctionCallbackPK15OpaqueJSContextP13OpaqueJSValueS3_mPKPKS2_PS5_); +} diff --git a/res/Info.plist b/res/Info.plist new file mode 100644 index 0000000..215b9bf --- /dev/null +++ b/res/Info.plist @@ -0,0 +1,30 @@ + + + + + CFBundleIdentifier + me.qusic.skia.preferences + CFBundleDisplayName + skiapref + CFBundlePackageType + BNDL + CFBundleVersion + 1.0 + CFBundleShortVersionString + 1.0.0 + DTPlatformName + iphoneos + MinimumOSVersion + 7.0 + CFBundleInfoDictionaryVersion + 6.0 + CFBundleDevelopmentRegion + English + CFBundleSignature + ???? + CFBundleExecutable + skiapref + NSPrincipalClass + SkiaPreferencesController + + diff --git a/res/add@2x.png b/res/add@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..77ef4f93c3a91265238b9083b5a58d0f1bc09191 GIT binary patch literal 3552 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 VUv8RC@|=IWv^5OWD^zVG{{sk)jvoL3 literal 0 HcmV?d00001 diff --git a/res/add@3x.png b/res/add@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..35f852b0f4441395a792d29e327599f7c0e9d7b4 GIT binary patch literal 5418 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 NRbFT+l*(DW{})h}V>SQ) literal 0 HcmV?d00001 diff --git a/res/config.js b/res/config.js new file mode 100644 index 0000000..0501309 --- /dev/null +++ b/res/config.js @@ -0,0 +1,118 @@ +/* Skia Configuration Script */ + +/* + * Predefined Functions + * + * primaryIpAddress(): string + * dnsResolve(host: string): string + * isPlainHostName(hostname: string): boolean + * hostNameDomainLevel(hostname: string): number + * isHostNameInDomain(hostname: string, domain: string): boolean + * isHostResolvable(host: string): boolean + * isHostInNetwork(host: string, network: string, netmask: string): boolean + * + */ + +/* + * The config script must define this function, which will be called + * by Skia for every network connection that is made by applications. + * + * queryProxy(app: string, host: string, port: number): {host: string, port: number, noCache: boolean} + * @app - bundle identifier of the application that made the connection. + * if it is not available then the value will be the process name. + * @host - destination host name or ip address. + * @port - destination port number. + * @returns - result object for the query. return null to bypass proxy. + * @returns.host - ip address of the designated proxy server. + * use null value to bypass proxy. + * @returns.port - port number of the designated proxy server. + * it must be a positive integer. + * @returns.noCache - whether cache the result or not. the cache will + * persist until the application is terminated. + * the default value is false. + * + */ + +function queryProxy(app, host, port) { + // These destinations are always connected directly and you do not need to check them here. + // 127.0.0.1/8, 10.0.0.0/255.0.0.0, 172.16.0.0/255.240.0.0, 192.168.0.0/255.255.0.0 and localhost. + + // You can have as many proxies as you wish. + var proxies = [ + {host: '127.0.0.1', port: 2000}, + {host: '127.0.0.1', port: 2001} + ]; + + // This connection should not leave your local network. + if (isPlainHostName(host)) { + return null; + } + + // Bypass some internal networks. + var internalNetworks = [ + ['222.205.0.0', '255.255.0.0'], + ['210.32.0.0', '255.255.0.0'] + ]; + for (var i = 0; i < internalNetworks.length; i++) { + if (isHostInNetwork(host, internalNetworks[i][0], internalNetworks[i][1])) { + return null; + } + } + + // Bypass some internal domains. + var internalDomains = [ + 'zju.edu.cn', + 'cc98.org', + 'nexushd.org' + ]; + for (i = 0; i < internalDomains.length; i++) { + if (isHostNameInDomain(host, internalDomains[i])) { + return null; + } + } + + // Some apps are fucked by GFW. + var blockedApps = [ + 'com.facebook.Paper', + 'com.facebook.Facebook', + 'com.atebits.Tweetie2' + ]; + for (i = 0; i < blockedApps.length; i++) { + if (app == blockedApps[i]) { + return proxies[0]; + } + } + + // Or if you would like to use a different proxy on some occasions. + if (app == 'com.google.ingress') { + return proxies[1]; + } + + // Now it is time for a long long list. + // Here we do not iterate over an array for better performance. + var blockedDomains = { + 'google.com': true, + 'gmail.com': true, + 'gstatic.com': true, + 'googleusercontent.com': true, + 'googleapis.com': true, + 'facebook.com': true, + 'fbcdn.net': true, + 'twitter.com': true, + 'twimg.com': true, + 't.co': true + }; + var dotPosition = 0; + var hostSubstring = host; + while (dotPosition >= 0) { + if (blockedDomains[hostSubstring]) { + // Random proxy, well, if you like. + return proxies[Math.floor(Math.random() * proxies.length)]; + } + dotPosition = host.indexOf('.', dotPosition + 1); + hostSubstring = host.slice(dotPosition + 1); + } + + // Finally, we may use direct connection as default. + return null; +} diff --git a/res/instance@2x.png b/res/instance@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f1e9d942d51a7d5df2f3d3d0906a477c0b9697d3 GIT binary patch literal 4314 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 GvHt-%uH*&) literal 0 HcmV?d00001 diff --git a/res/instance@3x.png b/res/instance@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..0dfd3acf4131c5dce461478a86cd0bfa7bb460e5 GIT binary patch literal 6614 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 literal 0 HcmV?d00001 diff --git a/res/skia@3x.png b/res/skia@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9fdac4c380c3287c7fcffd87bf05ff898890b2 GIT binary patch literal 8188 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 Yt|!a^AvBNQQvkpVWo;#df>qf60HKdl4*&oF literal 0 HcmV?d00001 diff --git a/res/view/highlight.css b/res/view/highlight.css new file mode 100644 index 0000000..f17caac --- /dev/null +++ b/res/view/highlight.css @@ -0,0 +1 @@ +.hljs{display:block;background:white;padding:0.5em;color:#333333;overflow-x:auto}.hljs-comment,.hljs-meta{color:#969896}.hljs-string,.hljs-variable,.hljs-template-variable,.hljs-strong,.hljs-emphasis,.hljs-quote{color:#df5000}.hljs-keyword,.hljs-selector-tag,.hljs-type{color:#a71d5d}.hljs-literal,.hljs-symbol,.hljs-bullet,.hljs-attribute{color:#0086b3}.hljs-section,.hljs-name{color:#63a35c}.hljs-tag{color:#333333}.hljs-title,.hljs-attr,.hljs-selector-id,.hljs-selector-class,.hljs-selector-attr,.hljs-selector-pseudo{color:#795da3}.hljs-addition{color:#55a532;background-color:#eaffea}.hljs-deletion{color:#bd2c00;background-color:#ffecec}.hljs-link{text-decoration:underline} \ No newline at end of file diff --git a/res/view/highlight.js b/res/view/highlight.js new file mode 100644 index 0000000..82dc908 --- /dev/null +++ b/res/view/highlight.js @@ -0,0 +1,2 @@ +/*! highlight.js v9.0.0 | BSD3 License | git.io/hljslicense */ +!function(e){"undefined"!=typeof exports?e(exports):(self.hljs=e({}),"function"==typeof define&&define.amd&&define("hljs",[],function(){return self.hljs}))}(function(e){function n(e){return e.replace(/&/gm,"&").replace(//gm,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0==t.index}function a(e){return/^(no-?highlight|plain|text)$/i.test(e)}function i(e){var n,t,r,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",t=/\blang(?:uage)?-([\w-]+)\b/i.exec(i))return E(t[1])?t[1]:"no-highlight";for(i=i.split(/\s+/),n=0,r=i.length;r>n;n++)if(E(i[n])||a(i[n]))return i[n]}function o(e,n){var t,r={};for(t in e)r[t]=e[t];if(n)for(t in n)r[t]=n[t];return r}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3==i.nodeType?a+=i.nodeValue.length:1==i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!=r[0].offset?e[0].offset"}function u(e){l+=""}function c(e){("start"==e.event?o:u)(e.node)}for(var s=0,l="",f=[];e.length||r.length;){var g=i();if(l+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g==e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g==e&&g.length&&g[0].offset==s);f.reverse().forEach(o)}else"start"==g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return l+n(a.substr(s))}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var u={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");u[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):Object.keys(a.k).forEach(function(e){c(e,a.k[e])}),a.k=u}a.lR=t(a.l||/\b\w+\b/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),void 0===a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push("self"==e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var l=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=l.length?t(l.join("|"),!0):{exec:function(){return null}}}}r(e)}function l(e,t,a,i){function o(e,n){for(var t=0;t";return i+=e+'">',i+n+o}function p(){if(!L.k)return n(M);var e="",t=0;L.lR.lastIndex=0;for(var r=L.lR.exec(M);r;){e+=n(M.substr(t,r.index-t));var a=g(L,r);a?(B+=a[1],e+=h(a[0],n(r[0]))):e+=n(r[0]),t=L.lR.lastIndex,r=L.lR.exec(M)}return e+n(M.substr(t))}function d(){var e="string"==typeof L.sL;if(e&&!R[L.sL])return n(M);var t=e?l(L.sL,M,!0,y[L.sL]):f(M,L.sL.length?L.sL:void 0);return L.r>0&&(B+=t.r),e&&(y[L.sL]=t.top),h(t.language,t.value,!1,!0)}function b(){return void 0!==L.sL?d():p()}function v(e,t){var r=e.cN?h(e.cN,"",!0):"";e.rB?(k+=r,M=""):e.eB?(k+=n(t)+r,M=""):(k+=r,M=t),L=Object.create(e,{parent:{value:L}})}function m(e,t){if(M+=e,void 0===t)return k+=b(),0;var r=o(t,L);if(r)return k+=b(),v(r,t),r.rB?0:t.length;var a=u(L,t);if(a){var i=L;i.rE||i.eE||(M+=t),k+=b();do L.cN&&(k+=""),B+=L.r,L=L.parent;while(L!=a.parent);return i.eE&&(k+=n(t)),M="",a.starts&&v(a.starts,""),i.rE?0:t.length}if(c(t,L))throw new Error('Illegal lexeme "'+t+'" for mode "'+(L.cN||"")+'"');return M+=t,t.length||1}var N=E(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var w,L=i||N,y={},k="";for(w=L;w!=N;w=w.parent)w.cN&&(k=h(w.cN,"",!0)+k);var M="",B=0;try{for(var C,j,I=0;;){if(L.t.lastIndex=I,C=L.t.exec(t),!C)break;j=m(t.substr(I,C.index-I),C[0]),I=C.index+j}for(m(t.substr(I)),w=L;w.parent;w=w.parent)w.cN&&(k+="");return{r:B,value:k,language:e,top:L}}catch(O){if(-1!=O.message.indexOf("Illegal"))return{r:0,value:n(t)};throw O}}function f(e,t){t=t||x.languages||Object.keys(R);var r={r:0,value:n(e)},a=r;return t.forEach(function(n){if(E(n)){var t=l(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}}),a.language&&(r.second_best=a),r}function g(e){return x.tabReplace&&(e=e.replace(/^((<[^>]+>|\t)+)/gm,function(e,n){return n.replace(/\t/g,x.tabReplace)})),x.useBR&&(e=e.replace(/\n/g,"
")),e}function h(e,n,t){var r=n?w[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function p(e){var n=i(e);if(!a(n)){var t;x.useBR?(t=document.createElementNS("http://www.w3.org/1999/xhtml","div"),t.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):t=e;var r=t.textContent,o=n?l(n,r,!0):f(r),s=u(t);if(s.length){var p=document.createElementNS("http://www.w3.org/1999/xhtml","div");p.innerHTML=o.value,o.value=c(s,u(p),r)}o.value=g(o.value),e.innerHTML=o.value,e.className=h(e.className,n,o.language),e.result={language:o.language,re:o.r},o.second_best&&(e.second_best={language:o.second_best.language,re:o.second_best.r})}}function d(e){x=o(x,e)}function b(){if(!b.called){b.called=!0;var e=document.querySelectorAll("pre code");Array.prototype.forEach.call(e,p)}}function v(){addEventListener("DOMContentLoaded",b,!1),addEventListener("load",b,!1)}function m(n,t){var r=R[n]=t(e);r.aliases&&r.aliases.forEach(function(e){w[e]=n})}function N(){return Object.keys(R)}function E(e){return e=(e||"").toLowerCase(),R[e]||R[w[e]]}var x={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},R={},w={};return e.highlight=l,e.highlightAuto=f,e.fixMarkup=g,e.highlightBlock=p,e.configure=d,e.initHighlighting=b,e.initHighlightingOnLoad=v,e.registerLanguage=m,e.listLanguages=N,e.getLanguage=E,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|like)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e});hljs.registerLanguage("javascript",function(e){return{aliases:["js"],k:{keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},e.ASM,e.QSM,{cN:"string",b:"`",e:"`",c:[e.BE,{cN:"subst",b:"\\$\\{",e:"\\}"}]},e.CLCM,e.CBCM,{cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{b:/\s*[);\]]/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:[e.CLCM,e.CBCM]}],i:/\[|%/},{b:/\$[(.]/},{b:"\\."+e.IR,r:0},{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#/}}); \ No newline at end of file diff --git a/res/view/index.html b/res/view/index.html new file mode 100644 index 0000000..5b678a8 --- /dev/null +++ b/res/view/index.html @@ -0,0 +1,14 @@ + + + + + Skia Configuration Script + + + + + + +
%@
+ + diff --git a/res/view/style.css b/res/view/style.css new file mode 100644 index 0000000..7c95d8a --- /dev/null +++ b/res/view/style.css @@ -0,0 +1,10 @@ +html, body { + margin: 0; + padding: 0; + height: 100%; + background-color: transparent; +} +pre, code { + margin: 0 !important; + background-color: transparent !important; +} diff --git a/res/view@2x.png b/res/view@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..83600321945a9cb2d5fa67167fbd4add18e975cf GIT binary patch literal 4582 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 KldXCUdH)|~TAnNb literal 0 HcmV?d00001 diff --git a/res/view@3x.png b/res/view@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..dabf1491f34173f658872c0229b4af9f9bb15b29 GIT binary patch literal 7286 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 NP+l4$RU=^>^gpMHvH$=8 literal 0 HcmV?d00001 diff --git a/scripts/postinst b/scripts/postinst new file mode 100644 index 0000000..df80b68 --- /dev/null +++ b/scripts/postinst @@ -0,0 +1,2 @@ +#!/bin/sh +launchctl load /Library/LaunchDaemons/me.qusic.skiad.plist diff --git a/scripts/prerm b/scripts/prerm new file mode 100644 index 0000000..e7cf97d --- /dev/null +++ b/scripts/prerm @@ -0,0 +1,2 @@ +#!/bin/sh +launchctl unload /Library/LaunchDaemons/me.qusic.skiad.plist diff --git a/skia.cpp b/skia.cpp new file mode 100644 index 0000000..284a42e --- /dev/null +++ b/skia.cpp @@ -0,0 +1,233 @@ +#include "skia.hpp" + +skia &skia::instance() { + static skia instance; + return instance; +} + +std::string skia::current_application() { + CFStringRef bundle_id = CFBundleGetIdentifier(CFBundleGetMainBundle()); + return bundle_id ? CFStringGetCStringPtr(bundle_id, CFStringGetSystemEncoding()) : getprogname(); +} + +socket_address skia::query_proxy(const std::string &target_name, const uint16_t &target_port, bool &no_cache) { + socket_address proxy; + bool no_cache_flag = false; + proxy_config.execute([&](JSGlobalContextRef context) { + JSStringRef query_function_name = JSStringCreateWithUTF8CString("__skia_queryProxy"); + JSObjectRef query_function = JSValueToObject(context, JSObjectGetProperty(context, JSContextGetGlobalObject(context), query_function_name, NULL), NULL); + JSStringRelease(query_function_name); + if (query_function == NULL) { + return; + } + JSStringRef application_string = JSStringCreateWithUTF8CString(current_application().c_str()); + JSStringRef target_name_string = JSStringCreateWithUTF8CString(target_name.c_str()); + JSValueRef arguments[] = { + JSValueMakeString(context, application_string), + JSValueMakeString(context, target_name_string), + JSValueMakeNumber(context, target_port), + }; + JSStringRelease(application_string); + JSStringRelease(target_name_string); + JSObjectRef result_object = JSValueToObject(context, JSObjectCallAsFunction(context, query_function, NULL, sizeof(arguments) / sizeof(arguments[0]), arguments, NULL), NULL); + if (result_object == NULL) { + return; + } + JSStringRef host_property = JSStringCreateWithUTF8CString("host"); + JSStringRef port_property = JSStringCreateWithUTF8CString("port"); + JSStringRef no_cache_property = JSStringCreateWithUTF8CString("noCache"); + JSStringRef host_string = JSValueToStringCopy(context, JSObjectGetProperty(context, result_object, host_property, NULL), NULL); + char host_buffer[INET6_ADDRSTRLEN]; + JSStringGetUTF8CString(host_string, host_buffer, sizeof(host_buffer)); + JSStringRelease(host_string); + uint16_t port_number = JSValueToNumber(context, JSObjectGetProperty(context, result_object, port_property, NULL), NULL); + no_cache_flag = JSValueToBoolean(context, JSObjectGetProperty(context, result_object, no_cache_property, NULL)); + JSStringRelease(host_property); + JSStringRelease(port_property); + JSStringRelease(no_cache_property); + if (inet_aton(host_buffer, reinterpret_cast(&proxy.addr)) == 1) { + proxy.port = htons(port_number); + } else { + proxy.addr = 0; + proxy.port = 0; + } + }); + no_cache = no_cache_flag; + return proxy; +} + +bool skia::should_bypass(const int &sock) { + if (sock < 0) { + return true; + } + int type; + socklen_t type_len = sizeof(type); + if (getsockopt(sock, SOL_SOCKET, SO_TYPE, &type, &type_len) != 0 || type != SOCK_STREAM) { + return true; + } + return false; +} + +bool skia::should_bypass(const struct sockaddr *addr) { + if (addr == NULL) { + return true; + } + sa_family_t family = addr->sa_family; + if (family != AF_INET && family != AF_INET6) { + return true; + } + return false; +} + +bool skia::should_bypass(const struct in6_addr &target_addr, const in_port_t &target_port, bool ipv6) { + if (ipv6) { + } else { + in_addr_t target_addr_v4 = target_addr.__u6_addr.__u6_addr32[0]; + for (const socket_network &bypass_network : bypass_networks) { + if ((target_addr_v4 & bypass_network.mask) == (bypass_network.addr & bypass_network.mask) && (bypass_network.port == 0 || target_port == bypass_network.port)) { + return true; + } + } + } + return false; +} + +bool skia::should_bypass(const std::string &target_name, const std::string &target_serv) { + if (target_name.length() == 0) { + return true; + } + struct in_addr addr_buffer; + if (inet_aton(target_name.c_str(), &addr_buffer) == 1) { + return true; + } + char localhost_buffer[] = "localhost", localhost_name_buffer[256]; + gethostname(localhost_name_buffer, sizeof(localhost_name_buffer)); + if (target_name == localhost_buffer || target_name == localhost_name_buffer) { + return true; + } + return false; +} + +void skia::extract_target(const struct sockaddr *addr, struct in6_addr &target_addr, in_port_t &target_port, bool &ipv6) { + ipv6 = addr->sa_family == AF_INET6; + if (ipv6) { + const struct sockaddr_in6 *address_in = reinterpret_cast(addr); + const struct in6_addr *in_addr = &address_in->sin6_addr; + if (in_addr->__u6_addr.__u6_addr32[0] == 0x0 && in_addr->__u6_addr.__u6_addr32[1] == 0x0 && in_addr->__u6_addr.__u6_addr16[4] == 0x0 && in_addr->__u6_addr.__u6_addr16[5] == 0xffff) { + memcpy(&target_addr, in_addr->__u6_addr.__u6_addr32 + 3, sizeof(struct in_addr)); + ipv6 = false; + } else { + memcpy(&target_addr, in_addr, sizeof(struct in6_addr)); + } + target_port = address_in->sin6_port; + } else { + const struct sockaddr_in *address_in = reinterpret_cast(addr); + const struct in_addr *in_addr = &address_in->sin_addr; + memcpy(&target_addr, in_addr, sizeof(struct in_addr)); + target_port = address_in->sin_port; + } +} + +socket_address skia::query_proxy(const std::string &target_name, const uint16_t &target_port) { + socket_address proxy; + if (target_name.length() == 0 || target_port == 0) { + return proxy; + } + std::string key = target_name + ":" + std::to_string(target_port); + mutex.lock_shared(); + auto entry = proxy_cache.find(key); + if (entry != proxy_cache.end()) { + proxy = entry->second; + mutex.unlock_shared(); + } else { + mutex.unlock_shared(); + bool no_cache = false; + proxy = query_proxy(target_name, target_port, no_cache); + if (!no_cache) { + mutex.lock(); + proxy_cache[key] = proxy; + mutex.unlock(); + } + } + return proxy; +} + +socket_address skia::query_proxy(const struct in6_addr &target_addr, const in_port_t &target_port, bool ipv6) { + std::string target_name; + if (!ipv6 && resolve_table::instance().is_resolved_addr(target_addr.__u6_addr.__u6_addr32[0])) { + target_name = resolve_table::instance().addr_to_name(target_addr.__u6_addr.__u6_addr32[0]); + } else { + char target_name_buffer[INET6_ADDRSTRLEN]; + target_name = inet_ntop(ipv6 ? AF_INET6 : AF_INET, &target_addr, target_name_buffer, sizeof(target_name_buffer)); + } + return query_proxy(target_name, ntohs(target_port)); +} + +resolve_table &resolve_table::instance() { + static resolve_table instance; + return instance; +} + +size_t resolve_table::make_index(const std::string &name) { + size_t result = 0; + mutex.lock(); + if (name_table.size() >= addr_count) { + for (auto entry = name_table.begin(); entry != name_table.end(); entry++) { + if (entry->second == index) { + name_table.erase(entry); + break; + } + } + } + name_table[name] = index; + index_table[index] = name; + result = index; + index = (index + 1) % addr_count; + mutex.unlock(); + return result; +} + +size_t resolve_table::name_to_index(const std::string &name) { + size_t index = 0; + mutex.lock_shared(); + auto entry = name_table.find(name); + if (entry != name_table.end()) { + index = entry->second; + mutex.unlock_shared(); + } else { + mutex.unlock_shared(); + index = make_index(name); + } + return index; +} + +std::string resolve_table::index_to_name(const size_t &index) { + std::string name; + mutex.lock_shared(); + auto entry = index_table.find(index); + if (entry != index_table.end()) { + name = entry->second; + } + mutex.unlock_shared(); + return name; +} + +in_addr_t resolve_table::index_to_addr(const size_t &index) { + return htonl((static_cast(addr_prefix) << bits_count) | static_cast(index + 1)); +} + +size_t resolve_table::addr_to_index(const in_addr_t &addr) { + return addr_count & (ntohl(addr) - 1); +} + +in_addr_t resolve_table::name_to_addr(const std::string &name) { + return index_to_addr(name_to_index(name)); +} + +std::string resolve_table::addr_to_name(const in_addr_t &addr) { + return index_to_name(addr_to_index(addr)); +} + +bool resolve_table::is_resolved_addr(const in_addr_t &addr) { + return reinterpret_cast(&addr)[0] == addr_prefix; +} diff --git a/skia.hpp b/skia.hpp new file mode 100644 index 0000000..20774b3 --- /dev/null +++ b/skia.hpp @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include +#include +#include "config.hpp" + +#define log_(level, format, args...) syslog(LOG_##level, "Skia: " format, ##args) +#define log(format, args...) log_(NOTICE, format, ##args) +#define err(format, args...) log_(ERR, format, ##args) + +#define DEBUG 0 + +#if DEBUG +#define debug(code) ({ (code); }) +#else +#define debug(code) +#endif + +struct socket_address { + in_addr_t addr = 0; + in_port_t port = 0; + socket_address() {} + socket_address(in_addr_t addr, in_port_t port): addr(htonl(addr)), port(htons(port)) {} +}; + +struct socket_network { + in_addr_t addr = 0, mask = 0; + in_port_t port = 0; + socket_network() {} + socket_network(in_addr_t addr, in_addr_t mask, in_port_t port): addr(htonl(addr)), mask(htonl(mask)), port(htons(port)) {} +}; + +class skia { +private: + std::unordered_map proxy_cache; + std::shared_timed_mutex mutex; + config proxy_config; + const std::vector bypass_networks = { + socket_network(0x7f000000, 0xff000000, 0), // loopback 127.0.0.0/255.0.0.0 + socket_network(0x0a000000, 0xff000000, 0), // private network 10.0.0.0/255.0.0.0 + socket_network(0xac100000, 0xfff00000, 0), // private network 172.16.0.0/255.240.0.0 + socket_network(0xc0a80000, 0xffff0000, 0), // private network 192.168.0.0/255.255.0.0 + }; + skia() {} + ~skia() {} + std::string current_application(); + socket_address query_proxy(const std::string &target_name, const uint16_t &target_port, bool &no_cache); +public: + static skia &instance(); + bool should_bypass(const int &sock); + bool should_bypass(const struct sockaddr *addr); + bool should_bypass(const struct in6_addr &target_addr, const in_port_t &target_port, bool ipv6); + bool should_bypass(const std::string &target_name, const std::string &target_serv); + void extract_target(const struct sockaddr *addr, struct in6_addr &target_addr, in_port_t &target_port, bool &ipv6); + socket_address query_proxy(const std::string &target_name, const uint16_t &target_port); + socket_address query_proxy(const struct in6_addr &target_addr, const in_port_t &target_port, bool ipv6); +}; + +class resolve_table { +private: + std::unordered_map name_table; + std::unordered_map index_table; + std::shared_timed_mutex mutex; + size_t index = 0; + const uint8_t addr_prefix = 240; + const uint8_t bits_count = (sizeof(in_addr_t) - sizeof(addr_prefix)) * 8; + const size_t addr_count = (1 << bits_count) - 1; + resolve_table() {} + ~resolve_table() {} + size_t make_index(const std::string &name); + size_t name_to_index(const std::string &name); + std::string index_to_name(const size_t &index); + in_addr_t index_to_addr(const size_t &index); + size_t addr_to_index(const in_addr_t &addr); +public: + static resolve_table &instance(); + in_addr_t name_to_addr(const std::string &name); + std::string addr_to_name(const in_addr_t &addr); + bool is_resolved_addr(const in_addr_t &addr); +}; diff --git a/skia.plist b/skia.plist new file mode 100644 index 0000000..daaf717 --- /dev/null +++ b/skia.plist @@ -0,0 +1,14 @@ + + + + + Filter + + Bundles + + com.apple.CFNetwork + com.apple.UIKit + + + + diff --git a/skiad.h b/skiad.h new file mode 100644 index 0000000..5f39d99 --- /dev/null +++ b/skiad.h @@ -0,0 +1,245 @@ +#import +#import +#import "config.hpp" + +#define SkiaIdentifier @"me.qusic.skia" +#define ShadowSocksIdentifier @"me.qusic.shadowsocks" +#define LaunchCtl @"/bin/launchctl" +#define DaemonPlistDirectory @"/Library/LaunchDaemons" +#define BundlePath @"/Library/PreferenceBundles/skiapref.bundle" +#define ProxyFile BundlePath"/proxy.js" +#define ConfigFile @"/User/Library/Preferences/me.qusic.skia.js" +#define ConfigSampleFile BundlePath"/config.js" +#define ConfigViewBaseURL BundlePath"/view" + +#define DaemonsMessage @"Daemons" +#define OperationMessage @"Operation" + +@interface CPDistributedMessagingCenter : NSObject ++ (instancetype)centerNamed:(NSString *)name; +- (void)runServerOnCurrentThread; +- (void)stopServer; +- (void)registerForMessageName:(NSString *)message target:(id)target selector:(SEL)selector; +- (BOOL)sendNonBlockingMessageName:(NSString *)message userInfo:(NSDictionary *)userInfo; +- (NSDictionary *)sendMessageAndReceiveReplyName:(NSString *)message userInfo:(NSDictionary *)userInfo error:(NSError * __autoreleasing *)errpt; +@end + +typedef NS_ENUM(NSInteger, NSTaskTerminationReason) { + NSTaskTerminationReasonExit = 1, + NSTaskTerminationReasonUncaughtSignal = 2 +}; + +@interface NSTask : NSObject +- (instancetype)init; +@property (copy) NSString *launchPath; +@property (copy) NSArray *arguments; +@property (copy) NSDictionary *environment; +@property (copy) NSString *currentDirectoryPath; +@property (retain) id standardInput; +@property (retain) id standardOutput; +@property (retain) id standardError; +- (void)launch; +- (void)interrupt; +- (void)terminate; +- (BOOL)suspend; +- (BOOL)resume; +@property (readonly) int processIdentifier; +@property (readonly, getter=isRunning) BOOL running; +@property (readonly) int terminationStatus; +@property (readonly) NSTaskTerminationReason terminationReason; +@property (copy) void (^terminationHandler)(NSTask *); +@property NSQualityOfService qualityOfService; +@end + +@interface NSTask (NSTaskConveniences) ++ (NSTask *)launchedTaskWithLaunchPath:(NSString *)path arguments:(NSArray *)arguments; +- (void)waitUntilExit; +@end + +@interface UIWebTiledView : UIView +- (void)setTilesOpaque:(BOOL)opaque; +@end + +@interface UIWebDocumentView : UIWebTiledView +- (void)setLoadsSynchronously:(BOOL)synchronously; +@end + +@interface UIWebBrowserView : UIWebDocumentView +@end + +@interface UIScrollView (Private) +- (void)setShowBackgroundShadow:(BOOL)show; +@end + +@interface _UIWebViewScrollView : UIScrollView +@end + +@interface UIWebView (Private) +- (UIWebBrowserView *)_browserView; +- (_UIWebViewScrollView *)_scrollView; +- (void)_setDrawsCheckeredPattern:(BOOL)draw; +@end + +typedef enum PSCellType { + PSGroupCell, + PSLinkCell, + PSLinkListCell, + PSListItemCell, + PSTitleValueCell, + PSSliderCell, + PSSwitchCell, + PSStaticTextCell, + PSEditTextCell, + PSSegmentCell, + PSGiantIconCell, + PSGiantCell, + PSSecureEditTextCell, + PSButtonCell, + PSEditTextViewCell, +} PSCellType; + +@interface PSSpecifier : NSObject { +@public + id target; + SEL getter; + SEL setter; + SEL action; + Class detailControllerClass; + PSCellType cellType; + Class editPaneClass; + UIKeyboardType keyboardType; + UITextAutocapitalizationType autoCapsType; + UITextAutocorrectionType autoCorrectionType; + unsigned int textFieldType; +@private + NSString *_name; + NSArray *_values; + NSDictionary *_titleDict; + NSDictionary *_shortTitleDict; + id _userInfo; + NSMutableDictionary *_properties; + SEL _buttonAction; + SEL _confirmationAction; + SEL _confirmationCancelAction; + SEL _controllerLoadAction; + BOOL _showContentString; +} +@property (retain) NSMutableDictionary *properties; +@property (retain) NSString *identifier; +@property (retain) NSString *name; +@property (retain) id userInfo; +@property (retain) id titleDictionary; +@property (retain) id shortTitleDictionary; +@property (retain) NSArray *values; +@property (nonatomic) SEL buttonAction; +@property (nonatomic) SEL confirmationAction; +@property (nonatomic) SEL confirmationCancelAction; +@property (nonatomic) SEL controllerLoadAction; ++ (instancetype)preferenceSpecifierNamed:(NSString *)title target:(id)target set:(SEL)set get:(SEL)get detail:(Class)detail cell:(PSCellType)cell edit:(Class)edit; ++ (PSSpecifier *)groupSpecifierWithName:(NSString *)title; ++ (PSSpecifier *)emptyGroupSpecifier; ++ (UITextAutocapitalizationType)autoCapsTypeForString:(PSSpecifier *)string; ++ (UITextAutocorrectionType)keyboardTypeForString:(PSSpecifier *)string; +- (id)propertyForKey:(NSString *)key; +- (void)setProperty:(id)property forKey:(NSString *)key; +- (void)removePropertyForKey:(NSString *)key; +- (void)loadValuesAndTitlesFromDataSource; +- (void)setValues:(NSArray *)values titles:(NSArray *)titles; +- (void)setValues:(NSArray *)values titles:(NSArray *)titles shortTitles:(NSArray *)shortTitles; +- (void)setupIconImageWithPath:(NSString *)path; +- (NSString *)identifier; +- (void)setTarget:(id)target; +- (void)setKeyboardType:(UIKeyboardType)type autoCaps:(UITextAutocapitalizationType)autoCaps autoCorrection:(UITextAutocorrectionType)autoCorrection; +@end + +@interface PSConfirmationSpecifier : PSSpecifier +@property (nonatomic, retain) NSString *title; +@property (nonatomic, retain) NSString *prompt; +@property (nonatomic, retain) NSString *okButton; +@property (nonatomic, retain) NSString *cancelButton; +@end + +@interface PSViewController : UIViewController { + PSSpecifier *_specifier; +} +@property (retain) PSSpecifier *specifier; +- (id)readPreferenceValue:(PSSpecifier *)specifier; +- (void)setPreferenceValue:(id)value specifier:(PSSpecifier *)specifier; +@end + +@interface PSListController : PSViewController { + NSArray *_specifiers; + UITableView *_table; +} +@property (retain, nonatomic) NSArray *specifiers; +- (UITableView *)table; +- (NSBundle *)bundle; +- (NSArray *)loadSpecifiersFromPlistName:(NSString *)plistName target:(id)target; +- (PSSpecifier *)specifierForID:(NSString *)identifier; +- (PSSpecifier *)specifierAtIndex:(NSInteger)index; +- (NSArray *)specifiersForIDs:(NSArray *)identifiers; +- (NSArray *)specifiersInGroup:(NSInteger)group; +- (BOOL)containsSpecifier:(PSSpecifier *)specifier; +- (NSInteger)numberOfGroups; +- (NSInteger)rowsForGroup:(NSInteger)group; +- (NSInteger)indexForRow:(NSInteger)row inGroup:(NSInteger)group; +- (BOOL)getGroup:(NSInteger *)group row:(NSInteger *)row ofSpecifier:(PSSpecifier *)specifier; +- (BOOL)getGroup:(NSInteger *)group row:(NSInteger *)row ofSpecifierID:(NSString *)identifier; +- (BOOL)getGroup:(NSInteger *)group row:(NSInteger *)row ofSpecifierAtIndex:(NSInteger )index; +- (void)addSpecifier:(PSSpecifier *)specifier; +- (void)addSpecifiersFromArray:(NSArray *)array; +- (void)addSpecifier:(PSSpecifier *)specifier animated:(BOOL)animated; +- (void)addSpecifiersFromArray:(NSArray *)array animated:(BOOL)animated; +- (void)insertSpecifier:(PSSpecifier *)specifier afterSpecifier:(PSSpecifier *)afterSpecifier; +- (void)insertSpecifier:(PSSpecifier *)specifier afterSpecifierID:(NSString *)afterSpecifierID; +- (void)insertSpecifier:(PSSpecifier *)specifier atIndex:(NSInteger)index; +- (void)insertSpecifier:(PSSpecifier *)specifier atEndOfGroup:(NSInteger)index; +- (void)insertContiguousSpecifiers:(NSArray *)spcifiers afterSpecifier:(PSSpecifier *)afterSpecifier; +- (void)insertContiguousSpecifiers:(NSArray *)spcifiers afterSpecifierID:(NSString *)afterSpecifierID; +- (void)insertContiguousSpecifiers:(NSArray *)spcifiers atIndex:(NSInteger)index; +- (void)insertContiguousSpecifiers:(NSArray *)spcifiers atEndOfGroup:(NSInteger)index; +- (void)insertSpecifier:(PSSpecifier *)specifier afterSpecifier:(PSSpecifier *)afterSpecifier animated:(BOOL)animated; +- (void)insertSpecifier:(PSSpecifier *)specifier afterSpecifierID:(NSString *)afterSpecifierID animated:(BOOL)animated; +- (void)insertSpecifier:(PSSpecifier *)specifier atIndex:(NSInteger)index animated:(BOOL)animated; +- (void)insertSpecifier:(PSSpecifier *)specifier atEndOfGroup:(NSInteger)index animated:(BOOL)animated; +- (void)insertContiguousSpecifiers:(NSArray *)spcifiers afterSpecifier:(PSSpecifier *)afterSpecifier animated:(BOOL)animated; +- (void)insertContiguousSpecifiers:(NSArray *)spcifiers afterSpecifierID:(NSString *)afterSpecifierID animated:(BOOL)animated; +- (void)insertContiguousSpecifiers:(NSArray *)spcifiers atIndex:(NSInteger)index animated:(BOOL)animated; +- (void)insertContiguousSpecifiers:(NSArray *)spcifiers atEndOfGroup:(NSInteger)index animated:(BOOL)animated; +- (void)replaceContiguousSpecifiers:(NSArray *)oldSpecifiers withSpecifiers:(NSArray *)newSpecifiers; +- (void)replaceContiguousSpecifiers:(NSArray *)oldSpecifiers withSpecifiers:(NSArray *)newSpecifiers animated:(BOOL)animated; +- (void)removeSpecifier:(PSSpecifier *)specifier; +- (void)removeSpecifierID:(NSString *)identifier; +- (void)removeSpecifierAtIndex:(NSInteger)index; +- (void)removeLastSpecifier; +- (void)removeContiguousSpecifiers:(NSArray *)specifiers; +- (void)removeSpecifier:(PSSpecifier *)specifier animated:(BOOL)animated; +- (void)removeSpecifierID:(NSString *)identifier animated:(BOOL)animated; +- (void)removeSpecifierAtIndex:(NSInteger)index animated:(BOOL)animated; +- (void)removeLastSpecifierAnimated:(BOOL)animated; +- (void)removeContiguousSpecifiers:(NSArray *)specifiers animated:(BOOL)animated; +- (void)reloadSpecifier:(PSSpecifier *)specifier; +- (void)reloadSpecifierID:(NSString *)identifier; +- (void)reloadSpecifierAtIndex:(NSInteger)index; +- (void)reloadSpecifier:(PSSpecifier *)specifier animated:(BOOL)animated; +- (void)reloadSpecifierID:(NSString *)identifier animated:(BOOL)animated; +- (void)reloadSpecifierAtIndex:(NSInteger)index animated:(BOOL)animated; +- (void)reloadSpecifiers; +- (void)updateSpecifiers:(NSArray *)oldSpecifiers withSpecifiers:(NSArray *)newSpecifiers; +- (void)updateSpecifiersInRange:(NSRange)range withSpecifiers:(NSArray *)newSpecifiers; +@end + +@interface PSEditingPane : UIView ++ (UIColor *)defaultBackgroundColor; +@end + +@interface PSTableCell : UITableViewCell +@end + +@interface PSEditableTableCell : PSTableCell +- (UITextField *)textField; +@end + +@interface PSTextViewTableCell : PSTableCell +- (UITextView *)textView; +@end diff --git a/skiad.mm b/skiad.mm new file mode 100644 index 0000000..6444fed --- /dev/null +++ b/skiad.mm @@ -0,0 +1,146 @@ +#import "skiad.h" + +@interface SkiaService : NSObject +@end + +@implementation SkiaService { + CPDistributedMessagingCenter *messagingCenter; +} + ++ (instancetype)sharedInstance { + static id instance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[self alloc]init]; + }); + return instance; +} + +- (instancetype)init { + self = [super init]; + if (self) { + messagingCenter = [CPDistributedMessagingCenter centerNamed:SkiaIdentifier]; + } + return self; +} + +- (void)run { + [messagingCenter runServerOnCurrentThread]; + [messagingCenter registerForMessageName:DaemonsMessage target:self selector:@selector(processDaemonsRequest:data:)]; + [messagingCenter registerForMessageName:OperationMessage target:self selector:@selector(processOperationRequest:data:)]; +} + +- (BOOL)validateDaemonName:(NSString *)name { + return YES + && name.length > 0 + && [name rangeOfString:@"^\\w+$" options:NSRegularExpressionSearch].location != NSNotFound; +} + +- (BOOL)validateDaemonConfig:(NSDictionary *)config { + return YES + && config[@"ServerAddress"].length > 0 + && config[@"ServerPort"].length > 0 + && config[@"LocalPort"].length > 0 + && config[@"Cipher"].length > 0 + && config[@"Key"].length > 0; +} + +- (NSString *)daemonPlistFileForName:(NSString *)name { + return [DaemonPlistDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@.plist", ShadowSocksIdentifier, name.lowercaseString]]; +} + +- (NSString *)daemonIdentifierForName:(NSString *)name { + return [NSString stringWithFormat:@"%@.%@", ShadowSocksIdentifier, name.lowercaseString]; +} + +- (NSDictionary *)daemonPlistDictionaryForName:(NSString *)name config:(NSDictionary *)config { + NSMutableDictionary *daemonPlist = [NSMutableDictionary dictionary]; + daemonPlist[@"Label"] = [self daemonIdentifierForName:name]; + daemonPlist[@"RunAtLoad"] = @(YES); + daemonPlist[@"KeepAlive"] = @(YES); + daemonPlist[@"ProgramArguments"] = @[ + @"/usr/bin/ss-local", + @"-s", config[@"ServerAddress"], + @"-p", config[@"ServerPort"], + @"-b", @"127.0.0.1", + @"-l", config[@"LocalPort"], + @"-m", config[@"Cipher"], + @"-k", config[@"Key"], + @"-t", @"300", + ]; + return daemonPlist; +} + +- (NSDictionary *> *)daemonsStatusDictionary { + NSMutableDictionary *daemonsStatus = [NSMutableDictionary dictionary]; + [[[NSFileManager defaultManager]contentsOfDirectoryAtPath:DaemonPlistDirectory error:nil]enumerateObjectsUsingBlock:^(NSString *filename, NSUInteger index, BOOL *stop) { + if ([filename hasPrefix:ShadowSocksIdentifier] && [filename.pathExtension isEqualToString:@"plist"]) { + NSString *name = [filename.stringByDeletingPathExtension substringFromIndex:ShadowSocksIdentifier.length + 1]; + NSDictionary *daemonPlist = [NSDictionary dictionaryWithContentsOfFile:[DaemonPlistDirectory stringByAppendingPathComponent:filename]]; + NSArray *daemonArguments = daemonPlist[@"ProgramArguments"]; + daemonsStatus[name] = @{ + @"ServerAddress": daemonArguments[[daemonArguments indexOfObject:@"-s"] + 1], + @"ServerPort": daemonArguments[[daemonArguments indexOfObject:@"-p"] + 1], + @"LocalAddress": daemonArguments[[daemonArguments indexOfObject:@"-b"] + 1], + @"LocalPort": daemonArguments[[daemonArguments indexOfObject:@"-l"] + 1], + @"Cipher": daemonArguments[[daemonArguments indexOfObject:@"-m"] + 1], + @"Key": daemonArguments[[daemonArguments indexOfObject:@"-k"] + 1], + }; + } + }]; + return daemonsStatus; +} + +- (void)runLaunchCtlCommand:(NSString *)command target:(NSString *)target { + [[NSTask launchedTaskWithLaunchPath:LaunchCtl arguments:@[command, target]]waitUntilExit]; +} + +- (NSDictionary *)processDaemonsRequest:(NSString *)request data:(NSDictionary *> *)data { + if (data.count > 0) { + [data enumerateKeysAndObjectsUsingBlock:^(NSString *name, NSDictionary *config, BOOL *stop) { + if ([self validateDaemonName:name]) { + NSString *daemonPlistFile = [self daemonPlistFileForName:name]; + if (config.count > 0) { + if ([self validateDaemonConfig:config]) { + [self runLaunchCtlCommand:@"unload" target:daemonPlistFile]; + [[self daemonPlistDictionaryForName:name config:config]writeToFile:daemonPlistFile atomically:YES]; + [self runLaunchCtlCommand:@"load" target:daemonPlistFile]; + } + } else { + [self runLaunchCtlCommand:@"unload" target:daemonPlistFile]; + [[NSFileManager defaultManager]removeItemAtPath:daemonPlistFile error:nil]; + } + } + }]; + return nil; + } else { + return [self daemonsStatusDictionary]; + } +} + +- (NSDictionary *)processOperationRequest:(NSString *)request data:(NSDictionary *)data { + [data enumerateKeysAndObjectsUsingBlock:^(NSString *name, NSString *operation, BOOL *stop) { + if ([self validateDaemonName:name]) { + NSString *daemonIdentifier = [self daemonIdentifierForName:name]; + if ([operation isEqualToString:@"Start"]) { + [self runLaunchCtlCommand:@"start" target:daemonIdentifier]; + } else if ([operation isEqualToString:@"Stop"]) { + [self runLaunchCtlCommand:@"stop" target:daemonIdentifier]; + } else if ([operation isEqualToString:@"Restart"]) { + [self runLaunchCtlCommand:@"stop" target:daemonIdentifier]; + [self runLaunchCtlCommand:@"start" target:daemonIdentifier]; + } + } + }]; + return nil; +} + +@end + +int main() { + @autoreleasepool { + [[SkiaService sharedInstance]run]; + [[NSRunLoop currentRunLoop]run]; + } + return 0; +} \ No newline at end of file diff --git a/skiad.plist b/skiad.plist new file mode 100644 index 0000000..d1146b5 --- /dev/null +++ b/skiad.plist @@ -0,0 +1,15 @@ + + + + + Labelme.qusic.skiad + ProgramArguments + + /usr/libexec/skiad + + MachServices + + me.qusic.skia + + + diff --git a/skiapref.mm b/skiapref.mm new file mode 100644 index 0000000..2c8f792 --- /dev/null +++ b/skiapref.mm @@ -0,0 +1,391 @@ +#import "skiad.h" + +static NSString * const SkiaDaemonsUpdateNotification = @"me.qusic.skia.daemonsUpdate"; + +static CPDistributedMessagingCenter *messagingCenter = [CPDistributedMessagingCenter centerNamed:SkiaIdentifier]; + +static UIImage *imageNamed(NSString *name) { + return [UIImage imageNamed:name inBundle:[NSBundle bundleWithPath:BundlePath] compatibleWithTraitCollection:nil]; +} + +@interface PSListItemsController : PSListController +@end + +@interface SkiaPreferencesController : PSListController +@end + +@interface SkiaDaemonController : PSListController +@end + +@interface SkiaConfigController : PSViewController +@end + +@interface SkiaTestController : PSListController +@end + +@interface SkiaPreferencesController () +@end + +@implementation SkiaPreferencesController + +- (instancetype)init { + self = [super init]; + if (self) { + [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(notificationAction:) name:SkiaDaemonsUpdateNotification object:nil]; + } + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter]removeObserver:self]; +} + +- (NSArray *)specifiers { + if (_specifiers == nil) { + NSMutableArray *specifiers = [NSMutableArray array]; + [specifiers addObjectsFromArray:self.daemonSpecifiers]; + [specifiers addObjectsFromArray:self.configSpecifiers]; + [specifiers addObjectsFromArray:self.aboutSpecifiers]; + _specifiers = specifiers; + } + return _specifiers; +} + +- (NSArray *)daemonSpecifiers { + NSMutableArray *specifiers = [NSMutableArray array]; + [specifiers addObject:[PSSpecifier groupSpecifierWithName:@"ShadowSocks Instances"]]; + [[messagingCenter sendMessageAndReceiveReplyName:DaemonsMessage userInfo:@{} error:nil]enumerateKeysAndObjectsUsingBlock:^(NSString *name, NSDictionary *properties, BOOL *stop) { + PSSpecifier *specifier = [PSSpecifier preferenceSpecifierNamed:name target:self set:NULL get:NULL detail:SkiaDaemonController.class cell:PSLinkCell edit:Nil]; + [specifier setUserInfo:@[name, properties]]; + [specifier setProperty:[NSString stringWithFormat:@"%@:%@", properties[@"LocalAddress"], properties[@"LocalPort"]] forKey:@"cellSubtitleText"]; + [specifier setProperty:imageNamed(@"instance") forKey:@"iconImage"]; + [specifiers addObject:specifier]; + }]; + PSSpecifier *addSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Add Instance" target:self set:NULL get:NULL detail:SkiaDaemonController.class cell:PSLinkCell edit:Nil]; + [addSpecifier setProperty:imageNamed(@"add") forKey:@"iconImage"]; + [specifiers addObject:addSpecifier]; + return specifiers; +} + +- (NSArray *)configSpecifiers { + PSSpecifier *viewSpecifier = [PSSpecifier preferenceSpecifierNamed:@"View Script" target:self set:NULL get:NULL detail:SkiaConfigController.class cell:PSLinkCell edit:Nil]; + [viewSpecifier setProperty:imageNamed(@"view") forKey:@"iconImage"]; + PSSpecifier *testSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Result Test" target:self set:NULL get:NULL detail:SkiaTestController.class cell:PSLinkCell edit:Nil]; + [testSpecifier setProperty:imageNamed(@"test") forKey:@"iconImage"]; + return @[[PSSpecifier groupSpecifierWithName:@"Proxy Configuration"], viewSpecifier, testSpecifier]; +} + +- (NSArray *)aboutSpecifiers { + PSSpecifier *twitterSpecifier = [PSSpecifier preferenceSpecifierNamed:@"@QusicS" target:self set:NULL get:NULL detail:Nil cell:PSButtonCell edit:Nil]; + twitterSpecifier.identifier = @"Twitter"; + twitterSpecifier.buttonAction = @selector(aboutAction:); + [twitterSpecifier setProperty:imageNamed(@"twitter") forKey:@"iconImage"]; + return @[[PSSpecifier groupSpecifierWithName:@"About"], twitterSpecifier]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath]; + PSSpecifier *specifier = [self specifierAtIndex:[self indexForRow:indexPath.row inGroup:indexPath.section]]; + NSString *subtitle = [specifier propertyForKey:@"cellSubtitleText"]; + if (subtitle != nil) { + cell.detailTextLabel.text = subtitle; + } + return cell; +} + +- (void)notificationAction:(NSNotification *)notification { + if ([notification.name isEqualToString:SkiaDaemonsUpdateNotification]) { + [self replaceContiguousSpecifiers:[self specifiersInGroup:0] withSpecifiers:self.daemonSpecifiers animated:YES]; + } +} + +- (void)aboutAction:(PSSpecifier *)specifier { + if ([specifier.identifier isEqualToString:@"Twitter"]) { + [[UIApplication sharedApplication]openURL:[NSURL URLWithString:@"https://twitter.com/QusicS"]]; + } +} + +@end + +@interface SkiaDaemonController () +@end + +@implementation SkiaDaemonController { + NSString *name; + NSMutableDictionary *properties; +} + +- (NSString *)daemonName { + return [NSString stringWithString:self.specifier.userInfo[0] ?: @""]; +} + +- (NSDictionary *)daemonProperties { + return [NSDictionary dictionaryWithDictionary:self.specifier.userInfo[1] ?: @{}]; +} + +- (void)setSpecifier:(PSSpecifier *)specifier { + [super setSpecifier:specifier]; + name = name ?: self.daemonName; + properties = properties ?: self.daemonProperties.mutableCopy; +} + +- (NSArray *)specifiers { + if (_specifiers == nil) { + NSMutableArray *specifiers = [NSMutableArray array]; + [specifiers addObjectsFromArray:self.fieldSpecifiers]; + [specifiers addObjectsFromArray:self.actionSpecifiers]; + _specifiers = specifiers; + } + return _specifiers; +} + +- (NSArray *)fieldSpecifiers { + PSSpecifier * (^fieldSpecifier)(NSString *, NSString *, UIKeyboardType, BOOL) = ^(NSString *identifier, NSString *displayName, UIKeyboardType keyboardType, BOOL secure) { + PSSpecifier *specifier = [PSSpecifier preferenceSpecifierNamed:displayName target:self set:@selector(setValue:specifier:) get:@selector(getValue:) detail:Nil cell:secure ? PSSecureEditTextCell : PSEditTextCell edit:Nil]; + specifier.identifier = identifier; + [specifier setKeyboardType:keyboardType autoCaps:UITextAutocapitalizationTypeNone autoCorrection:UITextAutocorrectionTypeNo]; + return specifier; + }; + PSSpecifier *nameSpecifier = fieldSpecifier(@"Name", @"Name", UIKeyboardTypeASCIICapable, NO); + PSSpecifier *serverAddressSpecifier = fieldSpecifier(@"ServerAddress", @"Server Address", UIKeyboardTypeURL, NO); + PSSpecifier *serverPortSpecifier = fieldSpecifier(@"ServerPort", @"Server Port", UIKeyboardTypeNumberPad, NO); + PSSpecifier *localPortSpecifier = fieldSpecifier(@"LocalPort", @"Local Port", UIKeyboardTypeNumberPad, NO); + PSSpecifier *cipherSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Encrypt Method" target:self set:@selector(setValue:specifier:) get:@selector(getValue:) detail:PSListItemsController.class cell:PSLinkListCell edit:Nil]; + cipherSpecifier.identifier = @"Cipher"; + static NSArray * const ciphers = @[@"table", @"rc4", @"rc4-md5", @"aes-128-cfb", @"aes-192-cfb", @"aes-256-cfb", @"bf-cfb", @"camellia-128-cfb", @"camellia-192-cfb", @"camellia-256-cfb", @"cast5-cfb", @"des-cfb", @"idea-cfb", @"rc2-cfb", @"seed-cfb", @"salsa20", @"chacha20"]; + [cipherSpecifier setValues:ciphers titles:ciphers]; + PSSpecifier *keySpecifier = fieldSpecifier(@"Key", @"Password", UIKeyboardTypeDefault, YES); + return @[ + [PSSpecifier groupSpecifierWithName:nil], + nameSpecifier, serverAddressSpecifier, serverPortSpecifier, localPortSpecifier, cipherSpecifier, keySpecifier, + ]; +} + +- (NSArray *)actionSpecifiers { + PSSpecifier * (^actionSpecifier)(NSString *, NSString *) = ^(NSString *actionName, NSString *confirmation) { + Class specifierClass = confirmation ? PSConfirmationSpecifier.class : PSSpecifier.class; + PSSpecifier *specifier = [specifierClass preferenceSpecifierNamed:actionName target:self set:NULL get:NULL detail:Nil cell:PSButtonCell edit:Nil]; + specifier.identifier = actionName; + if (confirmation == nil) { + specifier.buttonAction = @selector(specifierAction:); + } else { + PSConfirmationSpecifier *confirmationSpecifier = (PSConfirmationSpecifier *)specifier; + confirmationSpecifier.confirmationAction = @selector(specifierAction:); + confirmationSpecifier.title = actionName; + confirmationSpecifier.prompt = confirmation; + confirmationSpecifier.okButton = actionName; + confirmationSpecifier.cancelButton = @"Cancel"; + } + return specifier; + }; + NSMutableArray *specifiers = [NSMutableArray array]; + BOOL exists = self.daemonName.length > 0; + if (exists) { + [specifiers addObjectsFromArray:@[ + [PSSpecifier groupSpecifierWithName:nil], + actionSpecifier(@"Restart", @"This instance will be restarted and all active connections will be interrupted."), + ]]; + } + [specifiers addObjectsFromArray:@[ + [PSSpecifier groupSpecifierWithName:nil], + actionSpecifier(@"Save", nil), + actionSpecifier(@"Reset", @"All unsaved data will be lost."), + ]]; + if (exists) { + [specifiers addObjectsFromArray:@[ + [PSSpecifier groupSpecifierWithName:nil], + actionSpecifier(@"Delete", @"This instance will be deleted and this operation cannot be undone."), + ]]; + } + return specifiers; +} + +- (id)getValue:(PSSpecifier *)specifier { + if ([specifier.identifier isEqualToString:@"Name"]) { + return name; + } else { + return properties[specifier.identifier]; + } +} + +- (void)setValue:(id)value specifier:(PSSpecifier *)specifier { + if ([specifier.identifier isEqualToString:@"Name"]) { + name = value; + } else { + properties[specifier.identifier] = value; + } +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath]; + if (cell.tag == PSEditTextCell || cell.tag == PSSecureEditTextCell) { + UITextField *textField = ((PSEditableTableCell *)cell).textField; + textField.textAlignment = NSTextAlignmentRight; + } + return cell; +} + +- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { + [self.view endEditing:YES]; +} + +- (void)specifierAction:(PSSpecifier *)specifier { + if ([specifier.identifier isEqualToString:@"Save"]) { + void (^alertInvalidProperties)(NSString *) = ^(NSString *message) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Invalid Properties" message:message preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:NULL]]; + [self presentViewController:alertController animated:YES completion:NULL]; + }; + if (name.length == 0) { + alertInvalidProperties(@"Name cannot be empty."); + return; + } + if (properties[@"ServerAddress"].length == 0) { + alertInvalidProperties(@"Server Address cannot be empty."); + return; + } + if (properties[@"ServerPort"].length == 0) { + alertInvalidProperties(@"Server Port cannot be empty."); + return; + } + if (properties[@"LocalPort"].length == 0) { + alertInvalidProperties(@"Local Port cannot be empty."); + return; + } + if (properties[@"Cipher"].length == 0) { + alertInvalidProperties(@"Encrypt Method cannot be empty."); + return; + } + if (properties[@"Key"].length == 0) { + alertInvalidProperties(@"Password cannot be empty."); + return; + } + NSMutableDictionary *data = [NSMutableDictionary dictionary]; + data[name] = properties; + NSString *oldName = self.daemonName; + if (oldName.length > 0 && ![name isEqualToString:oldName]) { + data[oldName] = @{}; + } + [messagingCenter sendMessageAndReceiveReplyName:DaemonsMessage userInfo:data error:nil]; + [self.navigationController popViewControllerAnimated:YES]; + [[NSNotificationCenter defaultCenter]postNotificationName:SkiaDaemonsUpdateNotification object:nil]; + } else if ([specifier.identifier isEqualToString:@"Delete"]) { + [messagingCenter sendMessageAndReceiveReplyName:DaemonsMessage userInfo:@{self.daemonName: @{}} error:nil]; + [self.navigationController popViewControllerAnimated:YES]; + [[NSNotificationCenter defaultCenter]postNotificationName:SkiaDaemonsUpdateNotification object:nil]; + } else if ([specifier.identifier isEqualToString:@"Reset"]) { + name = self.daemonName; + properties = self.daemonProperties.mutableCopy; + [self reloadSpecifiers]; + } else { + [messagingCenter sendMessageAndReceiveReplyName:OperationMessage userInfo:@{self.daemonName: specifier.identifier} error:nil]; + } +} + +@end + +@interface SkiaConfigController () +@end + +@implementation SkiaConfigController { + UIWebView *webView; +} + +- (NSString *)configScriptContent { + NSFileManager *fileManager = [NSFileManager defaultManager]; + if (![fileManager fileExistsAtPath:ConfigFile]) { + [fileManager copyItemAtPath:ConfigSampleFile toPath:ConfigFile error:nil]; + } + return [NSString stringWithContentsOfFile:ConfigFile encoding:NSUTF8StringEncoding error:nil] ?: @""; +} + +- (void)setSpecifier:(PSSpecifier *)specifier { + [super setSpecifier:specifier]; + self.title = specifier.name; +} + +- (void)loadView { + webView = [[UIWebView alloc]initWithFrame:CGRectZero]; + [webView setDelegate:self]; + [webView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; + [webView setBackgroundColor:[PSEditingPane defaultBackgroundColor]]; + [webView setOpaque:NO]; + [webView setDataDetectorTypes:UIDataDetectorTypeNone]; + [webView _setDrawsCheckeredPattern:NO]; + [webView._browserView setTilesOpaque:NO]; + [webView._browserView setLoadsSynchronously:YES]; + [webView._scrollView setBackgroundColor:[UIColor clearColor]]; + [webView._scrollView setDecelerationRate:0.998]; + [webView._scrollView setShowBackgroundShadow:NO]; + self.view = webView; +} + +- (void)loadContent { + NSString *htmlFormatString = [NSString stringWithContentsOfFile:[ConfigViewBaseURL stringByAppendingPathComponent:@"index.html"] encoding:NSUTF8StringEncoding error:nil]; + [webView loadHTMLString:[NSString stringWithFormat:htmlFormatString, self.configScriptContent] baseURL:[NSURL fileURLWithPath:ConfigViewBaseURL]]; +} + +- (void)viewDidLoad { + [self loadContent]; +} + +- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { + if (navigationType == UIWebViewNavigationTypeLinkClicked) { + [[UIApplication sharedApplication]openURL:request.URL]; + return NO; + } + return YES; +} + +@end + +@interface SkiaTestController () +@end + +@implementation SkiaTestController { + NSString *script; +} + +- (void)setSpecifier:(PSSpecifier *)specifier { + [super setSpecifier:specifier]; + script = script ?: @"queryProxy(app, host, port)"; +} + +- (NSArray *)specifiers { + if (_specifiers == nil) { + PSSpecifier *scriptSpecifier = [PSSpecifier preferenceSpecifierNamed:nil target:self set:@selector(setValue:forSpecifier:) get:@selector(getValue:) detail:Nil cell:PSEditTextViewCell edit:Nil]; + [scriptSpecifier setProperty:@0 forKey:@"textViewBottomMargin"]; + PSSpecifier *evaluateSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Evaluate" target:self set:NULL get:NULL detail:Nil cell:PSButtonCell edit:Nil]; + evaluateSpecifier.buttonAction = @selector(specifierAction:); + _specifiers = @[[PSSpecifier groupSpecifierWithName:@"Script"], scriptSpecifier, evaluateSpecifier, [PSSpecifier groupSpecifierWithName:@"Result"]]; + } + return _specifiers; +} + +- (id)getValue:(PSSpecifier *)specifier { + return script; +} + +- (void)setValue:(id)value forSpecifier:(PSSpecifier *)specifier { + script = value; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath]; + if (cell.tag == PSEditTextViewCell) { + UITextView *textView = ((PSTextViewTableCell *)cell).textView; + textView.font = [UIFont fontWithName:@"Menlo" size:[UIFont smallSystemFontSize]]; + textView.autocapitalizationType = UITextAutocapitalizationTypeNone; + textView.autocorrectionType = UITextAutocorrectionTypeNo; + } + return cell; +} + +- (void)specifierAction:(PSSpecifier *)specifier { + [self.view endEditing:YES]; + NSString *result = [NSString stringWithCString:config().evaluate([script cStringUsingEncoding:NSUTF8StringEncoding]).c_str() encoding:NSUTF8StringEncoding]; + [[self specifierAtIndex:3]setProperty:result forKey:@"footerText"]; + [self reloadSpecifierAtIndex:3 animated:YES]; +} + +@end diff --git a/skiapref.plist b/skiapref.plist new file mode 100644 index 0000000..1ba73d2 --- /dev/null +++ b/skiapref.plist @@ -0,0 +1,19 @@ + + + + + entry + + cell + PSLinkCell + label + skia + icon + skia.png + bundle + skiapref + isController + + + +