text: /*
  
  Copyright (c) Microsoft Corporation.
  Licensed under the MIT License.
  
  etl2pcapng
  
  Converts packet captures in ETL format generated by ndiscap (the ETW provider
  in Windows that produces packet capture events) to pcapng format
  (readable by Wireshark).
  
  Issues:
  
  -ndiscap supports packet truncation and so does pcapng, but ndiscap doesn't
   currently log metadata about truncation in its events, so we try to infer
   the original fragment length from IP headers. This currently works for
   RAW and Ethernet frames. For LSOv2 packets, since the length field is not
   filled, we can't infer the original length, so we use the truncated length
   as the original length.
  
  */
  
  #define WIN32_LEAN_AND_MEAN 1
  #include <windows.h>
  #include <basetsd.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <evntrace.h>
  #include <evntcons.h>
  #include <tdh.h>
  #include <strsafe.h>
  #include <winsock2.h>
  #include <netiodef.h>
  #include <pcapng.h>
  
  #define USAGE \
  "etl2pcapng <infile> <outfile>\n" \
  "Converts a packet capture from etl to pcapng format.\n"
  
  // Increment when adding features
  #define VERSION "1.8.0"
  
  #define MAX_PACKET_SIZE 65535
  
  // From the ndiscap manifest
  #define KW_MEDIA_WIRELESS_WAN         0x200
  #define KW_MEDIA_NATIVE_802_11      0x10000
  #define KW_PACKET_START          0x40000000
  #define KW_PACKET_END            0x80000000
  #define KW_SEND                 0x100000000
  #define KW_RECEIVE              0x200000000
  
  #define tidPacketFragment            1001
  #define tidPacketMetadata            1002
  #define tidVMSwitchPacketFragment    1003
  
  // From: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/windot11/ns-windot11-dot11_extsta_recv_context
  #pragma pack(push,8)
  typedef struct _NDIS_OBJECT_HEADER {
      unsigned char  Type;
      unsigned char  Revision;
      unsigned short Size;
  } NDIS_OBJECT_HEADER, * PNDIS_OBJECT_HEADER;
  
  typedef struct DOT11_EXTSTA_RECV_CONTEXT {
      NDIS_OBJECT_HEADER Header;
      unsigned long      uReceiveFlags;
      unsigned long      uPhyId;
      unsigned long      uChCenterFrequency;
      unsigned short     usNumberOfMPDUsReceived;
      long               lRSSI;
      unsigned char      ucDataRate;
      unsigned long      uSizeMediaSpecificInfo;
      void               *pvMediaSpecificInfo;
      unsigned long long ullTimestamp;
  } DOT11_EXTSTA_RECV_CONTEXT, * PDOT11_EXTSTA_RECV_CONTEXT;
  #pragma pack(pop)
  
  // From: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/windot11/ne-windot11-_dot11_phy_type
  #define DOT11_PHY_TYPE_NAMES_MAX 10
  static const char* DOT11_PHY_TYPE_NAMES[] = {
      "Unknown",        // dot11_phy_type_unknown = 0
      "Fhss",           // dot11_phy_type_fhss = 1
      "Dsss",           // dot11_phy_type_dsss = 2
      "IrBaseband",     // dot11_phy_type_irbaseband = 3
      "802.11a",        // dot11_phy_type_ofdm = 4
      "802.11b",        // dot11_phy_type_hrdsss = 5
      "802.11g",        // dot11_phy_type_erp = 6
      "802.11n",        // dot11_phy_type_ht = 7
      "802.11ac",       // dot11_phy_type_vht = 8
      "802.11ad",       // dot11_phy_type_dmg = 9
      "802.11ax"        // dot11_phy_type_he = 10
  };
  
  HANDLE OutFile = INVALID_HANDLE_VALUE;
  unsigned long long NumFramesConverted = 0;
  BOOLEAN Pass2 = FALSE;
  char AuxFragBuf[MAX_PACKET_SIZE] = {0};
  unsigned long AuxFragBufOffset = 0;
  
  DOT11_EXTSTA_RECV_CONTEXT PacketMetadata;
  BOOLEAN AddWlanMetadata = FALSE;
  
  typedef struct _NDIS_NET_BUFFER_LIST_8021Q_INFO {
      union {
          struct {
              UINT32 UserPriority : 3;             // 802.1p priority
              UINT32 CanonicalFormatId : 1;        // always 0
              UINT32 VlanId : 12;                  // VLAN Identification
              UINT32 Reserved : 16;                // set to 0 for ethernet
          } TagHeader;
  
          struct {
              UINT32 UserPriority : 3;             // 802.1p priority
              UINT32 CanonicalFormatId : 1;        // always 0
              UINT32 VlanId : 12;                  // VLAN Identification
              UINT32 WMMInfo : 4;
              UINT32 Reserved : 12;                // set to 0 for Wireless LAN
          } WLanTagHeader;
  
          PVOID Value;
      };
  } NDIS_NET_BUFFER_LIST_8021Q_INFO, *PNDIS_NET_BUFFER_LIST_8021Q_INFO;
  
  // The max OOB data size might increase in the future. If it becomes larger than MaxNetBufferListInfo,
  // this tool will print a warning and the value of MaxNetBufferListInfo in the code should be increased.
  // From: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/nblinfo/ne-nblinfo-ndis_net_buffer_list_info
  #define MaxNetBufferListInfo 200
  #define Ieee8021QNetBufferListInfo 4
  #define NetBufferListHashValue 8
  PBYTE OobData[MaxNetBufferListInfo];
  
  typedef struct _VMSWITCH_SOURCE_INFO {
      unsigned long SourcePortId;
      char* SourcePortName;
      char* SourceNicName;
      char* SourceNicType;
  } VMSWITCH_SOURCE_INFO, *PVMSWITCH_SOURCE_INFO;
  
  typedef struct _VMSWITCH_PACKET_FRAGMENT {
      unsigned long SourcePortId;
      unsigned long DestinationCount;
      short VlanId;
      unsigned long RssHashValue;
  } VMSWITCH_PACKET_FRAGMENT, *PVMSWITCH_PACKET_FRAGMENT;
  
  BOOLEAN CurrentPacketIsVMSwitchPacketFragment = FALSE;
  VMSWITCH_PACKET_FRAGMENT VMSwitchPacketFragment;
  
  const GUID NdisCapId = { // Microsoft-Windows-NDIS-PacketCapture {2ED6006E-4729-4609-B423-3EE7BCD678EF}
      0x2ed6006e, 0x4729, 0x4609, 0xb4, 0x23, 0x3e, 0xe7, 0xbc, 0xd6, 0x78, 0xef};
  
  struct INTERFACE {
      struct INTERFACE* Next;
      unsigned long LowerIfIndex;
      unsigned long MiniportIfIndex;
      unsigned long PcapNgIfIndex;
      short Type;
      short VlanId;
  
      BOOLEAN IsVMNic;
      VMSWITCH_SOURCE_INFO VMNic;
  };
  
  #define IFACE_HT_SIZE 100
  struct INTERFACE* InterfaceHashTable[IFACE_HT_SIZE] = {0};
  unsigned long NumInterfaces = 0;
  
  unsigned long HashInterface(unsigned long LowerIfIndex)
  {
      if (CurrentPacketIsVMSwitchPacketFragment) {
          return VMSwitchPacketFragment.SourcePortId * (VMSwitchPacketFragment.VlanId + 1);
      } else {
          return LowerIfIndex;
      }
  }
  
  struct INTERFACE* GetInterface(unsigned long LowerIfIndex)
  {
      struct INTERFACE* Iface = InterfaceHashTable[HashInterface(LowerIfIndex) % IFACE_HT_SIZE];
      while (Iface != NULL) {
          if (CurrentPacketIsVMSwitchPacketFragment) {
              if (Iface->IsVMNic &&
                  Iface->LowerIfIndex == LowerIfIndex &&
                  Iface->VlanId == VMSwitchPacketFragment.VlanId &&
                  Iface->VMNic.SourcePortId == VMSwitchPacketFragment.SourcePortId) {
                  return Iface;
              }
          } else {
              if (!Iface->IsVMNic && Iface->LowerIfIndex == LowerIfIndex && Iface->VlanId == 0) {
                  return Iface;
              }
          }
          Iface = Iface->Next;
      }
      return NULL;
  }
  
  void AddInterface(PEVENT_RECORD ev, unsigned long LowerIfIndex, unsigned long MiniportIfIndex, short Type)
  {
      struct INTERFACE** Iface = &InterfaceHashTable[HashInterface(LowerIfIndex) % IFACE_HT_SIZE];
      struct INTERFACE* NewIface = malloc(sizeof(struct INTERFACE));
      if (NewIface == NULL) {
          printf("out of memory\n");
          exit(1);
      }
  
      NewIface->LowerIfIndex = LowerIfIndex;
      NewIface->MiniportIfIndex = MiniportIfIndex;
      NewIface->Type = Type;
      NewIface->VlanId = 0;
      NewIface->IsVMNic = FALSE;
  
      if (CurrentPacketIsVMSwitchPacketFragment) {
  
          NewIface->IsVMNic = TRUE;
  
          wchar_t Buffer[8192];
          PROPERTY_DATA_DESCRIPTOR Desc;
          int Err;
  
          // SourceNicName
          Desc.PropertyName = (unsigned long long)(L"SourceNicName");
          Desc.ArrayIndex = ULONG_MAX;
          ULONG ParamNameSize = 0;
          (void)TdhGetPropertySize(ev, 0, NULL, 1, &Desc, &ParamNameSize);
          NewIface->VMNic.SourceNicName = malloc((ParamNameSize / sizeof(wchar_t)) + 1);
          if (NewIface->VMNic.SourceNicName == NULL) {
              printf("out of memory\n");
              exit(1);
          }
          Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(Buffer), (PBYTE)Buffer);
          if (Err != NO_ERROR) {
              Buffer[0] = L'\0';
          }
          Buffer[ParamNameSize / sizeof(wchar_t) + 1] = L'\0';
          WideCharToMultiByte(CP_ACP,
              0,
              Buffer,
              -1,
              NewIface->VMNic.SourceNicName,
              ParamNameSize / sizeof(wchar_t) + 1,
              NULL,
              NULL);
          NewIface->VMNic.SourceNicName[wcslen(Buffer)] = '\0';
  
          // SourcePortName
          Desc.PropertyName = (unsigned long long)(L"SourcePortName");
          Desc.ArrayIndex = ULONG_MAX;
          (void)TdhGetPropertySize(ev, 0, NULL, 1, &Desc, &ParamNameSize);
          NewIface->VMNic.SourcePortName = malloc((ParamNameSize / sizeof(wchar_t)) + 1);
          if (NewIface->VMNic.SourcePortName == NULL) {
              printf("out of memory\n");
              exit(1);
          }
          Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(Buffer), (PBYTE)Buffer);
          if (Err != NO_ERROR) {
              Buffer[0] = L'\0';
          }
          Buffer[ParamNameSize / sizeof(wchar_t) + 1] = L'\0';
          WideCharToMultiByte(CP_ACP,
              0,
              Buffer,
              -1,
              NewIface->VMNic.SourcePortName,
              ParamNameSize / sizeof(wchar_t) + 1,
              NULL,
              NULL);
          NewIface->VMNic.SourcePortName[wcslen(Buffer)] = '\0';
  
          // SourceNicType
          Desc.PropertyName = (unsigned long long)(L"SourceNicType");
          Desc.ArrayIndex = ULONG_MAX;
          (void)TdhGetPropertySize(ev, 0, NULL, 1, &Desc, &ParamNameSize);
          NewIface->VMNic.SourceNicType = malloc((ParamNameSize / sizeof(wchar_t)) + 1);
          if (NewIface->VMNic.SourceNicType == NULL) {
              printf("out of memory\n");
              exit(1);
          }
          Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(Buffer), (PBYTE)Buffer);
          if (Err != NO_ERROR) {
              Buffer[0] = L'\0';
          }
          Buffer[ParamNameSize / sizeof(wchar_t) + 1] = L'\0';
          WideCharToMultiByte(CP_ACP,
              0,
              Buffer,
              -1,
              NewIface->VMNic.SourceNicType,
              ParamNameSize / sizeof(wchar_t) + 1,
              NULL,
              NULL);
          NewIface->VMNic.SourceNicType[wcslen(Buffer)] = '\0';
  
  
          NewIface->VMNic.SourcePortId = VMSwitchPacketFragment.SourcePortId;
          NewIface->VlanId = VMSwitchPacketFragment.VlanId;
      }
  
      NewIface->Next = *Iface;
  
      *Iface = NewIface;
      NumInterfaces++;
  }
  
  int __cdecl InterfaceCompareFn(const void* A, const void* B)
  {
      // MiniportIfIndex is the primary sort and LowerIfIndex is
      // the secondary sort, except that inside a group of interfaces
      // with the same MiniportIfIndex we want the one with
      // MiniportIfIndex==LowerIfIndex (i.e. the miniport) to come
      // first.
      unsigned long MA = (*((struct INTERFACE**)A))->MiniportIfIndex;
      unsigned long MB = (*((struct INTERFACE**)B))->MiniportIfIndex;
      unsigned long LA = (*((struct INTERFACE**)A))->LowerIfIndex;
      unsigned long LB = (*((struct INTERFACE**)B))->LowerIfIndex;
  
      if (MA == MB) {
          if (MA == LA) {
              // A is the miniport.
              return -1;
          } else if (MB == LB) {
              // B is the miniport.
              return 1;
          } else if (LA < LB) {
              return -1;
          } else if (LB < LA) {
              return 1;
          } else {
              return 0;
          }
      } else if (MA < MB) {
          return -1;
      } else {
          return 1;
      }
  }
  
  void WriteInterfaces()
  {
      // Sorts the interfaces, writes them to the pcapng file, and prints them
      // for user reference.
      struct INTERFACE** InterfaceArray;
      struct INTERFACE* Interface;
      unsigned int i, j;
      // IF_STRING_MAX_SIZE must be multiple of 4
      #define IF_STRING_MAX_SIZE 256
      char IfName[IF_STRING_MAX_SIZE];
      size_t IfNameLength = 0;
      char IfDesc[IF_STRING_MAX_SIZE];
      size_t IfDescLength = 0;
  
      InterfaceArray = (struct INTERFACE**)malloc(NumInterfaces * sizeof(struct INTERFACE*));
      if (InterfaceArray == NULL) {
          printf("out of memory\n");
          exit(1);
      }
  
      j = 0;
      for (i = 0; i < IFACE_HT_SIZE; i++) {
          for (Interface = InterfaceHashTable[i]; Interface != NULL; Interface = Interface->Next) {
              InterfaceArray[j++] = Interface;
          }
      }
  
      qsort(InterfaceArray, NumInterfaces, sizeof(struct INTERFACE*), InterfaceCompareFn);
  
      for (i = 0; i < NumInterfaces; i++) {
          Interface = InterfaceArray[i];
          Interface->PcapNgIfIndex = i;
          memset(IfName, 0, sizeof(IfName));
          memset(IfDesc, 0, sizeof(IfDesc));
          IfDescLength = 0;
          IfNameLength = 0;
  
          switch (Interface->Type) {
          case PCAPNG_LINKTYPE_ETHERNET:
              if (Interface->IsVMNic) {
                  printf("IF: medium=vNIC-%s\tID=%u\tIfIndex=%u\tVlanID=%i",
                      Interface->VMNic.SourceNicType,
                      Interface->PcapNgIfIndex,
                      Interface->LowerIfIndex,
                      Interface->VlanId
                  );
                  StringCchPrintfA(
                      IfName,
                      IF_STRING_MAX_SIZE,
                      "vNIC:%s:%s:%lu:%i",
                      Interface->VMNic.SourceNicType,
                      Interface->VMNic.SourcePortName,
                      Interface->LowerIfIndex,
                      Interface->VlanId
                  );
              } else {
                  printf("IF: medium=eth\t\t\tID=%u\tIfIndex=%u\tVlanID=%i", Interface->PcapNgIfIndex, Interface->LowerIfIndex, Interface->VlanId);
                  StringCchPrintfA(IfName, IF_STRING_MAX_SIZE, "eth:%lu:%i", Interface->LowerIfIndex, Interface->VlanId);
              }
              break;
          case PCAPNG_LINKTYPE_IEEE802_11:
              printf("IF: medium=wifi\t\t\tID=%u\tIfIndex=%u", Interface->PcapNgIfIndex, Interface->LowerIfIndex);
              StringCchPrintfA(IfName, IF_STRING_MAX_SIZE, "wifi:%lu", Interface->LowerIfIndex);
              break;
          case PCAPNG_LINKTYPE_RAW:
              printf("IF: medium=mbb\t\t\tID=%u\tIfIndex=%u", Interface->PcapNgIfIndex, Interface->LowerIfIndex);
              StringCchPrintfA(IfName, IF_STRING_MAX_SIZE, "mbb:%lu", Interface->LowerIfIndex);
              break;
          }
          StringCchLengthA(IfName, IF_STRING_MAX_SIZE, &IfNameLength);
  
          if (Interface->LowerIfIndex != Interface->MiniportIfIndex) {
              printf("\t(LWF over IfIndex %u)", Interface->MiniportIfIndex);
              StringCchPrintfA(IfDesc, IF_STRING_MAX_SIZE, "LWF over IfIndex %lu", Interface->MiniportIfIndex);
              StringCchLengthA(IfDesc, IF_STRING_MAX_SIZE, &IfDescLength);
          }
  
          if (Interface->VlanId != 0) {
              StringCchPrintfA(IfDesc+IfDescLength, IF_STRING_MAX_SIZE, " VlanID=%i ", Interface->VlanId);
              StringCchLengthA(IfDesc, IF_STRING_MAX_SIZE, &IfDescLength);
          }
  
          printf("\n");
  
          PcapNgWriteInterfaceDesc(
              OutFile,
              Interface->Type,
              MAX_PACKET_SIZE,
              IfName,
              (unsigned short)IfNameLength,
              IfDescLength != 0 ? IfDesc : NULL,
              (unsigned short)IfDescLength);
      }
  
      free(InterfaceArray);
  }
  
  void ParseVmSwitchPacketFragment(PEVENT_RECORD ev)
  {
      // Parse the current VMSwitch packet event for use elsewhere.
      // NB: Here we only do per-packet parsing. For any event fields that only need to be
      // parsed once and written into an INTERFACE, we do the parsing in AddInterface.
  
      PROPERTY_DATA_DESCRIPTOR Desc;
      int Err;
      PNDIS_NET_BUFFER_LIST_8021Q_INFO pNblVlanInfo;
  
      // Get VLAN from OOB
      unsigned long OobLength;
      Desc.PropertyName = (unsigned long long)L"OOBDataSize";
      Desc.ArrayIndex = ULONG_MAX;
      Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(OobLength), (PBYTE)&OobLength);
      if (Err != NO_ERROR) {
          printf("TdhGetProperty OobLength failed with %u\n", Err);
          return;
      }
  
      if (OobLength > sizeof(OobData)) {
          printf("OOB data of %lu bytes too large to fit in hardcoded buffer of size %lu\n", OobLength, (unsigned long)sizeof(OobData));
          return;
      }
  
      Desc.PropertyName = (unsigned long long)L"OOBData";
      Desc.ArrayIndex = ULONG_MAX;
      Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, OobLength, (PBYTE)&OobData);
      if (Err != NO_ERROR) {
          printf("TdhGetProperty Metadata failed with %u\n", Err);
          return;
      }
  
      pNblVlanInfo = (PNDIS_NET_BUFFER_LIST_8021Q_INFO)&OobData[Ieee8021QNetBufferListInfo];
      VMSwitchPacketFragment.VlanId = (short)pNblVlanInfo->TagHeader.VlanId;
  
      VMSwitchPacketFragment.RssHashValue = PtrToUlong((PVOID)OobData[NetBufferListHashValue]);
  
      // SourcePortId
      Desc.PropertyName = (unsigned long long)L"SourcePortId";
      Desc.ArrayIndex = ULONG_MAX;
      Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(VMSwitchPacketFragment.SourcePortId), (PBYTE)&VMSwitchPacketFragment.SourcePortId);
      if (Err != NO_ERROR) {
          printf("TdhGetProperty SourcePortId failed with %u\n", Err);
          return;
      }
  
      // DestinationCount
      Desc.PropertyName = (unsigned long long)L"DestinationCount";
      Desc.ArrayIndex = ULONG_MAX;
      Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(VMSwitchPacketFragment.DestinationCount), (PBYTE)&VMSwitchPacketFragment.DestinationCount);
      if (Err != NO_ERROR) {
          printf("TdhGetProperty DestinationCount failed with %u\n", Err);
          return;
      }
  }
  
  void WINAPI EventCallback(PEVENT_RECORD ev)
  {
      int Err;
      unsigned long LowerIfIndex;
  
      struct INTERFACE* Iface;
      unsigned long FragLength;
      PROPERTY_DATA_DESCRIPTOR Desc;
      ULARGE_INTEGER TimeStamp;
      short Type;
      unsigned long TotalFragmentLength;
      unsigned long InferredOriginalFragmentLength = 0;
      PETHERNET_HEADER EthHdr;
      PIPV4_HEADER Ipv4Hdr;
      PIPV6_HEADER Ipv6Hdr;
  
      if (!IsEqualGUID(&ev->EventHeader.ProviderId, &NdisCapId) ||
          (ev->EventHeader.EventDescriptor.Id != tidPacketFragment &&
           ev->EventHeader.EventDescriptor.Id != tidPacketMetadata &&
           ev->EventHeader.EventDescriptor.Id != tidVMSwitchPacketFragment)) {
          return;
      }
  
      CurrentPacketIsVMSwitchPacketFragment = (ev->EventHeader.EventDescriptor.Id == tidVMSwitchPacketFragment);
      if (CurrentPacketIsVMSwitchPacketFragment) {
          ParseVmSwitchPacketFragment(ev);
      }
  
      Desc.PropertyName = (unsigned long long)L"LowerIfIndex";
      Desc.ArrayIndex = ULONG_MAX;
      Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(LowerIfIndex), (PBYTE)&LowerIfIndex);
      if (Err != NO_ERROR) {
          printf("TdhGetProperty LowerIfIndex failed with %u\n", Err);
          return;
      }
  
      Iface = GetInterface(LowerIfIndex);
  
      if (!!(ev->EventHeader.EventDescriptor.Keyword & KW_MEDIA_NATIVE_802_11)) {
          Type = PCAPNG_LINKTYPE_IEEE802_11;
      } else if (!!(ev->EventHeader.EventDescriptor.Keyword & KW_MEDIA_WIRELESS_WAN)) {
          Type = PCAPNG_LINKTYPE_RAW;
      } else {
          Type = PCAPNG_LINKTYPE_ETHERNET;
      }
  
      if (!Pass2) {
          // Record the IfIndex if it's a new one.
          if (Iface == NULL) {
              unsigned long MiniportIfIndex;
              Desc.PropertyName = (unsigned long long)L"MiniportIfIndex";
              Desc.ArrayIndex = ULONG_MAX;
              Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(MiniportIfIndex), (PBYTE)&MiniportIfIndex);
              if (Err != NO_ERROR) {
                  printf("TdhGetProperty MiniportIfIndex failed with %u\n", Err);
                  return;
              }
              AddInterface(
                  ev,
                  LowerIfIndex,
                  MiniportIfIndex,
                  Type
              );
          } else if (Iface->Type != Type) {
              printf("WARNING: inconsistent media type in packet events!\n");
          }
          return;
      }
  
      if (Iface == NULL) {
          // We generated the list of interfaces directly from the
          // packet traces themselves, so there must be a bug.
          printf("ERROR: packet with unrecognized IfIndex\n");
          exit(1);
      }
  
      // Save off Ndis/Wlan metadata to be added to the next packet
      if (ev->EventHeader.EventDescriptor.Id == tidPacketMetadata) {
          unsigned long MetadataLength = 0;
          Desc.PropertyName = (unsigned long long)L"MetadataSize";
          Desc.ArrayIndex = ULONG_MAX;
          Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(MetadataLength), (PBYTE)&MetadataLength);
          if (Err != NO_ERROR) {
              printf("TdhGetProperty MetadataSize failed with %u\n", Err);
              return;
          }
  
          if (MetadataLength != sizeof(PacketMetadata)) {
              printf("Unknown Metadata length. Expected %lu, got %u\n", (unsigned long)sizeof(DOT11_EXTSTA_RECV_CONTEXT), MetadataLength);
              return;
          }
  
          Desc.PropertyName = (unsigned long long)L"Metadata";
          Desc.ArrayIndex = ULONG_MAX;
          Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, MetadataLength, (PBYTE)&PacketMetadata);
          if (Err != NO_ERROR) {
              printf("TdhGetProperty Metadata failed with %u\n", Err);
              return;
          }
  
          AddWlanMetadata = TRUE;
          return;
      }
  
      // N.B.: Here we are querying the FragmentSize property to get the
      // total size of the packet, and then reading that many bytes from
      // the Fragment property. This is unorthodox (normally you are
      // supposed to use TdhGetPropertySize to get the size of a property)
      // but required due to the way ndiscap puts packet contents in
      // multiple adjacent properties (which happen to be contiguous in
      // memory).
  
      Desc.PropertyName = (unsigned long long)L"FragmentSize";
      Desc.ArrayIndex = ULONG_MAX;
      Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(FragLength), (PBYTE)&FragLength);
      if (Err != NO_ERROR) {
          printf("TdhGetProperty FragmentSize failed with %u\n", Err);
          return;
      }
  
      if (FragLength > RTL_NUMBER_OF(AuxFragBuf) - AuxFragBufOffset) {
          printf("Packet too large (size = %u) and skipped\n", AuxFragBufOffset + FragLength);
          return;
      }
  
      Desc.PropertyName = (unsigned long long)L"Fragment";
      Desc.ArrayIndex = ULONG_MAX;
      Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, FragLength, (PBYTE)(AuxFragBuf + AuxFragBufOffset));
      if (Err != NO_ERROR) {
          printf("TdhGetProperty Fragment failed with %u\n", Err);
          return;
      }
  
      // 100ns since 1/1/1601 -> usec since 1/1/1970.
      // The offset of 11644473600 seconds can be calculated with a
      // couple of calls to SystemTimeToFileTime.
      TimeStamp.QuadPart = (ev->EventHeader.TimeStamp.QuadPart / 10) - 11644473600000000ll;
  
      // The KW_PACKET_START and KW_PACKET_END keywords are used as follows:
      // -A single-event packet has both KW_PACKET_START and KW_PACKET_END.
      // -A multi-event packet consists of an event with KW_PACKET_START followed
      //  by an event with KW_PACKET_END, with zero or more events with neither
      //  keyword in between.
      //
      // So, we accumulate fragments in AuxFragBuf until KW_PACKET_END is
      // encountered, then call PcapNgWriteEnhancedPacket and start over. There's
      // no need for us to even look for KW_PACKET_START.
      //
      // NB: Starting with Windows 8.1, only single-event packets are traced.
      // This logic is here to support packet captures from older systems.
  
      if (!!(ev->EventHeader.EventDescriptor.Keyword & KW_PACKET_END)) {
  
          if (ev->EventHeader.EventDescriptor.Keyword & KW_MEDIA_NATIVE_802_11 &&
              AuxFragBuf[1] & 0x40) {
              // Clear Protected bit in the case of 802.11
              // Ndis captures will be decrypted in the etl file
  
              AuxFragBuf[1] = AuxFragBuf[1] & 0xBF; // _1011_1111_ - Clear "Protected Flag"
          }
  
          // COMMENT_MAX_SIZE must be multiple of 4
          #define COMMENT_MAX_SIZE 256
          char Comment[COMMENT_MAX_SIZE] = { 0 };
          size_t CommentLength = 0;
  
          if (AddWlanMetadata) {
              if (PacketMetadata.uPhyId > DOT11_PHY_TYPE_NAMES_MAX) {
                  PacketMetadata.uPhyId = 0; // Set to unknown if outside known bounds.
              }
  
              Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d Packet Metadata: ReceiveFlags:0x%x, PhyType:%s, CenterCh:%u, NumMPDUsReceived:%u, RSSI:%d, DataRate:%u",
                  ev->EventHeader.ProcessId,
                  PacketMetadata.uReceiveFlags,
                  DOT11_PHY_TYPE_NAMES[PacketMetadata.uPhyId],
                  PacketMetadata.uChCenterFrequency,
                  PacketMetadata.usNumberOfMPDUsReceived,
                  PacketMetadata.lRSSI,
                  PacketMetadata.ucDataRate);
  
              AddWlanMetadata = FALSE;
              memset(&PacketMetadata, 0, sizeof(DOT11_EXTSTA_RECV_CONTEXT));
          } else if (CurrentPacketIsVMSwitchPacketFragment) {
              if (VMSwitchPacketFragment.DestinationCount > 0) {
                  Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d VlanId=%d SrcPortId=%d SrcNicType=%s SrcNicName=%s SrcPortName=%s DstNicCount=%d HashValue=%08lx",
                      ev->EventHeader.ProcessId,
                      Iface->VlanId,
                      Iface->VMNic.SourcePortId,
                      Iface->VMNic.SourceNicType,
                      Iface->VMNic.SourceNicName,
                      Iface->VMNic.SourcePortName,
                      VMSwitchPacketFragment.DestinationCount,
                      VMSwitchPacketFragment.RssHashValue
                  );
              } else {
                  Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d VlanId=%d SrcPortId=%d SrcNicType=%s SrcNicName=%s SrcPortName=%s HashValue=%08lx",
                      ev->EventHeader.ProcessId,
                      Iface->VlanId,
                      Iface->VMNic.SourcePortId,
                      Iface->VMNic.SourceNicType,
                      Iface->VMNic.SourceNicName,
                      Iface->VMNic.SourcePortName,
                      VMSwitchPacketFragment.RssHashValue
                  );
              }
          } else {
              Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d", ev->EventHeader.ProcessId);
          }
  
          if (Err != NO_ERROR) {
              printf("Failed converting comment to string with error: %u\n", Err);
          } else {
              Err = StringCchLengthA(Comment, COMMENT_MAX_SIZE, &CommentLength);
  
              if (Err != NO_ERROR) {
                  printf("Failed getting length of comment string with error: %u\n", Err);
                  CommentLength = 0;
                  memset(Comment, 0, COMMENT_MAX_SIZE);
              }
          }
  
          TotalFragmentLength = AuxFragBufOffset + FragLength;
  
          // Parse the packet to see if it's truncated. If so, try to recover the original length.
          if (Type == PCAPNG_LINKTYPE_ETHERNET) {
              if (TotalFragmentLength >= sizeof(ETHERNET_HEADER)) {
                  EthHdr = (PETHERNET_HEADER)AuxFragBuf;
                  if (ntohs(EthHdr->Type) == ETHERNET_TYPE_IPV4 &&
                      TotalFragmentLength >= sizeof(IPV4_HEADER) + sizeof(ETHERNET_HEADER)) {
                      Ipv4Hdr = (PIPV4_HEADER)(EthHdr + 1);
                      InferredOriginalFragmentLength = ntohs(Ipv4Hdr->TotalLength) + sizeof(ETHERNET_HEADER);
                  } else if (ntohs(EthHdr->Type) == ETHERNET_TYPE_IPV6 &&
                             TotalFragmentLength >= sizeof(IPV6_HEADER) + sizeof(ETHERNET_HEADER)) {
                      Ipv6Hdr = (PIPV6_HEADER)(EthHdr + 1);
                      InferredOriginalFragmentLength = ntohs(Ipv6Hdr->PayloadLength) + sizeof(IPV6_HEADER) + sizeof(ETHERNET_HEADER);
                  }
              }
          } else if (Type == PCAPNG_LINKTYPE_RAW) {
              // Raw frames begins with an IPv4/6 header.
              if (TotalFragmentLength >= sizeof(IPV4_HEADER)) {
                  Ipv4Hdr = (PIPV4_HEADER)AuxFragBuf;
                  if (Ipv4Hdr->Version == 4) {
                      InferredOriginalFragmentLength = ntohs(Ipv4Hdr->TotalLength) + sizeof(ETHERNET_HEADER);
                  } else if (Ipv4Hdr->Version == 6) {
                      Ipv6Hdr = (PIPV6_HEADER)(AuxFragBuf);
                      InferredOriginalFragmentLength = ntohs(Ipv6Hdr->PayloadLength) + sizeof(IPV6_HEADER) + sizeof(ETHERNET_HEADER);
                  }
              }
          }
  
          PcapNgWriteEnhancedPacket(
              OutFile,
              AuxFragBuf,
              TotalFragmentLength,
              // For LSO v2 packets, inferred original fragment length is ignored since length field in IP header is not filled.
              InferredOriginalFragmentLength <= TotalFragmentLength ? TotalFragmentLength : InferredOriginalFragmentLength,
              Iface->PcapNgIfIndex,
              !!(ev->EventHeader.EventDescriptor.Keyword & KW_SEND),
              TimeStamp.HighPart,
              TimeStamp.LowPart,
              CommentLength > 0 ? (char*)&Comment : NULL,
              (unsigned short)CommentLength);
  
          AuxFragBufOffset = 0;
          NumFramesConverted++;
      } else {
          AuxFragBufOffset += FragLength;
      }
  }
  
  int __cdecl wmain(int argc, wchar_t** argv)
  {
      int Err;
      EVENT_TRACE_LOGFILE LogFile;
      TRACEHANDLE TraceHandle;
      wchar_t* InFileName;
      wchar_t* OutFileName;
  
      if (argc == 2 &&
          (!wcscmp(argv[1], L"-v") ||
           !wcscmp(argv[1], L"--version"))) {
          printf("etl2pcapng version %s\n",VERSION);
          return 0;
      }
  
      if (argc != 3) {
          printf(USAGE);
          return ERROR_INVALID_PARAMETER;
      }
      InFileName = argv[1];
      OutFileName = argv[2];
  
      OutFile = CreateFile(OutFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
                           FILE_ATTRIBUTE_NORMAL, NULL);
      if (OutFile == INVALID_HANDLE_VALUE) {
          Err = GetLastError();
          printf("CreateFile called on %ws failed with %u\n", OutFileName, Err);
          if (Err == ERROR_SHARING_VIOLATION) {
              printf("The file appears to be open already.\n");
          }
          goto Done;
      }
  
      Err = PcapNgWriteSectionHeader(OutFile);
      if (Err != NO_ERROR) {
          goto Done;
      }
  
      ZeroMemory(&LogFile, sizeof(LogFile));
      LogFile.LogFileName = InFileName;
      LogFile.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD;
      LogFile.EventRecordCallback = EventCallback;
      LogFile.Context = NULL;
  
      TraceHandle = OpenTrace(&LogFile);
      if (TraceHandle == INVALID_PROCESSTRACE_HANDLE) {
          Err = GetLastError();
          printf("OpenTrace failed with %u\n", Err);
          goto Done;
      }
  
      // Read the ETL file twice.
      // Pass1: Gather interface information.
      // Pass2: Convert packet traces.
      Err = ProcessTrace(&TraceHandle, 1, 0, 0);
      if (Err != NO_ERROR) {
          printf("ProcessTrace failed with %u\n", Err);
          goto Done;
      }
  
      WriteInterfaces();
  
      Pass2 = TRUE;
  
      Err = ProcessTrace(&TraceHandle, 1, 0, 0);
      if (Err != NO_ERROR) {
          printf("ProcessTrace failed with %u\n", Err);
          goto Done;
      }
  
      if (NumFramesConverted == 0) {
          printf("Input ETL file does not contain a packet capture.\n");
      } else {
          printf("Converted %llu frames\n", NumFramesConverted);
      }
  
  Done:
      if (OutFile != INVALID_HANDLE_VALUE) {
          CloseHandle(OutFile);
      }
      return Err;
  }
  