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