Följ Black Week på SweClockers

c++ - läsa in valfri del av binär fil?

Permalänk
Medlem

c++ - läsa in valfri del av binär fil?

Hej,

Jag håller på att lära mig lite om pcap-filer (https://en.wikipedia.org/wiki/Pcap) och med detta behöver jag skriva ett program som åtminstone kan läsa in hela pcap-filheadern (https://www.ietf.org/archive/id/draft-gharris-opsawg-pcap-01....).

Från vad jag har läst och testat mig till så bör dessa funktioner vara tillämpliga för att läsa in en del av en binär fil till en vector eller array:

  1. https://en.cppreference.com/w/cpp/io/basic_fstream

  2. https://en.cppreference.com/w/cpp/io/basic_istream/read

  3. https://en.cppreference.com/w/cpp/io/basic_istream/seekg

Jag vet att testfilen jag har (tcp_1.pcap) har detta "magic number" (i hex-format via hexdump):

d4 c3 b2 a1

Jag letar efter en funktion som ser ut typ såhär (pseudo-kod nedan):

std::vector<unsigned char> to_byte_vector(std::string file_path, int start_index, int num_of_bytes) { std::vector<unsigned char> result; std::fstream s{file_path, std::ios::in | std::ios::binary}; //Check if file can be opened. if (!s.is_open()) { std::cerr << "Failed to open '" << file_path << "'" << '\n'; } else { /*Loop eller liknande för att läsa in num_of_bytes bytes från och med start_index och placera dem i result. Har ej kommit på den delen. Kan man använda 's.seekg(start_index)'?*/ if (s.read(reinterpret_cast<char*>(&n), sizeof(n))) { std::cout << std::hex << std::showbase << n << '\n'; } } return result; }

Anledningen till att jag inte bara läser in hela filen på en gång är att om jag behöver läsa t.ex. en fil som är mycket större än vad RAM tillåter så kommer min kod sannolikt att krascha. Testfilen må vara enbart ett par hundra bytes men jag vill få det rätt från början.

Kan någon hjälpa mig lite på vägen tack? Har suttit ett bra tag och jobbat ensam.

Permalänk
Medlem

Det är nog mmap() du letar efter om du kör Linux. vad som fungerar i andra os kan jag tyvärr inte svara på

Visa signatur

<3

Permalänk
Medlem

Du har väl i stort sett redan hittat lösningen, men det blir väl någonting sånt här:

// Allokera en vektor med num_of_bytes bytes. std::vector<std::byte> result(num_of_bytes); // Sök till rätt position. s.seekg(start_index); // Läs in num_of_bytes bytes till vektorn. s.read(reinterpret_cast<char*>(&result.front()), num_of_bytes);

Sen vill du kanske sprinkla lite felkontroller på det också, t.ex. kolla att seekg lyckades och att rätt antal bytes kunde läsas in.

Vill man vara lite mer C++-aktig så kan man även läsa in datan med t.ex. std::istreambuf_iterator, men det blir mycket långsammare eftersom datan då läses in byte för byte.

Permalänk
Hedersmedlem

Jag förstår inte varför du ska seek():a, headern har du ju i början av filen, så det är väl bara att läsa 24 byte (eller vad det nu är) från början av filen in till en buffer och sedan läsa behandla den som en struct?

Permalänk
Hedersmedlem

Pseudokoden bör ju bli något enligt följande (med reservation för att jag inte kodat i modern C++, så det här är väldigt C-aktigt):

struct pcap_header { uint32_t magic; uint16_t v_major; uint16_t v_minor; uint32_t reserved1; uint32_t reserved2; uint32_t snap_len; uint8_t fcs_field; // får man plocka isär sedan med bitmatte uint8_t fcs_padding; // för att alignmenten ska bli rätt uint16_t link_type; }; void read_pcap_header(const char *filename) { struct pcap_header h; int fd, ret; fd = open("filnamn", "r"); if (fd < 0) { perror("open"); return; } // här läser vi 24 byte från början av filen bara, ingen seek behövs, rätt in i datastrukturen bara. typsäkerhet, vad är det för något? ret = read(fd, (void*)&h, sizeof h); // filen stängs oavsett om läsningen lyckades eller misslyckades, det här är säkert mkt renare i c++ close(fd); // kolla att vi faktiskt fått en hel header if (ret != sizeof h) { fprintf(stderr, "short read - only read %d bytes from pcap file!"); } // här hanterar du headern enligt önskemål, bl.a. får du läsa magic number för att reda ut om du behöver byte-swappa fälten, eller så gör du det i dina accessormetoder }

Permalänk
Medlem
Skrivet av perost:

Du har väl i stort sett redan hittat lösningen, men det blir väl någonting sånt här:

// Allokera en vektor med num_of_bytes bytes. std::vector<std::byte> result(num_of_bytes); // Sök till rätt position. s.seekg(start_index); // Läs in num_of_bytes bytes till vektorn. s.read(reinterpret_cast<char*>(&result.front()), num_of_bytes);

Sen vill du kanske sprinkla lite felkontroller på det också, t.ex. kolla att seekg lyckades och att rätt antal bytes kunde läsas in.

Vill man vara lite mer C++-aktig så kan man även läsa in datan med t.ex. std::istreambuf_iterator, men det blir mycket långsammare eftersom datan då läses in byte för byte.

Jag implementerade er algoritm:

#include <iostream> #include <string> #include <fstream> #include <cstdint> #include <sstream> #include <vector> #include <cstddef> std::vector<std::byte> to_byte_vector(std::string file_path, int byte_start_index, int num_of_bytes) { std::vector<std::byte> result(num_of_bytes); std::fstream s{file_path, std::ios::in | std::ios::binary}; //Check if file can be opened. if (!s.is_open()) { std::cerr << "Failed to open '" << file_path << "'" << '\n'; } else { s.seekg(byte_start_index); s.read(reinterpret_cast<char*>(&result.front()), num_of_bytes); } return result; } int main() { std::string filename{"pcap_files/tcp_1.pcap"}; auto res = to_byte_vector(filename, 0, 4); std::cout << "Byte vector size/length: " << res.size() << '\n'; return 0; }

Och fick detta resultat:

alexl@PD70PNP:/mnt/Storage_SSD/C++_Projects/pcap_parser$ make g++ -std=c++20 -Wall -o obj/main.o -c src/main.cpp g++ -std=c++20 -Wall -o pcap_parser obj/main.o obj/pcap.o alexl@PD70PNP:/mnt/Storage_SSD/C++_Projects/pcap_parser$ ./pcap_parser Byte vector size/length: 4 alexl@PD70PNP:/mnt/Storage_SSD/C++_Projects/pcap_parser$

EDIT: När jag försöker skriva ut en av byte-värdena så ger den bara noll:

#include <iostream> #include <string> #include <fstream> #include <cstdint> #include <sstream> #include <vector> #include <cstddef> #include <iomanip> #include <bitset> /* // Allokera en vektor med num_of_bytes bytes. std::vector<std::byte> result(num_of_bytes); // Sök till rätt position. s.seekg(byte_start_index); // Läs in num_of_bytes bytes till vektorn. s.read(reinterpret_cast<char*>(&result.front()), num_of_bytes); */ std::vector<std::byte> to_byte_vector(std::string file_path, unsigned int byte_start_index, unsigned int num_of_bytes) { std::vector<std::byte> result(num_of_bytes); std::fstream s{file_path, std::ios::in | std::ios::binary}; //Check if file can be opened. if (!s.is_open()) { std::cerr << "Failed to open '" << file_path << "'" << '\n'; } else { s.seekg(byte_start_index); s.read(reinterpret_cast<char*>(&result.front()), num_of_bytes); } return result; } /* std::string byte_to_hex_str(std::byte &b, std::size_t len) { } */ int main() { std::string filename{"pcap_files/tcp_1.pcap"}; auto res = to_byte_vector(filename, 0, 4); std::cout << "Byte vector size/length: " << res.size() << '\n'; std::cout << std::hex << std::bitset<8>(std::to_integer<int>(res[1])) << '\n'; return 0; }

alexl@PD70PNP:/mnt/Storage_SSD/C++_Projects/pcap_parser$ ./pcap_parser Byte vector size/length: 4 00000000

Jag är inte riktigt med på vad som går fel. Kan det vara så att ingen data hamnar i vektorn?

Permalänk
Medlem
Skrivet av Apollo11:

Jag implementerade er algoritm:
...
Jag är inte riktigt med på vad som går fel. Kan det vara så att ingen data hamnar i vektorn?

Konstigt, jag skapade en fil med pcap-byten och körde programmet på den, då får jag:

Byte vector size/length: 4 11000011

Och tar jag bort bitset-anropet så att std::hex faktiskt gör något så skriver programmet ut c3 istället som förväntat. Så jag vet faktiskt inte vad som går fel för dig, har du kollat att filen du försöker läsa är ok?

Permalänk
Medlem

Det har hänt lite för mycket med C++ sedan jag kodade i det språket förra århundradet, så jag vågar inte kommentera din kod.

Men vad är det du vill göra egentligen?

Jag har svårt att se någon anledning alls att skriva en egen parser till ett filformat där det finns ett färdigt BSD-licensierat lib och dessutom ett gäng wrappers i det språk du kör.

Filformatet ser i och för sig relativt enkelt ut i sin nuvarande version (dom är uppe i 2.4), men hela poängen är väl att tyda den infångade trafiken? Det hade i alla fall jag helst sluppit att göra själv i den mån det redan finns färdiga saker att utnyttja.

Permalänk
Medlem
Skrivet av KAD:

Det har hänt lite för mycket med C++ sedan jag kodade i det språket förra århundradet, så jag vågar inte kommentera din kod.

Men vad är det du vill göra egentligen?

Jag har svårt att se någon anledning alls att skriva en egen parser till ett filformat där det finns ett färdigt BSD-licensierat lib och dessutom ett gäng wrappers i det språk du kör.

Filformatet ser i och för sig relativt enkelt ut i sin nuvarande version (dom är uppe i 2.4), men hela poängen är väl att tyda den infångade trafiken? Det hade i alla fall jag helst sluppit att göra själv i den mån det redan finns färdiga saker att utnyttja.

Jag har fått i uppdrag av min chef på min praktik på ett företag att skriva ett litet program i ett valfritt språk som kan göra vad skrivs i OP. Att det finns färdiga bibliotek vet han och jag. Tanken är jag ska få en bättre förståelse samt nya erfarenheter.

Permalänk
Medlem
Skrivet av perost:

Konstigt, jag skapade en fil med pcap-byten och körde programmet på den, då får jag:

Byte vector size/length: 4 11000011

Och tar jag bort bitset-anropet så att std::hex faktiskt gör något så skriver programmet ut c3 istället som förväntat. Så jag vet faktiskt inte vad som går fel för dig, har du kollat att filen du försöker läsa är ok?

Jag körde detta:

alexl@PD70PNP:/mnt/Storage_SSD/C++_Projects/pcap_parser/pcap_files$ file tcp_1.pcap tcp_1.pcap: empty

Det verkar som om filen har blivit tömd, hämtade den igen och nu fungerar det:

alexl@PD70PNP:/mnt/Storage_SSD/C++_Projects/pcap_parser$ ./pcap_parser Byte vector size/length: 4 c3

Tack så mycket!