[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.hntohl 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;
}

由于 某些原因,我希望大家可以保持学术诚信,自己完成这道题目。作为回报,你的码力将会有所提升。

posted @ 2024-04-10 12:03  MrPython  阅读(13)  评论(0)    收藏  举报  来源