Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial import for new version

  • Loading branch information...
commit efc60aa461305c1348c24b566e8d4de2478b7e0b 1 parent 1bbf782
@ccp0101 authored
Showing with 4,115 additions and 0 deletions.
  1. +231 −0 iTransmission/NSLogger/LoggerClient.h
  2. +2,286 −0 iTransmission/NSLogger/LoggerClient.m
  3. +113 −0 iTransmission/NSLogger/LoggerCommon.h
  4. BIN  iTransmission/SVSegmentedControl/SVSegmentedControl.bundle/inner-shadow.png
  5. BIN  iTransmission/SVSegmentedControl/SVSegmentedControl.bundle/inner-shadow@2x.png
  6. +46 −0 iTransmission/SVSegmentedControl/SVSegmentedControl.h
  7. +433 −0 iTransmission/SVSegmentedControl/SVSegmentedControl.m
  8. +26 −0 iTransmission/SVSegmentedControl/SVSegmentedThumb.h
  9. +230 −0 iTransmission/SVSegmentedControl/SVSegmentedThumb.m
  10. BIN  iTransmission/SVWebViewController/SVWebViewController.bundle/iPad/action.png
  11. BIN  iTransmission/SVWebViewController/SVWebViewController.bundle/iPad/back.png
  12. BIN  iTransmission/SVWebViewController/SVWebViewController.bundle/iPad/forward.png
  13. BIN  iTransmission/SVWebViewController/SVWebViewController.bundle/iPad/refresh.png
  14. BIN  iTransmission/SVWebViewController/SVWebViewController.bundle/iPad/stop.png
  15. BIN  iTransmission/SVWebViewController/SVWebViewController.bundle/iPhone/back.png
  16. BIN  iTransmission/SVWebViewController/SVWebViewController.bundle/iPhone/back@2x.png
  17. BIN  iTransmission/SVWebViewController/SVWebViewController.bundle/iPhone/forward.png
  18. BIN  iTransmission/SVWebViewController/SVWebViewController.bundle/iPhone/forward@2x.png
  19. +33 −0 iTransmission/SVWebViewController/SVWebViewController.h
  20. +460 −0 iTransmission/SVWebViewController/SVWebViewController.m
  21. BIN  iTransmission/bar-bg@2x.png
  22. BIN  iTransmission/browser-original.png
  23. BIN  iTransmission/settings-icon.png
  24. BIN  iTransmission/statusbar-bg.png
  25. BIN  iTransmission/texture_darkwhite.png
  26. BIN  iTransmission/texture_white.png
  27. +257 −0 make_depend/build.sh
