diff options
| author | Sebastian Vater | 2025-07-24 14:29:01 +0200 |
|---|---|---|
| committer | Sebastian Vater | 2025-07-24 14:29:01 +0200 |
| commit | 2cb9de551583b0fb95e70d9fabd388e1e714ed65 (patch) | |
| tree | 52ad41b093ebe16bbc4fa59f21489616c0533fe1 | |
| parent | Adapted code conventions to expected style. Also added CRC32C calculation. Fi... (diff) | |
| download | dnbd3-2cb9de551583b0fb95e70d9fabd388e1e714ed65.tar.gz dnbd3-2cb9de551583b0fb95e70d9fabd388e1e714ed65.tar.xz dnbd3-2cb9de551583b0fb95e70d9fabd388e1e714ed65.zip | |
Fixed some typos and changed header digest CRC32C functions to store the result directly in the iSCSI packet data. Also added CRC32C calculation for data digest with big endian storage functions. Finally, added a ultra performant hash map implementation for the text key=value handling of iSCSI packets.
| -rw-r--r-- | src/server/iscsi.c | 662 | ||||
| -rw-r--r-- | src/server/iscsi.h | 120 |
2 files changed, 757 insertions, 25 deletions
diff --git a/src/server/iscsi.c b/src/server/iscsi.c index 14dadc6..925e4e8 100644 --- a/src/server/iscsi.c +++ b/src/server/iscsi.c @@ -39,10 +39,567 @@ */ /** + * Creates a ultra hardcore speed optimized empty hash map and allocates + * enough buckets to hold default capacity elements. + * The speed optimizations require all keys having a size of + * a multiple of 8 bytes with zero padding. + * TODO: Move all hash map related functions to different source file + * later and implement in a lock-free way for better concurrency. + * + * @return A pointer to the hash map structure or NULL in case of an error. + */ +iscsi_hashmap *iscsi_hashmap_create() +{ + iscsi_hashmap *map = (iscsi_hashmap *) malloc( sizeof(iscsi_hashmap) ); + + if ( map == NULL ) { + logadd( LOG_ERROR, "iscsi_hashmap_create: Out of memory while allocating iSCSI hash map" ); + + return map; + } + + map->capacity = (1UL << ISCSI_HASHMAP_DEFAULT_CAPACITY); + + map->buckets = (iscsi_hashmap_bucket *) calloc( map->capacity, sizeof(struct iscsi_hashmap_bucket) ); + + if ( map->buckets == NULL ) { + free( map ); + + logadd( LOG_ERROR, "iscsi_hashmap_create: Out of memory while allocating iSCSI hash map buckets" ); + + return NULL; + } + + map->count = 0; + map->removed_count = 0; + map->first = NULL; + map->last = (iscsi_hashmap_bucket *) &map->first; + + return map; +} + +/** + * Deallocates all buckets and the hash map itself allocated + * by iscsi_hashmap_create. The elements associated with the + * buckets are NOT freed by this function, this has to be done + * either manually or using the function iscsi_hashmap_iterate. + * + * @param[in] map Pointer to hash map and its buckets to deallocate. + * If this is NULL, nothing is done. + */ +void iscsi_hashmap_destroy(iscsi_hashmap *map) +{ + if ( map != NULL ) { + if ( map->buckets != NULL ) + free( map->buckets ); + + free( map ); + } +} + +/** + * Puts an old bucket into a resized hash map. + * + * @param[in] map Pointer to resized hash map, may NOT be NULL, so + * be careful. + * @param[in] old_entry The old bucket to be put into the resized + * hash map. + * @return New bucket where the bucket has been put into. + */ +static iscsi_hashmap_bucket *iscsi_hashmap_resize_entry(iscsi_hashmap *map, const iscsi_hashmap_bucket *old_entry) +{ + uint32_t index = old_entry->hash & (map->capacity - 1); + + for ( ;; ) { + iscsi_hashmap_bucket *entry = &map->buckets[index]; + + if ( entry->key == NULL ) { + *entry = *old_entry; + + return entry; + } + + index = (index + 1) & (map->capacity - 1); + } +} + +/** + * Resizes a hash map by doubling its bucket capacity. if any + * buckets have been removed, they are finally purged. The + * old bucket list is freed after the resize operation has + * been finished. + * + * @param[in] map Pointer to hash map to resize. This may NOT be + * NULL, so be careful. + * @retval -1 An error occured during resize. + * @retval 0 Hash map has been resized successfully. + */ +static int iscsi_hashmap_resize(iscsi_hashmap *map) +{ + const uint old_capacity = map->capacity; + iscsi_hashmap_bucket *old_buckets = map->buckets; + + map->capacity <<= ISCSI_HASHMAP_RESIZE_FACTOR; + + map->buckets = (iscsi_hashmap_bucket *) calloc( map->capacity, sizeof(struct iscsi_hashmap_bucket) ); + + if ( map->buckets == NULL ) { + map->capacity = old_capacity; + map->buckets = old_buckets; + + return -1; + } + + map->last = (iscsi_hashmap_bucket *) &map->first; + map->count -= map->removed_count; + map->removed_count = 0; + + do { + iscsi_hashmap_bucket *current = map->last->next; + + if ( current->key == NULL ) { + map->last->next = current->next; + + continue; + } + + map->last->next = iscsi_hashmap_resize_entry(map, map->last->next); + map->last = map->last->next; + } while ( map->last->next != NULL ); + + free( old_buckets ); + + return 0; +} + +/** + * Calculates the hash code of data with a specified length. + * + * @param[in] data Pointer to data to be hashed, NULL is NOT + * an allowed here, so be careful. Data needs 8 byte alignment + * and needs to be zero padded. + * @param[in] len Number of bytes of hash data, must be larger + * than 0 and is rounded up to the nearest 8 byte integer prior + * calculating the hash code, so be careful. + * @return Hash code of data. + */ +static inline uint32_t iscsi_hashmap_hash_data(const uint8_t *data, const size_t len) +{ + size_t num_blocks = iscsi_align(len, ISCSI_HASHMAP_KEY_ALIGN) >> ISCSI_HASHMAP_KEY_ALIGN_SHIFT; + uint64_t hash = ISCSI_HASHMAP_HASH_INITIAL; + + do { + hash ^= *(uint64_t *) data++; // Hash 8 bytes of data at once + hash *= ISCSI_HASHMAP_HASH_XOR; + } while ( --num_blocks > 0UL ); + + return (uint32_t) (hash ^ hash >> 32ULL); +} + +/** + * Finds a bucket by key of a specified hash map by + * key, key size and hash code. This function may + * only be called if the bucket is guaranteed to + * be found, otherwise this function hangs, so be + * careful. + * + * @param[in] map Pointer to hash map where the key to be + * searched for is located, may NOT be NULL, so be careful. + * @param[in] key Pointer to key. NULL is invalid, so be + * careful. + * @param[in] key_size Number of bytes for the key. + * @param[in] hash Hash of the key to be searched for. + * @return Pointer to found bucket. + */ +static iscsi_hashmap_bucket *iscsi_hashmap_find_entry(iscsi_hashmap *map, const uint8_t *key, size_t key_size, uint32_t hash) +{ + uint32_t index = hash & (map->capacity - 1); + + for ( ;; ) { + iscsi_hashmap_bucket *entry = &map->buckets[index]; + + if ( (entry->key == NULL && entry->value == NULL) || (entry->key != NULL && entry->key_size == key_size && entry->hash == hash && (memcmp( entry->key, key, key_size ) == 0)) ) + return entry; + + index = (index + 1) & (map->capacity - 1); + } +} + +/** + * Creates a key from data and size and ensures + * its requirements for usage in hash map buckets. + * Currently keys to be used in a hash map bucket + * require a size of multiple by 8 bytes with + * the zero padding. + * + * @param[in] data Pointer to data to construct the key + * from and may NOT be NULL, so be careful. + * @param[in] len Length of the data to construct the key + * from, MUST be larger than 0, so be careful. + * @return Pointer to generated usable key or NULL in + * case of an error (usually memory exhaustion). + */ +uint8_t *iscsi_hashmap_create_key(const uint8_t *data, const size_t len) +{ + const size_t key_size = iscsi_align(len, ISCSI_HASHMAP_KEY_ALIGN); + uint8_t *key = (uint8_t *) malloc( key_size ); + + if ( key == NULL ) { + logadd( LOG_ERROR, "iscsi_hashmap_create_key: Out of memory while allocating iSCSI hash map key" ); + + return key; + } + + memcpy( key, data, len ); + memset( key + len, 0, (key_size - len) ); // Zero pad additional bytes in case length is not a multiple of 8 + + return key; +} + +/** + * Deallocates a key allocated with the function + * iscsi_hashmap_create_key. + * + * @param[in] key Pointer to key to deallocate, may NOT + * be NULL, so be careful. + */ +void iscsi_hashmap_destroy_key(uint8_t *key) { + free( key ); +} + +/** + * Adds a key / value pair to a specified hash map + * bucket list, if it doesn't exist already. The + * buckets are resized automatically if required. + * This function neither does make a copy of the key, + * nor of the value. Keys should be allocated using + * the function iscsi_hashmap_create_key or freed by + * using iscsi_hashmap_destroy_key in order to + * ensure the alignment and padding requirements. + * + * @param[in] map Pointer to hash map where the key and + * value pair should be added to, may NOT be NULL, so + * be careful. + * @param[in] key Pointer to zero padded key. NULL is + * an invalid pointer here, so be careful. + * @param[in] key_size Number of bytes for the key, MUST + * be a multiple of 8 bytes which is NOT checked, so + * be careful. + * @param[in] value Value of the key to add, NULL is + * allowed. + * @retval -1 Adding key / value pair would have required + * hash map resizing which failed (probably due to + * memory exhaustion). + * @retval 0 Key / value pair was added successfully. + */ +int iscsi_hashmap_put(iscsi_hashmap *map, const uint8_t *key, const size_t key_size, uint8_t *value) +{ + if ( ((map->count + 1) > map->capacity) && (iscsi_hashmap_resize( map ) < 0) ) + return -1; + + const uint32_t hash = iscsi_hashmap_hash_data( key, key_size ); + iscsi_hashmap_bucket *entry = iscsi_hashmap_find_entry( map, key, key_size, hash ); + + if ( entry->key == NULL ) { + map->last->next = entry; + map->last = entry; + entry->next = NULL; + + map->count++; + + entry->key = key; + entry->key_size = key_size; + entry->hash = hash; + } + + entry->value = value; + + return 0; +} + +/** + * Adds a key / value pair if it doesn't exist + * using the value of `*out_in_val`. If the pair + * does exist, value will be set in `*out_in`, + * meaning the value of the pair will be in + * '*out_in` regardless of whether or not it it + * existed in the first place. + * The buckets are resized automatically if required. + * This function neither does make a copy of the key, + * nor of the value. Keys should be allocated using + * the function iscsi_hashmap_create_key or freed by + * using iscsi_hashmap_destroy_key in order to + * ensure the alignment and padding requirements. + * + * @param[in] map Pointer to hash map where the key and + * value pair should be added to, may NOT be NULL, so + * be careful. + * @param[in] key Pointer to zero padded key. NULL is + * an invalid pointer here, so be careful. + * @param[in] key_size Number of bytes for the key, MUST + * be a multiple of 8 bytes which is NOT checked, so + * be careful. + * @param[in,out] out_in_value Value of the key to add, + * NULL is allowed. + * @retval -1 Adding key / value pair would have required + * hash map resizing which failed (probably due to + * memory exhaustion). + * @retval 0 Key / value pair was added successfully. + * @retval 1 Key already existed. + */ +int iscsi_hashmap_get_put(iscsi_hashmap *map, const uint8_t *key, const size_t key_size, uint8_t **out_in_value) +{ + if ( ((map->count + 1) > map->capacity) && (iscsi_hashmap_resize( map ) < 0) ) + return -1; + + const uint32_t hash = iscsi_hashmap_hash_data( key, key_size ); + iscsi_hashmap_bucket *entry = iscsi_hashmap_find_entry( map, key, key_size, hash ); + + if ( entry->key == NULL ) { + map->last->next = entry; + map->last = entry; + entry->next = NULL; + + map->count++; + + entry->value = *out_in_value; + entry->key = key; + entry->key_size = key_size; + entry->hash = hash; + + return 0; + } + + *out_in_value = entry->value; + + return 1; +} + +/** + * Adds a key / value pair to a specified hash map + * bucket list. If the key already exists, it will + * be overwritten and a callback function will be + * invoked in order to allow, e.g. deallocation of + * resources, if necessary. The buckets are resized + * automatically if required. This function neither + * does make a copy of the key, nor of the value. + * Keys should be allocated using the function + * iscsi_hashmap_create_key or freed by using + * iscsi_hashmap_destroy_key in order to ensure the + * alignment and padding requirements. + * + * @param[in] map Pointer to hash map where the key and + * value pair should be added to, may NOT be NULL, so + * be careful. + * @param[in] key Pointer to zero padded key. NULL is + * an invalid pointer here, so be careful. + * @param[in] key_size Number of bytes for the key, MUST + * be a multiple of 8 bytes which is NOT checked, so + * be careful. + * @param[in] value Value of the key to add, NULL is + * allowed. + * @param[in] callback Callback function which allows, + * for example, a dallocation of resources for the + * overwritten key and value pair. The function is + * invoked just before overwriting the old values. + * This may NOT be NULL, so take caution. + * @param[in] user_data Pointer to user specific data + * passed to the callback function in case more + * information is needed. + * @return -1 in case adding key / value pair would + * have required hash map resizing which failed + * (probably due to memory exhaustion), 0 if the + * Key / value pair was added successfully and + * the callback function also returned 0, otherwise + * the return value got by the callbuck function. + */ +int iscsi_hashmap_put_free(iscsi_hashmap *map, const uint8_t *key, const size_t key_size, uint8_t *value, iscsi_hashmap_callback callback, uint8_t *user_data) +{ + if ( ((map->count + 1) > map->capacity) && (iscsi_hashmap_resize( map ) < 0) ) + return -1; + + const uint32_t hash = iscsi_hashmap_hash_data( key, key_size ); + iscsi_hashmap_bucket *entry = iscsi_hashmap_find_entry( map, key, key_size, hash ); + + if ( entry->key == NULL ) { + map->last->next = entry; + map->last = entry; + entry->next = NULL; + + map->count++; + + entry->key = key; + entry->key_size = key_size; + entry->hash = hash; + entry->value = value; + + return 0; + } + + int err = callback( entry->key, key_size, entry->value, user_data ); + + entry->key = key; + entry->value = value; + + return err; +} + +/** + * Retrieves the value of a specified key from a hash map. Since the + * hash map supports NULL values, it is stored in an output variable. + * + * @param[in] map Pointer to the hash map to be searched + * for the key of which the value should be retrieved and + * may not be NULL, so take caution. + * @param[in] key Pointer to zero padded key. NULL is + * an invalid pointer here, so be careful. + * @param[in] key_size Number of bytes for the key, MUST + * be a multiple of 8 bytes which is NOT checked, so + * be careful. + * @param[out] out_value Pointer where the value of the found key + * is stored, maybe NULL if either the key's value is NULL or + * in case the key was not found. The pointer to the value itself + * may NOT be NULL, so be careful. + * @retval TRUE The key has been found and its value stored + * in the 'out_value' parameter. + * @retval FALSE The key has not been found and NULL has been + * stored in the 'out_value' parameter. + */ +int iscsi_hashmap_get(iscsi_hashmap *map, const uint8_t *key, const size_t key_size, uint8_t **out_value) +{ + const uint32_t hash = iscsi_hashmap_hash_data( key, key_size ); + iscsi_hashmap_bucket *entry = iscsi_hashmap_find_entry( map, key, key_size, hash ); + + *out_value = entry->value; + + return entry->key != NULL; +} + +/** + * Removes an element from the bucket list of the hash map. + * Buckets are marked as removed by setting their key and + * value to NULL. The actual removal will be done upon next + * resize operation. If the specified key already has been + * removed, this function will do nothing. + * + * @param[in] map Pointer to the hash map to remove from + * and may not be NULL, so take caution. + * @param[in] key Pointer to zero padded key. NULL is + * an invalid pointer here, so be careful. + * @param[in] key_size Number of bytes for the key, MUST + * be a multiple of 8 bytes which is NOT checked, so + * be careful. + */ +void iscsi_hashmap_remove(iscsi_hashmap *map, const uint8_t *key, const size_t key_size) +{ + const uint32_t hash = iscsi_hashmap_hash_data( key, key_size ); + iscsi_hashmap_bucket *entry = iscsi_hashmap_find_entry( map, key, key_size, hash ); + + if ( entry->key != NULL ) { + entry->key = NULL; + entry->value = NULL; + + map->removed_count++; + } +} + +/** + * Removes an element from the bucket list of the hash map. + * Buckets are marked as removed by setting their key and + * value to NULL. The actual removal will be done upon next + * resize operation. A callback function is invoked if the + * key to be removed is found in the bucket list and allows, + * e.g. to free any resources associated with the key. If + * the key is not found, this function will do nothing. + * + * @param[in] map Pointer to the hash map to remove from + * and may not be NULL, so take caution. + * @param[in] key Pointer to zero padded key. NULL is + * an invalid pointer here, so be careful. + * @param[in] key_size Number of bytes for the key, MUST + * be a multiple of 8 bytes which is NOT checked, so + * be careful. + * @param[in] callback Callback function which allows, + * for example, a dallocation of resources for the + * key and value pair to be removed. The function is + * invoked just before marking the key / value pair + * as removed. This may NOT be NULL, so take caution. + * @param[in] user_data Pointer to user specific data + * passed to the callback function in case more + * information is needed. + */ +void iscsi_hashmap_remove_free(iscsi_hashmap *map, const uint8_t *key, const size_t key_size, iscsi_hashmap_callback callback, uint8_t *user_data) +{ + const uint32_t hash = iscsi_hashmap_hash_data( key, key_size ); + iscsi_hashmap_bucket *entry = iscsi_hashmap_find_entry( map, key, key_size, hash ); + + if ( entry->key != NULL ) { + callback( entry->key, entry->key_size, entry->value, user_data ); + + entry->key = NULL; + entry->value = NULL; + + map->removed_count++; + } +} + +/** + * Returns the number of elements stored in the specified + * hash map. Elements marked for removal are not included. + * + * @param[in] map Pointer to the hash map to count the + * number of elements, may NOT be NULL, so take caution. + * @return Number of elements currently in use by the + * hash map. Buckets marked for removal are not counted. + */ +int iscsi_hashmap_size(iscsi_hashmap *map) +{ + return (map->count - map->removed_count); +} + +/** + * An iterator through the elements of a specified + * hash map which uses a callback function for each + * element not marked for removal, which also can + * abort the iteration, if necessary. + * + * @param[in] map Pointer to the hash map to iterate + * through, may NOT be NULL, so take caution. + * @param[in] callback Callback function to be + * invoked for each element not marked for removal + * in the hash map. If the return value of the callback + * function is below zero, the iteration will stop. + * @param[in] user_data Pointer to user specific data + * passed to the callback function in case more + * information is needed. + * @return The return code from the last invoked + * callback function. A negative value indicates an + * abortion of the iteration process. + */ +int iscsi_hashmap_iterate(iscsi_hashmap *map, iscsi_hashmap_callback callback, uint8_t *user_data) +{ + iscsi_hashmap_bucket *current = map->first; + int err = 0; + + while ( current != NULL ) { + if ( current->key != NULL ) { + err = callback( current->key, current->key_size, current->value, user_data ); + + if ( err < 0 ) + break; + } + + current = current->next; + } + + return err; +} + +/** * Allocates an iSCSI packet data Basic Header Segment (BHS) * and zero fills the structure. * - * @return Returns a pointer to BHS structure with all fields + * @return a pointer to BHS structure with all fields * initialized or NULL if the allocation failed. */ iscsi_bhs_packet *iscsi_create_packet() @@ -106,7 +663,7 @@ iscsi_bhs_packet *iscsi_append_ahs_packet(iscsi_bhs_packet *packet_data, const u } const uint32_t old_pkt_size = (const uint32_t) sizeof(struct iscsi_bhs_packet) + (packet_data->total_ahs_len << 2UL); - const uint32_t new_pkt_size = old_pkt_size + iscsi_align(ahs_len, 4); + const uint32_t new_pkt_size = old_pkt_size + iscsi_align(ahs_len, ISCSI_ALIGN_SIZE); if ( new_pkt_size > (sizeof(struct iscsi_bhs_packet) + 1020UL) ) { logadd( LOG_ERROR, "iscsi_append_ahs_packet: Total numer of AHS packets exceeds 255 DWORDs" ); @@ -153,7 +710,7 @@ int iscsi_get_ahs_packets(const iscsi_bhs_packet *packet_data) while ( (int32_t) ahs_len > 0L ) { uint32_t len = iscsi_get_be16(ahs_pkt->len) + offsetof(struct iscsi_ahs_packet, data); // Total length of current AHS packet - len = iscsi_align(len, 4); + len = iscsi_align(len, ISCSI_ALIGN_SIZE); ahs_len -= len; ahs_pkt = ((uint8_t *) ahs_pkt) + (len - offsetof(struct iscsi_ahs_packet, data)); // Advance pointer to next AHS packet @@ -188,7 +745,7 @@ iscsi_ahs_packet *iscsi_get_ahs_packet(const iscsi_bhs_packet *packet_data, cons return ahs_pkt; uint32_t len = iscsi_get_be16(ahs_pkt->len) + offsetof(struct iscsi_ahs_packet, data); // Total length of current AHS packet - len = iscsi_align(len, 4); + len = iscsi_align(len, ISCSI_ALIGN_SIZE); ahs_len -= len; ahs_pkt = ((uint8_t *) ahs_pkt) + (len - offsetof(struct iscsi_ahs_packet, data)); // Advance pointer to next AHS packet @@ -199,13 +756,6 @@ iscsi_ahs_packet *iscsi_get_ahs_packet(const iscsi_bhs_packet *packet_data, cons return NULL; } -static inline void iscsi_put_be24(uint8_t *data, uint32_t val) -{ - data[0] = (uint8_t) (val >> 16UL); - data[1] = (uint8_t) (val >> 8UL); - data[2] = (uint8_t) val; -} - /** * Constructs and appends DataSegment (DS) to already allocated packet data. * There is no guarantee that the pointer stays the same. Any references @@ -238,12 +788,12 @@ iscsi_bhs_packet *iscsi_append_ds_packet(iscsi_bhs_packet *packet_data, const in } const uint32_t old_pkt_size = (const uint32_t) sizeof(struct iscsi_bhs_packet) + ((uint32_t) packet_data->total_ahs_len << 2UL); - const uint32_t new_pkt_size = old_pkt_size + header_digest_size + iscsi_align(ds_len, 4) + data_digest_size; + const uint32_t new_pkt_size = old_pkt_size + header_digest_size + iscsi_align(ds_len, ISCSI_ALIGN_SIZE) + data_digest_size; packet_data = (iscsi_bhs_packet *) realloc(packet_data, new_pkt_size); if ( packet_data == NULL ) { - logadd( LOG_ERROR, "iscsi_append_ahs_packet: Out of memory while allocating iSCSI AHS packet data for appending" ); + logadd( LOG_ERROR, "iscsi_append_ds_packet: Out of memory while allocating iSCSI DS packet data for appending" ); return packet_data; } @@ -290,18 +840,17 @@ static const uint32_t crc32c_lut[] = { // Created with a polynomial reflect valu /** * Calculates CRC32C with 0x82F63B78 polynomial reflect according to iSCSI specs + * TODO: Implement optimized SSE4.2 and ARM versions * * @param[in] data Pointer to data to calculate CRC32C for. * @param[in] len Length of data to be calculated. Must be * divisable by 4 which is guaranteed by iSCSI standard. * @param[in] crc32c Previous CRC32C in case of multiple passes. - * @return Returns CRC32C value. THis function cannot fail. + * @return CRC32C value. THis function cannot fail. */ static inline uint32_t iscsi_crc32c_update(const uint8_t *data, const uint len, uint32_t crc32c) { - uint i; - - for ( i = 0; i < len; i += 4 ) { + for ( uint i = 0; i < len; i += 4 ) { crc32c = (crc32c >> 8UL) ^ crc32c_lut[(crc32c ^ data[i]) & 0xFF]; crc32c = (crc32c >> 8UL) ^ crc32c_lut[(crc32c ^ data[i + 1]) & 0xFF]; crc32c = (crc32c >> 8UL) ^ crc32c_lut[(crc32c ^ data[i + 2]) & 0xFF]; @@ -312,16 +861,81 @@ static inline uint32_t iscsi_crc32c_update(const uint8_t *data, const uint len, } /** - * Calculates CRC32C with 0x82F63B78 polynomial reflect according to iSCSI specs + * Calculates header digest (CRC32C) with 0x82F63B78 polynomial reflect + * according to iSCSI specs and stores the result in the iSCSI packet + * data. This function cannot fail. * * @param[in] packet_data Pointer to ISCSI BHS packet to calculate CRC32C for. - * @return Returns CRC32C value of BHS packet. THis function cannot fail. */ -uint32_t iscsi_calc_header_digest(iscsi_bhs_packet *packet_data) +void iscsi_calc_header_digest(const iscsi_bhs_packet *packet_data) +{ + const uint32_t len = sizeof(struct iscsi_bhs_packet) + ((const uint32_t) packet_data->total_ahs_len << 2UL); + uint8_t *hdr_digest = ((uint8_t *) packet_data) + len; + const uint32_t crc32c = iscsi_crc32c_update( (const uint8_t *) packet_data, iscsi_align(len, 4), ISCSI_CRC32C_INITIAL ) ^ ISCSI_CRC32C_XOR; + + iscsi_put_be32( hdr_digest, crc32c ); +} + +/** + * Verifies header digest (CRC32C) with 0x82F63B78 polynomial reflect + * according to iSCSI specs. This function cannot fail. + * + * @param[in] packet_data Pointer to ISCSI BHS packet to validate CRC32C for. + * @return True if CRC32C matches the stored value, false otherwise. + */ +int iscsi_validate_header_digest(const iscsi_bhs_packet *packet_data) { - const uint32_t crc32c = iscsi_crc32c_update( (uint8_t *) packet_data, ISCSI_BHS_SIZE + ((uint32_t) packet_data->total_ahs_len << 2UL), ISCSI_CRC32C_INITIAL ); + const uint32_t len = sizeof(struct iscsi_bhs_packet) + ((const uint32_t) packet_data->total_ahs_len << 2UL); + const uint8_t *hdr_digest = ((uint8_t *) packet_data) + len; + const uint32_t pkt_crc32c = *(uint32_t *) hdr_digest; + const uint32_t crc32c = iscsi_crc32c_update( (const uint8_t *) packet_data, len, ISCSI_CRC32C_INITIAL ) ^ ISCSI_CRC32C_XOR; - return crc32c ^ ISCSI_CRC32C_XOR; + return iscsi_get_be32(pkt_crc32c) == crc32c; +} + +/** + * Calculates data digest (CRC32) with 0x82F63B78 polynomial reflect + * of a whole DataSegment (CRC32C) according to the iSCSI specs. + * The resulting CRC32C will be stored in the iSCSI packet. + * + * @param[in] packet_data Pointer to ISCSI DS packet to calculate CRC32C for. + * @param[in] header_digest_size Length of optional header digest (0 or 4 for now) in + * order to calculate correct DataSegment index. The header digest size IS NOT checked + * for conforming to iSCSI specs, so be careful. + */ +void iscsi_calc_data_digest(const iscsi_bhs_packet *packet_data, const int header_digest_size) +{ + const uint32_t ds_idx = (const uint32_t) sizeof(struct iscsi_bhs_packet) + ((const uint32_t) packet_data->total_ahs_len << 2UL) + header_digest_size; + const uint8_t *data = ((uint8_t *) packet_data) + ds_idx; + const uint32_t ds_len = iscsi_get_be24(packet_data->ds_len); + const uint32_t len = iscsi_align(ds_len, 4); + uint8_t *data_digest = ((uint8_t *) packet_data) + ds_idx + len; + const uint32_t crc32c = iscsi_crc32c_update( data, len, ISCSI_CRC32C_INITIAL ) ^ ISCSI_CRC32C_XOR; + + iscsi_put_be32( data_digest, crc32c ); +} + +/** + * Verifies data digest (CRC32C) with 0x82F63B78 polynomial reflect + * according to iSCSI specs. This function cannot fail. + * + * @param[in] packet_data Pointer to ISCSI BHS packet to calculate CRC32C for. + * @param[in] header_digest_size Length of optional header digest (0 or 4 for now) in + * order to calculate correct DataSegment index. The header digest size IS NOT checked + * for conforming to iSCSI specs, so be careful. + * @return True if CRC32C matches the stored value, false otherwise. + */ +int iscsi_validate_data_digest(const iscsi_bhs_packet *packet_data, const int header_digest_size) +{ + const uint32_t ds_idx = (const uint32_t) sizeof(struct iscsi_bhs_packet) + ((const uint32_t) packet_data->total_ahs_len << 2UL) + header_digest_size; + const uint8_t *data = ((uint8_t *) packet_data) + ds_idx; + const uint32_t ds_len = iscsi_get_be24(packet_data->ds_len); + const uint32_t len = iscsi_align(ds_len, 4); + const uint8_t *data_digest = data + len; + const uint32_t pkt_crc32c = *(uint32_t *) data_digest; + const uint32_t crc32c = iscsi_crc32c_update( (const uint8_t *) data, len, ISCSI_CRC32C_INITIAL ) ^ ISCSI_CRC32C_XOR; + + return iscsi_get_be32(pkt_crc32c) == crc32c; } /** @@ -334,9 +948,9 @@ uint32_t iscsi_calc_header_digest(iscsi_bhs_packet *packet_data) * not be NULL. * @param[in] len Length of packet data to be checked for iSCSI, must be * at least 48 bytes (minimum BHS header size). - * @return Returns 0 if it's clearly a iSCSI packet (detected without + * @return 0 if it's clearly a iSCSI packet (detected without * heuristics) or a positive integfer value up to 65536, if heuristics - * were necessaty. A negative value is returned in case of an error or + * were necessary. A negative value is returned in case of an error or * if it's clearly not an iSCSI packet. */ int iscsi_validate_packet(const struct iscsi_bhs_packet *packet_data, const uint32_t len) diff --git a/src/server/iscsi.h b/src/server/iscsi.h index 7e508ac..0f1419a 100644 --- a/src/server/iscsi.h +++ b/src/server/iscsi.h @@ -28,6 +28,27 @@ #define iscsi_get_be24(x) ((x) & 0xFFFFFFUL) #define iscsi_get_be32(x) (x) #define iscsi_get_be64(x) (x) + +static inline void iscsi_put_be16(uint8_t *data, const uint16_t val) +{ + (*(uint16_t *) data) = val; +} + +static inline void iscsi_put_be24(uint8_t *data, const uint32_t val) +{ + (*(uint16_t *) data) = (uint16_t) (val >> 8U); + data[2] = (uint8_t) val; +} + +static inline void iscsi_put_be32(uint8_t *data, const uint32_t val) +{ + (*(uint32_t *) *data) = val; +} + +static inline void iscsi_put_be64(uint8_t *data, const uint64_t val) +{ + (*(uint64_t *) data) = val; +} #elif defined(__LITTLE_ENDIAN__) || (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN) || (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || defined(__i386__) || defined(__i386) || defined(__x86_64) #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) // GCC or CLang @@ -47,6 +68,38 @@ #define iscsi_get_be32(x) ((((uint32_t) (x) & 0xFFUL) << 24UL) | (((uint32_t) (x) & 0xFF00UL) << 8UL) | (((uint32_t) (x) & 0xFF0000UL) >> 8UL) | (((uint32_t) (x) >> 24UL))) #define iscsi_get_be64(x) ((uint64_t)((((x) & 0xFFULL) << 56ULL) | (((x) & 0xFF00ULL) << 40ULL) | (((x) & 0xFF0000ull) << 24ULL) | (((x) & 0xFF000000ULL) << 8ULL) | (((x) & 0xFF00000000ULL) >> 8ULL) | (((x) & 0xFF0000000000ULL) >> 24ULL) | (((x) & 0xFF000000000000ULL) >> 40ULL) | (((x) & 0xFF00000000000000ULL) >> 56ULL))) #endif +static inline void iscsi_put_be16(uint8_t *data, const uint16_t val) +{ + data[0] = (uint8_t) (val >> 8U); + data[1] = (uint8_t) val; +} + +static inline void iscsi_put_be24(uint8_t *data, const uint32_t val) +{ + data[0] = (uint8_t) (val >> 16UL); + data[1] = (uint8_t) (val >> 8UL); + data[2] = (uint8_t) val; +} + +static inline void iscsi_put_be32(uint8_t *data, const uint32_t val) +{ + data[0] = (uint8_t) (val >> 24UL); + data[1] = (uint8_t) (val >> 16UL); + data[2] = (uint8_t) (val >> 8UL); + data[3] = (uint8_t) val; +} + +static inline void iscsi_put_be64(uint8_t *data, const uint64_t val) +{ + data[0] = (uint8_t) (val >> 56ULL); + data[1] = (uint8_t) (val >> 48ULL); + data[2] = (uint8_t) (val >> 40ULL); + data[3] = (uint8_t) (val >> 32ULL); + data[4] = (uint8_t) (val >> 24ULL); + data[5] = (uint8_t) (val >> 16ULL); + data[6] = (uint8_t) (val >> 8ULL); + data[7] = (uint8_t) val; +} #else #error "Unknown CPU endianness" #endif @@ -54,11 +107,72 @@ // Align a value so that it's evenly divisable by n #define iscsi_align(x, n) (((x) + (n) - 1) & ~((n) - 1)) +#define ISCSI_HASHMAP_DEFAULT_CAPACITY 5UL // Shift factor with base 1 +#define ISCSI_HASHMAP_RESIZE_FACTOR 1UL // Number of bits to shift left when resizing +#define ISCSI_HASHMAP_KEY_ALIGN_SHIFT 3UL // Key data shift value for alignment enforcement +#define ISCSI_HASHMAP_KEY_ALIGN (1UL << (ISCSI_HASHMAP_KEY_ALIGN_SHIFT)) // Key data size must be multiple of 8 bytes by now +#define ISCSI_HASHMAP_HASH_INITIAL 2166136261UL // Initial hash code +#define ISCSI_HASHMAP_HASH_XOR 0xBF58476D1CE4E5B9ULL // XOR value for hash code + +typedef struct iscsi_hashmap_bucket { + struct iscsi_hashmap_bucket *next; // Must be first element + + const uint8_t *key; // Data used as key, zero padding + size_t key_size; // Size of key, must be a multiple of 8 bytes + uint32_t hash; // Hash code + uint8_t *value; // Value +} iscsi_hashmap_bucket; + +typedef struct iscsi_hashmap { + iscsi_hashmap_bucket *buckets; // Hashmap buckets + uint capacity; // Current capacity in elements + uint count; // Number of buckets + uint removed_count; // Number of removed buckets + iscsi_hashmap_bucket *first; // First bucket of linked list + iscsi_hashmap_bucket *last; // Last bucket, allows faster traversion +} iscsi_hashmap; + +/** + * Callback function. This is a pointer to a + * function for various purposes like iterating + * through a hash map. It is also used for replacing + * already existing keys or for key removal. + * + * @param[in] key Pointer to zero padded key. NULL is + * an invalid pointer here, so be careful. + * @param[in] key_size Number of bytes for the key, MUST + * be a multiple of 8 bytes which is NOT checked, so + * be careful. + * @param[in] value Value of the key, NULL is allowed. + * @return A negative result indicates as fatal error, + * 0 means successful operation and a positive value + * indicates a non-fatal error or a warning. + */ +typedef int (*iscsi_hashmap_callback)(const uint8_t *key, size_t key_size, uint8_t *value, uint8_t *user_data); // A Callback for iterating over map, freeing and removing entries. + // user_data is free for personal use + +iscsi_hashmap *hashmap_create(); // Creates an empty hash map with default capacity specified as above +void iscsi_hashmap_destroy(iscsi_hashmap *map); // Deallocates the hash map objects and buckets, not elements. + // Use iscsi_hashmap_iterate to deallocate the elements themselves +uint8_t *iscsi_hashmap_create_key(const uint8_t *data, const size_t len); // Creates a key suitable for hashmap usage (ensures 8-byte boundary and zero padding) +void iscsi_hashmap_destroy_key(uint8_t *key); // Deallocates all resources acquired by iscsi_hashmap_create_key +int iscsi_hashmap_put(iscsi_hashmap *map, const uint8_t *key, const size_t key_size, uint8_t *value); // Assigns key / value pair to hash map without making copies +int iscsi_hashmap_get_put(iscsi_hashmap *map, const uint8_t *key, const size_t key_size, uint8_t **out_in_value); // Assigns key / value pair to hash map without making copies +int iscsi_hashmap_put_free(iscsi_hashmap *map, const uint8_t *key, const size_t key_size, uint8_t *value, iscsi_hashmap_callback callback, uint8_t *user_data); // Assigns key / value pair to hash map without making copies + // with callback function in case the key already exists +int iscsi_hashmap_get(iscsi_hashmap *map, const uint8_t *key, const size_t key_size, uint8_t **out_val); // Retrieves the value of a specified key +void iscsi_hashmap_remove(iscsi_hashmap *map, const uint8_t *key, const size_t key_size); // Marks an element for removal by setting key and value both to NULL +void iscsi_hashmap_remove_free(iscsi_hashmap *map, const uint8_t *key, const size_t key_size, iscsi_hashmap_callback callback, uint8_t *user_data); // Marks an element for removal by setting key and value both to NULL, + // but invokes a callback function before actual marking for removal. +int iscsi_hashmap_size(iscsi_hashmap *map); // Retrieves the number of elements of the hash map, ignoring elements marked for removal +int iscsi_hashmap_iterate(iscsi_hashmap *map, iscsi_hashmap_callback callback, uint8_t *user_data); // Iterator with callback function invoked on each element which has not been removed + /* iSCSI protocol stuff (all WORD/DWORD/QWORD values are big endian by default unless specified otherwise). */ #define ISCSI_BHS_SIZE 48 // iSCSI Basic Header Segment size #define ISCSI_DIGEST_SIZE 4 // iSCSI header and data digest size (CRC32C) +#define ISCSI_ALIGN_SIZE 4 // iSCSI packet data alignment (BHS, AHS and DS) // CRC32C constants for header and data digest #define ISCSI_CRC32C_INITIAL 0xFFFFFFFFUL @@ -2723,7 +2837,11 @@ void iscsi_destroy_packet(iscsi_bhs_packet *packet_data); // Free resources allo iscsi_bhs_packet *iscsi_append_ahs_packet(iscsi_bhs_packet *packet_data, const uint32_t ahs_len); // Allocate and initialize an iSCSI AHS packet and append to existing data stream int iscsi_get_ahs_packets(const iscsi_bhs_packet *packet_data); // Counts number of AHS packets in an iSCSI data packet stream iscsi_ahs_packet *iscsi_get_ahs_packet(const iscsi_bhs_packet *packet_data, const int index); // Retrieves the pointer to an specific AHS packet by index -iscsi_bhs_packet *iscsi_append_ds_packet(iscsi_bhs_packet *packet_data, const int header_digest_size, const uint32_t ds_len, const int data_digest_size); +iscsi_bhs_packet *iscsi_append_ds_packet(iscsi_bhs_packet *packet_data, const int header_digest_size, const uint32_t ds_len, const int data_digest_size); // Allocate and initialize an iSCSI DS packet and append to existing data stream +void iscsi_calc_header_digest(const iscsi_bhs_packet *packet_data); // Calculate and store iSCSI header digest (CRC32C) +int iscsi_validate_header_digest(const iscsi_bhs_packet *packet_data); // Validates a stored iSCSI header digest (CRC32C) with actual header data +void iscsi_calc_data_digest(const iscsi_bhs_packet *packet_data, const int header_digest_size); // Calculate iSCSI data digest (CRC32C) +int iscsi_validate_data_digest(const iscsi_bhs_packet *packet_data, const int header_digest_size); // Validates a stored iSCSI data digest (CRC32C) with actual DataSegment int iscsi_validate_packet(const struct iscsi_bhs_packet *packet_data, const uint32_t len); // Check if valid iSCSI packet and validate if necessarily #endif /* DNBD3_ISCSI_H_ */ |
