Netzwerkpakete packen und entpacken



  • Hallo Leute,

    ich wollte mal fragen, ob ihr eine Template-Bibliothek kennt, bei der man Paket-Informationen deklarieren kann (Also ein Netzwerkpaket hat am Offset X ein Feld mit Bitlänge Y, vielleicht noch signed unsigned und dann Komposit-Typen), die einem das Packen und Entpacken der entsprechenden Nachrichten abnimmt. Ich könnte mir vorstellen, dass man soetwas öfter mal braucht, andererseits scheint es für mich eine aufwändige Umsetzung zu sein (aligned/unaligned, Maschinenwortgröße etc., halt einige Sonderfälle). Vielleicht hat sich ja mal jemand die Mühe gemacht?

    Viele Grüße





  • Sorry

    1. nicht Template-basiert
    2. Ist nur für die Daten - Netzwerken musst du selber machen



  • Danke Dir!
    Ja, Template-Basiert nur darum, weil ich mir eben vorstelle, dass da halt entsprechend nur Reads&Writes entstehen, ein bissl Bit-Shifting und vielleicht Sign-Extension. Das soll auf nem Mikrocontroller möglichst wenig drumherum erzeugen. Ich wäre jetzt zu faul, für das Protokoll alles per Hand zu machen, drum hab' ich schonmal angefangen, soetwas zu implementieren... Das Protokoll hab' ich nicht in der Hand und in der Tat soll der Mechanismus sozusagen eher dabei helfen ein Protokoll umzusetzen, denn eines darzustellen.

    Viele Grüße!



  • Hrmmm,
    da ich an einer besseren Lösung interessiert bin, fühlt euch zu einem Wettbewerb aufgerufen, das einfach noch viel toller umzusetzen und mir mal so richtig zu zeigen, dass ich sowieso nix drauf habe! 🙂
    Die Funktion soll die größten Reads aus einem Puffer machen, die beim Alignment des Puffers möglich sind, allerdings auch die kleinsten, die nötig sind, um alles zu lesen und zur Sicherheit nicht über das Ende des Puffers hinweglesen. Und dann am besten gleich noch die Byte-Ordnung des Puffers beachten (wird wohl hauptsächlich Netzerk sein, also big endian).

    #include <type_traits>
    #include <iostream>
    
    using int8 = signed char;
    using uint8 = unsigned char;
    using int16 = short;
    using uint16 = unsigned short;
    using int32 = int;
    using uint32 = unsigned int;
    using int64 = long long;
    using uint64 = unsigned long long;
    
    namespace detail {
    
    	template< typename T, unsigned int Bits >
    	constexpr T mask() {
    		return ((T(1) << (Bits-1)) - 1)<<1 | 1;
    	}
    
    	template< typename T >
    	constexpr T max(T a, T b)
    	{
    		return (a > b) ? a : b;
    	}
    
    	template< typename T >
    	constexpr T min(T a, T b)
    	{
    		return (a > b) ? b : a;
    	}
    
    	template< typename T >
    	constexpr T abs(T val)
    	{
    		return (val < 0) ? -val : val;
    	}
    
    	template< uint8 SourceByte, uint8 DestByte, typename T >
    	T shift_byte(T val) {
    		if (SourceByte > DestByte) {
    			return (val >> (abs(SourceByte - DestByte) * 8)) & (T(0xFF) << (DestByte * 8));
    		}
    		else {
    			return (val << (abs(DestByte - SourceByte) * 8)) & (T(0xFF) << (DestByte * 8));
    		}
    	}
    
    	template< uint8 ByteA, uint8 ByteB, typename T >
    	T flip_bytes(T val) {
    		return shift_byte<ByteA, ByteB>(val) | shift_byte<ByteB, ByteA>(val);
    	}
    
    	uint8 reverse_bytes(uint8 val) {
    		return val;
    	}
    
    	uint16 reverse_bytes(uint16 val)
    	{
    		return flip_bytes<0, 1>(val);
    	}
    
    	uint32 reverse_bytes(uint32 val) {
    		return flip_bytes<3, 0>(val) | flip_bytes<2, 1>(val);
    	}
    
    	uint64 reverse_bytes(uint64 val) {
    		return flip_bytes<7, 0>(val) | flip_bytes<6, 1>(val) | flip_bytes<5, 2>(val) | flip_bytes<4, 3>(val);
    	}
    
    	template< typename... T > struct list {};
    
    	using access_types = list<uint8, uint16, uint32, uint64>;
    
    	//
    	// Aligned types
    	//
    
    	// Remove all types that would not create aligned reads from a buffer that is aligned with given Value
    	template< unsigned int Alignment, typename AccessList, typename ResultList = list<> >
    	struct aligned_types;
    
    	template< unsigned int Alignment, typename Type0, typename... Types, typename... ResultTypes >
    	struct aligned_types< Alignment, list<Type0, Types...>, list<ResultTypes...>> {
    		using type = std::conditional_t< Alignment % std::alignment_of<Type0>::value == 0,
    			typename aligned_types<Alignment, list<Types...>, list<Type0, ResultTypes...>>::type,
    			typename aligned_types<Alignment, list<Types...>, list<ResultTypes...>>::type>;
    	};
    
    	template< unsigned int Alignment, typename... ResultTypes >
    	struct aligned_types< Alignment, list<>, list<ResultTypes...>> {
    		using type = list<ResultTypes...>;
    	};
    
    	template< unsigned int Alignment, typename TypeList >
    	using aligned_types_t = typename aligned_types<Alignment, TypeList>::type;
    
    	//
    	// aligned_offset: calculate buffer offset for next read
    	//
    
    	template< typename T >
    	constexpr uint32 aligned_offset(uint32 FieldOffsetBits)
    	{
    		return ((FieldOffsetBits / 8) / std::alignment_of<T>::value) * std::alignment_of<T>::value;
    	}
    
    	//
    	// reads_all: returns true if a read reads all field bits or more
    	//
    
    	template< typename T >
    	constexpr bool reads_all( uint32 FieldOffsetBits, uint32 FieldSizeBits ) {
    		return (aligned_offset<T>(FieldOffsetBits) + sizeof(T)) * 8 >= (FieldOffsetBits + FieldSizeBits);
    	}
    
    	//
    	// best_read: find the best access type for the next read. Tries to read all with the smallest read possible or reads with the largest access type.
    	//
    
    	template< uint32 BufferSize, uint32 OffsetBits, uint32 FieldBits, typename ReadTypes, typename Result = uint8 >
    	struct best_read;
    
    	template< uint32 BufferSize, uint32 OffsetBits, uint32 FieldBits, typename ReadType0, typename... ReadTypes, typename Result >
    	struct best_read< BufferSize, OffsetBits, FieldBits, list< ReadType0, ReadTypes... >, Result >
    	{
    		static constexpr bool result_reads_all = reads_all<Result>(OffsetBits, FieldBits);
    		static constexpr bool type0_reads_all  = reads_all<ReadType0>(OffsetBits, FieldBits);
    
    		using type = std::conditional_t<
    			(aligned_offset<ReadType0>(OffsetBits) + sizeof(ReadType0) <= BufferSize)	// must not read past buffer end
    			&& ((result_reads_all && type0_reads_all && sizeof(ReadType0) < sizeof(Result))	// if both read the whole field, prefer the smaller read
    				|| (!result_reads_all && sizeof(ReadType0) > sizeof(Result))),	// if result does not read the whole field, prefer type0 if it is larger
    			typename best_read< BufferSize, OffsetBits, FieldBits, list< ReadTypes... >, ReadType0 >::type,
    			typename best_read< BufferSize, OffsetBits, FieldBits, list< ReadTypes... >, Result >::type
    		>;
    	};
    
    	template< uint32 BufferSize, uint32 OffsetBits, uint32 FieldBits, typename Result >
    	struct best_read< BufferSize, OffsetBits, FieldBits, list<>, Result >
    	{
    		using type = Result;
    	};
    
    	template< uint32 BufferSize, uint32 OffsetBits, uint32 FieldBits, typename ReadTypes >
    	using best_read_t = typename best_read<BufferSize, OffsetBits, FieldBits, ReadTypes >::type;
    
    	struct little_endian {};
    	struct big_endian {};
    
    	using host_byte_order = little_endian;
    
    	// must not read past end of packet
    	template<typename DestType, uint32 BufferAlignment, uint32 BufferSize, uint32 StartBits, uint32 EndBits, typename SourceByteOrder = big_endian>
    	struct packing {
    		static_assert((sizeof(DestType) * 8) >= (EndBits - StartBits), "Destination type too narrow");
    
    		// First prune read type list by buffer alignment
    		using allowed_read_types = aligned_types_t<BufferAlignment, access_types>;
    
    		// Find the smallest read type that reads the whole field or the largest read type that does not read past the end of the buffer
    		using read_type = best_read_t<BufferSize, StartBits, EndBits - StartBits, allowed_read_types >;
    
    		static constexpr uint32 alignment = std::alignment_of<read_type>::value;
    		static constexpr uint32 readbits = sizeof(read_type) * 8;
    		static constexpr uint32 offset = aligned_offset<read_type>(StartBits);
    		static constexpr uint32 readstartbits = offset * 8;
    		static constexpr uint32 readendbits = offset * 8 + readbits;
    
    		static constexpr uint32 remaining_bits = max<int32>(EndBits - readendbits, 0);
    		static constexpr uint32 prebits = max<int32>(StartBits - readstartbits, 0);
    		static constexpr uint32 postbits = max<int32>(readendbits - EndBits, 0);
    		static constexpr uint32 significant_bits = readbits - prebits - postbits;
    
    		static inline DestType read_assemble(const void* ptr) {
    			read_type temp_value = *reinterpret_cast<const read_type*>(static_cast<const char*>(ptr) + offset);
    
    			if (!std::is_same<host_byte_order, SourceByteOrder>::value) {
    				temp_value = reverse_bytes(temp_value);
    			}
    
    			DestType read_value = static_cast<DestType>(temp_value >> postbits);
    
    			if (significant_bits != sizeof(DestType)*8) {
    				// Ich hatte so den Eindruck, dass der Compiler bei einer vollbesetzten Maske nicht hinterherkommt...
    				read_value &= mask<DestType, significant_bits>();
    			}
    
    			if (remaining_bits > 0) {		
    				if (std::is_same<SourceByteOrder, host_byte_order>::value) {
    					read_value = (packing<DestType, BufferAlignment, BufferSize, min(readendbits, EndBits), EndBits, SourceByteOrder >::read_assemble(ptr) << (significant_bits % (sizeof(DestType)*8))) | read_value;
    				} else {
    					read_value = (read_value << remaining_bits) | packing<DestType, BufferAlignment, BufferSize, min(readendbits, EndBits), EndBits, SourceByteOrder >::read_assemble(ptr);
    				}
    			}
    
    			return read_value;
    		}
    
    		static DestType read(const void* ptr) {
    			DestType val = read_assemble(ptr);
    
    			if (std::is_signed<DestType>::value && (val & (DestType(1) << (EndBits-StartBits-1)))) {
    				// sign extend
    				val |= ~mask<DestType, EndBits - StartBits>();
    			}
    
    			return val;
    		}
    
    	};
    
    	template<typename DestType, uint32 BufferAlignment, uint32 BufferSize, uint32 StartBits, typename SourceByteOrder>
    	struct packing<DestType, BufferAlignment, BufferSize, StartBits, StartBits, SourceByteOrder> {
    		static inline DestType read_assemble(const void* ptr) {
    			return 0;
    		}
    	};
    
    }
    
    int main()
    {
    	// gehe von little-endian aus...
    	char arr[8];
    	uint64& val = *reinterpret_cast<uint64*>(arr);
    
    	std::cin >> val;
    	val = detail::reverse_bytes(val);	// -> big endian
    
    	auto value_1 = detail::packing<uint32, 1, sizeof(val), 0, 18>::read(arr+1);
    	auto value_2 = detail::packing<uint32, 2, sizeof(val), 0, 18>::read(arr+1);	// das hier ist wohl unaligned...
    	auto value_4 = detail::packing<uint32, 4, sizeof(val), 0, 18>::read(arr+1); // dito
    	auto value_8 = detail::packing<uint32, 8, sizeof(val), 0, 18>::read(arr+1); // dito
    
    	std::cout << value_1 << "\n" << value_2 << "\n" << value_4 << "\n" << value_8 << "\n";
    }
    


  • Hrmmm, also jetzt verwirre ich mich selbst.
    Mal angenommen, irgendjemand käme auf die Idee, die Pakete so zu packen, dass Typen größer als ein Byte nicht auf Byte-Grenzen gepackt sind, sondern meinetwegen auf Nibble-Grenzen oder irgendetwas krummes. Und dann wieder unter Beachtung der Byte-Order. Nun liest der Empfänger ja in seiner eigenen Byte-Order und dann komme ich auf die Fälle im verlinkten Bild: http://postimg.org/image/ctdgf2r5h/
    Wobei man wohl auch schauen muss, ob die signigifikanten Bits der Bytes auf die Grenzen aufgeteilt werden?
    Auf jeden Fall sieht Little-Endian-Speicherzugriff für mich irgendwie unelegant aus, falls ich mich da jetzt nicht komplett verdacht habe, ich habe nach den 32-Bit Speicherzugriffen mal die Aktionen versucht herauszufinden, die nötig sind, um den Wert wiederherszustellen.
    Kommen solche Fälle in der Praxis vor? Habe ich da irgendeinen Gedankenfehler?



  • Mal angenommen, irgendjemand käme auf die Idee, die Pakete so zu packen, dass Typen größer als ein Byte nicht auf Byte-Grenzen gepackt sind, sondern meinetwegen auf Nibble-Grenzen oder irgendetwas krummes.

    Auf Bussen die wenig Nutzdatenbreite haben und wenig ID's oder ID's noch ne andere Bedeutung haben, ist sowas schon gebräuchlich ....
    Die ganzen Fahrzeug Busse (CAN,LIN,Flexray .... ) z.b.

    Wenn dann noch Stg's mit unterschiedlichen Chips hasst (MSB / LSB) dann hasst genau den Brainfuck 🙂
    Signallänge: 12 bit
    Type: unsingned
    Startbit: 3
    Bitorder: MSB
    ...

    Auf jeden Fall sieht Little-Endian-Speicherzugriff für mich irgendwie unelegant aus

    Den Speicherzugriff kannst dir doch ned aussuchen oder ?
    DU hasst die daten im speicher stehen so wie es der Treiber aus dem "Device" liest. Ist die Interpretation anders als auf deinem PC, musst du quasi shiften ... oder überlässt das funktionen die dafür da sind, wenns sowas gibt. (beim Netzwerk gibts das meiste zum glück schon für little_endian -> Big_endian und zurück).
    Auf ethernet seite hasst das nicht Byte aligned gedöns zum glück nicht, oder recht selten ^^

    Ich hab auch alles per hand geschiftet ...

    Ciao ...


Anmelden zum Antworten