Merge pull request #634 from holbrookt/fix-tcp-endpoint-crash

Fix Weave TCPEndPoint crash in some lwIP cases
diff --git a/src/device-manager/cocoa/NLLogging.h b/src/device-manager/cocoa/NLLogging.h
index e0c1f94..baae2ff 100644
--- a/src/device-manager/cocoa/NLLogging.h
+++ b/src/device-manager/cocoa/NLLogging.h
@@ -22,15 +22,53 @@
  *
  */
 
-#if DEBUG
-#define WDM_LOG_DEBUG NSLog
-#define WDM_LOG_ERROR NSLog
+#import <Foundation/Foundation.h>
+#import "NLWeaveLogging.h"
 
-#define WDM_LOG_METHOD_SIG() ({ NSLog(@"[<%@: %p> %@]", NSStringFromClass([self class]), self, NSStringFromSelector(_cmd)); })
-#else
-// do nothing
+NS_ASSUME_NONNULL_BEGIN
+
+#if DEBUG
+
+/** Name of the logging module for the Objective-C platform-specific component. */
+static NSString * const kNLWeaveDeviceManagerCocoaModuleName = @"DM-Cocoa";
+
+/** Macros for logging Weave messages of various log levels from platform-specific code. */
+#define WDM_LOG_DEBUG(fmt, ...) _WDM_LOG(NLLogLevelDetail, fmt, ##__VA_ARGS__)
+#define WDM_LOG_INFO(fmt, ...) _WDM_LOG(NLLogLevelProgress, fmt, ##__VA_ARGS__)
+#define WDM_LOG_ERROR(fmt, ...) _WDM_LOG(NLLogLevelError, fmt, ##__VA_ARGS__)
+
+/** Macro for logging a method signature (must be called from an Objective-C method). */
+#define WDM_LOG_METHOD_SIG() WDM_LOG_INFO(@"<%@: %p>", NSStringFromClass([self class]), self)
+
+/**
+ * @def _WDM_LOG(logLevel, fmt, ...)
+ *
+ * @brief
+ *   Helper macro for formatting Weave logs and delegating log handling to @c NLWeaveLogging.
+ *
+ *  @param logLevel An @c NLLogLevel specifying the level of the log message.
+ *  @param fmt The log message format.
+ *  @param ... A variable number of arguments to be added to the log message format.
+ */
+#define _WDM_LOG(logLevel, fmt, ...)                                                                                               \
+    do {                                                                                                                           \
+        NSString * formattedMessage = ([NSString                                                                                   \
+            stringWithFormat:@"%s:%d %@", __PRETTY_FUNCTION__, __LINE__, [NSString stringWithFormat:fmt, ##__VA_ARGS__]]);         \
+                                                                                                                                   \
+        [NLWeaveLogging handleWeaveLogFromModule:NLLogModuleCocoa                                                                  \
+                                      moduleName:kNLWeaveDeviceManagerCocoaModuleName                                              \
+                                           level:logLevel                                                                          \
+                                formattedMessage:formattedMessage];                                                                \
+    } while (0);
+
+#else // DEBUG
+
+// Do nothing – strip logs from release builds.
 #define WDM_LOG_DEBUG(...)
+#define WDM_LOG_INFO(...)
 #define WDM_LOG_ERROR(...)
 #define WDM_LOG_METHOD_SIG() ({})
 
-#endif
+#endif // DEBUG
+
+NS_ASSUME_NONNULL_END
diff --git a/src/device-manager/cocoa/NLWeaveLogWriter.h b/src/device-manager/cocoa/NLWeaveLogWriter.h
new file mode 100644
index 0000000..0946bc1
--- /dev/null
+++ b/src/device-manager/cocoa/NLWeaveLogWriter.h
@@ -0,0 +1,51 @@
+/*
+ *
+ *    Copyright (c) 2020 Google LLC
+ *    All rights reserved.
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+/**
+ *    @file
+ *      This file defines the @c NLWeaveLogWriter protocol for an external Weave log writer.
+ *
+ */
+
+#import <Foundation/Foundation.h>
+#import "NLWeaveLogging.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Defines an interface for an external log writer to register to receive Weave logs.
+ *
+ * External clients can implement this protocol to receive Weave log messages for custom handling –
+ * the shared @c NLWeaveLogWriter can be set using +[NLWeaveLogging setSharedLogWriter:].
+ */
+@protocol NLWeaveLogWriter <NSObject>
+
+/**
+ * Method called with individual Weave log messages as they are logged.
+ *
+ * @param logModule The Weave module in which the log was created.
+ * @param logLevel The logging level of the log.
+ * @param logMessage The formatted log message.
+ */
+- (void)writeLogFromModule:(NLLogModule)logModule
+                     level:(NLLogLevel)logLevel
+                   message:(NSString *)logMessage NS_SWIFT_NAME(writeLog(from:level:message:));
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/device-manager/cocoa/NLWeaveLogging.h b/src/device-manager/cocoa/NLWeaveLogging.h
new file mode 100644
index 0000000..00289c3
--- /dev/null
+++ b/src/device-manager/cocoa/NLWeaveLogging.h
@@ -0,0 +1,128 @@
+/*
+ *
+ *    Copyright (c) 2020 Google LLC
+ *    All rights reserved.
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+/**
+ *    @file
+ *      This file implements a platform-specific Weave log interface.
+ *
+ */
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol NLWeaveLogWriter;
+
+/**
+ * Weave logging modules – for indicating which component created a log.
+ *
+ * @note
+ *   Must align with the order of nl::Weave::Logging::LogModule values.
+ */
+typedef NS_ENUM(NSInteger, NLLogModule) {
+    NLLogModuleNotSpecified = 0,
+
+    NLLogModuleInet,
+    NLLogModuleBle,
+    NLLogModuleMessageLayer,
+    NLLogModuleSecurityManager,
+    NLLogModuleExchangeManager,
+    NLLogModuleTLV,
+    NLLogModuleASN1,
+    NLLogModuleCrypto,
+    NLLogModuleDeviceManager,
+    NLLogModuleAlarm,
+    NLLogModuleBDX,
+    NLLogModuleDataManagement,
+    NLLogModuleDeviceControl,
+    NLLogModuleDeviceDescription,
+    NLLogModuleEcho,
+    NLLogModuleFabricProvisioning,
+    NLLogModuleNetworkProvisioning,
+    NLLogModuleServiceDirectory,
+    NLLogModuleServiceProvisioning,
+    NLLogModuleSoftwareUpdate,
+    NLLogModuleTokenPairing,
+    NLLogModuleHeatLink,
+    NLLogModuleTimeService,
+    NLLogModuleWeaveTunnel,
+    NLLogModuleHeartbeat,
+    NLLogModuleWeaveSystemLayer,
+    NLLogModuleDropcamLegacyPairing,
+    NLLogModuleEventLogging,
+    NLLogModuleSupport,
+
+    // Module for logs originating from the Objective-C @c NLLogging macros.
+    //
+    // Must NOT overlap with the values mapped from nl::Weave::Logging::LogModule.
+    NLLogModuleCocoa = 100,
+};
+
+/**
+ * Logging levels – for indicating the relative importance of a log message.
+ *
+ * @note
+ *   Must align with the order of nl::Weave::Logging::LogCategory values.
+ */
+typedef NS_ENUM(NSInteger, NLLogLevel) {
+    NLLogLevelNone = 0,
+    NLLogLevelError,
+    NLLogLevelProgress,
+    NLLogLevelDetail,
+    NLLogLevelRetain,
+};
+
+/**
+ * Platform-specific component for managing Weave log messages.
+ *
+ * Exposes an interface for external clients to configure the shared @c NLWeaveLogWriter – the log
+ * writer will receive Weave logs from both the shared C++ source code and the platform-specific
+ * Objective-C source code. This allows clients to connect Weave logs to their own logging system.
+ */
+@interface NLWeaveLogging : NSObject
+
+#pragma Logging Configuration
+
+/**
+ * Sets the shared @c NLWeaveLogWriter to start receiving Weave logs.
+ *
+ * @note
+ *   This should be called prior to any Weave operations to ensure the log writer has been
+ *   configured before any logs are written. The log writer will only receive messages logged after
+ *   it has been set as the shared writer.
+ */
++ (void)setSharedLogWriter:(nullable id<NLWeaveLogWriter>)logWriter;
+
+#pragma Log Methods
+
+/**
+ * Internal handler method for logging a message to the console and notifying the shared log writer.
+ *
+ * @param logModule The logging module to which the log belongs.
+ * @param logModuleName The name of the logging module.
+ * @param logLevel The level of the log message.
+ * @param formattedLogMessage The formatted log message.
+ */
++ (void)handleWeaveLogFromModule:(NLLogModule)logModule
+                      moduleName:(NSString *)logModuleName
+                           level:(NLLogLevel)logLevel
+                formattedMessage:(NSString *)formattedLogMessage;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/device-manager/cocoa/NLWeaveLogging.mm b/src/device-manager/cocoa/NLWeaveLogging.mm
index 64f1e95..dcfa832 100644
--- a/src/device-manager/cocoa/NLWeaveLogging.mm
+++ b/src/device-manager/cocoa/NLWeaveLogging.mm
@@ -26,6 +26,8 @@
 #include <stdio.h>
 
 #import <Foundation/Foundation.h>
+#import "NLWeaveLogWriter.h"
+#import "NLWeaveLogging.h"
 
 #if TARGET_OS_IPHONE
 #import <UIKit/UIKit.h>
@@ -34,15 +36,14 @@
 
 #include <Weave/Support/logging/WeaveLogging.h>
 
+NS_ASSUME_NONNULL_BEGIN
+
 #if WEAVE_ERROR_LOGGING || WEAVE_PROGRESS_LOGGING || WEAVE_DETAIL_LOGGING
 
-#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)                                                                                 \
-    ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
+#pragma mark - nl::Weave::Logging::Log
 
 namespace nl {
-
 namespace Weave {
-
     namespace Logging {
 
         /*
@@ -90,28 +91,170 @@
 
                 vsnprintf(formattedMsg + prefixLen, sizeof(formattedMsg) - prefixLen, aMsg, v);
 
-                // For versions >= iOS 10, NSLog is not using Apple System logger anymore
-                // So for the logs to always show up in device console, we need to use os_log
-                // with OS_LOG_DEFAULT which always gets logged in accordance with system's
-                // standard behavior
-#if TARGET_OS_IPHONE
-                if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"10.0")) {
-                    os_log(OS_LOG_DEFAULT, "%s", formattedMsg);
-                } else {
-                    NSLog(@"%s", formattedMsg);
-                }
-#else
-                NSLog(@"%s", formattedMsg);
-#endif
+                // Pass the formatted log to @c NLWeaveLogging for handling.
+                [NLWeaveLogging handleWeaveLogFromModule:(NLLogModule) aModule
+                                              moduleName:[NSString stringWithUTF8String:moduleName]
+                                                   level:(NLLogLevel) aCategory
+                                        formattedMessage:[NSString stringWithUTF8String:formattedMsg]];
             }
 
             va_end(v);
         }
 
     } // namespace Logging
-
 } // namespace Weave
-
 } // namespace nl
 
 #endif // WEAVE_ERROR_LOGGING || WEAVE_PROGRESS_LOGGING || WEAVE_DETAIL_LOGGING
+
+#if TARGET_OS_IPHONE && DEBUG
+
+/**
+ * @def IS_OPERATING_SYSTEM_AT_LEAST_VERSION(version)
+ *
+ * @brief
+ *   Compares the current version of operating system to the given version.
+ *
+ *  @param version An @c NSOperatingSystemVersion describing an iOS operating system version.
+ *
+ *  @return A boolean indicating whether the operating system version is at least the given version.
+ */
+#define IS_OPERATING_SYSTEM_AT_LEAST_VERSION(version)                                                                              \
+    ([NSProcessInfo instancesRespondToSelector:@selector(isOperatingSystemAtLeastVersion:)] &&                                     \
+        [[NSProcessInfo new] isOperatingSystemAtLeastVersion:version])
+
+/** Minimum iOS version with OSLog support (iOS 10.0.0). */
+static const NSOperatingSystemVersion kMinimumOSLogOperatingSystemVersion
+    = { .majorVersion = 10, .minorVersion = 0, .patchVersion = 0 };
+
+/** Root of the OSLog subsystem for Weave logs. */
+static NSString * const kWeaveOSLogSubsystem = @"com.nest.weave";
+
+/** Name of the subsystem for Weave logs from the shared C++ code. */
+static NSString * const kWeaveCppComponent = @"cpp";
+
+/** Name of the subsystem for Weave logs from the platform-specific Objective-C code. */
+static NSString * const kWeaveCocoaComponent = @"cocoa";
+
+/** Root of the OSLog category for Weave logs. */
+static NSString * const kWeaveOSLogCategory = @"WEAVE";
+
+#pragma mark - @c NLOSLogStore
+
+/** Stores a dictionary mapping each @c NLLogModule to an OSLog logging component. */
+@interface NLOSLogStore : NSObject
+
+/** Returns the @c os_log_t for the given @c NLLogModule, creating a new one if needed. */
++ (os_log_t)logForModule:(NLLogModule)logModule name:(NSString *)moduleName;
+
+@end
+
+@implementation NLOSLogStore
+
+/** Map of @c NLLogModule values to their OS Log object. */
+static NSMutableDictionary<NSNumber *, os_log_t> * gModuleMap;
+
+#pragma mark Initialization
+
++ (void)initialize
+{
+    if (self != [NLOSLogStore class]) {
+        return;
+    }
+
+    @synchronized(self) {
+        gModuleMap = [[NSMutableDictionary alloc] init];
+    }
+}
+
+#pragma mark Public
+
++ (os_log_t)logForModule:(NLLogModule)logModule name:(NSString *)logModuleName
+{
+    @synchronized(self) {
+        if (!gModuleMap[@(logModule)]) {
+            gModuleMap[@(logModule)] = [self createLogForModule:logModule name:logModuleName];
+        }
+
+        return gModuleMap[@(logModule)];
+    }
+}
+
+#pragma mark Private
+
++ (os_log_t)createLogForModule:(NLLogModule)logModule name:(NSString *)name
+{
+    NSString * component = (logModule == NLLogModuleCocoa ? kWeaveCocoaComponent : kWeaveCppComponent);
+    NSString * subsystem = [NSString stringWithFormat:@"%@.%@", kWeaveOSLogSubsystem, component];
+    NSString * category = [NSString stringWithFormat:@"%@.%@", kWeaveOSLogCategory, name];
+    return os_log_create(subsystem.UTF8String, category.UTF8String);
+}
+
+@end
+
+#endif // TARGET_OS_IPHONE && DEBUG
+
+#pragma mark - @c NLWeaveLogging
+
+@implementation NLWeaveLogging
+
+/** The shared external log writer that is subscribed to receive Weave logs. */
+static __nullable id<NLWeaveLogWriter> gSharedWriter = nil;
+
+#pragma mark - Public
+
++ (void)setSharedLogWriter:(nullable id<NLWeaveLogWriter>)logWriter
+{
+    @synchronized(self) {
+        gSharedWriter = logWriter;
+    }
+}
+
++ (void)handleWeaveLogFromModule:(NLLogModule)logModule
+                      moduleName:(NSString *)logModuleName
+                           level:(NLLogLevel)logLevel
+                formattedMessage:(NSString *)formattedLogMessage
+{
+    if (!formattedLogMessage) {
+        return;
+    }
+
+    // 1. Write the log to the system console (in debug builds).
+#if DEBUG
+    [self writeConsoleLog:formattedLogMessage logModule:logModule logModuleName:logModuleName logLevel:logLevel];
+#endif // DEBUG
+
+    // 2. Notify the shared log writer.
+    id<NLWeaveLogWriter> writer = gSharedWriter;
+    [writer writeLogFromModule:logModule level:logLevel message:formattedLogMessage];
+}
+
+#pragma mark - Private
+
+#if DEBUG
+
+/** Writes a log message to the system console. */
++ (void)writeConsoleLog:(NSString *)formattedLogMessage
+              logModule:(NLLogModule)logModule
+          logModuleName:(NSString *)logModuleName
+               logLevel:(NLLogLevel)logLevel
+{
+#if TARGET_OS_IPHONE
+    // For iOS 10+, OSLog replaces Apple System Logger (and NSLog).
+    if (IS_OPERATING_SYSTEM_AT_LEAST_VERSION(kMinimumOSLogOperatingSystemVersion)) {
+        os_log_t log = [NLOSLogStore logForModule:logModule name:logModuleName];
+        os_log_type_t type = (logLevel == NLLogLevelError ? OS_LOG_TYPE_ERROR : OS_LOG_TYPE_DEFAULT);
+        os_log_with_type(log, type, "%{public}s", formattedLogMessage.UTF8String);
+        return;
+    }
+#endif // TARGET_OS_IPHONE
+
+    // Fallback to NSLog for both iOS <9 and non-iPhone targets.
+    NSLog(@"%@", formattedLogMessage);
+}
+
+#endif // DEBUG
+
+@end
+
+NS_ASSUME_NONNULL_END