[THUSC 2019工程] A. PCAP 文件解析与读写
既然是工程题,那就尽量让程序良构起来吧!
我们按照手册所说定义两个结构体:
struct pcap_hdr {
// 所有字段都是大端序
uint32_t magic_number;// 用于文件类型识别,始终为 0xA1B2C3D4,
uint16_t version_major;// 始终为 2
uint16_t version_minor;// 始终为 4
int32_t thiszone;// 始终为 0
uint32_t sigfigs;// 始终为 0
uint32_t snaplen;// 允许的最大包长度,始终为 262144
uint32_t network;// 数据类型,本次学习题中始终为 1 (以太网)
} __attribute__ ((packed));
struct pcaprec_hdr{
// 所有字段都是大端序
uint32_t ts_sec;// 时间戳(秒)
uint32_t ts_usec;// 时间戳(微秒)
uint32_t incl_len;// 该片段的存储长度
uint32_t orig_len;// 该片段实际的长度
} __attribute__ ((packed));
其中 __attribute__ ((packed))
是为了避免因内存对齐导致中间出现神秘空位,输入失败。
我们再将对结构体的输入输出方法定义好:
friend std::istream& pcap_hdr::operator>>(std::istream& in,pcap_hdr& data){
in.read((char*)&data,sizeof(pcap_hdr));
data.magic_number=ntohl(data.magic_number),
data.version_major=ntohs(data.version_major),
data.version_minor=ntohs(data.version_minor),
data.thiszone=ntohl(data.thiszone),
data.sigfigs=ntohl(data.sigfigs),
data.snaplen=ntohl(data.snaplen),
data.network=ntohl(data.network);
assert(data.magic_number==0xA1B2C3D4&& // 输入错误导致 RE,方便调试
data.version_major==2&&
data.version_minor==4&&
data.thiszone==0&&
data.sigfigs==0&&
data.snaplen==262144&&
data.network==1
);
return in;
}
friend std::ostream& operator<<(std::ostream& out,pcap_hdr data){
data.magic_number=htonl(data.magic_number),
data.version_major=htons(data.version_major),
data.version_minor=htons(data.version_minor),
data.thiszone=htonl(data.thiszone),
data.sigfigs=htonl(data.sigfigs),
data.snaplen=htonl(data.snaplen),
data.network=htonl(data.network);
out.write((char*)&data,sizeof(pcap_hdr));
return out;
}
friend std::istream& pcaprec_hdr::operator>>(std::istream& in,pcaprec_hdr& data){
in.read((char*)&data,sizeof(pcaprec_hdr));
data.ts_sec=ntohl(data.ts_sec),
data.ts_usec=ntohl(data.ts_usec),
data.incl_len=ntohl(data.incl_len),
data.orig_len=ntohl(data.orig_len);
assert(data.ts_usec<1e6&&data.incl_len==data.orig_len);
return in;
}
friend std::ostream& operator<<(std::ostream& out,pcaprec_hdr data){
data.incl_len=htonl(data.incl_len),
data.orig_len=htonl(data.orig_len),
data.ts_sec=htonl(data.ts_sec),
data.ts_usec=htonl(data.ts_usec);
out.write((char*)&data,sizeof(pcaprec_hdr));
return out;
}
这里,我使用 std::basic_istream::read
直接将流内的二进制数据写入结构体中,std::basic_ostream::write
将结构体的内容写入流。两个函数都接受一个 const char*
和一个整数,分别表示要写/读的内存地址和要操作的字节数。将指向结构体的类型强制转换,与结构体大小一起传入即可。
ntohl
ntohs
htonl
htons
定义在 netinet/in.h
。ntohl
ntohs
分别将 32 位/16 位大端序整数转换为本机使用的端序,htonl
htons
分别将 32 位/16 位本机使用的端序转换为大端序。在输入输出操作前,需要使用这些函数处理端序问题。
为了好玩 我还定义了两个辅助结构体,分别将报文与文件进行封装。
struct pcaprec{
pcaprec_hdr header;
std::vector<uint8_t> data;
pcaprec():header(),data(){}
friend std::istream& operator>>(std::istream& in,pcaprec& data){
in>>data.header;
data.data.resize(data.header.orig_len);
in.read(data.data.data(),data.header.orig_len);
return in;
}
friend std::ostream& operator<<(std::ostream& out,pcaprec const& data){
out<<data.header;
out.write((char*)data.data.data(),data.header.incl_len);
return out;
}
};
struct pcap{
pcap_hdr header;
std::vector<pcaprec> data;
pcap():header(),data(){}
friend std::istream& operator>>(std::istream& in,pcap& data){
in>>data.header;
while (!in.eof()) data.data.push_back([](std::istream& in){
pcaprec p;in>>p;return p;
}(in));
data.data.pop_back();
return in;
}
friend std::ostream& operator<<(std::ostream& out,pcap const& data){
out<<data.header;
for (pcaprec const& i:data.data) out<<i;
return out;
}
};
struct pcap{
pcap_hdr header;
std::vector<pcaprec> data;
pcap():header(),data(){}
friend std::istream& operator>>(std::istream& in,pcap& data){
in>>data.header;
while (!in.eof()) data.data.push_back([](std::istream& in){
pcaprec p;in>>p;return p;
}(in));
data.data.pop_back(); // cin 读取到 eof 后总还要多读一位,只能 pop 掉了
return in;
}
friend std::ostream& operator<<(std::ostream& out,pcap const& data){
out<<data.header;
for (pcaprec const& i:data.data) out<<i;
return out;
}
};
做完了这些准备工作,main 函数的内容就相当简单了。
附上我的代码:
#ifndef ONLINE_JUDGE
std::ifstream fin("./data/P10344.in",std::ifstream::binary);
std::ofstream fout("./data/P10344.out",std::ofstream::binary);
#else
std::istream& fin=std::cin;
std::ostream& fout=std::cout;
#endif
struct pcap_hdr {
// 所有字段都是大端序
uint32_t magic_number;// 用于文件类型识别,始终为 0xA1B2C3D4,
uint16_t version_major;// 始终为 2
uint16_t version_minor;// 始终为 4
int32_t thiszone;// 始终为 0
uint32_t sigfigs;// 始终为 0
uint32_t snaplen;// 允许的最大包长度,始终为 262144
uint32_t network;// 数据类型,本次学习题中始终为 1 (以太网)
friend std::istream& operator>>(std::istream& in,pcap_hdr& data){
in.read((char*)&data,sizeof(pcap_hdr));
data.magic_number=ntohl(data.magic_number),
data.version_major=ntohs(data.version_major),
data.version_minor=ntohs(data.version_minor),
data.thiszone=ntohl(data.thiszone),
data.sigfigs=ntohl(data.sigfigs),
data.snaplen=ntohl(data.snaplen),
data.network=ntohl(data.network);
assert(data.magic_number==0xA1B2C3D4&&
data.version_major==2&&
data.version_minor==4&&
data.thiszone==0&&
data.sigfigs==0&&
data.snaplen==262144&&
data.network==1
);
return in;
}
friend std::ostream& operator<<(std::ostream& out,pcap_hdr data){
data.magic_number=htonl(data.magic_number),
data.version_major=htons(data.version_major),
data.version_minor=htons(data.version_minor),
data.thiszone=htonl(data.thiszone),
data.sigfigs=htonl(data.sigfigs),
data.snaplen=htonl(data.snaplen),
data.network=htonl(data.network);
out.write((char*)&data,sizeof(pcap_hdr));
return out;
}
} __attribute__ ((packed));
struct pcaprec_hdr{
// 所有字段都是大端序
uint32_t ts_sec;// 时间戳(秒)
uint32_t ts_usec;// 时间戳(微秒)
uint32_t incl_len;// 该片段的存储长度
uint32_t orig_len;// 该片段实际的长度
friend std::istream& operator>>(std::istream& in,pcaprec_hdr& data){
in.read((char*)&data,sizeof(pcaprec_hdr));
data.ts_sec=ntohl(data.ts_sec),
data.ts_usec=ntohl(data.ts_usec),
data.incl_len=ntohl(data.incl_len),
data.orig_len=ntohl(data.orig_len);
assert(data.ts_usec<1e6&&data.incl_len==data.orig_len);
return in;
}
friend std::ostream& operator<<(std::ostream& out,pcaprec_hdr data){
data.incl_len=htonl(data.incl_len),
data.orig_len=htonl(data.orig_len),
data.ts_sec=htonl(data.ts_sec),
data.ts_usec=htonl(data.ts_usec);
out.write((char*)&data,sizeof(pcaprec_hdr));
return out;
}
} __attribute__ ((packed));
struct pcaprec{
pcaprec_hdr header;
std::vector<uint8_t> data;
pcaprec():header(),data(){}
friend std::istream& operator>>(std::istream& in,pcaprec& data){
in>>data.header;
data.data.resize(data.header.orig_len);
in.read((char*)data.data.data(),data.header.orig_len);
return in;
}
friend std::ostream& operator<<(std::ostream& out,pcaprec const& data){
out<<data.header;
out.write((char*)data.data.data(),data.header.incl_len);
return out;
}
};
struct pcap{
pcap_hdr header;
std::vector<pcaprec> data;
pcap():header(),data(){}
friend std::istream& operator>>(std::istream& in,pcap& data){
in>>data.header;
while (!in.eof()) data.data.push_back([](std::istream& in){
pcaprec p;in>>p;return p;
}(in));
data.data.pop_back();
return in;
}
friend std::ostream& operator<<(std::ostream& out,pcap const& data){
out<<data.header;
for (pcaprec const& i:data.data) out<<i;
return out;
}
};
int main(void){
std::ios::sync_with_stdio(false),std::cin.tie(nullptr),std::cout.tie(nullptr);
pcap f;fin>>f;
pcap nf;
nf.header=f.header;
for (auto i:f.data)
if (i.header.orig_len<=1000) nf.data.push_back(i);
sort(nf.data.begin(),nf.data.end(),
[](pcaprec const& a,pcaprec const& b){
return (a.header.ts_sec)*(uint64_t)1e6+a.header.ts_usec
<(b.header.ts_sec)*(uint64_t)1e6+b.header.ts_usec;
}
);
fout<<nf;
return 0;
}
由于 某些原因,我希望大家可以保持学术诚信,自己完成这道题目。作为回报,你的码力将会有所提升。