The goal of converting from a pointer to bytes that have been read from the network to a pointer to a structure is to interpret the bytes as that structure type. This relies on compiler features that are not required by the C standard.
The code (wifi_promiscuous_pkt_t *)buff converts the pointer buff to a pointer to the type wifi_promiscuous_pkt_t. This relies on buff being aligned as required for the structure,1 and it requires that the conversion produce a pointer to the same place in memory but with a different type.2
Then const wifi_promiscuous_pkt_t *ppkt = (wifi_promiscuous_pkt_t *)buff; defines a new pointer ppkt and initializes it to point to the same memory buff points to. The goal is to have references such as ppkt->rx_ctrl interpret the data as the data for the wifi_promiscuous_pkt_t structure instead of as the raw uint8_t bytes.
The C standard does not define the behavior here.3 Compilers may support this by defining the behavior themselves. Notably, the compiler needs to support aliasing memory: Accessing memory using a type other than the original type used to define or store data there.
For illustration, let’s say the type of rx_ctrl, wifi_pkt_rx_ctrl_t, is a four-byte int. Then, when these requirements above are met, ppkt->rx_ctrl_t will access the first four bytes at buff and interpret them as if they were an int.
(If those requirements are not met, then a C-standard way to reinterpret the bytes is to make a new object and copy the data into it: wifi_promiscuous_pkt_t temp; memcpy(temp, buff, sizeof temp + extra for the payload length);, after which temp.rx_ctrl will provide the int value. However, depending on circumstances and compiler quality, that can cause a lot of extra memory copying we would like to avoid. Accessing the memory directly is preferable, if it is supported. Another alternative is to read the data directly from the network into the target object instead of into a buffer of bytes.)
After this, const wifi_ieee80211_packet_t *ipkt = (wifi_ieee80211_packet_t *)ppkt->payload; does the same thing with ppkt->payload instead of buff. Once ppkt has been set up, ppkt->payload points to the bytes starting after the rx_ctrl member.4 This pointer is then converted to point to the type wifi_ieee80211_packet_t, and ipkt is initialized with the new pointer.
As before, the intent is that ipkt->hdr and ipkt->payload will refer to the data in the first structure’s payload area as if that data contained a wifi_ieee80211_packet_t structure. However, there is another problem here. As noted above, buff may pointed to well-aligned memory if it was allocated with malloc. But ipkt does not point to the same place as buff. It points to ppkt->payload, which is some number of bytes after the start because it follows the rx_ctrl member. So we do not know, from this code alone, that ipkt is properly aligned for a wifi_ieee80211_packet_t. Possibly it is, if the size of the rx_ctrl member is a multiple of the alignment requirement for the hdr member.
Without comments in the code speaking to this, this requirement may have been neglected. The code might work because rx_ctrl is an okay size for this to work, but it might generate an alignment trap. If not now, then possibly in the future when the types involved change.
Footnotes
1 If buff points to the start of memory allocated with malloc or another standard memory allocation routine, it will be correctly aligned for any fundamental type. Or, if it was defined as an array, correct alignment can be requested with the standard _Alignas keyword or possibly with a compiler extension. If it was merely defined as an ordinary array of uint8_t, is not guaranteed to be aligned as required for this use.
2 For general pointer-to-object conversions, the standard only requires that a pointer converted to another pointer-to-object type and then back to its original type points to the original object. It does not guarantee the pointer is otherwise usable in the other type. But this is common in C implementations.
3 In general, accessing an array of uint8_t using other types violates the rule in C 2018 6.5 7, which says that memory which has been defined with a certain type, or, for dynamic memory, assigned with a certain type, shall be accessed only by certain “matching” types or by a “character” (byte) type.
4 Formally, ppkt->payload designates an array, and that array is automatically converted to a pointer. Also, the array is declared to have zero elements. That is not defined by the C standard, but GCC and other compilers supported it as a way to allow variable length data at the end of a structure. Since C 1999, the standard way to do this is to declare the member with [] instead of [0].