View
231 iTransmission/NSLogger/LoggerClient.h
@@ -0,0 +1,231 @@
+/*
+ * LoggerClient.h
+ *
+ * version 1.0 2011-10-30
+ *
+ * Part of NSLogger (client side)
+ * https://github.com/fpillet/NSLogger
+ *
+ * BSD license follows (http://www.opensource.org/licenses/bsd-license.php)
+ *
+ * Copyright (c) 2010-2011 Florent Pillet All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. Neither the name of Florent
+ * Pillet nor the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
+ * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+#import <unistd.h>
+#import <pthread.h>
+#import <libkern/OSAtomic.h>
+#import <Foundation/Foundation.h>
+#import <CoreFoundation/CoreFoundation.h>
+#import <SystemConfiguration/SystemConfiguration.h>
+#if !TARGET_OS_IPHONE
+#import <CoreServices/CoreServices.h>
+#endif
+
+// This define is here so that user application can test whether NSLogger Client is
+// being included in the project, and potentially configure their macros accordingly
+#define NSLOGGER_WAS_HERE 1
+
+// Set this to 0 if you absolutely NOT want any access to Cocoa (Objective-C, NS* calls)
+// We need a couple ones to reliably obtain the thread number and device information
+// Note that since we need NSAutoreleasePool when using Cocoa in the logger's worker thread,
+// we need to put Cocoa in multithreading mode. Also, ALLOW_COCOA_USE allows the client code
+// to use NSLog()-style message formatting (less verbose than CFShow()-style) through the
+// use of -[NSString stringWithFormat:arguments:]
+#define ALLOW_COCOA_USE 1
+
+/* -----------------------------------------------------------------
+ * Logger option flags & default options
+ * -----------------------------------------------------------------
+ */
+enum {
+ kLoggerOption_LogToConsole = 0x01,
+ kLoggerOption_BufferLogsUntilConnection = 0x02,
+ kLoggerOption_BrowseBonjour = 0x04,
+ kLoggerOption_BrowseOnlyLocalDomain = 0x08,
+ kLoggerOption_UseSSL = 0x10
+};
+
+#define LOGGER_DEFAULT_OPTIONS (kLoggerOption_BufferLogsUntilConnection | \
+ kLoggerOption_BrowseBonjour | \
+ kLoggerOption_BrowseOnlyLocalDomain | \
+ kLoggerOption_UseSSL)
+
+/* -----------------------------------------------------------------
+ * Structure defining a Logger
+ * -----------------------------------------------------------------
+ */
+typedef struct
+{
+ CFStringRef bufferFile; // If non-NULL, all buffering is done to the specified file instead of in-memory
+ CFStringRef host; // Viewer host to connect to (instead of using Bonjour)
+ UInt32 port; // port on the viewer host
+
+ CFMutableArrayRef bonjourServiceBrowsers; // Active service browsers
+ CFMutableArrayRef bonjourServices; // Services being tried
+ CFNetServiceBrowserRef bonjourDomainBrowser; // Domain browser
+
+ CFMutableArrayRef logQueue; // Message queue
+ pthread_mutex_t logQueueMutex;
+ pthread_cond_t logQueueEmpty;
+
+ pthread_t workerThread; // The worker thread responsible for Bonjour resolution, connection and logs transmission
+ CFRunLoopSourceRef messagePushedSource; // A message source that fires on the worker thread when messages are available for send
+ CFRunLoopSourceRef bufferFileChangedSource; // A message source that fires on the worker thread when the buffer file configuration changes
+
+ CFWriteStreamRef logStream; // The connected stream we're writing to
+ CFWriteStreamRef bufferWriteStream; // If bufferFile not NULL and we're not connected, points to a stream for writing log data
+ CFReadStreamRef bufferReadStream; // If bufferFile not NULL, points to a read stream that will be emptied prior to sending the rest of in-memory messages
+
+ SCNetworkReachabilityRef reachability; // The reachability object we use to determine when the target host becomes reachable
+ CFRunLoopTimerRef checkHostTimer; // A timer to regularly check connection to the defined host, along with reachability for added reliability
+
+ uint8_t *sendBuffer; // data waiting to be sent
+ NSUInteger sendBufferSize;
+ NSUInteger sendBufferUsed; // number of bytes of the send buffer currently in use
+ NSUInteger sendBufferOffset; // offset in sendBuffer to start sending at
+
+ int32_t messageSeq; // sequential message number (added to each message sent)
+
+ // settings
+ uint32_t options; // Flags, see enum above
+ CFStringRef bonjourServiceType; // leave NULL to use the default
+ CFStringRef bonjourServiceName; // leave NULL to use the first one available
+
+ // internal state
+ BOOL connected; // Set to YES once the write stream declares the connection open
+ volatile BOOL quit; // Set to YES to terminate the logger worker thread's runloop
+ BOOL incompleteSendOfFirstItem; // set to YES if we are sending the first item in the queue and it's bigger than what the buffer can hold
+} Logger;
+
+
+/* -----------------------------------------------------------------
+ * LOGGING FUNCTIONS
+ * -----------------------------------------------------------------
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Functions to set and get the default logger
+extern void LoggerSetDefaultLogger(Logger *aLogger);
+extern Logger *LoggerGetDefaultLogger(void);
+
+// Initialize a new logger, set as default logger if this is the first one
+// Options default to:
+// - logging to console = NO
+// - buffer until connection = YES
+// - browse Bonjour = YES
+// - browse only locally on Bonjour = YES
+extern Logger* LoggerInit(void);
+
+// Set logger options if you don't want the default options (see above)
+extern void LoggerSetOptions(Logger *logger, uint32_t options);
+
+// Set Bonjour logging names, so you can force the logger to use a specific service type
+// or direct logs to the machine on your network which publishes a specific name
+extern void LoggerSetupBonjour(Logger *logger, CFStringRef bonjourServiceType, CFStringRef bonjourServiceName);
+
+// Directly set the viewer host (hostname or IP address) and port we want to connect to. If set, LoggerStart() will
+// try to connect there first before trying Bonjour
+extern void LoggerSetViewerHost(Logger *logger, CFStringRef hostName, UInt32 port);
+
+
+// Configure the logger to use a local file for buffering, instead of memory.
+// - If you initially set a buffer file after logging started but while a logger connection
+// has not been acquired, the contents of the log queue will be written to the buffer file
+// the next time a logging function is called, or when LoggerStop() is called.
+// - If you want to change the buffering file after logging started, you should first
+// call LoggerStop() the call LoggerSetBufferFile(). Note that all logs stored in the previous
+// buffer file WON'T be transferred to the new file in this case.
+extern void LoggerSetBufferFile(Logger *logger, CFStringRef absolutePath);
+
+// Activate the logger, try connecting
+extern void LoggerStart(Logger *logger);
+
+//extern void LoggerConnectToHost(CFDataRef address, int port);
+
+// Deactivate and free the logger.
+extern void LoggerStop(Logger *logger);
+
+// Pause the current thread until all messages from the logger have been transmitted
+// this is useful to use before an assert() aborts your program. If waitForConnection is YES,
+// LoggerFlush() will block even if the client is not currently connected to the desktop
+// viewer. You should be using NO most of the time, but in some cases it can be useful.
+extern void LoggerFlush(Logger *logger, BOOL waitForConnection);
+
+/* Logging functions. Each function exists in four versions:
+ *
+ * - one without a Logger instance (uses default logger) and without filename/line/function (no F suffix)
+ * - one without a Logger instance but with filename/line/function (F suffix)
+ * - one with a Logger instance (use a specific Logger) and without filename/line/function (no F suffix)
+ * - one with a Logger instance (use a specific Logger) and with filename/line/function (F suffix)
+ *
+ * The exception being the single LogMessageCompat() function which is designed to be a drop-in replacement for NSLog()
+ *
+ */
+
+// Log a message, calling format compatible with NSLog
+extern void LogMessageCompat(NSString *format, ...);
+
+// Log a message. domain can be nil if default domain.
+extern void LogMessage(NSString *domain, int level, NSString *format, ...);
+extern void LogMessageF(const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSString *format, ...);
+extern void LogMessageTo(Logger *logger, NSString *domain, int level, NSString *format, ...);
+extern void LogMessageToF(Logger *logger, const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSString *format, ...);
+
+// Log a message. domain can be nil if default domain (versions with va_list format args instead of ...)
+extern void LogMessage_va(NSString *domain, int level, NSString *format, va_list args);
+extern void LogMessageF_va(const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSString *format, va_list args);
+extern void LogMessageTo_va(Logger *logger, NSString *domain, int level, NSString *format, va_list args);
+extern void LogMessageToF_va(Logger *logger, const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSString *format, va_list args);
+
+// Send binary data to remote logger
+extern void LogData(NSString *domain, int level, NSData *data);
+extern void LogDataF(const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSData *data);
+extern void LogDataTo(Logger *logger, NSString *domain, int level, NSData *data);
+extern void LogDataToF(Logger *logger, const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSData *data);
+
+// Send image data to remote logger
+extern void LogImageData(NSString *domain, int level, int width, int height, NSData *data);
+extern void LogImageDataF(const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, int width, int height, NSData *data);
+extern void LogImageDataTo(Logger *logger, NSString *domain, int level, int width, int height, NSData *data);
+extern void LogImageDataToF(Logger *logger, const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, int width, int height, NSData *data);
+
+// Mark the start of a block. This allows the remote logger to group blocks together
+extern void LogStartBlock(NSString *format, ...);
+extern void LogStartBlockTo(Logger *logger, NSString *format, ...);
+
+// Mark the end of a block
+extern void LogEndBlock(void);
+extern void LogEndBlockTo(Logger *logger);
+
+// Log a marker (text can be null)
+extern void LogMarker(NSString *text);
+extern void LogMarkerTo(Logger *logger, NSString *text);
+
+#ifdef __cplusplus
+};
+#endif
View
2,286 iTransmission/NSLogger/LoggerClient.m
@@ -0,0 +1,2286 @@
+/*
+ * LoggerClient.m
+ *
+ * version 1.0 2011-10-30
+ *
+ * Main implementation of the NSLogger client side code
+ * Part of NSLogger (client side)
+ * https://github.com/fpillet/NSLogger
+ *
+ * BSD license follows (http://www.opensource.org/licenses/bsd-license.php)
+ *
+ * Copyright (c) 2010-2011 Florent Pillet All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. Neither the name of Florent
+ * Pillet nor the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
+ * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+#import <sys/time.h>
+#import "ITLogger.h"
+#if !TARGET_OS_IPHONE
+ #import <sys/types.h>
+ #import <sys/sysctl.h>
+ #import <dlfcn.h>
+#elif ALLOW_COCOA_USE
+ #import <UIKit/UIKit.h>
+#endif
+#import <fcntl.h>
+
+#import "LoggerClient.h"
+#import "LoggerCommon.h"
+
+/* --------------------------------------------------------------------------------
+ * IMPLEMENTATION NOTES:
+ *
+ * The logger runs in a separate thread. It is written
+ * in straight C for maximum compatibility with all runtime environments
+ * (does not use the Objective-C runtime, only uses unix and CoreFoundation
+ * calls, except for get the thread name and device information, but these
+ * can be disabled by setting ALLOW_COCOA_USE to 0).
+ *
+ * It is suitable for use in both Cocoa and low-level code. It does not activate
+ * Cocoa multi-threading (no call to [NSThread detachNewThread...]). You can start
+ * logging very early (as soon as your code starts running), logs will be
+ * buffered and sent to the log viewer as soon as a connection is acquired.
+ * This makes the logger suitable for use in conditions where you usually
+ * don't have a connection to a remote machine yet (early wakeup, network
+ * down, etc).
+ *
+ * When you call one of the public logging functions, the logger is designed
+ * to return to your application as fast as possible. It enqueues logs to
+ * send for processing by its own thread, while your application keeps running.
+ *
+ * The logger does buffer logs while not connected to a desktop
+ * logger. It uses Bonjour to find a logger on the local network, and can
+ * optionally connect to a remote logger identified by an IP address / port
+ * or a Host Name / port.
+ *
+ * The logger can optionally output its log to the console, like NSLog().
+ *
+ * The logger can optionally buffer its logs to a file for which you specify the
+ * full path. Upon connection to the desktop viewer, the file contents are
+ * transmitted to the viewer prior to sending new logs. When the whole file
+ * content has been transmitted, it is emptied.
+ *
+ * Multiple loggers can coexist at the same time. You can perfectly use a
+ * logger for your debug traces, and another that connects remotely to help
+ * diagnostic issues while the application runs on your user's device.
+ *
+ * Using the logger's flexible packet format, you can customize logging by
+ * creating your own log types, and customize the desktop viewer to display
+ * runtime information panels for your application.
+ * --------------------------------------------------------------------------------
+ */
+
+/* Logger internal debug flags */
+// Set to 0 to disable internal debug completely
+// Set to 1 to activate console logs when running the logger itself
+// Set to 2 to see every logging call issued by the app, too
+#define LOGGER_DEBUG 0
+#ifdef NSLog
+ #undef NSLog
+#endif
+
+// Internal debugging stuff for the logger itself
+#if LOGGER_DEBUG
+ #define LOGGERDBG LoggerDbg
+ #if LOGGER_DEBUG > 1
+ #define LOGGERDBG2 LoggerDbg
+ #else
+ #define LOGGERDBG2(format, ...) do{}while(0)
+ #endif
+ // Internal logging function prototype
+ static void LoggerDbg(CFStringRef format, ...);
+#else
+ #define LOGGERDBG(format, ...) do{}while(0)
+ #define LOGGERDBG2(format, ...) do{}while(0)
+#endif
+
+// small set of macros for proper ARC/non-ARC compilation support
+// with added cruft to support non-clang compilers
+#undef LOGGER_ARC_MACROS_DEFINED
+#if defined(__has_feature)
+ #if __has_feature(objc_arc)
+ #define CAST_TO_CFSTRING __bridge CFStringRef
+ #define CAST_TO_CFDATA __bridge CFDataRef
+ #define RELEASE(obj) do{}while(0)
+ #define AUTORELEASE_POOL_BEGIN @autoreleasepool{
+ #define AUTORELEASE_POOL_END }
+ #define LOGGER_ARC_MACROS_DEFINED
+ #endif
+#endif
+#if !defined(LOGGER_ARC_MACROS_DEFINED)
+ #define CAST_TO_CFSTRING CFStringRef
+ #define CAST_TO_CFDATA CFDataRef
+ #define RELEASE(obj) [obj release]
+ #define AUTORELEASE_POOL_BEGIN NSAutoreleasePool *__pool=[[NSAutoreleasePool alloc] init];
+ #define AUTORELEASE_POOL_END [__pool drain];
+#endif
+#undef LOGGER_ARC_MACROS_DEFINED
+
+/* Local prototypes */
+static void* LoggerWorkerThread(Logger *logger);
+static void LoggerWriteMoreData(Logger *logger);
+static void LoggerPushMessageToQueue(Logger *logger, CFDataRef message);
+
+// Bonjour management
+static void LoggerStartBonjourBrowsing(Logger *logger);
+static void LoggerStopBonjourBrowsing(Logger *logger);
+static BOOL LoggerBrowseBonjourForServices(Logger *logger, CFStringRef domainName);
+static void LoggerServiceBrowserCallBack(CFNetServiceBrowserRef browser, CFOptionFlags flags, CFTypeRef domainOrService, CFStreamError* error, void *info);
+
+// Reachability and reconnect timer
+static void LoggerStartReachabilityChecking(Logger *logger);
+static void LoggerStopReachabilityChecking(Logger *logger);
+static void LoggerReachabilityCallBack(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info);
+static void LoggerTimedReconnectCallback(CFRunLoopTimerRef timer, void *info);
+
+// Connection & stream management
+static void LoggerTryConnect(Logger *logger);
+static void LoggerWriteStreamCallback(CFWriteStreamRef ws, CFStreamEventType event, void* info);
+#if LOGGER_DEBUG
+static void LoggerReadStreamCallback(CFReadStreamRef ws, CFStreamEventType event, void* info);
+#endif
+
+// File buffering
+static void LoggerCreateBufferWriteStream(Logger *logger);
+static void LoggerCreateBufferReadStream(Logger *logger);
+static void LoggerEmptyBufferFile(Logger *logger);
+static void LoggerFileBufferingOptionsChanged(Logger *logger);
+static void LoggerFlushQueueToBufferStream(Logger *logger, BOOL firstEntryIsClientInfo);
+
+// Encoding functions
+static void LoggerPushClientInfoToFrontOfQueue(Logger *logger);
+static void LoggerMessageAddTimestampAndThreadID(CFMutableDataRef encoder);
+static void LogDataInternal(Logger *logger, NSString *domain, int level, NSData *data, int binaryOrImageType);
+
+static CFMutableDataRef LoggerMessageCreate();
+static void LoggerMessageUpdateDataHeader(CFMutableDataRef data);
+static void LoggerMessageAddInt16(CFMutableDataRef data, int16_t anInt, int key);
+static void LoggerMessageAddInt32(CFMutableDataRef data, int32_t anInt, int key);
+static void LoggerMessageAddInt64(CFMutableDataRef data, int64_t anInt, int key);
+static void LoggerMessageAddString(CFMutableDataRef data, CFStringRef aString, int key);
+static void LoggerMessageAddData(CFMutableDataRef data, CFDataRef theData, int key, int partType);
+static uint32_t LoggerMessageGetSeq(CFDataRef message);
+
+/* Static objects */
+static Logger* volatile sDefaultLogger = NULL;
+static pthread_mutex_t sDefaultLoggerMutex = PTHREAD_MUTEX_INITIALIZER;
+
+// -----------------------------------------------------------------------------
+#pragma mark -
+#pragma mark Default logger
+// -----------------------------------------------------------------------------
+void LoggerSetDefaultLogger(Logger *defaultLogger)
+{
+ pthread_mutex_lock(&sDefaultLoggerMutex);
+ sDefaultLogger = defaultLogger;
+ pthread_mutex_unlock(&sDefaultLoggerMutex);
+}
+
+Logger *LoggerGetDefaultLogger(void)
+{
+ if (sDefaultLogger == NULL)
+ {
+ pthread_mutex_lock(&sDefaultLoggerMutex);
+ Logger *logger = LoggerInit();
+ if (sDefaultLogger == NULL)
+ {
+ sDefaultLogger = logger;
+ logger = NULL;
+ }
+ pthread_mutex_unlock(&sDefaultLoggerMutex);
+ if (logger != NULL)
+ LoggerStop(logger);
+ }
+ return sDefaultLogger;
+}
+
+// -----------------------------------------------------------------------------
+#pragma mark -
+#pragma mark Initialization and setup
+// -----------------------------------------------------------------------------
+Logger *LoggerInit(void)
+{
+ LOGGERDBG(CFSTR("LoggerInit defaultLogger=%p"), sDefaultLogger);
+
+ Logger *logger = (Logger *)malloc(sizeof(Logger));
+ bzero(logger, sizeof(Logger));
+
+ logger->logQueue = CFArrayCreateMutable(NULL, 32, &kCFTypeArrayCallBacks);
+ pthread_mutex_init(&logger->logQueueMutex, NULL);
+ pthread_cond_init(&logger->logQueueEmpty, NULL);
+
+ logger->bonjourServiceBrowsers = CFArrayCreateMutable(NULL, 4, &kCFTypeArrayCallBacks);
+ logger->bonjourServices = CFArrayCreateMutable(NULL, 4, &kCFTypeArrayCallBacks);
+
+ // for now we don't grow the send buffer, just use one page of memory which should be enouh
+ // (bigger messages will be sent separately)
+ logger->sendBuffer = (uint8_t *)malloc(4096);
+ logger->sendBufferSize = 4096;
+
+ logger->options = LOGGER_DEFAULT_OPTIONS;
+
+ logger->quit = NO;
+
+ // Set this logger as the default logger is none exist already
+ if (!pthread_mutex_trylock(&sDefaultLoggerMutex))
+ {
+ if (sDefaultLogger == NULL)
+ sDefaultLogger = logger;
+ pthread_mutex_unlock(&sDefaultLoggerMutex);
+ }
+
+ return logger;
+}
+
+void LoggerSetOptions(Logger *logger, uint32_t options)
+{
+ LOGGERDBG(CFSTR("LoggerSetOptions options=0x%08lx"), options);
+
+ if (logger == NULL)
+ logger = LoggerGetDefaultLogger();
+ if (logger != NULL)
+ logger->options = options;
+}
+
+void LoggerSetupBonjour(Logger *logger, CFStringRef bonjourServiceType, CFStringRef bonjourServiceName)
+{
+ LOGGERDBG(CFSTR("LoggerSetupBonjour serviceType=%@ serviceName=%@"), bonjourServiceType, bonjourServiceName);
+
+ if (logger == NULL)
+ logger = LoggerGetDefaultLogger();
+ if (logger != NULL)
+ {
+ if (bonjourServiceType != NULL)
+ CFRetain(bonjourServiceType);
+ if (bonjourServiceName != NULL)
+ CFRetain(bonjourServiceName);
+ if (logger->bonjourServiceType != NULL)
+ CFRelease(logger->bonjourServiceType);
+ if (logger->bonjourServiceName != NULL)
+ CFRelease(logger->bonjourServiceName);
+ logger->bonjourServiceType = bonjourServiceType;
+ logger->bonjourServiceName = bonjourServiceName;
+ }
+}
+
+void LoggerSetViewerHost(Logger *logger, CFStringRef hostName, UInt32 port)
+{
+ if (logger == NULL)
+ logger = LoggerGetDefaultLogger();
+ if (logger == NULL)
+ return;
+
+ if (logger->host != NULL)
+ {
+ CFRelease(logger->host);
+ logger->host = NULL;
+ }
+ if (hostName != NULL)
+ {
+ logger->host = CFStringCreateCopy(NULL, hostName);
+ logger->port = port;
+ }
+}
+
+void LoggerSetBufferFile(Logger *logger, CFStringRef absolutePath)
+{
+ if (logger == NULL)
+ {
+ logger = LoggerGetDefaultLogger();
+ if (logger == NULL)
+ return;
+ }
+
+ BOOL change = ((logger->bufferFile != NULL && absolutePath == NULL) ||
+ (logger->bufferFile == NULL && absolutePath != NULL) ||
+ (logger->bufferFile != NULL && absolutePath != NULL && CFStringCompare(logger->bufferFile, absolutePath, 0) != kCFCompareEqualTo));
+ if (change)
+ {
+ if (logger->bufferFile != NULL)
+ {
+ CFRelease(logger->bufferFile);
+ logger->bufferFile = NULL;
+ }
+ if (absolutePath != NULL)
+ logger->bufferFile = CFStringCreateCopy(NULL, absolutePath);
+ if (logger->bufferFileChangedSource != NULL)
+ CFRunLoopSourceSignal(logger->bufferFileChangedSource);
+ }
+}
+
+void LoggerStart(Logger *logger)
+{
+ // will do nothing if logger is already started
+ if (logger == NULL)
+ logger = LoggerGetDefaultLogger();
+
+ if (logger->workerThread == NULL)
+ {
+ // Start the work thread which performs the Bonjour search,
+ // connects to the logging service and forwards the logs
+ LOGGERDBG(CFSTR("LoggerStart logger=%p"), logger);
+ pthread_create(&logger->workerThread, NULL, (void *(*)(void *))&LoggerWorkerThread, logger);
+ }
+}
+
+void LoggerStop(Logger *logger)
+{
+ LOGGERDBG(CFSTR("LoggerStop"));
+
+ pthread_mutex_lock(&sDefaultLoggerMutex);
+ if (logger == NULL || logger == sDefaultLogger)
+ {
+ logger = sDefaultLogger;
+ sDefaultLogger = NULL;
+ }
+ pthread_mutex_unlock(&sDefaultLoggerMutex);
+
+ if (logger != NULL)
+ {
+ if (logger->workerThread != NULL)
+ {
+ logger->quit = YES;
+ pthread_join(logger->workerThread, NULL);
+ }
+
+ CFRelease(logger->bonjourServiceBrowsers);
+ CFRelease(logger->bonjourServices);
+ free(logger->sendBuffer);
+ if (logger->host != NULL)
+ CFRelease(logger->host);
+ if (logger->bufferFile != NULL)
+ CFRelease(logger->bufferFile);
+ if (logger->bonjourServiceType != NULL)
+ CFRelease(logger->bonjourServiceType);
+ if (logger->bonjourServiceName != NULL)
+ CFRelease(logger->bonjourServiceName);
+
+ // to make sure potential errors are catched, set the whole structure
+ // to a value that will make code crash if it tries using pointers to it.
+ memset(logger, 0x55, sizeof(Logger));
+
+ free(logger);
+ }
+}
+
+void LoggerFlush(Logger *logger, BOOL waitForConnection)
+{
+ // Special case: if nothing has ever been logged, don't bother
+ if (logger == NULL && sDefaultLogger == NULL)
+ return;
+ if (logger == NULL)
+ logger = LoggerGetDefaultLogger();
+ if (logger != NULL &&
+ pthread_self() != logger->workerThread &&
+ (logger->connected || logger->bufferFile != NULL || waitForConnection))
+ {
+ pthread_mutex_lock(&logger->logQueueMutex);
+ if (CFArrayGetCount(logger->logQueue) > 0)
+ pthread_cond_wait(&logger->logQueueEmpty, &logger->logQueueMutex);
+ pthread_mutex_unlock(&logger->logQueueMutex);
+ }
+}
+
+static void LoggerDbg(CFStringRef format, ...)
+{
+ // Internal debugging function
+ // (what do you think, that we use the Logger to debug itself ??)
+ if (format != NULL)
+ {
+ AUTORELEASE_POOL_BEGIN
+ va_list args;
+ va_start(args, format);
+ CFStringRef s = CFStringCreateWithFormatAndArguments(NULL, NULL, (CFStringRef)format, args);
+ va_end(args);
+ if (s != NULL)
+ {
+ CFShow(s);
+ CFRelease(s);
+ }
+ AUTORELEASE_POOL_END
+ }
+}
+
+// -----------------------------------------------------------------------------
+#pragma mark -
+#pragma mark Main processing
+// -----------------------------------------------------------------------------
+static void *LoggerWorkerThread(Logger *logger)
+{
+ LOGGERDBG(CFSTR("Start LoggerWorkerThread"));
+
+#if !TARGET_OS_IPHONE
+ // Register thread with Garbage Collector on Mac OS X if we're running an OS version that has GC
+ void (*registerThreadWithCollector_fn)(void);
+ registerThreadWithCollector_fn = (void(*)(void)) dlsym(RTLD_NEXT, "objc_registerThreadWithCollector");
+ if (registerThreadWithCollector_fn)
+ (*registerThreadWithCollector_fn)();
+#endif
+
+ // Create and get the runLoop for this thread
+ CFRunLoopRef runLoop = CFRunLoopGetCurrent();
+
+ // Create the run loop source that signals when messages have been added to the runloop
+ // this will directly trigger a WriteMoreData() call, which will or won't write depending
+ // on whether we're connected and there's space available in the stream
+ CFRunLoopSourceContext context;
+ bzero(&context, sizeof(context));
+ context.info = logger;
+ context.perform = (void *)&LoggerWriteMoreData;
+ logger->messagePushedSource = CFRunLoopSourceCreate(NULL, 0, &context);
+ if (logger->messagePushedSource == NULL)
+ {
+ // Failing to create the runloop source for pushing messages is a major failure.
+ // This NSLog is intentional. We WANT console output in this case
+ NSLog(@"*** NSLogger: Worker thread failed creating runLoop source, switching to console logging.");
+ logger->options |= kLoggerOption_LogToConsole;
+ logger->workerThread = NULL;
+ return NULL;
+ }
+ CFRunLoopAddSource(runLoop, logger->messagePushedSource, kCFRunLoopDefaultMode);
+
+ // Create the buffering stream if needed
+ if (logger->bufferFile != NULL)
+ LoggerCreateBufferWriteStream(logger);
+
+ // Create the runloop source that lets us know when file buffering options change
+ context.perform = (void *)&LoggerFileBufferingOptionsChanged;
+ logger->bufferFileChangedSource = CFRunLoopSourceCreate(NULL, 0, &context);
+ if (logger->bufferFileChangedSource == NULL)
+ {
+ // This failure MUST be logged to console
+ NSLog(@"*** NSLogger Warning: failed creating a runLoop source for file buffering options change.");
+ }
+ else
+ CFRunLoopAddSource(runLoop, logger->bufferFileChangedSource, kCFRunLoopDefaultMode);
+
+ // Start Bonjour browsing, wait for remote logging service to be found
+ if (logger->host == NULL && (logger->options & kLoggerOption_BrowseBonjour))
+ {
+ LOGGERDBG(CFSTR("-> logger configured for Bonjour, no direct host set -- trying Bonjour first"));
+ LoggerStartBonjourBrowsing(logger);
+ }
+ else if (logger->host != NULL)
+ {
+ LOGGERDBG(CFSTR("-> logger configured with direct host, trying it first"));
+ LoggerTryConnect(logger);
+ }
+
+ // Run logging thread until LoggerStop() is called
+ NSTimeInterval timeout = 0.10;
+ while (!logger->quit)
+ {
+ int result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, true);
+ if (result == kCFRunLoopRunFinished || result == kCFRunLoopRunStopped)
+ break;
+ if (result == kCFRunLoopRunHandledSource)
+ {
+ timeout = 0.0;
+ continue;
+ }
+ timeout = fmin(0.10, timeout+0.0005);
+
+ // Make sure we restart connection attempts if we get disconnected
+ if (!logger->connected &&
+ !CFArrayGetCount(logger->bonjourServices) &&
+ !CFArrayGetCount(logger->bonjourServiceBrowsers) &&
+ !CFArrayGetCount(logger->bonjourServices))
+ {
+ if (logger->options & kLoggerOption_BrowseBonjour)
+ LoggerStartBonjourBrowsing(logger);
+ else if (logger->host != NULL && logger->reachability == NULL && logger->checkHostTimer == NULL)
+ LoggerTryConnect(logger);
+ }
+ }
+
+ // Cleanup
+ if (logger->options & kLoggerOption_BrowseBonjour)
+ LoggerStopBonjourBrowsing(logger);
+ LoggerStopReachabilityChecking(logger);
+
+ if (logger->logStream != NULL)
+ {
+ CFWriteStreamSetClient(logger->logStream, 0, NULL, NULL);
+ CFWriteStreamClose(logger->logStream);
+ CFRelease(logger->logStream);
+ logger->logStream = NULL;
+ }
+
+ if (logger->bufferWriteStream == NULL && logger->bufferFile != NULL)
+ {
+ // If there are messages in the queue and LoggerStop() was called and
+ // a buffer file was set just before LoggerStop() was called, flush
+ // the log queue to the buffer file
+ pthread_mutex_lock(&logger->logQueueMutex);
+ CFIndex outstandingMessages = CFArrayGetCount(logger->logQueue);
+ pthread_mutex_unlock(&logger->logQueueMutex);
+ if (outstandingMessages)
+ LoggerCreateBufferWriteStream(logger);
+ }
+
+ if (logger->bufferWriteStream != NULL)
+ {
+ CFWriteStreamClose(logger->bufferWriteStream);
+ CFRelease(logger->bufferWriteStream);
+ logger->bufferWriteStream = NULL;
+ }
+
+ if (logger->messagePushedSource != NULL)
+ {
+ CFRunLoopSourceInvalidate(logger->messagePushedSource);
+ CFRelease(logger->messagePushedSource);
+ logger->messagePushedSource = NULL;
+ }
+
+ if (logger->bufferFileChangedSource != NULL)
+ {
+ CFRunLoopSourceInvalidate(logger->bufferFileChangedSource);
+ CFRelease(logger->bufferFileChangedSource);
+ logger->bufferFileChangedSource = NULL;
+ }
+
+ // if the client ever tries to log again against us, make sure that logs at least
+ // go to console
+ logger->options |= kLoggerOption_LogToConsole;
+ logger->workerThread = NULL;
+ return NULL;
+}
+
+static CFStringRef LoggerCreateStringRepresentationFromBinaryData(CFDataRef data)
+{
+ CFMutableStringRef s = CFStringCreateMutable(NULL, 0);
+ unsigned int offset = 0;
+ unsigned int dataLen = (unsigned int)CFDataGetLength(data);
+ char buffer[1+6+16*3+1+16+1+1+1];
+ buffer[0] = '\0';
+ const unsigned char *q = (unsigned char *)CFDataGetBytePtr(data);
+ if (dataLen == 1)
+ CFStringAppend(s, CFSTR("Raw data, 1 byte:\n"));
+ else
+ CFStringAppendFormat(s, NULL, CFSTR("Raw data, %u bytes:\n"), dataLen);
+ while (dataLen)
+ {
+ int i, j, b = sprintf(buffer," %04x: ", offset);
+ for (i=0; i < 16 && i < (int)dataLen; i++)
+ sprintf(&buffer[b+3*i], "%02x ", (int)q[i]);
+ for (j=i; j < 16; j++)
+ strcat(buffer, " ");
+
+ b = (int)strlen(buffer);
+ buffer[b++] = '\'';
+ for (i=0; i < 16 && i < (int)dataLen; i++, q++)
+ {
+ if (*q >= 32 && *q < 128)
+ buffer[b++] = *q;
+ else
+ buffer[b++] = ' ';
+ }
+ for (j=i; j < 16; j++)
+ buffer[b++] = ' ';
+ buffer[b++] = '\'';
+ buffer[b++] = '\n';
+ buffer[b] = 0;
+
+ CFStringRef bufferStr = CFStringCreateWithBytesNoCopy(NULL, (const UInt8 *)buffer, strlen(buffer), kCFStringEncodingISOLatin1, false, kCFAllocatorNull);
+ CFStringAppend(s, bufferStr);
+ CFRelease(bufferStr);
+
+ dataLen -= i;
+ offset += i;
+ }
+ return s;
+}
+
+static void LoggerLogToConsole(CFDataRef data)
+{
+ // Decode and log a message to the console. Doing this from the worker thread
+ // allow us to serialize logging, which is a benefit that NSLog() doesn't have.
+ // Only drawback is that we have to decode our own message, but that is a minor hassle.
+
+ struct timeval timestamp;
+ bzero(&timestamp, sizeof(timestamp));
+ int type = LOGMSG_TYPE_LOG, contentsType = PART_TYPE_STRING;
+ int imgWidth=0, imgHeight=0;
+ CFStringRef message = NULL;
+ CFStringRef thread = NULL;
+
+ // decode message contents
+ uint8_t *p = (uint8_t *)CFDataGetBytePtr(data) + 4;
+ uint16_t partCount;
+ memcpy(&partCount, p, 2);
+ partCount = ntohs(partCount);
+ p += 2;
+ while (partCount--)
+ {
+ uint8_t partKey = *p++;
+ uint8_t partType = *p++;
+ uint32_t partSize;
+ if (partType == PART_TYPE_INT16)
+ partSize = 2;
+ else if (partType == PART_TYPE_INT32)
+ partSize = 4;
+ else if (partType == PART_TYPE_INT64)
+ partSize = 8;
+ else
+ {
+ memcpy(&partSize, p, 4);
+ p += 4;
+ partSize = ntohl(partSize);
+ }
+ CFTypeRef part = NULL;
+ uint32_t value32 = 0;
+ uint64_t value64 = 0;
+ if (partSize > 0)
+ {
+ if (partType == PART_TYPE_STRING)
+ {
+ // trim whitespace and newline at both ends of the string
+ uint8_t *q = p;
+ uint32_t l = partSize;
+ while (l && (*q == ' ' || *q == '\t' || *q == '\n' || *q == '\r'))
+ q++, l--;
+ uint8_t *r = q + l - 1;
+ while (l && (*r == ' ' || *r == '\t' || *r == '\n' || *r == '\r'))
+ r--, l--;
+ part = CFStringCreateWithBytesNoCopy(NULL, q, l, kCFStringEncodingUTF8, false, kCFAllocatorNull);
+ }
+ else if (partType == PART_TYPE_BINARY)
+ {
+ part = CFDataCreateWithBytesNoCopy(NULL, p, partSize, kCFAllocatorNull);
+ }
+ else if (partType == PART_TYPE_IMAGE)
+ {
+ // ignore image data, we can't log it to console
+ }
+ else if (partType == PART_TYPE_INT32)
+ {
+ memcpy(&value32, p, 4);
+ value32 = ntohl(value32);
+ }
+ else if (partType == PART_TYPE_INT64)
+ {
+ memcpy(&value64, p, 8);
+ value64 = CFSwapInt64BigToHost(value64);
+ }
+ p += partSize;
+ }
+ switch (partKey)
+ {
+ case PART_KEY_MESSAGE_TYPE:
+ type = (int)value32;
+ break;
+ case PART_KEY_TIMESTAMP_S: // timestamp with seconds-level resolution
+ timestamp.tv_sec = (partType == PART_TYPE_INT64) ? (__darwin_time_t)value64 : (__darwin_time_t)value32;
+ break;
+ case PART_KEY_TIMESTAMP_MS: // millisecond part of the timestamp (optional)
+ timestamp.tv_usec = ((partType == PART_TYPE_INT64) ? (__darwin_suseconds_t)value64 : (__darwin_suseconds_t)value32) * 1000;
+ break;
+ case PART_KEY_TIMESTAMP_US: // microsecond part of the timestamp (optional)
+ timestamp.tv_usec = (partType == PART_TYPE_INT64) ? (__darwin_suseconds_t)value64 : (__darwin_suseconds_t)value32;
+ break;
+ case PART_KEY_THREAD_ID:
+ if (thread == NULL) // useless test, we know what we're doing but clang analyzer doesn't...
+ {
+ if (partType == PART_TYPE_INT32)
+ thread = CFStringCreateWithFormat(NULL, NULL, CFSTR("thread 0x%08x"), value32);
+ else if (partType == PART_TYPE_INT64)
+ thread = CFStringCreateWithFormat(NULL, NULL, CFSTR("thread 0x%qx"), value64);
+ else if (partType == PART_TYPE_STRING && part != NULL)
+ thread = CFRetain(part);
+ }
+ break;
+ case PART_KEY_MESSAGE:
+ if (part != NULL)
+ {
+ if (partType == PART_TYPE_STRING)
+ message = CFRetain(part);
+ else if (partType == PART_TYPE_BINARY)
+ message = LoggerCreateStringRepresentationFromBinaryData(part);
+ }
+ contentsType = partType;
+ break;
+ case PART_KEY_IMAGE_WIDTH:
+ imgWidth = (partType == PART_TYPE_INT32 ? (int)value32 : (int)value64);
+ break;
+ case PART_KEY_IMAGE_HEIGHT:
+ imgHeight = (partType == PART_TYPE_INT32 ? (int)value32 : (int)value64);
+ break;
+ default:
+ break;
+ }
+ if (part != NULL)
+ CFRelease(part);
+ }
+
+ // Prepare the final representation and log to console
+ CFMutableStringRef s = CFStringCreateMutable(NULL, 0);
+
+ char buf[32];
+ struct tm t;
+ gmtime_r(&timestamp.tv_sec, &t);
+ strftime(buf, sizeof(buf)-1, "%T", &t);
+ CFStringRef ts = CFStringCreateWithBytesNoCopy(NULL, (const UInt8 *)buf, strlen(buf), kCFStringEncodingASCII, false, kCFAllocatorNull);
+ CFStringAppend(s, ts);
+ CFRelease(ts);
+
+ if (contentsType == PART_TYPE_IMAGE)
+ message = CFStringCreateWithFormat(NULL, NULL, CFSTR("<image width=%d height=%d>"), imgWidth, imgHeight);
+
+ char threadNamePadding[16];
+ threadNamePadding[0] = 0;
+ if (thread != NULL && CFStringGetLength(thread) < 16)
+ {
+ int n = 16 - (int)CFStringGetLength(thread);
+ memset(threadNamePadding, ' ', n);
+ threadNamePadding[n] = 0;
+ }
+ CFStringAppendFormat(s, NULL, CFSTR(".%04d %s%@ | %@"),
+ (int)(timestamp.tv_usec / 1000),
+ threadNamePadding, (thread == NULL) ? CFSTR("") : thread,
+ message ? message : CFSTR(""));
+
+ if (thread != NULL)
+ CFRelease(thread);
+ if (message)
+ CFRelease(message);
+
+ if (type == LOGMSG_TYPE_LOG || type == LOGMSG_TYPE_MARK)
+ CFShow(s);
+
+ CFRelease(s);
+}
+
+static void LoggerWriteMoreData(Logger *logger)
+{
+ if (!logger->connected)
+ {
+ if (logger->options & kLoggerOption_LogToConsole)
+ {
+ pthread_mutex_lock(&logger->logQueueMutex);
+ while (CFArrayGetCount(logger->logQueue))
+ {
+ LoggerLogToConsole((CFDataRef)CFArrayGetValueAtIndex(logger->logQueue, 0));
+ CFArrayRemoveValueAtIndex(logger->logQueue, 0);
+ }
+ pthread_mutex_unlock(&logger->logQueueMutex);
+ pthread_cond_broadcast(&logger->logQueueEmpty);
+ }
+ else if (logger->bufferWriteStream != NULL)
+ {
+ LoggerFlushQueueToBufferStream(logger, NO);
+ }
+ return;
+ }
+
+ if (CFWriteStreamCanAcceptBytes(logger->logStream))
+ {
+ // prepare archived data with log queue contents, unblock the queue as soon as possible
+ CFMutableDataRef sendFirstItem = NULL;
+ if (logger->sendBufferUsed == 0)
+ {
+ // pull more data from the log queue
+ if (logger->bufferReadStream != NULL)
+ {
+ if (!CFReadStreamHasBytesAvailable(logger->bufferReadStream))
+ {
+ CFReadStreamClose(logger->bufferReadStream);
+ CFRelease(logger->bufferReadStream);
+ logger->bufferReadStream = NULL;
+ LoggerEmptyBufferFile(logger);
+ }
+ else
+ {
+ logger->sendBufferUsed = CFReadStreamRead(logger->bufferReadStream, logger->sendBuffer, logger->sendBufferSize);
+ }
+ }
+ else
+ {
+ pthread_mutex_lock(&logger->logQueueMutex);
+ while (CFArrayGetCount(logger->logQueue))
+ {
+ CFDataRef d = (CFDataRef)CFArrayGetValueAtIndex(logger->logQueue, 0);
+ CFIndex dsize = CFDataGetLength(d);
+ if ((logger->sendBufferUsed + dsize) > logger->sendBufferSize)
+ break;
+ memcpy(logger->sendBuffer + logger->sendBufferUsed, CFDataGetBytePtr(d), dsize);
+ logger->sendBufferUsed += dsize;
+ CFArrayRemoveValueAtIndex(logger->logQueue, 0);
+ logger->incompleteSendOfFirstItem = NO;
+ }
+ pthread_mutex_unlock(&logger->logQueueMutex);
+ }
+ if (logger->sendBufferUsed == 0)
+ {
+ // are we done yet?
+ pthread_mutex_lock(&logger->logQueueMutex);
+ if (CFArrayGetCount(logger->logQueue) == 0)
+ {
+ pthread_mutex_unlock(&logger->logQueueMutex);
+ pthread_cond_broadcast(&logger->logQueueEmpty);
+ return;
+ }
+
+ // first item is too big to fit in a single packet, send it separately
+ sendFirstItem = (CFMutableDataRef)CFArrayGetValueAtIndex(logger->logQueue, 0);
+ logger->incompleteSendOfFirstItem = YES;
+ pthread_mutex_unlock(&logger->logQueueMutex);
+ logger->sendBufferOffset = 0;
+ }
+ }
+
+ // send data over the socket. We try hard to be failsafe and if we have to send
+ // data in fragments, we make sure that in case a disconnect occurs we restart
+ // sending the whole message(s)
+ if (logger->sendBufferUsed != 0)
+ {
+ CFIndex written = CFWriteStreamWrite(logger->logStream,
+ logger->sendBuffer + logger->sendBufferOffset,
+ logger->sendBufferUsed - logger->sendBufferOffset);
+ if (written < 0)
+ {
+ // We'll get an event if the stream closes on error. Don't discard the data,
+ // it will be sent as soon as a connection is re-acquired.
+ return;
+ }
+ if ((logger->sendBufferOffset + written) < logger->sendBufferUsed)
+ {
+ // everything couldn't be sent at once
+ logger->sendBufferOffset += written;
+ }
+ else
+ {
+ logger->sendBufferUsed = 0;
+ logger->sendBufferOffset = 0;
+ }
+ }
+ else if (sendFirstItem)
+ {
+ CFIndex length = CFDataGetLength(sendFirstItem) - logger->sendBufferOffset;
+ CFIndex written = CFWriteStreamWrite(logger->logStream,
+ CFDataGetBytePtr(sendFirstItem) + logger->sendBufferOffset,
+ length);
+ if (written < 0)
+ {
+ // We'll get an event if the stream closes on error
+ return;
+ }
+ if (written < length)
+ {
+ // The output pipe is full, and the first item has not been sent completely
+ // We need to reduce the remaining data on the first item so it can be taken
+ // care of at the next iteration. We take advantage of the fact that each item
+ // in the queue is actually a mutable data block
+ // @@@ NOTE: IF WE GET DISCONNECTED WHILE DOING THIS, THINGS WILL GO WRONG
+ // NEED TO UPDATE THIS LOGIC
+ CFDataReplaceBytes((CFMutableDataRef)sendFirstItem, CFRangeMake(0, written), NULL, 0);
+ return;
+ }
+
+ // we are done sending the first item in the queue, remove it now
+ pthread_mutex_lock(&logger->logQueueMutex);
+ CFArrayRemoveValueAtIndex(logger->logQueue, 0);
+ logger->incompleteSendOfFirstItem = NO;
+ pthread_mutex_unlock(&logger->logQueueMutex);
+ logger->sendBufferOffset = 0;
+ }
+
+ pthread_mutex_lock(&logger->logQueueMutex);
+ int remainingMsgs = CFArrayGetCount(logger->logQueue);
+ pthread_mutex_unlock(&logger->logQueueMutex);
+ if (remainingMsgs == 0)
+ pthread_cond_broadcast(&logger->logQueueEmpty);
+ }
+}
+
+// -----------------------------------------------------------------------------
+#pragma mark -
+#pragma mark File buffering functions
+// -----------------------------------------------------------------------------
+static void LoggerCreateBufferWriteStream(Logger *logger)
+{
+ LOGGERDBG(CFSTR("LoggerCreateBufferWriteStream to file %@"), logger->bufferFile);
+ CFURLRef fileURL = CFURLCreateWithFileSystemPath(NULL, logger->bufferFile, kCFURLPOSIXPathStyle, false);
+ if (fileURL != NULL)
+ {
+ // Create write stream to file
+ logger->bufferWriteStream = CFWriteStreamCreateWithFile(NULL, fileURL);
+ CFRelease(fileURL);
+ if (logger->bufferWriteStream != NULL)
+ {
+ // Set flag to append new data to buffer file
+ CFWriteStreamSetProperty(logger->bufferWriteStream, kCFStreamPropertyAppendToFile, kCFBooleanTrue);
+
+ // Open the buffer stream for writing
+ if (!CFWriteStreamOpen(logger->bufferWriteStream))
+ {
+ CFRelease(logger->bufferWriteStream);
+ logger->bufferWriteStream = NULL;
+ }
+ else
+ {
+ // Write client info and flush the queue contents to buffer file
+ LoggerPushClientInfoToFrontOfQueue(logger);
+ LoggerFlushQueueToBufferStream(logger, YES);
+ }
+ }
+ }
+ if (logger->bufferWriteStream == NULL)
+ {
+ CFShow(CFSTR("NSLogger Warning: failed opening buffer file for writing:"));
+ CFShow(logger->bufferFile);
+ }
+}
+
+static void LoggerCreateBufferReadStream(Logger *logger)
+{
+ LOGGERDBG(CFSTR("LoggerCreateBufferReadStream from file %@"), logger->bufferFile);
+ CFURLRef fileURL = CFURLCreateWithFileSystemPath(NULL, logger->bufferFile, kCFURLPOSIXPathStyle, false);
+ if (fileURL != NULL)
+ {
+ // Create read stream from file
+ logger->bufferReadStream = CFReadStreamCreateWithFile(NULL, fileURL);
+ CFRelease(fileURL);
+ if (logger->bufferReadStream != NULL)
+ {
+ if (!CFReadStreamOpen(logger->bufferReadStream))
+ {
+ CFRelease(logger->bufferReadStream);
+ logger->bufferReadStream = NULL;
+ }
+ }
+ }
+}
+
+static void LoggerEmptyBufferFile(Logger *logger)
+{
+ // completely remove the buffer file from storage
+ LOGGERDBG(CFSTR("LoggerEmptyBufferFile %@"), logger->bufferFile);
+ if (logger->bufferFile != NULL)
+ {
+ CFIndex bufferSize = 1 + CFStringGetLength(logger->bufferFile) * 3;
+ char *buffer = (char *)malloc(bufferSize);
+ if (buffer != NULL)
+ {
+ if (CFStringGetFileSystemRepresentation(logger->bufferFile, buffer, bufferSize))
+ {
+ // remove file
+ unlink(buffer);
+ }
+ free(buffer);
+ }
+ }
+}
+
+static void LoggerFileBufferingOptionsChanged(Logger *logger)
+{
+ // File buffering options changed:
+ // - close the current buffer file stream, if any
+ // - create a new one, if needed
+ LOGGERDBG(CFSTR("LoggerFileBufferingOptionsChanged bufferFile=%@"), logger->bufferFile);
+ if (logger->bufferWriteStream != NULL)
+ {
+ CFWriteStreamClose(logger->bufferWriteStream);
+ CFRelease(logger->bufferWriteStream);
+ logger->bufferWriteStream = NULL;
+ }
+ if (logger->bufferFile != NULL)
+ LoggerCreateBufferWriteStream(logger);
+}
+
+static void LoggerFlushQueueToBufferStream(Logger *logger, BOOL firstEntryIsClientInfo)
+{
+ LOGGERDBG(CFSTR("LoggerFlushQueueToBufferStream"));
+ pthread_mutex_lock(&logger->logQueueMutex);
+ if (logger->incompleteSendOfFirstItem)
+ {
+ // drop anything being sent
+ logger->sendBufferUsed = 0;
+ logger->sendBufferOffset = 0;
+ }
+ logger->incompleteSendOfFirstItem = NO;
+
+ // Write outstanding messages to the buffer file (streams don't detect disconnection
+ // until the next write, where we could lose one or more messages)
+ if (!firstEntryIsClientInfo && logger->sendBufferUsed)
+ CFWriteStreamWrite(logger->bufferWriteStream, logger->sendBuffer + logger->sendBufferOffset, logger->sendBufferUsed - logger->sendBufferOffset);
+
+ int n = 0;
+ while (CFArrayGetCount(logger->logQueue))
+ {
+ CFDataRef data = CFArrayGetValueAtIndex(logger->logQueue, 0);
+ CFIndex dataLength = CFDataGetLength(data);
+ CFIndex written = CFWriteStreamWrite(logger->bufferWriteStream, CFDataGetBytePtr(data), dataLength);
+ if (written != dataLength)
+ {
+ // couldn't write all data to file, maybe storage run out of space?
+ CFShow(CFSTR("NSLogger Error: failed flushing the whole queue to buffer file:"));
+ CFShow(logger->bufferFile);
+ break;
+ }
+ CFArrayRemoveValueAtIndex(logger->logQueue, 0);
+ if (n == 0 && firstEntryIsClientInfo && logger->sendBufferUsed)
+ {
+ // try hard: write any outstanding messages to the buffer file, after the client info
+ CFWriteStreamWrite(logger->bufferWriteStream, logger->sendBuffer + logger->sendBufferOffset, logger->sendBufferUsed - logger->sendBufferOffset);
+ }
+ n++;
+ }
+ logger->sendBufferUsed = 0;
+ logger->sendBufferOffset = 0;
+ pthread_mutex_unlock(&logger->logQueueMutex);
+}
+
+// -----------------------------------------------------------------------------
+#pragma mark -
+#pragma mark Bonjour browsing
+// -----------------------------------------------------------------------------
+static void LoggerStartBonjourBrowsing(Logger *logger)
+{
+ LOGGERDBG(CFSTR("LoggerStartBonjourBrowsing"));
+
+ if (logger->options & kLoggerOption_BrowseOnlyLocalDomain)
+ {
+ LOGGERDBG(CFSTR("Logger configured to search only the local domain, searching for services on: local."));
+ if (!LoggerBrowseBonjourForServices(logger, CFSTR("local.")) && logger->host == NULL)
+ {
+ LOGGERDBG(CFSTR("*** Logger: could not browse for services in domain local., no remote host configured: reverting to console logging. ***"));
+ logger->options |= kLoggerOption_LogToConsole;
+ }
+ }
+ else
+ {
+ LOGGERDBG(CFSTR("Logger configured to search all domains, browsing for domains first"));
+ CFNetServiceClientContext context = {0, (void *)logger, NULL, NULL, NULL};
+ CFRunLoopRef runLoop = CFRunLoopGetCurrent();
+ logger->bonjourDomainBrowser = CFNetServiceBrowserCreate(NULL, &LoggerServiceBrowserCallBack, &context);
+ CFNetServiceBrowserScheduleWithRunLoop(logger->bonjourDomainBrowser, runLoop, kCFRunLoopCommonModes);
+ if (!CFNetServiceBrowserSearchForDomains(logger->bonjourDomainBrowser, false, NULL))
+ {
+ // An error occurred, revert to console logging if there is no remote host
+ LOGGERDBG(CFSTR("*** Logger: could not browse for domains, reverting to console logging. ***"));
+ CFNetServiceBrowserUnscheduleFromRunLoop(logger->bonjourDomainBrowser, runLoop, kCFRunLoopCommonModes);
+ CFRelease(logger->bonjourDomainBrowser);
+ logger->bonjourDomainBrowser = NULL;
+ if (logger->host == NULL)
+ logger->options |= kLoggerOption_LogToConsole;
+ }
+ }
+}
+
+static void LoggerStopBonjourBrowsing(Logger *logger)
+{
+ LOGGERDBG(CFSTR("LoggerStopBonjourBrowsing"));
+
+ // stop browsing for domains
+ if (logger->bonjourDomainBrowser != NULL)
+ {
+ CFNetServiceBrowserStopSearch(logger->bonjourDomainBrowser, NULL);
+ CFNetServiceBrowserUnscheduleFromRunLoop(logger->bonjourDomainBrowser, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+ CFNetServiceBrowserInvalidate(logger->bonjourDomainBrowser);
+ CFRelease(logger->bonjourDomainBrowser);
+ logger->bonjourDomainBrowser = NULL;
+ }
+
+ // stop browsing for services
+ CFIndex idx;
+ for (idx = 0; idx < CFArrayGetCount(logger->bonjourServiceBrowsers); idx++)
+ {
+ CFNetServiceBrowserRef browser = (CFNetServiceBrowserRef)CFArrayGetValueAtIndex(logger->bonjourServiceBrowsers, idx);
+ CFNetServiceBrowserStopSearch(browser, NULL);
+ CFNetServiceBrowserUnscheduleFromRunLoop(browser, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+ CFNetServiceBrowserInvalidate(browser);
+ }
+ CFArrayRemoveAllValues(logger->bonjourServiceBrowsers);
+
+ // Forget all services
+ CFArrayRemoveAllValues(logger->bonjourServices);
+}
+
+static BOOL LoggerBrowseBonjourForServices(Logger *logger, CFStringRef domainName)
+{
+ BOOL result = NO;
+ CFNetServiceClientContext context = {0, (void *)logger, NULL, NULL, NULL};
+ CFRunLoopRef runLoop = CFRunLoopGetCurrent();
+
+ CFNetServiceBrowserRef browser = CFNetServiceBrowserCreate(NULL, (CFNetServiceBrowserClientCallBack)&LoggerServiceBrowserCallBack, &context);
+ CFNetServiceBrowserScheduleWithRunLoop(browser, runLoop, kCFRunLoopCommonModes);
+ CFStreamError error;
+
+ // try to use the user-specfied service type if any, fallback on our
+ // default service type
+ CFStringRef serviceType = logger->bonjourServiceType;
+ if (serviceType == NULL)
+ {
+ if (logger->options & kLoggerOption_UseSSL)
+ serviceType = LOGGER_SERVICE_TYPE_SSL;
+ else
+ serviceType = LOGGER_SERVICE_TYPE;
+ }
+ if (!CFNetServiceBrowserSearchForServices(browser, domainName, serviceType, &error))
+ {
+ LOGGERDBG(CFSTR("Logger can't start search on domain: %@ (error %d)"), domainName, error.error);
+ CFNetServiceBrowserUnscheduleFromRunLoop(browser, runLoop, kCFRunLoopCommonModes);
+ CFNetServiceBrowserInvalidate(browser);
+ }
+ else
+ {
+ LOGGERDBG(CFSTR("Logger started search for services of type %@ in domain %@"), serviceType, domainName);
+ CFArrayAppendValue(logger->bonjourServiceBrowsers, browser);
+ result = YES;
+ }
+ CFRelease(browser);
+ return result;
+}
+
+static void LoggerServiceBrowserCallBack (CFNetServiceBrowserRef browser,
+ CFOptionFlags flags,
+ CFTypeRef domainOrService,
+ CFStreamError* error,
+ void* info)
+{
+ LOGGERDBG(CFSTR("LoggerServiceBrowserCallback browser=%@ flags=0x%04x domainOrService=%@ error=%d"), browser, flags, domainOrService, error==NULL ? 0 : error->error);
+
+ Logger *logger = (Logger *)info;
+ assert(logger != NULL);
+
+ if (flags & kCFNetServiceFlagRemove)
+ {
+ if (!(flags & kCFNetServiceFlagIsDomain))
+ {
+ CFNetServiceRef service = (CFNetServiceRef)domainOrService;
+ CFIndex idx;
+ for (idx = 0; idx < CFArrayGetCount(logger->bonjourServices); idx++)
+ {
+ if (CFArrayGetValueAtIndex(logger->bonjourServices, idx) == service)
+ {
+ CFNetServiceUnscheduleFromRunLoop(service, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+ CFNetServiceClientContext context = {0, NULL, NULL, NULL, NULL};
+ CFNetServiceSetClient(service, NULL, &context);
+ CFNetServiceCancel(service);
+ CFArrayRemoveValueAtIndex(logger->bonjourServices, idx);
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ if (flags & kCFNetServiceFlagIsDomain)
+ {
+ // start searching for services in this domain
+ LoggerBrowseBonjourForServices(logger, (CFStringRef)domainOrService);
+ }
+ else
+ {
+ // a service has been found
+ LOGGERDBG(CFSTR("Logger found service: %@"), domainOrService);
+ CFNetServiceRef service = (CFNetServiceRef)domainOrService;
+ if (service != NULL)
+ {
+ // if the user has specified that Logger shall only connect to the specified
+ // Bonjour service name, check it now. This makes things easier in a teamwork
+ // environment where multiple instances of NSLogger viewer may run on the
+ // same network
+ if (logger->bonjourServiceName != NULL)
+ {
+ LOGGERDBG(CFSTR("-> looking for services of name %@"), logger->bonjourServiceName);
+ CFStringRef name = CFNetServiceGetName(service);
+ if (name == NULL || kCFCompareEqualTo != CFStringCompare(name, logger->bonjourServiceName, kCFCompareCaseInsensitive | kCFCompareDiacriticInsensitive))
+ {
+ LOGGERDBG(CFSTR("-> service name %@ does not match requested service name, ignoring."), name, logger->bonjourServiceName);
+ return;
+ }
+ }
+ CFArrayAppendValue(logger->bonjourServices, service);
+ LoggerTryConnect(logger);
+ }
+ }
+ }
+}
+
+// -----------------------------------------------------------------------------
+#pragma mark -
+#pragma mark Reachability
+// -----------------------------------------------------------------------------
+static void LoggerStartReachabilityChecking(Logger *logger)
+{
+ if (logger->host != NULL && logger->reachability == NULL)
+ {
+ LOGGERDBG(CFSTR("Starting SCNetworkReachability to wait for host %@ to be reachable"), logger->host);
+
+ CFIndex length = CFStringGetLength(logger->host) * 3;
+ char *buffer = (char *)malloc(length + 1);
+ CFStringGetBytes(logger->host, CFRangeMake(0, CFStringGetLength(logger->host)), kCFStringEncodingUTF8, '?', false, (UInt8 *)buffer, length, &length);
+ buffer[length] = 0;
+
+ logger->reachability = SCNetworkReachabilityCreateWithName(NULL, buffer);
+
+ SCNetworkReachabilityContext context = {0, logger, NULL, NULL, NULL};
+ SCNetworkReachabilitySetCallback(logger->reachability, &LoggerReachabilityCallBack, &context);
+ SCNetworkReachabilityScheduleWithRunLoop(logger->reachability, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+
+ free(buffer);
+
+ // Also start a timer that will try to reconnect every N seconds
+ if (logger->checkHostTimer == NULL)
+ {
+ CFRunLoopTimerContext timerCtx = {
+ .version = 0,
+ .info = logger,
+ .retain = NULL,
+ .release = NULL,
+ .copyDescription = NULL
+ };
+ logger->checkHostTimer = CFRunLoopTimerCreate(NULL,
+ CFAbsoluteTimeGetCurrent() + 5,
+ 5, // reconnect interval
+ 0,
+ 0,
+ &LoggerTimedReconnectCallback,
+ &timerCtx);
+ if (logger->checkHostTimer != NULL)
+ {
+ LOGGERDBG(CFSTR("Starting the TimedReconnect timer to regularly retry the connection"));
+ CFRunLoopAddTimer(CFRunLoopGetCurrent(), logger->checkHostTimer, kCFRunLoopCommonModes);
+ }
+ }
+ }
+}
+
+static void LoggerStopReachabilityChecking(Logger *logger)
+{
+ if (logger->reachability != NULL)
+ {
+ LOGGERDBG(CFSTR("Stopping SCNetworkReachability"));
+ SCNetworkReachabilityUnscheduleFromRunLoop(logger->reachability, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+ CFRelease(logger->reachability);
+ logger->reachability = NULL;
+ }
+ if (logger->checkHostTimer != NULL)
+ {
+ CFRunLoopTimerInvalidate(logger->checkHostTimer);
+ CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), logger->checkHostTimer, kCFRunLoopCommonModes);
+ CFRelease(logger->checkHostTimer);
+ logger->checkHostTimer = NULL;
+ }
+}
+
+static void LoggerReachabilityCallBack(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info)
+{
+ Logger *logger = (Logger *)info;
+ assert(logger != NULL);
+ LOGGERDBG(CFSTR("LoggerReachabilityCallBack called with flags=0x%08lx"), flags);
+ if (flags & kSCNetworkReachabilityFlagsReachable)
+ {
+ // target host became reachable. If we have not other open connection,
+ // try direct connection to the host
+ if (logger->logStream == NULL && logger->host != NULL)
+ {
+ LOGGERDBG(CFSTR("-> host %@ became reachable, trying to connect."), logger->host);
+ LoggerTryConnect(logger);
+ }
+ }
+}
+
+static void LoggerTimedReconnectCallback(CFRunLoopTimerRef timer, void *info)
+{
+ Logger *logger = (Logger *)info;
+ assert(logger != NULL);
+ LOGGERDBG(CFSTR("LoggerTimedReconnectCallback"));
+ if (logger->logStream == NULL && logger->host != NULL)
+ {
+ LOGGERDBG(CFSTR("-> trying to reconnect to host %@"), logger->host);
+ LoggerTryConnect(logger);
+ }
+ else
+ {
+ LOGGERDBG(CFSTR("-> timer not needed anymore, removing it form runloop"));
+ CFRunLoopTimerInvalidate(timer);
+ CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), logger->checkHostTimer, kCFRunLoopCommonModes);
+ CFRelease(timer);
+ logger->checkHostTimer = NULL;
+ }
+}
+
+// -----------------------------------------------------------------------------
+#pragma mark -
+#pragma mark Stream management
+// -----------------------------------------------------------------------------
+static BOOL LoggerConfigureAndOpenStream(Logger *logger)
+{
+ // configure and open stream
+ LOGGERDBG(CFSTR("LoggerConfigureAndOpenStream configuring and opening log stream"));
+ CFStreamClientContext context = {0, (void *)logger, NULL, NULL, NULL};
+ if (CFWriteStreamSetClient(logger->logStream,
+ (kCFStreamEventOpenCompleted |
+ kCFStreamEventCanAcceptBytes |
+ kCFStreamEventErrorOccurred |
+ kCFStreamEventEndEncountered),
+ &LoggerWriteStreamCallback,
+ &context))
+ {
+ if (logger->options & kLoggerOption_UseSSL)
+ {
+ // Configure stream to require a SSL connection
+ LOGGERDBG(CFSTR("-> configuring SSL"));
+ const void *SSLKeys[] = {
+ kCFStreamSSLLevel,
+ kCFStreamSSLValidatesCertificateChain,
+ kCFStreamSSLIsServer,
+ kCFStreamSSLPeerName
+ };
+ const void *SSLValues[] = {
+ kCFStreamSocketSecurityLevelNegotiatedSSL,
+ kCFBooleanFalse, // no certificate chain validation (we use a self-signed certificate)
+ kCFBooleanFalse, // not a server
+ kCFNull
+ };
+
+#if TARGET_OS_IPHONE
+ // workaround for TLS in iOS 5 as per TN2287
+ // see http://developer.apple.com/library/ios/#technotes/tn2287/_index.html#//apple_ref/doc/uid/DTS40011309
+ // if we are running iOS 5 or later, use a special mode that allows the stack to downgrade gracefully
+ #if ALLOW_COCOA_USE
+ NSString *versionString = [[UIDevice currentDevice] systemVersion];
+ if ([versionString compare:@"5.0" options:NSNumericSearch] != NSOrderedAscending)
+ SSLValues[0] = CFSTR("kCFStreamSocketSecurityLevelTLSv1_0SSLv3");
+ #else
+ // we can't find out, assume we _may_ be on iOS 5 but can't be certain
+ // go for SSLv3 which works without the TLS 1.2 / 1.1 / 1.0 downgrade issue
+ SSLValues[0] = kCFStreamSocketSecurityLevelSSLv3;
+ #endif
+#endif
+
+ CFDictionaryRef SSLDict = CFDictionaryCreate(NULL, SSLKeys, SSLValues, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ CFWriteStreamSetProperty(logger->logStream, kCFStreamPropertySSLSettings, SSLDict);
+ CFRelease(SSLDict);
+ }
+
+ CFWriteStreamScheduleWithRunLoop(logger->logStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+
+ if (CFWriteStreamOpen(logger->logStream))
+ {
+ LOGGERDBG(CFSTR("-> stream open attempt, waiting for open completion"));
+ return YES;
+ }
+
+ LOGGERDBG(CFSTR("-> stream open failed."));
+
+ CFWriteStreamSetClient(logger->logStream, kCFStreamEventNone, NULL, NULL);
+ if (CFWriteStreamGetStatus(logger->logStream) == kCFStreamStatusOpen)
+ CFWriteStreamClose(logger->logStream);
+ CFWriteStreamUnscheduleFromRunLoop(logger->logStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+ }
+ else
+ {
+ LOGGERDBG(CFSTR("-> stream set client failed."));
+ }
+ CFRelease(logger->logStream);
+ logger->logStream = NULL;
+ return NO;
+}
+
+static void LoggerTryConnect(Logger *logger)
+{
+ // Try connecting to the next address in the sConnectAttempts array
+ LOGGERDBG(CFSTR("LoggerTryConnect, %d services registered, current stream=%@"), CFArrayGetCount(logger->bonjourServices), logger->logStream);
+
+ // If we already have a connection established or being attempted, stop here
+ if (logger->logStream != NULL)
+ {
+ LOGGERDBG(CFSTR("-> another connection is opened or in progress, giving up for now"));
+ return;
+ }
+
+ // If there are discovered Bonjour services, try them now
+ while (CFArrayGetCount(logger->bonjourServices))
+ {
+ CFNetServiceRef service = (CFNetServiceRef)CFArrayGetValueAtIndex(logger->bonjourServices, 0);
+ LOGGERDBG(CFSTR("-> Trying to open write stream to service %@"), service);
+ CFStreamCreatePairWithSocketToNetService(NULL, service, NULL, &logger->logStream);
+ CFArrayRemoveValueAtIndex(logger->bonjourServices, 0);
+ if (logger->logStream == NULL)
+ {
+ // create pair failed
+ LOGGERDBG(CFSTR("-> failed."));
+ }
+ else if (LoggerConfigureAndOpenStream(logger))
+ {
+ // open is now in progress
+ return;
+ }
+ }
+
+ // If there is a host to directly connect to, try it now (this will happen before
+ // Bonjour kicks in, Bonjour being handled as a fallback solution if direct Host
+ // fails)
+ if (logger->host != NULL)
+ {
+ LOGGERDBG(CFSTR("-> Trying to open direct connection to host %@ port %u"), logger->host, logger->port);
+ CFStreamCreatePairWithSocketToHost(NULL, logger->host, logger->port, NULL, &logger->logStream);
+ if (logger->logStream == NULL)
+ {
+ // Create stream failed
+ LOGGERDBG(CFSTR("-> failed."));
+ if (logger->logStream != NULL)
+ {
+ CFRelease(logger->logStream);
+ logger->logStream = NULL;
+ }
+ }
+ else if (LoggerConfigureAndOpenStream(logger))
+ {
+ // open is now in progress
+ return;
+ }
+
+ // Could not connect to host: start Reachability so we know when target host becomes reachable
+ // and can try to connect again
+ LoggerStartReachabilityChecking(logger);
+ }
+
+ // Finally, if Bonjour is enabled and not started yet, start it now.
+ if ((logger->options & kLoggerOption_BrowseBonjour) &&
+ (logger->bonjourDomainBrowser == NULL || CFArrayGetCount(logger->bonjourServiceBrowsers) == 0))
+ {
+ LoggerStartBonjourBrowsing(logger);
+ }
+}
+
+static void LoggerWriteStreamCallback(CFWriteStreamRef ws, CFStreamEventType event, void* info)
+{
+ Logger *logger = (Logger *)info;
+ assert(ws == logger->logStream);
+ switch (event)
+ {
+ case kCFStreamEventOpenCompleted:
+ // A stream open was complete. Cancel all bonjour browsing,
+ // service resolution and connection attempts, and try to
+ // write existing buffer contents
+ LOGGERDBG(CFSTR("Logger CONNECTED"));
+ logger->connected = YES;
+ LoggerStopBonjourBrowsing(logger);
+ LoggerStopReachabilityChecking(logger);
+ if (logger->bufferWriteStream != NULL)
+ {
+ // now that a connection is acquired, we can stop logging to a file
+ CFWriteStreamClose(logger->bufferWriteStream);
+ CFRelease(logger->bufferWriteStream);
+ logger->bufferWriteStream = NULL;
+ }
+ if (logger->bufferFile != NULL)
+ {
+ // if a buffer file was defined, try to read its contents
+ LoggerCreateBufferReadStream(logger);
+ }
+ LoggerPushClientInfoToFrontOfQueue(logger);
+ LoggerWriteMoreData(logger);
+ break;
+
+ case kCFStreamEventCanAcceptBytes:
+ LoggerWriteMoreData(logger);
+ break;
+
+ case kCFStreamEventErrorOccurred: {
+ CFErrorRef error = CFWriteStreamCopyError(ws);
+ LOGGERDBG(CFSTR("Logger stream error: %@"), error);
+ CFRelease(error);
+ // Fall-thru
+ }
+
+ case kCFStreamEventEndEncountered:
+ if (logger->connected)
+ {
+ LOGGERDBG(CFSTR("Logger DISCONNECTED"));
+ logger->connected = NO;
+ }
+ CFWriteStreamSetClient(logger->logStream, 0, NULL, NULL);
+ CFWriteStreamUnscheduleFromRunLoop(logger->logStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+ CFWriteStreamClose(logger->logStream);
+
+ CFRelease(logger->logStream);
+ logger->logStream = NULL;
+
+ if (logger->bufferReadStream != NULL)
+ {
+ // In the case the connection drops before we have flushed the
+ // whole contents of the file, we choose to keep it integrally
+ // and retransmit it when reconnecting to the viewer. The reason
+ // of this choice is that we may have transmitted only part of
+ // a message, and this may cause errors on the desktop side.
+ CFReadStreamClose(logger->bufferReadStream);
+ CFRelease(logger->bufferReadStream);
+ logger->bufferReadStream = NULL;
+ }
+ if (logger->bufferFile != NULL && logger->bufferWriteStream == NULL)
+ LoggerCreateBufferWriteStream(logger);
+
+ if (logger->host != NULL && !(logger->options & kLoggerOption_BrowseBonjour))
+ LoggerStartReachabilityChecking(logger);
+ else
+ LoggerTryConnect(logger);
+ break;
+ }
+}
+
+// -----------------------------------------------------------------------------
+#pragma mark -
+#pragma mark Internal encoding functions
+// -----------------------------------------------------------------------------
+static void LoggerMessageAddTimestamp(CFMutableDataRef encoder)
+{
+ struct timeval t;
+ if (gettimeofday(&t, NULL) == 0)
+ {
+#if __LP64__
+ LoggerMessageAddInt64(encoder, t.tv_sec, PART_KEY_TIMESTAMP_S);
+ LoggerMessageAddInt64(encoder, t.tv_usec, PART_KEY_TIMESTAMP_US);
+#else
+ LoggerMessageAddInt32(encoder, t.tv_sec, PART_KEY_TIMESTAMP_S);
+ LoggerMessageAddInt32(encoder, t.tv_usec, PART_KEY_TIMESTAMP_US);
+#endif
+ }
+ else
+ {
+ time_t ts = time(NULL);
+#if __LP64__
+ LoggerMessageAddInt64(encoder, ts, PART_KEY_TIMESTAMP_S);
+#else
+ LoggerMessageAddInt32(encoder, ts, PART_KEY_TIMESTAMP_S);
+#endif
+ }
+}
+
+static void LoggerMessageAddTimestampAndThreadID(CFMutableDataRef encoder)
+{
+ LoggerMessageAddTimestamp(encoder);
+
+ BOOL hasThreadName = NO;
+#if ALLOW_COCOA_USE
+ // Getting the thread number is tedious, to say the least. Since there is
+ // no direct way to get it, we have to do it sideways. Note that it can be dangerous
+ // to use any Cocoa call when in a multithreaded application that only uses non-Cocoa threads
+ // and for which Cocoa's multithreading has not been activated. We test for this case.
+ if ([NSThread isMultiThreaded] || [NSThread isMainThread])
+ {
+ AUTORELEASE_POOL_BEGIN
+ NSThread *thread = [NSThread currentThread];
+ NSString *name = [thread name];
+ if (![name length])
+ {
+ if ([thread isMainThread])
+ name = @"Main thread";
+ else
+ {
+ name = [thread description];
+ NSRange range = [name rangeOfString:@"num = "];
+ if (range.location != NSNotFound)
+ {
+ name = [NSString stringWithFormat:@"Thread %@",
+ [name substringWithRange:NSMakeRange(range.location + range.length,
+ [name length] - range.location - range.length - 1)]];
+ }
+ }
+ }
+ if (name != nil)
+ {
+ LoggerMessageAddString(encoder, (CAST_TO_CFSTRING)name, PART_KEY_THREAD_ID);
+ hasThreadName = YES;
+ }
+ AUTORELEASE_POOL_END
+ }
+#endif
+ if (!hasThreadName)
+ {
+#if __LP64__
+ LoggerMessageAddInt64(encoder, (int64_t)pthread_self(), PART_KEY_THREAD_ID);
+#else
+ LoggerMessageAddInt32(encoder, (int32_t)pthread_self(), PART_KEY_THREAD_ID);
+#endif
+ }
+}
+
+static void LoggerMessageUpdateDataHeader(CFMutableDataRef data)
+{
+ // update the data header with updated part count and size
+ UInt8 *p = CFDataGetMutableBytePtr(data);
+ uint32_t size = htonl(CFDataGetLength(data) - 4);
+ uint16_t partCount = htons(ntohs(*(uint16_t *)(p + 4)) + 1);
+ memcpy(p, &size, 4);
+ memcpy(p+4, &partCount, 2);
+}
+
+static CFMutableDataRef LoggerMessageCreate()
+{
+ CFMutableDataRef data = CFDataCreateMutable(NULL, 0);
+ CFDataIncreaseLength(data, 6);
+ UInt8 *p = CFDataGetMutableBytePtr(data);
+ p[3] = 2; // size 0x00000002 in big endian
+ return data;
+}
+
+static void LoggerMessageAddInt16(CFMutableDataRef data, int16_t anInt, int key)
+{
+ uint16_t partData = htonl(anInt);
+ uint8_t keyAndType[2] = {(uint8_t)key, PART_TYPE_INT16};
+ CFDataAppendBytes(data, (const UInt8 *)&keyAndType, 2);
+ CFDataAppendBytes(data, (const UInt8 *)&partData, 2);
+ LoggerMessageUpdateDataHeader(data);
+}
+
+static void LoggerMessageAddInt32(CFMutableDataRef data, int32_t anInt, int key)
+{
+ uint32_t partData = htonl(anInt);
+ uint8_t keyAndType[2] = {(uint8_t)key, PART_TYPE_INT32};
+ CFDataAppendBytes(data, (const UInt8 *)&keyAndType, 2);
+ CFDataAppendBytes(data, (const UInt8 *)&partData, 4);
+ LoggerMessageUpdateDataHeader(data);
+}
+
+static void LoggerMessageAddInt64(CFMutableDataRef data, int64_t anInt, int key)
+{
+ uint32_t partData[2] = {htonl((uint32_t)(anInt >> 32)), htonl((uint32_t)anInt)};
+ uint8_t keyAndType[2] = {(uint8_t)key, PART_TYPE_INT64};
+ CFDataAppendBytes(data, (const UInt8 *)&keyAndType, 2);
+ CFDataAppendBytes(data, (const UInt8 *)&partData, 8);
+ LoggerMessageUpdateDataHeader(data);
+}
+
+static void LoggerMessageAddCString(CFMutableDataRef data, const char *aString, int key)
+{
+ if (aString == NULL || *aString == 0)
+ return;
+
+ // convert to UTF-8
+ int len = (int)strlen(aString);
+ uint8_t *buf = malloc(2 * len);
+ if (buf != NULL)
+ {
+ int i, n = 0;
+ for (i = 0; i < len; i++)
+ {
+ uint8_t c = (uint8_t)(*aString++);
+ if (c < 0x80)
+ buf[n++] = c;
+ else {
+ buf[n++] = 0xC0 | (c >> 6);
+ buf[n++] = (c & 0x6F) | 0x80;
+ }
+ }
+ if (n)
+ {
+ uint32_t partSize = htonl(n);
+ uint8_t keyAndType[2] = {(uint8_t)key, PART_TYPE_STRING};
+ CFDataAppendBytes(data, (const UInt8 *)&keyAndType, 2);
+ CFDataAppendBytes(data, (const UInt8 *)&partSize, 4);
+ CFDataAppendBytes(data, buf, n);
+ LoggerMessageUpdateDataHeader(data);
+ }
+ free(buf);
+ }
+}
+
+static void LoggerMessageAddString(CFMutableDataRef data, CFStringRef aString, int key)
+{
+ if (aString == NULL)
+ aString = CFSTR("");
+
+ // All strings are UTF-8 encoded
+ uint8_t keyAndType[2] = {(uint8_t)key, PART_TYPE_STRING};
+ uint32_t partSize = 0;
+ uint8_t *bytes = NULL;
+
+ CFIndex stringLength = CFStringGetLength(aString);
+ CFIndex bytesLength = stringLength * 4;
+ if (stringLength)
+ {
+ bytes = (uint8_t *)malloc(stringLength * 4 + 4);
+ CFStringGetBytes(aString, CFRangeMake(0, stringLength), kCFStringEncodingUTF8, '?', false, bytes, bytesLength, &bytesLength);
+ partSize = htonl(bytesLength);
+ }
+
+ CFDataAppendBytes(data, (const UInt8 *)&keyAndType, 2);
+ CFDataAppendBytes(data, (const UInt8 *)&partSize, 4);
+ if (partSize)
+ CFDataAppendBytes(data, bytes, bytesLength);
+
+ if (bytes != NULL)
+ free(bytes);
+ LoggerMessageUpdateDataHeader(data);
+}
+
+static void LoggerMessageAddData(CFMutableDataRef data, CFDataRef theData, int key, int partType)
+{
+ if ( theData != nil ){
+ uint8_t keyAndType[2] = {(uint8_t)key, (uint8_t)partType};
+ CFIndex dataLength = CFDataGetLength(theData);
+ uint32_t partSize = htonl(dataLength);
+ CFDataAppendBytes(data, (const UInt8 *)&keyAndType, 2);
+ CFDataAppendBytes(data, (const UInt8 *)&partSize, 4);
+ if (partSize)
+ CFDataAppendBytes(data, CFDataGetBytePtr(theData), dataLength);
+ LoggerMessageUpdateDataHeader(data);
+ }
+}
+
+static uint32_t LoggerMessageGetSeq(CFDataRef message)
+{
+ // Extract the sequence number from a message. When pushing messages to the queue,
+ // we use this to guarantee the logging order according to the seq#
+ uint32_t seq = 0;
+ uint8_t *p = (uint8_t *)CFDataGetBytePtr(message) + 4;
+ uint16_t partCount;
+ memcpy(&partCount, p, 2);
+ partCount = ntohs(partCount);
+ p += 2;
+ while (partCount--)
+ {
+ uint8_t partKey = *p++;
+ uint8_t partType = *p++;
+ uint32_t partSize;
+ if (partType == PART_TYPE_INT16)
+ partSize = 2;
+ else if (partType == PART_TYPE_INT32)
+ partSize = 4;
+ else if (partType == PART_TYPE_INT64)
+ partSize = 8;
+ else
+ {
+ memcpy(&partSize, p, 4);
+ p += 4;
+ partSize = ntohl(partSize);
+ }
+ if (partKey == PART_KEY_MESSAGE_SEQ)
+ {
+ memcpy(&seq, p, sizeof(uint32_t));
+ seq = ntohl(seq);
+ break;
+ }
+ p += partSize;
+ }
+ return seq;
+}
+
+// -----------------------------------------------------------------------------
+#pragma mark -
+#pragma mark Private logging functions
+// -----------------------------------------------------------------------------
+static void LoggerPushClientInfoToFrontOfQueue(Logger *logger)
+{
+ // Extract client information from the main bundle, as well as platform info,
+ // and assmble it to a message that will be put in front of the queue
+ // Helps desktop viewer display who's talking to it
+ // Note that we must be called from the logger work thread, as we don't
+ // run through the message port to transmit this message to the queue
+ CFBundleRef bundle = CFBundleGetMainBundle();
+ if (bundle == NULL)
+ return;
+ CFMutableDataRef encoder = LoggerMessageCreate();
+ if (encoder != NULL)
+ {
+ LoggerMessageAddTimestamp(encoder);
+ LoggerMessageAddInt32(encoder, LOGMSG_TYPE_CLIENTINFO, PART_KEY_MESSAGE_TYPE);
+
+ CFStringRef version = (CFStringRef)CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleVersionKey);
+ if (version != NULL && CFGetTypeID(version) == CFStringGetTypeID())
+ LoggerMessageAddString(encoder, version, PART_KEY_CLIENT_VERSION);
+ CFStringRef name = (CFStringRef)CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleNameKey);
+ if (name != NULL)
+ LoggerMessageAddString(encoder, name, PART_KEY_CLIENT_NAME);
+
+#if TARGET_OS_IPHONE && ALLOW_COCOA_USE
+ if ([NSThread isMultiThreaded] || [NSThread isMainThread])
+ {
+ AUTORELEASE_POOL_BEGIN
+ UIDevice *device = [UIDevice currentDevice];
+ LoggerMessageAddString(encoder, (CAST_TO_CFSTRING)device.uniqueIdentifier, PART_KEY_UNIQUEID);
+ LoggerMessageAddString(encoder, (CAST_TO_CFSTRING)device.systemVersion, PART_KEY_OS_VERSION);
+ LoggerMessageAddString(encoder, (CAST_TO_CFSTRING)device.systemName, PART_KEY_OS_NAME);
+ LoggerMessageAddString(encoder, (CAST_TO_CFSTRING)device.model, PART_KEY_CLIENT_MODEL);
+ AUTORELEASE_POOL_END
+ }
+#elif TARGET_OS_MAC
+ SInt32 versionMajor, versionMinor, versionFix;
+ Gestalt(gestaltSystemVersionMajor, &versionMajor);
+ Gestalt(gestaltSystemVersionMinor, &versionMinor);
+ Gestalt(gestaltSystemVersionBugFix, &versionFix);
+ CFStringRef osVersion = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d.%d.%d"), versionMajor, versionMinor, versionFix);
+ LoggerMessageAddString(encoder, osVersion, PART_KEY_OS_VERSION);
+ CFRelease(osVersion);
+ LoggerMessageAddString(encoder, CFSTR("Mac OS X"), PART_KEY_OS_NAME);
+
+ char buf[64];
+ size_t len;
+ int ncpu = 0;
+ bzero(buf, sizeof(buf));
+ len = sizeof(buf)-1;
+ sysctlbyname("hw.model", buf, &len, NULL, 0);
+ len = sizeof(ncpu);
+ sysctlbyname("hw.ncpu", &ncpu, &len, NULL, 0);
+ sprintf(buf+strlen(buf), " - %d * ", ncpu);
+ len = sizeof(buf)-strlen(buf)-1;
+ sysctlbyname("hw.machine", buf+strlen(buf), &len, NULL, 0);
+
+ CFStringRef s = CFStringCreateWithCString(NULL, buf, kCFStringEncodingASCII);
+ LoggerMessageAddString(encoder, s, PART_KEY_CLIENT_MODEL);
+ CFRelease(s);
+#endif
+ pthread_mutex_lock(&logger->logQueueMutex);
+ CFArrayInsertValueAtIndex(logger->logQueue, logger->incompleteSendOfFirstItem ? 1 : 0, encoder);
+ pthread_mutex_unlock(&logger->logQueueMutex);
+
+ CFRelease(encoder);
+ }
+}
+
+static void LoggerPushMessageToQueue(Logger *logger, CFDataRef message)
+{
+ // Add the message to the log queue and signal the runLoop source that will trigger
+ // a send on the worker thread.
+ pthread_mutex_lock(&logger->logQueueMutex);
+ CFIndex idx = CFArrayGetCount(logger->logQueue);
+ if (idx)
+ {
+ // to prevent out-of-order messages (as much as possible), we try to transmit messages in the
+ // order their sequence number was generated. Since the seq is generated first-thing,
+ // we can provide fine-grained ordering that gives a reasonable idea of the order
+ // the logging calls were made (useful for precise information about multithreading code)
+ uint32_t lastSeq, seq = LoggerMessageGetSeq(message);
+ do {
+ lastSeq = LoggerMessageGetSeq(CFArrayGetValueAtIndex(logger->logQueue, idx-1));
+ } while (lastSeq > seq && --idx > 0);
+ }
+ if (idx >= 0)
+ CFArrayInsertValueAtIndex(logger->logQueue, idx, message);
+ else
+ CFArrayAppendValue(logger->logQueue, message);
+ pthread_mutex_unlock(&logger->logQueueMutex);
+
+ if (logger->messagePushedSource != NULL)
+ {
+ // One case where the pushed source may be NULL is if the client code
+ // immediately starts logging without initializing the logger first.
+ // In this case, the worker thread has not completed startup, so we don't need
+ // to fire the runLoop source
+ CFRunLoopSourceSignal(logger->messagePushedSource);
+ }
+ else if (logger->workerThread == NULL && (logger->options & kLoggerOption_LogToConsole))
+ {
+ // In this case, a failure creating the message runLoop source forces us
+ // to always log to console
+ pthread_mutex_lock(&logger->logQueueMutex);
+ while (CFArrayGetCount(logger->logQueue))
+ {
+ LoggerLogToConsole(CFArrayGetValueAtIndex(logger->logQueue, 0));
+ CFArrayRemoveValueAtIndex(logger->logQueue, 0);
+ }
+ pthread_mutex_unlock(&logger->logQueueMutex);
+ }
+}
+
+static void LogMessageTo_internal(Logger *logger,
+ const char *filename,
+ int lineNumber,
+ const char *functionName,
+ NSString *domain,
+ int level,
+ NSString *format,
+ va_list args)
+{
+ if (logger == NULL)
+ {
+ logger = LoggerGetDefaultLogger();
+ LoggerStart(logger);
+ }
+
+ int32_t seq = OSAtomicIncrement32Barrier(&logger->messageSeq);
+ LOGGERDBG2(CFSTR("%ld LogMessage"), seq);
+
+ CFMutableDataRef encoder = LoggerMessageCreate();
+ if (encoder != NULL)
+ {
+ LoggerMessageAddTimestampAndThreadID(encoder);
+ LoggerMessageAddInt32(encoder, LOGMSG_TYPE_LOG, PART_KEY_MESSAGE_TYPE);
+ LoggerMessageAddInt32(encoder, seq, PART_KEY_MESSAGE_SEQ);
+ if (domain != nil && [domain length])
+ LoggerMessageAddString(encoder, (CAST_TO_CFSTRING)domain, PART_KEY_TAG);
+ if (level)
+ LoggerMessageAddInt32(encoder, level, PART_KEY_LEVEL);
+ if (filename != NULL)
+ LoggerMessageAddCString(encoder, filename, PART_KEY_FILENAME);
+ if (lineNumber)
+ LoggerMessageAddInt32(encoder, lineNumber, PART_KEY_LINENUMBER);
+ if (functionName != NULL)
+ LoggerMessageAddCString(encoder, functionName, PART_KEY_FUNCTIONNAME);
+
+#if ALLOW_COCOA_USE
+ // Go though NSString to avoid low-level logging of CF datastructures (i.e. too detailed NSDictionary, etc)
+ NSString *msgString = [[NSString alloc] initWithFormat:format arguments:args];
+ if (msgString != nil)
+ {
+ LoggerMessageAddString(encoder, (CAST_TO_CFSTRING)msgString, PART_KEY_MESSAGE);
+ RELEASE(msgString);
+ }
+#else
+ CFStringRef msgString = CFStringCreateWithFormatAndArguments(NULL, NULL, (CFStringRef)format, args);
+ if (msgString != NULL)
+ {
+ LoggerMessageAddString(encoder, msgString, PART_KEY_MESSAGE);
+ CFRelease(msgString);
+ }