Merge pull request #628 from aaronokano/cleanup/aaronokano/logging-management-log-typo-fix

LoggingManagement: Fix typo in log message
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
diff --git a/src/inet/TCPEndPoint.cpp b/src/inet/TCPEndPoint.cpp
index fdfb6d8..21f498d 100644
--- a/src/inet/TCPEndPoint.cpp
+++ b/src/inet/TCPEndPoint.cpp
@@ -708,12 +708,6 @@
 
 #if WEAVE_SYSTEM_CONFIG_USE_LWIP
 
-    if (mUnsentQueue == NULL)
-    {
-        mUnsentQueue = data;
-        mUnsentOffset = 0;
-    }
-
 #if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT
     if (!mUserTimeoutTimerRunning)
     {
@@ -1199,6 +1193,10 @@
 #endif // WEAVE_SYSTEM_CONFIG_USE_SOCKETS
 
 #endif // INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT
+
+#if WEAVE_SYSTEM_CONFIG_USE_LWIP
+    mUnackedLength = 0;
+#endif // WEAVE_SYSTEM_CONFIG_USE_LWIP
 }
 
 INET_ERROR TCPEndPoint::DriveSending()
@@ -1219,44 +1217,60 @@
         uint16_t sendWindowSize = tcp_sndbuf(mTCP);
 
         // If there's data to be sent and the send window is open...
