LidarView源码分析(十四)vtkPacketFileReader类

简介

vtkPacketFileReader是对libpcap的包装(windows系统下是winpcap,linux下是libpcap)。winpcap已经停止维护。可以使用Npcap。在LidarView中仍然使用的是winpcap(http://github.com/patmarion/winpcap.git)。

vtkPacketFileReader负责对pcap数据进行读取,并可以获取或者设定指定的文件位置。

头文件

在头文件中定义了存储数据和数据信息的结构体,声明了用于读取和构造报头的函数。然后声明了vtkPacketFileReader类。

// Some versions of libpcap do not have PCAP_NETMASK_UNKNOWN
#if !defined(PCAP_NETMASK_UNKNOWN)
#define PCAP_NETMASK_UNKNOWN 0xffffffff
#endif

//------------------------------------------------------------------------------
// IPv6 fragment IDs are 4 bytes, IPv4 are only 2.
typedef uint32_t FragmentIdentificationT;

//------------------------------------------------------------------------------
// Packet fragment offsets are measured in blocks/steps of 8 bytes.
constexpr unsigned int FRAGMENT_OFFSET_STEP = 8;

//------------------------------------------------------------------------------
/*!
 * @brief Track fragment reassembly to confirm that all data has been
 *        reassembled before passing it on.
 */
struct FragmentTracker // 用于存储数据
{
  // The incrementally reassembled data.
  std::vector Data;
  // Set when the final fragment is encountered.
  unsigned int ExpectedSize = 0;
  // Incremented as fragments are added to the data.
  unsigned int CurrentSize = 0;
};

//------------------------------------------------------------------------------
/*!
 * @brief Fragment info required for reconstructing fragmented IP packets.
 */
struct FragmentInfo
{
  FragmentIdentificationT Identification = 0;
  uint16_t Offset = 0;
  bool MoreFragments = false;

  void Reset()
  {
    this->Identification = 0;
    this->Offset = 0;
    this->MoreFragments = false;
  }
};

namespace IPHeaderFunctions
{ // 辅助函数
  //----------------------------------------------------------------------------
  /*!
   * @brief      Inspect the IP header to get fragment information.
   * @param[in]  data         A pointer to the bytes of the IP header.
   * @param[out] fragmentInfo The collected fragment info.
   * @return     True if the information could be retrieved, false otherwise.
   */
  LVIONETWORK_EXPORT bool getFragmentInfo(unsigned char const * data, FragmentInfo & fragmentInfo);

  //----------------------------------------------------------------------------
  /*!
   * @brief     Determine the IP header length by inspection.
   * @param[in] data A pointer to the first byte of the IP header.
   * @return    The number of bytes in the IP header, or 0 if this could not be
   *            determined.
   */
  LVIONETWORK_EXPORT unsigned int getIPHeaderLength(unsigned char const* data);
  
  bool buildReassembledIPHeader(unsigned char* iphdrdata, const unsigned int ipHeaderLength, const unsigned int payloadSize);
}

//------------------------------------------------------------------------------
class LVIONETWORK_EXPORT vtkPacketFileReader
{
public:
  vtkPacketFileReader() // 构造函数
  {
    this->PCAPFile = 0;  // pcap_t* PCAPFile;
  }

  ~vtkPacketFileReader() // 析构函数
  {
    this->Close(); // 关闭文件
  }
  
  /**
   * @brief Open Pcap capture
   * @param[in] filename pcap file name.
   * @param[in] filter_arg kernel-level packet filter parameter.
   * @param[in] reassemble if disabled, data will be unusable as is.
   *             This is solely used when saving a Pcap to file and original fragmentation is sought.
   * Returns true is successful.
  */
  bool Open(const std::string& filename, std::string filter_arg="udp", bool reassemble = true);
  bool IsOpen() { return (this->PCAPFile != 0); }
  void Close();

  const std::string& GetLastError() { return this->LastError; }
  const std::string& GetFileName() { return this->FileName; }

  void GetFilePosition(fpos_t* position); // 获取当前文件流的位置
  void SetFilePosition(fpos_t* position); // 设置文件流的位置

  /**
   * @brief Read next UDP payload of the capture.
   * @param[in] data A pointer to the first byte of UDP Payload.
   * @param[in] dataLength Size in bytes of the UDP Payload.
   * @param[out] timeSinceStart Network timestamp relative to the capture start time.
   * @param[out] headerReference A pointer to pointer to the Frame header (Optional).
   * @param[out] dataHeaderLength A pointer to the size of the Frame header (Optional).
   * Returns 'data' pointer to payload data of size 'dataLength'
   * Full Frame of size 'headerReference' is available,
   * header data is located at 'data - dataHeaderLength'
  */
  bool NextPacket(const unsigned char*& data, unsigned int& dataLength, double& timeSinceStart,
    pcap_pkthdr** headerReference = NULL, unsigned int* dataHeaderLength = NULL);

protected:
  pcap_t* PCAPFile;
  std::string FileName;
  std::string LastError;
  timeval StartTime;
  unsigned int FrameHeaderLength;
  bool Reassemble;

private:
  //! @brief A map of fragmented packet IDs to the collected array of fragments.
  std::unordered_map Fragments;

  //! @brief The ID of the last completed fragment.
  FragmentIdentificationT AssembledId = 0;

  //! @brief True if there is a reassembled packet to remove.
  bool RemoveAssembled = false;
};

源文件

NextPacket

该函数用于读取pcap文件流数据,并将数据存储在FragmentTracker结构体中。然后将传输的数据指针指向该数据,以便调用者使用。

// 读取文件流数据
// data 指针指向数据(UDP payload)
// dataLength说明数据的长度
// timeSinceStart数据获取时间,网络时间,相对于开始的时间差
bool vtkPacketFileReader::NextPacket(const unsigned char*& data, unsigned int& dataLength, double& timeSinceStart,
  pcap_pkthdr** headerReference, unsigned int* dataHeaderLength )
{
  if (!this->PCAPFile) // 检查是否已经打开文件
  {
    return false;
  }

  //Clear Previous Reassembly Data if needed
  if (this->RemoveAssembled)
  {
    auto it = this->Fragments.find(this->AssembledId);
    this->Fragments.erase(it);
    this->AssembledId = 0;
    this->RemoveAssembled = false;
  }

  dataLength = 0;
  pcap_pkthdr* header;
  FragmentInfo fragmentInfo;
  fragmentInfo.MoreFragments = true;
  
  while (fragmentInfo.MoreFragments)
  {
    unsigned char const * tmpData = nullptr; // 临时数据指针
    // 获取数据
    // this->PCAPFile:当前打开的文件 (传入参数)
    // header:数据报头 (传出参数)
    // temData:数据地址  (传出参数)
    int returnValue = pcap_next_ex(this->PCAPFile, &header, &tmpData); 
    // 返回值:
    // 1: 成功;
    // 0:超时
    // PCAP_ERROR(-1):读取数据时发生错误
    // PCAP_ERROR_BREAK(-2):到达文件末尾,没有更多数据。
    // PCAP_ERROR_NOT_ACTIVATED(-3):读取的文件已经创建但是没有被激活。
    if (returnValue < 0) 
    { // 无法读取文件,关闭文件并退出函数。
      this->Close();
      return false;
    }

    // Collect header values before they are removed.
    // 获取数据信息
    // this->FrameHeaderLength在打开文件时进行赋值。
    // fragmentInfo保存报头信息,传出参数。
    IPHeaderFunctions::getFragmentInfo(tmpData + this->FrameHeaderLength, fragmentInfo);
    timeSinceStart = GetElapsedTime(header->ts, this->StartTime); // 将时间高位和低位组合成完整时间。
    
    //Consts
    // 获取报头长度
    const unsigned int ipHeaderLength = IPHeaderFunctions::getIPHeaderLength(tmpData + this->FrameHeaderLength);
    if (ipHeaderLength == 0)
    { 
      continue;
    } 
    const unsigned int ethHeaderLength = this->FrameHeaderLength;
    const unsigned int udpHeaderLength = 8;
    
    //CaptureLen cropping
    const unsigned int frameLength    = (std::min)(header->len,header->caplen); //windows.h conflict bypass
    const unsigned int ipPayloadLength = frameLength - (ethHeaderLength + ipHeaderLength);
    
    // 获取数据指针
    unsigned char const * ipPayloadPtr = tmpData + ethHeaderLength + ipHeaderLength;
    
    //Provide Header Info if requested
    if (headerReference != NULL && dataHeaderLength != NULL)
    {
      *headerReference = header;
      *dataHeaderLength = ethHeaderLength + ipHeaderLength + udpHeaderLength;
    }
    
    //IP Reassembly
    if ( (fragmentInfo.MoreFragments || fragmentInfo.Offset > 0) && this->Reassemble )
    {
      const unsigned int offset_frag  = fragmentInfo.Offset * FRAGMENT_OFFSET_STEP;
      const unsigned int offset       = ethHeaderLength + ipHeaderLength + offset_frag;
      const unsigned int requiredSize = offset + ipPayloadLength;

      auto & fragmentTracker = this->Fragments[fragmentInfo.Identification];
      auto & reassembledData = fragmentTracker.Data;
      if (requiredSize > reassembledData.size())
      {
        reassembledData.resize(requiredSize);
      }
      // 复制数据,数据保存在this->Fragments中。
      std::copy(ipPayloadPtr, ipPayloadPtr + ipPayloadLength, reassembledData.begin() + offset);

      // Update current collected payload size.
      fragmentTracker.CurrentSize += ipPayloadLength;

      // There may be gaps of size FRAGMENT_OFFSET_STEP between fragments.
      // Add this to the current size, assumes that  *
      // Any given fragment is always larger than FRAGMENT_OFFSET_STEP,
      // so that it cannot be omitted accidentally.
      if (fragmentInfo.MoreFragments) // 在getFragmentInfo函数中进行判断,该信息在数据报头中。
      {
        // WIP I doubt this increment has any use, gap mentioned above is included within offset_frag
        // WIP commenting this line and replacing >= with == has no effects
        fragmentTracker.CurrentSize += FRAGMENT_OFFSET_STEP; 
      }else{
        // Set the expected size if this is the final fragment.
        fragmentTracker.ExpectedSize = offset_frag + ipPayloadLength;
      }

      // Return Reassembled Packet if complete.
      if (fragmentTracker.ExpectedSize > 0 && fragmentTracker.CurrentSize >= fragmentTracker.ExpectedSize)
      {
        //Build Reassembled Packet Header off the last one received
        // 拷贝报头
        std::copy(
          tmpData,
          ipPayloadPtr,
          reassembledData.begin()
        );
        //Frame Header
        header->len    = ethHeaderLength + ipHeaderLength  + fragmentTracker.ExpectedSize ;
        header->caplen = header->len;
        //IP Header
        IPHeaderFunctions::buildReassembledIPHeader(reassembledData.data() + ethHeaderLength, ipHeaderLength, fragmentTracker.ExpectedSize);

        // Delete the associated data on the next iteration.
        this->AssembledId = fragmentInfo.Identification;
        this->RemoveAssembled = true;
        
        //Point to dataPayload
        data       = reassembledData.data() + ethHeaderLength + ipHeaderLength + udpHeaderLength;
        dataLength = fragmentTracker.ExpectedSize - udpHeaderLength;
        return true;
      }
    }
    //Self Standing IP packet or Reassembly is not desired
    else
    {
      //Point to dataPayload
      data       = ipPayloadPtr    + udpHeaderLength;
      dataLength = ipPayloadLength - udpHeaderLength;
      return true;
    }
  }
  return true;
}

Open

该函数用于打开pcap文件,调用了pcap库的打开文件函数。

// 打开pcap格式文件(tcpdump/libcap format)
// 使用winpcap库进行操作。
bool vtkPacketFileReader::Open(const std::string& filename, std::string filter_arg, bool reassemble)
{
  //Open savefile in tcpdump/libcap format
  char errbuff[PCAP_ERRBUF_SIZE];
  // 调用winpcap函数,打开pcap文件
  pcap_t* pcapFile = pcap_open_offline(filename.c_str(), errbuff);
  if (!pcapFile)
  { // 打开文件失败,保存错误信息。退出函数。
    this->LastError = errbuff;
    return false;
  }
  
  //Compute filter for the kernel-level filtering engine
  // 将用户制定的过滤策略编译到过滤程序中 filter_arg是过滤参数。
  bpf_program filter;
  if (pcap_compile(pcapFile, &filter, filter_arg.c_str(), 0, PCAP_NETMASK_UNKNOWN) == -1)
  {
    this->LastError = pcap_geterr(pcapFile);
    return false;
  }
  //Associate filter to pcap
  // 用于设置过滤器,将过滤器添加到该pcap文件中。
  if (pcap_setfilter(pcapFile, &filter) == -1)
  {
    // 设置过滤器失败,保存失败信息,退出函数。
    this->LastError = pcap_geterr(pcapFile);
    return false;
  }

  //Determine Datalink header size
  const unsigned int loopback_header_size = 4;
  const unsigned int ethernet_header_size = 14;
  auto linktype = pcap_datalink(pcapFile);
  switch (linktype)
  {
    case DLT_EN10MB:
      this->FrameHeaderLength = ethernet_header_size;
      break;
    case DLT_NULL:
      this->FrameHeaderLength = loopback_header_size;
      break;
    default:
      this->LastError = "Unknown link type in pcap file. Cannot tell where the payload is.";
      return false;
  }

  this->FileName = filename;
  this->PCAPFile = pcapFile;
  this->StartTime.tv_sec = this->StartTime.tv_usec = 0;
  this->Reassemble = reassemble;
  return true;
}

Close

关闭文件。

// 关闭文件。调用winpcap库中的关闭文件函数pcap_close。
void vtkPacketFileReader::Close()
{
  if (this->PCAPFile)
  {
    pcap_close(this->PCAPFile);
    this->PCAPFile = 0;
    this->FileName.clear();
  }
}

GetFilePosition

获取当前读取的位置,通过pcap_fgetpos 或者 pcap_file + fgetpos库进行获取。

// 获取当前文件流位置,调用winpcap库的pcap_fgetpos函数
// 如果是linux系统,则调用libpcap库中的pcap_file函数获取系统文件,然后调用fgetpos函数获取位置。
void vtkPacketFileReader::GetFilePosition(fpos_t* position)
{
#ifdef _MSC_VER
  pcap_fgetpos(this->PCAPFile, position);
#else
  FILE* f = pcap_file(this->PCAPFile);
  fgetpos(f, position);
#endif
}

SetFilePosition

设置读取文件的位置。通过pcap_fsetpos或者pcap_file + fsetpos函数进行设置。

// 设置当前文件流的位置。调用winpcap库的pcap_fsetpos函数
// 如果是linux系统,则调用libpcap库中的pcap_file获取文件,然后调用fsetpos函数设置位置。
void vtkPacketFileReader::SetFilePosition(fpos_t* position)
{
#ifdef _MSC_VER
  pcap_fsetpos(this->PCAPFile, position);
#else
  FILE* f = pcap_file(this->PCAPFile);
  fsetpos(f, position);
#endif
}

你可能感兴趣的:(LidarView,c++,自动驾驶,信息可视化,qt)