| /* |
| * |
| * Copyright (c) 2020-2021 Project CHIP Authors |
| * Copyright (c) 2013-2018 Nest Labs, Inc. |
| * |
| * 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 constants, globals and interfaces common to |
| * and used by all CHP Inet layer library test applications and |
| * tools. |
| * |
| * NOTE: These do not comprise a public part of the CHIP API and |
| * are subject to change without notice. |
| * |
| */ |
| |
| #ifndef __STDC_LIMIT_MACROS |
| #define __STDC_LIMIT_MACROS |
| #endif |
| #ifndef __STDC_FORMAT_MACROS |
| #define __STDC_FORMAT_MACROS |
| #endif |
| |
| #include "TestInetCommon.h" |
| #include "TestInetCommonOptions.h" |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <vector> |
| |
| #include <inttypes.h> |
| #include <stdint.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <lib/support/CHIPMem.h> |
| #include <lib/support/ErrorStr.h> |
| #include <lib/support/ScopedBuffer.h> |
| #include <platform/PlatformManager.h> |
| #include <system/SystemClock.h> |
| |
| #if CHIP_SYSTEM_CONFIG_USE_LWIP |
| #include <lwip/dns.h> |
| #if (LWIP_VERSION_MAJOR == 2) && (LWIP_VERSION_MINOR == 0) |
| #include <lwip/ip6_route_table.h> |
| #endif // (LWIP_VERSION_MAJOR == 2) && (LWIP_VERSION_MINOR == 0) |
| #include <lwip/init.h> |
| #include <lwip/netif.h> |
| #include <lwip/sys.h> |
| #include <lwip/tcpip.h> |
| #include <netif/etharp.h> |
| |
| #if CHIP_TARGET_STYLE_UNIX |
| |
| // TapAddrAutoconf and TapInterface are only needed for LwIP on |
| // sockets simulation in which a host tap/tun interface is used to |
| // proxy the LwIP stack onto a host native network interface. |
| |
| #include "TapAddrAutoconf.h" |
| #include "TapInterface.h" |
| #endif // CHIP_TARGET_STYLE_UNIX |
| #endif // CHIP_SYSTEM_CONFIG_USE_LWIP |
| |
| using namespace chip; |
| using namespace chip::Inet; |
| |
| System::LayerImpl gSystemLayer; |
| |
| Inet::UDPEndPointManagerImpl gUDP; |
| Inet::TCPEndPointManagerImpl gTCP; |
| |
| #if CHIP_SYSTEM_CONFIG_USE_LWIP && !(CHIP_SYSTEM_CONFIG_LWIP_SKIP_INIT) |
| static sys_mbox_t * sLwIPEventQueue = NULL; |
| static unsigned int sLwIPAcquireCount = 0; |
| |
| static void AcquireLwIP(void) |
| { |
| if (sLwIPAcquireCount++ == 0) |
| { |
| sys_mbox_new(sLwIPEventQueue, 100); |
| } |
| } |
| |
| static void ReleaseLwIP(void) |
| { |
| #if (LWIP_VERSION_MAJOR == 2) && (LWIP_VERSION_MINOR == 0) |
| if (sLwIPAcquireCount > 0 && --sLwIPAcquireCount == 0) |
| { |
| #if defined(INCLUDE_vTaskDelete) && INCLUDE_vTaskDelete |
| // FreeRTOS need to delete the task not return from it. |
| tcpip_finish(reinterpret_cast<tcpip_will_finish_fn>(vTaskDelete), NULL); |
| #else // defined(INCLUDE_vTaskDelete) && INCLUDE_vTaskDelete |
| tcpip_finish(NULL, NULL); |
| #endif // defined(INCLUDE_vTaskDelete) && INCLUDE_vTaskDelete |
| } |
| #endif // (LWIP_VERSION_MAJOR == 2) && (LWIP_VERSION_MINOR == 0) |
| } |
| |
| #if CHIP_TARGET_STYLE_UNIX |
| // TapAddrAutoconf and TapInterface are only needed for LwIP on |
| // sockets simulation in which a host tap/tun interface is used to |
| // proxy the LwIP stack onto a host native network interface. |
| // CollectTapAddresses() is only available on such targets. |
| static std::vector<TapInterface> sTapIFs; |
| #endif // CHIP_TARGET_STYLE_UNIX |
| |
| static std::vector<struct netif> sNetIFs; // interface to filter |
| |
| static bool NetworkIsReady(); |
| static void OnLwIPInitComplete(void * arg); |
| #endif // CHIP_SYSTEM_CONFIG_USE_LWIP && !(CHIP_SYSTEM_CONFIG_LWIP_SKIP_INIT) |
| |
| char gDefaultTapDeviceName[32]; |
| bool gDone = false; |
| |
| void InetFailError(CHIP_ERROR err, const char * msg) |
| { |
| if (err != CHIP_NO_ERROR) |
| { |
| fprintf(stderr, "%s: %s\n", msg, ErrorStr(err)); |
| exit(-1); |
| } |
| } |
| |
| static void UseStdoutLineBuffering() |
| { |
| // Set stdout to be line buffered with a buffer of 512 (will flush on new line |
| // or when the buffer of 512 is exceeded). |
| #if CHIP_CONFIG_MEMORY_MGMT_MALLOC |
| constexpr char * buf = nullptr; |
| #else |
| static char buf[512]; |
| #endif // CHIP_CONFIG_MEMORY_MGMT_MALLOC |
| setvbuf(stdout, buf, _IOLBF, 512); |
| } |
| |
| void InitTestInetCommon() |
| { |
| chip::Platform::MemoryInit(); |
| UseStdoutLineBuffering(); |
| } |
| |
| void ShutdownTestInetCommon() |
| { |
| chip::Platform::MemoryShutdown(); |
| } |
| |
| void InitSystemLayer() |
| { |
| #if CHIP_SYSTEM_CONFIG_USE_LWIP && !(CHIP_SYSTEM_CONFIG_LWIP_SKIP_INIT) |
| AcquireLwIP(); |
| #endif // CHIP_SYSTEM_CONFIG_USE_LWIP && !(CHIP_SYSTEM_CONFIG_LWIP_SKIP_INIT) |
| |
| gSystemLayer.Init(); |
| } |
| |
| void ShutdownSystemLayer() |
| { |
| gSystemLayer.Shutdown(); |
| |
| #if CHIP_SYSTEM_CONFIG_USE_LWIP && !(CHIP_SYSTEM_CONFIG_LWIP_SKIP_INIT) |
| ReleaseLwIP(); |
| #endif // CHIP_SYSTEM_CONFIG_USE_LWIP && !(CHIP_SYSTEM_CONFIG_LWIP_SKIP_INIT) |
| } |
| |
| #if CHIP_SYSTEM_CONFIG_USE_LWIP && !(CHIP_SYSTEM_CONFIG_LWIP_SKIP_INIT) |
| static void PrintNetworkState() |
| { |
| char intfName[chip::Inet::InterfaceId::kMaxIfNameLength]; |
| |
| for (size_t j = 0; j < gNetworkOptions.TapDeviceName.size(); j++) |
| { |
| struct netif * netIF = &(sNetIFs[j]); |
| #if CHIP_TARGET_STYLE_UNIX |
| // TapAddrAutoconf and TapInterface are only needed for LwIP on |
| // sockets simulation in which a host tap/tun interface is used to |
| // proxy the LwIP stack onto a host native network interface. |
| // CollectTapAddresses() is only available on such targets. |
| |
| TapInterface * tapIF = &(sTapIFs[j]); |
| #endif // CHIP_TARGET_STYLE_UNIX |
| InterfaceId(netIF).GetInterfaceName(intfName, sizeof(intfName)); |
| |
| printf("LwIP interface ready\n"); |
| printf(" Interface Name: %s\n", intfName); |
| printf(" Tap Device: %s\n", gNetworkOptions.TapDeviceName[j]); |
| #if CHIP_TARGET_STYLE_UNIX |
| // TapAddrAutoconf and TapInterface are only needed for LwIP on |
| // sockets simulation in which a host tap/tun interface is used to |
| // proxy the LwIP stack onto a host native network interface. |
| // CollectTapAddresses() is only available on such targets. |
| |
| printf(" MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n", tapIF->macAddr[0], tapIF->macAddr[1], tapIF->macAddr[2], |
| tapIF->macAddr[3], tapIF->macAddr[4], tapIF->macAddr[5]); |
| #endif // CHIP_TARGET_STYLE_UNIX |
| |
| #if INET_CONFIG_ENABLE_IPV4 |
| printf(" IPv4 Address: %s\n", ipaddr_ntoa(&(netIF->ip_addr))); |
| printf(" IPv4 Mask: %s\n", ipaddr_ntoa(&(netIF->netmask))); |
| printf(" IPv4 Gateway: %s\n", ipaddr_ntoa(&(netIF->gw))); |
| #endif // INET_CONFIG_ENABLE_IPV4 |
| for (int i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) |
| { |
| if (!ip6_addr_isany(netif_ip6_addr(netIF, i))) |
| { |
| printf(" IPv6 address: %s, 0x%02x\n", ip6addr_ntoa(netif_ip6_addr(netIF, i)), netif_ip6_addr_state(netIF, i)); |
| } |
| } |
| } |
| } |
| #endif // CHIP_SYSTEM_CONFIG_USE_LWIP && !(CHIP_SYSTEM_CONFIG_LWIP_SKIP_INIT) |
| |
| void InitNetwork() |
| { |
| #if CHIP_SYSTEM_CONFIG_USE_LWIP && !(CHIP_SYSTEM_CONFIG_LWIP_SKIP_INIT) |
| |
| // If an tap device name hasn't been specified, derive one from the IPv6 interface id. |
| |
| if (gNetworkOptions.TapDeviceName.empty()) |
| { |
| for (size_t j = 0; j < gNetworkOptions.LocalIPv6Addr.size(); j++) |
| { |
| uint64_t iid = gNetworkOptions.LocalIPv6Addr[j].InterfaceId(); |
| char * tap_name = (char *) chip::Platform::MemoryAlloc(sizeof(gDefaultTapDeviceName)); |
| assert(tap_name); |
| snprintf(tap_name, sizeof(gDefaultTapDeviceName), "chip-dev-%x", static_cast<uint16_t>(iid)); |
| gNetworkOptions.TapDeviceName.push_back(tap_name); |
| } |
| } |
| |
| #if CHIP_TARGET_STYLE_UNIX |
| // TapAddrAutoconf and TapInterface are only needed for LwIP on |
| // sockets simulation in which a host tap/tun interface is used to |
| // proxy the LwIP stack onto a host native network interface. |
| // CollectTapAddresses() is only available on such targets. |
| |
| sTapIFs.clear(); |
| #endif // CHIP_TARGET_STYLE_UNIX |
| sNetIFs.clear(); |
| |
| for (size_t j = 0; j < gNetworkOptions.TapDeviceName.size(); j++) |
| { |
| #if CHIP_TARGET_STYLE_UNIX |
| // TapAddrAutoconf and TapInterface are only needed for LwIP on |
| // sockets simulation in which a host tap/tun interface is used to |
| // proxy the LwIP stack onto a host native network interface. |
| // CollectTapAddresses() is only available on such targets. |
| |
| TapInterface tapIF; |
| sTapIFs.push_back(tapIF); |
| #endif // CHIP_TARGET_STYLE_UNIX |
| struct netif netIF; |
| sNetIFs.push_back(netIF); |
| } |
| |
| #if CHIP_TARGET_STYLE_UNIX |
| |
| // TapAddrAutoconf and TapInterface are only needed for LwIP on |
| // sockets simulation in which a host tap/tun interface is used to |
| // proxy the LwIP stack onto a host native network interface. |
| // CollectTapAddresses() is only available on such targets. |
| |
| err_t lwipErr; |
| |
| for (size_t j = 0; j < gNetworkOptions.TapDeviceName.size(); j++) |
| { |
| lwipErr = TapInterface_Init(&(sTapIFs[j]), gNetworkOptions.TapDeviceName[j], NULL); |
| if (lwipErr != ERR_OK) |
| { |
| printf("Failed to initialize tap device %s: %s\n", gNetworkOptions.TapDeviceName[j], |
| ErrorStr(System::MapErrorLwIP(lwipErr))); |
| exit(EXIT_FAILURE); |
| } |
| } |
| #endif // CHIP_TARGET_STYLE_UNIX |
| |
| tcpip_init(OnLwIPInitComplete, NULL); |
| |
| // Lock LwIP stack |
| LOCK_TCPIP_CORE(); |
| |
| for (size_t j = 0; j < gNetworkOptions.TapDeviceName.size(); j++) |
| { |
| std::vector<char *> addrsVec; |
| |
| addrsVec.clear(); |
| |
| #if CHIP_TARGET_STYLE_UNIX |
| |
| // TapAddrAutoconf and TapInterface are only needed for LwIP on |
| // sockets simulation in which a host tap/tun interface is used to |
| // proxy the LwIP stack onto a host native network interface. |
| // CollectTapAddresses() is only available on such targets. |
| if (gNetworkOptions.TapUseSystemConfig) |
| { |
| CollectTapAddresses(addrsVec, gNetworkOptions.TapDeviceName[j]); |
| } |
| #endif // CHIP_TARGET_STYLE_UNIX |
| #if INET_CONFIG_ENABLE_IPV4 |
| |
| IPAddress ip4Addr = (j < gNetworkOptions.LocalIPv4Addr.size()) ? gNetworkOptions.LocalIPv4Addr[j] : IPAddress::Any; |
| for (size_t n = 0; n < addrsVec.size(); n++) |
| { |
| IPAddress auto_addr; |
| if (IPAddress::FromString(addrsVec[n], auto_addr) && auto_addr.IsIPv4()) |
| { |
| ip4Addr = auto_addr; |
| } |
| } |
| |
| #if CHIP_TARGET_STYLE_UNIX |
| // TapAddrAutoconf and TapInterface are only needed for LwIP on |
| // sockets simulation in which a host tap/tun interface is used to |
| // proxy the LwIP stack onto a host native network interface. |
| // CollectTapAddresses() is only available on such targets. |
| |
| IPAddress ip4Gateway = (j < gNetworkOptions.IPv4GatewayAddr.size()) ? gNetworkOptions.IPv4GatewayAddr[j] : IPAddress::Any; |
| |
| { |
| ip4_addr_t ip4AddrLwIP, ip4NetmaskLwIP, ip4GatewayLwIP; |
| |
| ip4AddrLwIP = ip4Addr.ToIPv4(); |
| IP4_ADDR(&ip4NetmaskLwIP, 255, 255, 255, 0); |
| ip4GatewayLwIP = ip4Gateway.ToIPv4(); |
| netif_add(&(sNetIFs[j]), &ip4AddrLwIP, &ip4NetmaskLwIP, &ip4GatewayLwIP, &(sTapIFs[j]), TapInterface_SetupNetif, |
| tcpip_input); |
| } |
| #endif // CHIP_TARGET_STYLE_UNIX |
| |
| #endif // INET_CONFIG_ENABLE_IPV4 |
| |
| netif_create_ip6_linklocal_address(&(sNetIFs[j]), 1); |
| |
| #if (LWIP_VERSION_MAJOR == 2) && (LWIP_VERSION_MINOR == 0) |
| if (j < gNetworkOptions.LocalIPv6Addr.size()) |
| { |
| ip6_addr_t ip6addr = gNetworkOptions.LocalIPv6Addr[j].ToIPv6(); |
| s8_t index; |
| netif_add_ip6_address_with_route(&(sNetIFs[j]), &ip6addr, 64, &index); |
| // add ipv6 route for ipv6 address |
| if (j < gNetworkOptions.IPv6GatewayAddr.size()) |
| { |
| static ip6_addr_t br_ip6_addr = gNetworkOptions.IPv6GatewayAddr[j].ToIPv6(); |
| struct ip6_prefix ip6_prefix; |
| ip6_prefix.addr = Inet::IPAddress::Any.ToIPv6(); |
| ip6_prefix.prefix_len = 0; |
| ip6_add_route_entry(&ip6_prefix, &sNetIFs[j], &br_ip6_addr, NULL); |
| } |
| if (index >= 0) |
| { |
| netif_ip6_addr_set_state(&(sNetIFs[j]), index, IP6_ADDR_PREFERRED); |
| } |
| } |
| for (size_t n = 0; n < addrsVec.size(); n++) |
| { |
| IPAddress auto_addr; |
| if (IPAddress::FromString(addrsVec[n], auto_addr) && !auto_addr.IsIPv4()) |
| { |
| ip6_addr_t ip6addr = auto_addr.ToIPv6(); |
| s8_t index; |
| if (auto_addr.IsIPv6LinkLocal()) |
| continue; // skip over the LLA addresses, LwIP is aready adding those |
| if (auto_addr.IsIPv6Multicast()) |
| continue; // skip over the multicast addresses from host for now. |
| netif_add_ip6_address_with_route(&(sNetIFs[j]), &ip6addr, 64, &index); |
| if (index >= 0) |
| { |
| netif_ip6_addr_set_state(&(sNetIFs[j]), index, IP6_ADDR_PREFERRED); |
| } |
| } |
| } |
| #endif // (LWIP_VERSION_MAJOR == 2) && (LWIP_VERSION_MINOR == 0) |
| |
| netif_set_up(&(sNetIFs[j])); |
| netif_set_link_up(&(sNetIFs[j])); |
| } |
| |
| netif_set_default(&(sNetIFs[0])); |
| // UnLock LwIP stack |
| |
| UNLOCK_TCPIP_CORE(); |
| |
| while (!NetworkIsReady()) |
| { |
| constexpr uint32_t kSleepTimeMilliseconds = 100; |
| ServiceEvents(kSleepTimeMilliseconds); |
| } |
| |
| // FIXME: this is kinda nasty :( |
| // Force new IP address to be ready, bypassing duplicate detection. |
| |
| for (size_t j = 0; j < gNetworkOptions.TapDeviceName.size(); j++) |
| { |
| if (j < gNetworkOptions.LocalIPv6Addr.size()) |
| { |
| netif_ip6_addr_set_state(&(sNetIFs[j]), 2, 0x30); |
| } |
| else |
| { |
| netif_ip6_addr_set_state(&(sNetIFs[j]), 1, 0x30); |
| } |
| } |
| |
| PrintNetworkState(); |
| |
| AcquireLwIP(); |
| |
| #endif // CHIP_SYSTEM_CONFIG_USE_LWIP && !(CHIP_SYSTEM_CONFIG_LWIP_SKIP_INIT) |
| |
| gTCP.Init(gSystemLayer); |
| gUDP.Init(gSystemLayer); |
| } |
| |
| void ServiceEvents(uint32_t aSleepTimeMilliseconds) |
| { |
| static bool printed = false; |
| |
| if (!printed) |
| { |
| #if CHIP_SYSTEM_CONFIG_USE_LWIP && !(CHIP_SYSTEM_CONFIG_LWIP_SKIP_INIT) |
| if (NetworkIsReady()) |
| #endif |
| { |
| printf("CHIP node ready to service events\n"); |
| fflush(stdout); |
| printed = true; |
| } |
| } |
| |
| // Start a timer (with a no-op callback) to ensure that WaitForEvents() does not block longer than aSleepTimeMilliseconds. |
| gSystemLayer.StartTimer( |
| System::Clock::Milliseconds32(aSleepTimeMilliseconds), [](System::Layer *, void *) -> void {}, nullptr); |
| |
| #if CHIP_SYSTEM_CONFIG_USE_SOCKETS |
| gSystemLayer.PrepareEvents(); |
| gSystemLayer.WaitForEvents(); |
| gSystemLayer.HandleEvents(); |
| #endif |
| |
| #if CHIP_SYSTEM_CONFIG_USE_LWIP |
| if (gSystemLayer.IsInitialized()) |
| { |
| static uint32_t sRemainingSystemLayerEventDelay = 0; |
| |
| if (sRemainingSystemLayerEventDelay == 0) |
| { |
| #if defined(CHIP_DEVICE_LAYER_TARGET_OPEN_IOT_SDK) |
| // We need to terminate event loop after performance single step. |
| // Event loop processing work items until StopEventLoopTask is called. |
| // Scheduling StopEventLoop task guarantees correct operation of the loop. |
| chip::DeviceLayer::PlatformMgr().ScheduleWork( |
| [](intptr_t) -> void { chip::DeviceLayer::PlatformMgr().StopEventLoopTask(); }, (intptr_t) nullptr); |
| #endif // CHIP_DEVICE_LAYER_TARGET_OPEN_IOT_SDK |
| chip::DeviceLayer::PlatformMgr().RunEventLoop(); |
| sRemainingSystemLayerEventDelay = gNetworkOptions.EventDelay; |
| } |
| else |
| sRemainingSystemLayerEventDelay--; |
| |
| gSystemLayer.HandlePlatformTimer(); |
| } |
| #if CHIP_TARGET_STYLE_UNIX |
| // TapAddrAutoconf and TapInterface are only needed for LwIP on |
| // sockets simulation in which a host tap/tun interface is used to |
| // proxy the LwIP stack onto a host native network interface. |
| // CollectTapAddresses() is only available on such targets. |
| |
| TapInterface_Select(&(sTapIFs[0]), &(sNetIFs[0]), aSleepTime, gNetworkOptions.TapDeviceName.size()); |
| #endif // CHIP_TARGET_STYLE_UNIX |
| #endif // CHIP_SYSTEM_CONFIG_USE_LWIP |
| } |
| |
| #if CHIP_SYSTEM_CONFIG_USE_LWIP && !(CHIP_SYSTEM_CONFIG_LWIP_SKIP_INIT) |
| static bool NetworkIsReady() |
| { |
| bool ready = true; |
| |
| for (size_t j = 0; j < gNetworkOptions.TapDeviceName.size(); j++) |
| { |
| for (int i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) |
| { |
| if (!ip6_addr_isany(netif_ip6_addr(&(sNetIFs[j]), i)) && ip6_addr_istentative(netif_ip6_addr_state(&(sNetIFs[j]), i))) |
| { |
| ready = false; |
| break; |
| } |
| } |
| } |
| return ready; |
| } |
| |
| static void OnLwIPInitComplete(void * arg) |
| { |
| printf("Waiting for addresses assignment...\n"); |
| } |
| |
| #endif // CHIP_SYSTEM_CONFIG_USE_LWIP && !(CHIP_SYSTEM_CONFIG_LWIP_SKIP_INIT) |
| |
| void ShutdownNetwork() |
| { |
| gTCP.ForEachEndPoint([](TCPEndPoint * lEndPoint) -> Loop { |
| gTCP.ReleaseEndPoint(lEndPoint); |
| return Loop::Continue; |
| }); |
| gTCP.Shutdown(); |
| |
| gUDP.ForEachEndPoint([](UDPEndPoint * lEndPoint) -> Loop { |
| gUDP.ReleaseEndPoint(lEndPoint); |
| return Loop::Continue; |
| }); |
| gUDP.Shutdown(); |
| #if CHIP_SYSTEM_CONFIG_USE_LWIP && !(CHIP_SYSTEM_CONFIG_LWIP_SKIP_INIT) |
| ReleaseLwIP(); |
| #endif |
| } |
| |
| void DumpMemory(const uint8_t * mem, uint32_t len, const char * prefix, uint32_t rowWidth) |
| { |
| int indexWidth = snprintf(nullptr, 0, "%" PRIX32, len); |
| |
| if (indexWidth < 4) |
| indexWidth = 4; |
| |
| for (uint32_t i = 0; i < len; i += rowWidth) |
| { |
| printf("%s%0*" PRIX32 ": ", prefix, indexWidth, i); |
| |
| uint32_t rowEnd = i + rowWidth; |
| |
| uint32_t j = i; |
| for (; j < rowEnd && j < len; j++) |
| printf("%02X ", mem[j]); |
| |
| for (; j < rowEnd; j++) |
| printf(" "); |
| |
| for (j = i; j < rowEnd && j < len; j++) |
| if (isprint(static_cast<char>(mem[j]))) |
| printf("%c", mem[j]); |
| else |
| printf("."); |
| |
| printf("\n"); |
| } |
| } |
| |
| void DumpMemory(const uint8_t * mem, uint32_t len, const char * prefix) |
| { |
| const uint32_t kRowWidth = 16; |
| |
| DumpMemory(mem, len, prefix, kRowWidth); |
| } |