-        bool canSend = (mUnsentQueue != NULL && sendWindowSize > 0);
+        bool canSend = (RemainingToSend() > 0 && sendWindowSize > 0);
         if (canSend)
         {
+            // Find first packet buffer with remaining data to send by skipping
+            // all sent but un-acked data.
+            TCPEndPoint::BufferOffset startOfUnsent = FindStartOfUnsent();
+            const Weave::System::PacketBuffer* currentUnsentBuf = startOfUnsent.buffer;
+            uint16_t unsentOffset = startOfUnsent.offset;
+
             // While there's data to be sent and a window to send it in...
             do
             {
-                uint16_t bufDataLen = mUnsentQueue->DataLength();
+                VerifyOrDie(currentUnsentBuf != NULL);
+
+                uint16_t bufDataLen = currentUnsentBuf->DataLength();
 
                 // Get a pointer to the start of unsent data within the first buffer on the unsent queue.
-                uint8_t *sendData = mUnsentQueue->Start() + mUnsentOffset;
+                const uint8_t *sendData = currentUnsentBuf->Start() + unsentOffset;
 
                 // Determine the amount of data to send from the current buffer.
-                uint16_t sendLen = bufDataLen - mUnsentOffset;
+                uint16_t sendLen = bufDataLen - unsentOffset;
                 if (sendLen > sendWindowSize)
                     sendLen = sendWindowSize;
 
-                // Adjust the unsent data offset by the length of data to be written. If the entire buffer
-                // has been sent advance to the next one.
-                mUnsentOffset += sendLen;
-                if (mUnsentOffset == bufDataLen)
-                {
-                    mUnsentQueue = mUnsentQueue->Next();
-                    mUnsentOffset = 0;
-                }
-
-                // Adjust the remaining window size.
-                sendWindowSize -= sendLen;
-
-                // Determine if there's more data to be sent after this buffer.
-                canSend = (mUnsentQueue != NULL && sendWindowSize > 0);
-
                 // Call LwIP to queue the data to be sent, telling it if there's more data to come.
+                // Data is queued in-place as a reference within the source packet buffer. It is
+                // critical that the underlying packet buffer not be freed until the data
+                // is acknowledged, otherwise retransmissions could use an invalid
+                // backing. Using TCP_WRITE_FLAG_COPY would eliminate this requirement, but overall
+                // requires many more memory allocations which may be problematic when very
+                // memory-constrained or when using pool-based allocations.
                 lwipErr = tcp_write(mTCP, sendData, sendLen, (canSend) ? TCP_WRITE_FLAG_MORE : 0);
                 if (lwipErr != ERR_OK)
                 {
                     err = Weave::System::MapErrorLwIP(lwipErr);
                     break;
                 }
+                // Start accounting for the data sent as yet-to-be-acked.
+                mUnackedLength += sendLen;
+
+                // Adjust the unsent data offset by the length of data that was written.
+                // If the entire buffer has been sent advance to the next one.
+                unsentOffset += sendLen;
+                if (unsentOffset == bufDataLen)
+                {
+                    currentUnsentBuf = currentUnsentBuf->Next();
+                    unsentOffset = 0;
+                }
+
+                // Adjust the remaining window size.
+                sendWindowSize -= sendLen;
+
+                // Determine if there's more data to be sent after this buffer.
+                canSend = (RemainingToSend() > 0 && sendWindowSize > 0);
             } while (canSend);
 
             // Call LwIP to send the queued data.
@@ -1274,7 +1288,7 @@
         if (err == INET_NO_ERROR)
         {
             // If in the SendShutdown state and the unsent queue is now empty, shutdown the PCB for sending.
-            if (State == kState_SendShutdown && mUnsentQueue == NULL)
+            if (State == kState_SendShutdown && (RemainingToSend() == 0))
             {
                 lwipErr = tcp_shutdown(mTCP, 0, 1);
                 if (lwipErr != ERR_OK)
@@ -1571,6 +1585,9 @@
         mSendQueue = NULL;
         PacketBuffer::Free(mRcvQueue);
         mRcvQueue = NULL;
+#if WEAVE_SYSTEM_CONFIG_USE_LWIP
+        mUnackedLength = 0;
+#endif // WEAVE_SYSTEM_CONFIG_USE_LWIP
 
         // Call the appropriate app callback if allowed.
         if (!suppressCallback)
@@ -1752,6 +1769,67 @@
 
 #if WEAVE_SYSTEM_CONFIG_USE_LWIP
 
+uint32_t TCPEndPoint::RemainingToSend()
+{
+    if (mSendQueue == NULL)
+    {
+        return 0;
+    }
+    else
+    {
+        // We can never have reported more unacked data than there is pending
+        // in the send queue! This would indicate a critical accounting bug.
+        VerifyOrDie(mUnackedLength <= mSendQueue->TotalLength());
+
+        return mSendQueue->TotalLength() - mUnackedLength;
+    }
+}
+
+TCPEndPoint::BufferOffset TCPEndPoint::FindStartOfUnsent()
+{
+    // Find first packet buffer with remaining data to send by skipping
+    // all sent but un-acked data. This is necessary because of the Consume()
+    // call in HandleDataSent(), which potentially releases backing memory for
+    // fully-sent packet buffers, causing an invalidation of all possible
+    // offsets one might have cached. The TCP acnowledgements may come back
+    // with a variety of sizes depending on prior activity, and size of the
+    // send window. The only way to ensure we get the correct offsets into
+    // unsent data while retaining the buffers that have un-acked data is to
+    // traverse all sent-but-unacked data in the chain to reach the beginning
+    // of ready-to-send data.
+    Weave::System::PacketBuffer* currentUnsentBuf = mSendQueue;
+    uint16_t unsentOffset = 0;
+    uint32_t leftToSkip = mUnackedLength;
+
+    VerifyOrDie(leftToSkip < mSendQueue->TotalLength());
+
+    while (leftToSkip > 0)
+    {
+        VerifyOrDie(currentUnsentBuf != NULL);
+        uint16_t bufDataLen = currentUnsentBuf->DataLength();
+        if (leftToSkip >= bufDataLen)
+        {
+            // We have more to skip than current packet buffer size.
+            // Follow the chain to continue.
+            currentUnsentBuf = currentUnsentBuf->Next();
+            leftToSkip -= bufDataLen;
+        }
+        else
+        {
+            // Done skipping all data, currentUnsentBuf is first packet buffer
+            // containing unsent data.
+            unsentOffset = leftToSkip;
+            leftToSkip = 0;
+        }
+    }
+
+    TCPEndPoint::BufferOffset startOfUnsent;
+    startOfUnsent.buffer = currentUnsentBuf;
+    startOfUnsent.offset = unsentOffset;
+
+    return startOfUnsent;
+}
+
 INET_ERROR TCPEndPoint::GetPCB(IPAddressType addrType)
 {
     // IMMPORTANT: This method MUST be called with the LwIP stack LOCKED!
@@ -1843,8 +1921,24 @@
 {
     if (IsConnected())
     {
+        // Ensure we do not have internal inconsistency in the lwIP, which
+        // could cause invalid pointer accesses.
+        if (lenSent > mUnackedLength)
+        {
+            WeaveLogError(Inet, "Got more ACKed bytes (%d) than were pending (%d)", (int)lenSent, (int)mUnackedLength);
+            DoClose(INET_ERROR_UNEXPECTED_EVENT, false);
+            return;
+        }
+        else if (mSendQueue == NULL)
+        {
+            WeaveLogError(Inet, "Got ACK for %d bytes but data backing gone", (int)lenSent);
+            DoClose(INET_ERROR_UNEXPECTED_EVENT, false);
+            return;
+        }
+
         // Consume data off the head of the send queue equal to the amount of data being acknowledged.
         mSendQueue = mSendQueue->Consume(lenSent);
+        mUnackedLength -= lenSent;
 
 #if INET_CONFIG_OVERRIDE_SYSTEM_TCP_USER_TIMEOUT
         // Only change the UserTimeout timer if lenSent > 0,
@@ -1852,7 +1946,7 @@
         // across.
         if (lenSent > 0)
         {
-            if (mSendQueue == NULL)
+            if (RemainingToSend() == 0)
             {
                 // If the output queue has been flushed then stop the timer.
 
@@ -1880,12 +1974,12 @@
         if (OnDataSent != NULL)
             OnDataSent(this, lenSent);
 
-        // If unsent data exists, attempt to sent it now...
-        if (mUnsentQueue != NULL)
+        // If unsent data exists, attempt to send it now...
+        if (RemainingToSend() > 0)
             DriveSending();
 
         // If in the closing state and the send queue is now empty, attempt to transition to closed.
-        if (State == kState_Closing && mSendQueue == NULL)
+        if ((State == kState_Closing) && (RemainingToSend() == 0))
             DoClose(INET_NO_ERROR, false);
     }
 }
diff --git a/src/inet/TCPEndPoint.h b/src/inet/TCPEndPoint.h
index cc4c326..bd570fe 100644
--- a/src/inet/TCPEndPoint.h
+++ b/src/inet/TCPEndPoint.h
@@ -621,9 +621,16 @@
     void StopConnectTimer(void);
 
 #if WEAVE_SYSTEM_CONFIG_USE_LWIP
-    Weave::System::PacketBuffer *mUnsentQueue;
-    uint16_t mUnsentOffset;
+    struct BufferOffset {
+        const Weave::System::PacketBuffer *buffer;
+        uint32_t offset;
+    };
 
+    uint32_t mUnackedLength;                            // Amount sent but awaiting ACK. Used as a form of reference count
+                                                        // to hang-on to backing packet buffers until they are no longer needed.
+
+    uint32_t RemainingToSend();
+    BufferOffset FindStartOfUnsent();
     INET_ERROR GetPCB(IPAddressType addrType);
     void HandleDataSent(uint16_t len);
     void HandleDataReceived(Weave::System::PacketBuffer *buf);
diff --git a/src/lib/core/WeaveError.cpp b/src/lib/core/WeaveError.cpp
index 699b50f..875f723 100644
--- a/src/lib/core/WeaveError.cpp
+++ b/src/lib/core/WeaveError.cpp
@@ -232,6 +232,7 @@
     case WEAVE_ERROR_SESSION_KEY_SUSPENDED                      : desc = "Session key suspended"; break;
     case WEAVE_ERROR_UNSUPPORTED_WIRELESS_REGULATORY_DOMAIN     : desc = "Unsupported wireless regulatory domain"; break;
     case WEAVE_ERROR_UNSUPPORTED_WIRELESS_OPERATING_LOCATION    : desc = "Unsupported wireless operating location"; break;
+    case WEAVE_ERROR_WDM_EVENT_TOO_BIG                          : desc = "The WDM Event is too big to be successfully transmitted to a peer node"; break;
     }
 #endif // !WEAVE_CONFIG_SHORT_ERROR_STR
 
diff --git a/src/lib/core/WeaveError.h b/src/lib/core/WeaveError.h
index 096294d..2cadb51 100644
--- a/src/lib/core/WeaveError.h
+++ b/src/lib/core/WeaveError.h
@@ -1757,6 +1757,17 @@
  */
 #define WEAVE_ERROR_UNSUPPORTED_WIRELESS_OPERATING_LOCATION      _WEAVE_ERROR(185)
 
+/**
+ *  @def WEAVE_ERROR_WDM_EVENT_TOO_BIG
+ *
+ *  @brief
+ *    The specified event is too big to be successfully transmitted to a peer node,
+ *    e.g., it exceeds the maximum WDM Notification message size
+ *    limit.
+ *
+ */
+#define WEAVE_ERROR_WDM_EVENT_TOO_BIG                            _WEAVE_ERROR(186)
+
 
 /**
  *  @}
diff --git a/src/lib/core/WeaveFabricState.cpp b/src/lib/core/WeaveFabricState.cpp
index 4ead562..5a5bfaf 100644
--- a/src/lib/core/WeaveFabricState.cpp
+++ b/src/lib/core/WeaveFabricState.cpp
@@ -300,6 +300,8 @@
 
     sessionEndCallbackList = NULL;
 
+    BoundConnectionClosedForSession = NULL;
+
     State = kState_Initialized;
 
     return WEAVE_NO_ERROR;
diff --git a/src/lib/profiles/data-management/Current/LoggingManagement.cpp b/src/lib/profiles/data-management/Current/LoggingManagement.cpp
index 5ff01af..19b78a2 100644
--- a/src/lib/profiles/data-management/Current/LoggingManagement.cpp
+++ b/src/lib/profiles/data-management/Current/LoggingManagement.cpp
@@ -1314,6 +1314,8 @@
         CircularEventBuffer * buffer = mEventBuffer;
         do
         {
+            VerifyOrExit((WDM_MAX_NOTIFICATION_SIZE - WDM_NOTIFY_REQUEST_META_INFO_BYTES_MAX) >= writer.GetLengthWritten(),
+                         err = WEAVE_ERROR_WDM_EVENT_TOO_BIG);
             VerifyOrExit(buffer->mBuffer.GetQueueSize() >= writer.GetLengthWritten(), err = WEAVE_ERROR_BUFFER_TOO_SMALL);
             if (buffer->IsFinalDestinationForImportance(inSchema.mImportance))
                 break;
@@ -1329,6 +1331,7 @@
     if (err != WEAVE_NO_ERROR)
     {
         mEventBuffer->mBuffer = checkpoint;
+        WeaveLogError(EventLogging, "Failed to log event for profile id: 0x%x (err: %d)", inSchema.mProfileId, err);
     }
     else if (inSchema.mImportance <= GetCurrentImportance(inSchema.mProfileId))
     {
@@ -1340,7 +1343,7 @@
             GetImportanceBuffer(inSchema.mImportance)->AddEventUTC(opts.timestamp.utcTimestamp);
 
 #if WEAVE_CONFIG_EVENT_LOGGING_VERBOSE_DEBUG_LOGS
-            WeaveLogDetail(
+            WeaveLogProgress(
                 EventLogging, "LogEvent event id: %u importance: %u profile id: 0x%x structure id: 0x%x utc timestamp: 0x%" PRIx64,
                 event_id, inSchema.mImportance, inSchema.mProfileId, inSchema.mStructureType, opts.timestamp.utcTimestamp);
 #endif // WEAVE_CONFIG_EVENT_LOGGING_VERBOSE_DEBUG_LOGS
@@ -1351,7 +1354,7 @@
             GetImportanceBuffer(inSchema.mImportance)->AddEvent(opts.timestamp.systemTimestamp);
 
 #if WEAVE_CONFIG_EVENT_LOGGING_VERBOSE_DEBUG_LOGS
-            WeaveLogDetail(
+            WeaveLogProgress(
                 EventLogging, "LogEvent event id: %u importance: %u profile id: 0x%x structure id: 0x%x sys timestamp: 0x%" PRIx32,
                 event_id, inSchema.mImportance, inSchema.mProfileId, inSchema.mStructureType, opts.timestamp.systemTimestamp);
 #endif // WEAVE_CONFIG_EVENT_LOGGING_VERBOSE_DEBUG_LOGS
@@ -1943,13 +1946,19 @@
         if ((mExchangeMgr != NULL) && (mExchangeMgr->MessageLayer != NULL) && (mExchangeMgr->MessageLayer->SystemLayer != NULL))
         {
             mExchangeMgr->MessageLayer->SystemLayer->ScheduleWork(LoggingFlushHandler, this);
+            WeaveLogProgress(EventLogging, "Scheduled flush for urgent event.");
         }
         else
         {
             err              = WEAVE_ERROR_INCORRECT_STATE;
             mUploadRequested = false;
+            WeaveLogError(EventLogging, "Schedule flush failed with error: %s", ErrorStr(err));
         }
     }
+    else if (inRequestFlush)
+    {
+        WeaveLogProgress(EventLogging, "Flush already scheduled, no need to schedule an additional flush.");
+    }
 
     return err;
 }
diff --git a/src/lib/profiles/data-management/Current/NotificationEngine.h b/src/lib/profiles/data-management/Current/NotificationEngine.h
index 792b89a..22cfad7 100644
--- a/src/lib/profiles/data-management/Current/NotificationEngine.h
+++ b/src/lib/profiles/data-management/Current/NotificationEngine.h
@@ -33,6 +33,13 @@
 #include <Weave/Profiles/data-management/TraitData.h>
 #include <Weave/Profiles/data-management/TraitCatalog.h>
 
+// Reserve bytes that would account for additional formulation overhead
+// of a Notify Request(beyond the event and the data lists) containing
+// meta information, e.g., SubscriptionId, etc. This is a conservative
+// estimate that should serve as a loose upper bound, and help in bounding
+// the maximum size of a WDM event.
+#define WDM_NOTIFY_REQUEST_META_INFO_BYTES_MAX  (64)
+
 namespace nl {
 namespace Weave {
 namespace Profiles {
diff --git a/src/lib/profiles/weave-tunneling/WeaveTunnelAgent.cpp b/src/lib/profiles/weave-tunneling/WeaveTunnelAgent.cpp
index 3f1f554..e9375d3 100644
--- a/src/lib/profiles/weave-tunneling/WeaveTunnelAgent.cpp
+++ b/src/lib/profiles/weave-tunneling/WeaveTunnelAgent.cpp
@@ -365,6 +365,18 @@
     return mTunAgentState;
 }
 
+/**
+ * Get a pointer to the Weave Tunnel Connection.
+ *
+ * @return WeaveConnection pointer for an established Weave Tunnel. If tunnel is
+ *         not in the established state, a null pointer is returned.
+ *
+ */
+nl::Weave::WeaveConnection * WeaveTunnelAgent::GetPrimaryTunnelConnection(void) const
+{
+    return mPrimaryTunConnMgr.GetTunnelConnection();
+}
+
 #if WEAVE_CONFIG_PERSIST_CONNECTED_SESSION
 void WeaveTunnelAgent::SetCallbacksForPersistedTunnelConnection(WeaveTunnelConnectionMgr::PersistedSecureSessionExistsFunct aIsPersistedTunnelSessionPresent,
                                                                 WeaveTunnelConnectionMgr::LoadPersistedSessionFunct aLoadPersistedTunnelSession)
diff --git a/src/lib/profiles/weave-tunneling/WeaveTunnelAgent.h b/src/lib/profiles/weave-tunneling/WeaveTunnelAgent.h
index a252005..2188729 100644
--- a/src/lib/profiles/weave-tunneling/WeaveTunnelAgent.h
+++ b/src/lib/profiles/weave-tunneling/WeaveTunnelAgent.h
@@ -216,6 +216,12 @@
  */
     AgentState GetWeaveTunnelAgentState(void);
 
+/**
+ * Get the Primary Tunnel Connection
+ *
+ */
+    nl::Weave::WeaveConnection * GetPrimaryTunnelConnection(void) const;
+
 #if WEAVE_CONFIG_TUNNEL_TCP_USER_TIMEOUT_SUPPORTED
 /**
  *  Configure the TCP user timeout option on the primary tunnel connection.
diff --git a/src/lib/profiles/weave-tunneling/WeaveTunnelConnectionMgr.cpp b/src/lib/profiles/weave-tunneling/WeaveTunnelConnectionMgr.cpp
index 7e4c5a5..ab137c2 100644
--- a/src/lib/profiles/weave-tunneling/WeaveTunnelConnectionMgr.cpp
+++ b/src/lib/profiles/weave-tunneling/WeaveTunnelConnectionMgr.cpp
@@ -650,6 +650,27 @@
 }
 
 /**
+ * Get a pointer to the Weave Tunnel Connection object for this
+ * Tunnel Connection Manager.
+ *
+ * @return pointer to the WeaveConnection object for the Tunnel. If Tunnel is
+ *         not in an established state, a null pointer is returned
+ *
+ */
+
+nl::Weave::WeaveConnection * WeaveTunnelConnectionMgr::GetTunnelConnection(void) const
+{
+    if (mConnectionState == kState_ConnectionEstablished || mConnectionState == kState_TunnelOpen)
+    {
+        return mServiceCon;
+    }
+    else
+    {
+        return NULL;
+    }
+}
+
+/**
  * Handler to receive tunneled IPv6 packets from the Service TCP connection and forward to the Tunnel
  * EndPoint interface after decapsulating the raw IPv6 packet from inside the tunnel header.
  *
diff --git a/src/lib/profiles/weave-tunneling/WeaveTunnelConnectionMgr.h b/src/lib/profiles/weave-tunneling/WeaveTunnelConnectionMgr.h
index aac22c0..aee2a96 100644
--- a/src/lib/profiles/weave-tunneling/WeaveTunnelConnectionMgr.h
+++ b/src/lib/profiles/weave-tunneling/WeaveTunnelConnectionMgr.h
@@ -241,6 +241,11 @@
  */
     void StopAndReconnectTunnelConn(ReconnectParam & reconnParam);
 
+/**
+ * Get a pointer to the Weave Tunnel Connection object for this
+ * Tunnel Connection Manager.
+ */
+    nl::Weave::WeaveConnection * GetTunnelConnection(void) const;
   private:
 
     WEAVE_ERROR StartServiceTunnelConn(uint64_t destNodeId, IPAddress destIPAddr,
diff --git a/src/test-apps/TestErrorStr.cpp b/src/test-apps/TestErrorStr.cpp
index 6716cbf..51655db 100644
--- a/src/test-apps/TestErrorStr.cpp
+++ b/src/test-apps/TestErrorStr.cpp
@@ -294,6 +294,7 @@
       WEAVE_ERROR_SESSION_KEY_SUSPENDED,
       WEAVE_ERROR_UNSUPPORTED_WIRELESS_REGULATORY_DOMAIN,
       WEAVE_ERROR_UNSUPPORTED_WIRELESS_OPERATING_LOCATION,
+      WEAVE_ERROR_WDM_EVENT_TOO_BIG,
 
       WEAVE_ERROR_TUNNEL_ROUTING_RESTRICTED,
 
diff --git a/src/test-apps/happy/bin/weave-state.py b/src/test-apps/happy/bin/weave-state.py
index 7f6537c..c33845a 100755
--- a/src/test-apps/happy/bin/weave-state.py
+++ b/src/test-apps/happy/bin/weave-state.py
@@ -40,7 +40,7 @@
                                    ["help", "quiet"])
 
     except getopt.GetoptError as err:
-    	print(WeaveState.WeaveState.__doc__)
+        print(WeaveState.WeaveState.__doc__)
         print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))