/* dns_thrash, Copyright David R. Albrecht, 2009.
 * http://davidralbrecht.com/
 * This tool made available under the terms of the BSD license
 * http://www.opensource.org/licenses/bsd-license.php
 */

#include <stdio.h>
#include <pcap.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include "main.h"

int main (int argc, const char * argv[]) {
	dns_packet_t packet;
	int generating_packet = 0;
	struct timeval tv;
	char errbuf[PCAP_ERRBUF_SIZE];
	pcap_t * trace_handle;
	pcap_dumper_t * dumper_handle;
	
	// Should allow (1) a filename argument and (2) number of packets to
	// generate here
	
	// Link type=ethernet (from savefile.c)
	if((trace_handle = pcap_open_dead(DLT_EN10MB, 65535)) == NULL) {
		printf("Error opening trace file %s\n", errbuf);
		exit(1);
	}
	
	if((dumper_handle = pcap_dump_open(trace_handle, "trace-out.pcap")) == NULL) {
		printf("Error in pcap_dump_open(), aborting\n");
		exit(1);
	}
	
	tv.tv_sec = 1;	// Make the first packet arrive at time 1.0
	tv.tv_usec = 0;	
	
	for(generating_packet = 0; generating_packet < 1000000; ++generating_packet) {
		generate_5tuple(&packet);
		generate_payload(&packet);
		
		write_udp_hdr(&packet);
		write_ip_hdr(&packet);
		write_eth_hdr(&packet);

		write_full_packet(tv, &packet, dumper_handle);
		
		// Cleanup
		delete_packet(&packet);
		increment_timeval(&tv);
	}

	pcap_dump_flush(dumper_handle);
	pcap_close(trace_handle);

    return 0;
}

/* generate_5tuple function
 * This function takes a dns_packet structure and assigns the source/dst
 * IP addresses and port numbers.  Protocol headers are written later after
 * payloads, when we can calculate checksums.
 */

// TODO: Figure out a sensible way to assign IP addresses here, because addresses
// can affect how flows are load-balanced
void generate_5tuple(dns_packet_t * packet) {
	// Randomize the source and dest addr, and sport
	packet->saddr_nbo = random();
	packet->daddr_nbo = random();
	packet->sport_nbo = (uint16_t) random();
	
	// Old code to assign fixed IP addresses to the flows
	//inet_pton(AF_INET, "10.0.0.1", &(packet->saddr_nbo));
	//inet_pton(AF_INET, "64.198.222.199", &(packet->daddr_nbo));
	//packet->sport_nbo = htons(12345);

	packet->dport_nbo = htons(53);
	packet->prot_num = 0x11;
}

/* write_full_packet function
 * This function assembles the entire packet and writes it into the 
 * pcap dumpfile
 */
void write_full_packet(const struct timeval tv, const dns_packet_t * dns_buf, pcap_dumper_t * dumper_handle) {
	struct pcap_pkthdr hdr;
	void* assembled_buf;
	
	int total_packet_len = ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN + dns_buf->payload_len;
	if((assembled_buf = malloc(total_packet_len * sizeof(unsigned char))) == NULL) {
		printf("Error allocating buffer to assemble packet in write_full_packet\n");
		exit(1);
	}
	
	memcpy(assembled_buf, dns_buf->eth_hdr, ETH_HDR_LEN);
	memcpy(assembled_buf + ETH_HDR_LEN * sizeof(unsigned char), dns_buf->ip_hdr,
		IP_HDR_LEN);
	memcpy(assembled_buf + (ETH_HDR_LEN + IP_HDR_LEN) * sizeof(unsigned char), 
		dns_buf->udp_hdr, UDP_HDR_LEN);
	memcpy(assembled_buf + (ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN) * sizeof(unsigned char),
		dns_buf->dns_payload, dns_buf->payload_len);

	// Fill in the pcap_pkthdr struct
	memcpy(&(hdr.ts), &tv, sizeof(struct timeval));
	hdr.caplen = hdr.len = total_packet_len;

	pcap_dump((unsigned char *) dumper_handle, &hdr, (unsigned char *) assembled_buf);
	free(assembled_buf);
	
	return;
}

/* increment_timeval function
 * Increment the value in the tv struct by 10ms.  There might be lingering performance 
 * implications of using a constant value, but I don't really think so.
 */
void increment_timeval(struct timeval * tv) {
	tv->tv_usec += 10000;		// This shouldn't overflow because 1E6 is the max allowed
								// value, and a long can hold waaaay more than that
	
	// Perform the carry
	if(tv->tv_usec >= 1000000) {
		tv->tv_usec -= 1000000;
		++tv->tv_sec;
	}
	
	return;
}

/* write_eth_hdr function
 * Place an ethernet header onto the head of the packet
 */
void write_eth_hdr(dns_packet_t * packet) {
	int i;

	// Allocate the memory for the eth header
	if((packet->eth_hdr = (unsigned char *) malloc(ETH_HDR_LEN * sizeof(unsigned char))) == NULL) {
		printf("Error allocating memory in write_eth_hdr(), aborting\n");
		exit(1);
	}

	// Write random dst/src MAC addrs into the packet
	for(i=0; i < 12; ++i) {
		(packet->eth_hdr)[i] = (unsigned char) (random() % 256);
	}
	
	// Packet type: IP (0x0800)
	(packet->eth_hdr)[12] = (unsigned char) 8;
	(packet->eth_hdr)[13] = (unsigned char) 0;
	
	return;
}

/* write_udp_hdr function
 * Write a UDP header onto the packet
 */
void write_udp_hdr(dns_packet_t * packet) {
	uint16_t len_nbo = htons(packet->payload_len + UDP_HDR_LEN);
	uint16_t udp_checksum = 0;	// Checksum computed with csum field=0
	
	// Allocate space for UDP header
	if((packet->udp_hdr = malloc(UDP_HDR_LEN)) == NULL) {
		printf("Error allocating space for UDP header, aborting\n");
		exit(1);
	}
	
	memcpy(packet->udp_hdr, &(packet->sport_nbo), sizeof(uint16_t));
	memcpy(packet->udp_hdr + 2*sizeof(unsigned char), &(packet->dport_nbo),
		sizeof(uint16_t));
	memcpy(packet->udp_hdr + 4*sizeof(unsigned char), &len_nbo, 
		sizeof(uint16_t));
	memcpy(packet->udp_hdr + 6*sizeof(unsigned char), &udp_checksum,
		sizeof(uint16_t));

	// Compute the real checksum and write it into the packet
	udp_checksum = udp_sum(packet);
	memcpy(packet->udp_hdr + 6*sizeof(unsigned char), &udp_checksum,
		sizeof(uint16_t));

	return;
}

/* write_ip_hdr function
 * Write an IP header onto the packet
 */
void write_ip_hdr(dns_packet_t * packet) {
	short int total_len = htons(IP_HDR_LEN + UDP_HDR_LEN + packet->payload_len);
	short int ip_id = htons(((uint16_t) random()));
	short int ip_flags = htons(0x4000);
	uint16_t header_checksum = 0;
	
	if((packet->ip_hdr = malloc(IP_HDR_LEN * sizeof(unsigned char))) == NULL) {
		printf("Error allocating IP header in write_ip_hdr(), aborting\n");
		exit(1);
	}
	
	// IPv4, header is five dwords (4-byte units) long
	packet->ip_hdr[0] = (unsigned char) 0x45;
	packet->ip_hdr[1] = 0;		// no diffserv
	memcpy(packet->ip_hdr + 2*sizeof(unsigned char), &total_len, sizeof(uint16_t));
	memcpy(packet->ip_hdr + 4*sizeof(unsigned char), &ip_id, sizeof(uint16_t));
	// Don't fragment, frag offset=0
	memcpy(packet->ip_hdr + 6*sizeof(unsigned char), &ip_flags, sizeof(uint16_t));
	packet->ip_hdr[8] = (unsigned char) 64;			// TTL=64
	packet->ip_hdr[9] = (unsigned char) packet->prot_num;
	// Zero the checksum field initially
	memcpy(packet->ip_hdr + 10*sizeof(unsigned char), &header_checksum, sizeof(uint16_t));
	memcpy(packet->ip_hdr + 12*sizeof(unsigned char), &(packet->saddr_nbo), sizeof(uint32_t));
	memcpy(packet->ip_hdr + 16*sizeof(unsigned char), &(packet->daddr_nbo), sizeof(uint32_t));
	
	header_checksum = ip_sum(packet);
	memcpy(packet->ip_hdr + 10*sizeof(unsigned char), &header_checksum, sizeof(uint16_t));
	
	return;
}

/* delete_packet function
 * Deallocate all dynamically allocated memory used by the packet
 */
void delete_packet(dns_packet_t * packet) {
	free(packet->eth_hdr);
	free(packet->ip_hdr);
	free(packet->udp_hdr);
	free(packet->dns_payload);
	
	return;
}

/* generate_payload function
 * Generate a randomized, hard-to-parse DNS payload
 */
void generate_payload(dns_packet_t * packet) {
	uint16_t num_questions_hbo, answer_rrs_hbo, authority_rrs_hbo, additional_rrs_hbo, flags_hbo, payload_cursor;
	dns_name_record_t* names_array;
	int on_rr, names_written;
	
	payload_cursor = names_written = 0;
	
	// Allocate space to hold this packet
	packet->dns_payload = (unsigned char *) malloc(DNS_SCRATCH_LEN);
	num_questions_hbo = 1;				// Stick with 1 question always for now
	
	// Sometimes we want questions, other times answers.  For now split them 50/50
//	if(random() % 2 == 0)	{	// Question
//		answer_rrs_hbo = authority_rrs_hbo = additional_rrs_hbo = 0;
//		flags_hbo = 0x0100;
//	} else {					// Answer
		answer_rrs_hbo = 1;
		authority_rrs_hbo = additional_rrs_hbo = 0;
		flags_hbo = 0x8180;
//	}
	
	if((names_array = malloc((num_questions_hbo + answer_rrs_hbo + authority_rrs_hbo +
		additional_rrs_hbo) * sizeof(dns_name_record_t))) == NULL) {
		printf("Error allocating memory for DNS name storage, aborting\n");
		exit(1);
	}

	payload_cursor = write_dns_header(packet, payload_cursor, flags_hbo, num_questions_hbo, 
		answer_rrs_hbo, authority_rrs_hbo, additional_rrs_hbo);
	
	// Generate our questions, plus answer, authority, and additional RRs
	for(on_rr=0; on_rr < num_questions_hbo; ++on_rr) {
		payload_cursor = generate_question(packet, payload_cursor, names_array, &names_written);
	}
	for(on_rr=0; on_rr < answer_rrs_hbo; ++on_rr) {
		if(on_rr < 1) {
			payload_cursor = generate_answer_waterfall(packet, payload_cursor, names_array, &names_written);
		}
		else {
			payload_cursor = generate_answer_cdepth(packet, payload_cursor, names_array, &names_written, 1);
		}
	}
	for(on_rr=0; on_rr < authority_rrs_hbo; ++on_rr) {
		//payload_cursor = generate_authority_rr();
	}
	for(on_rr=0; on_rr < additional_rrs_hbo; ++on_rr) {
		//payload_cursor = generate_additional_rr();
	}

	packet->payload_len = payload_cursor;

	free(names_array);
	return;
}

/* generate_answer_cdepth
 * Generate a DNS answer at constant recursion depth
 */
uint16_t generate_answer_cdepth(dns_packet_t* packet, uint16_t payload_cursor, dns_name_record_t* 
	names, int* names_written, int recur_depth) {
	char* name;
	int name_len, label_len, num_labels;
	uint16_t atype_nbo, aclass_nbo, data_len_nbo;
	uint32_t ttl_nbo, addr_nbo;
	
	label_len = 2;
	num_labels = 3;
	
	// Generate a name: 3 labels, 3 characters each
	generate_name(&name, &name_len, num_labels, label_len);
	
	// Copy the name into the target buffer
	memcpy(packet->dns_payload + payload_cursor * sizeof(unsigned char), name, name_len);
	payload_cursor += name_len;
	(packet->dns_payload)[payload_cursor++] = (unsigned char) 0xc0;	// Pointer
	(packet->dns_payload)[payload_cursor++] = (unsigned char) (names[recur_depth - 1].offset_hbo);
	
	atype_nbo = aclass_nbo = htons(0x01);	// ATYPE and ACLASS DNS flags
	ttl_nbo = htonl(300);					// TTL = 5 min (300 sec)
	data_len_nbo = htons(4);				// IP addresses ("data") are 4 bytes long
	addr_nbo = random();					// Generate a long random number (IP address)
	
	memcpy(packet->dns_payload + payload_cursor * sizeof(unsigned char), &atype_nbo, sizeof(atype_nbo));
	payload_cursor += sizeof(atype_nbo);
	memcpy(packet->dns_payload + payload_cursor * sizeof(unsigned char), &aclass_nbo, sizeof(aclass_nbo));
	payload_cursor += sizeof(aclass_nbo);
	memcpy(packet->dns_payload + payload_cursor * sizeof(unsigned char), &ttl_nbo, sizeof(ttl_nbo));
	payload_cursor += sizeof(ttl_nbo);
	memcpy(packet->dns_payload + payload_cursor * sizeof(unsigned char), &data_len_nbo, sizeof(data_len_nbo));
	payload_cursor += sizeof(data_len_nbo);
	memcpy(packet->dns_payload + payload_cursor * sizeof(unsigned char), &addr_nbo, sizeof(addr_nbo));
	payload_cursor += sizeof(addr_nbo);


	free(name);	// Dump the buffer that holds the constructed name
	
	return payload_cursor;

	
}
/* generate_answer_waterfall function
 * Compose a DNS answer, and write it into the packet
 */
uint16_t generate_answer_waterfall(dns_packet_t* packet, uint16_t payload_cursor, dns_name_record_t * names,
	int* names_written) {
	char* name;
	int name_len, label_len, num_labels;
	uint16_t atype_nbo, aclass_nbo, data_len_nbo;
	uint32_t ttl_nbo, addr_nbo;
	
	//label_len = round(rand_normal_bm(6, 1));		// Sample from N(6,1)
	//num_labels = round(rand_normal_bm(3, 0.5));		// Sample from N(3,0.5)
	label_len = 2;
	num_labels = 3;
	
	// Generate a name: 3 labels, 3 characters each
	generate_name(&name, &name_len, num_labels, label_len);
	
	// Make this label one level more recursive than the last one
	names[*names_written].offset_hbo = payload_cursor;
	names[*names_written].recur_depth = 1;	// TODO: Add 1 to recursion depth at each pass
	++(*names_written);						// One more name written
	
	// Copy the name into the target buffer
	memcpy(packet->dns_payload + payload_cursor * sizeof(unsigned char), name, name_len);
	payload_cursor += name_len;
	(packet->dns_payload)[payload_cursor++] = (unsigned char) 0xc0;	// Pointer
	(packet->dns_payload)[payload_cursor++] = (unsigned char) (names[*names_written - 2].offset_hbo);
	
	atype_nbo = aclass_nbo = htons(0x01);	// ATYPE and ACLASS DNS flags
	ttl_nbo = htonl(300);					// TTL = 5 min (300 sec)
	data_len_nbo = htons(4);				// IP addresses ("data") are 4 bytes long
	addr_nbo = random();					// Generate a long random number (IP address)
	
	memcpy(packet->dns_payload + payload_cursor * sizeof(unsigned char), &atype_nbo, sizeof(atype_nbo));
	payload_cursor += sizeof(atype_nbo);
	memcpy(packet->dns_payload + payload_cursor * sizeof(unsigned char), &aclass_nbo, sizeof(aclass_nbo));
	payload_cursor += sizeof(aclass_nbo);
	memcpy(packet->dns_payload + payload_cursor * sizeof(unsigned char), &ttl_nbo, sizeof(ttl_nbo));
	payload_cursor += sizeof(ttl_nbo);
	memcpy(packet->dns_payload + payload_cursor * sizeof(unsigned char), &data_len_nbo, sizeof(data_len_nbo));
	payload_cursor += sizeof(data_len_nbo);
	memcpy(packet->dns_payload + payload_cursor * sizeof(unsigned char), &addr_nbo, sizeof(addr_nbo));
	payload_cursor += sizeof(addr_nbo);


	free(name);	// Dump the buffer that holds the constructed name
	
	return payload_cursor;
	}

/* generate_question function
 * Compose a DNS question and write it into the packet
 */
uint16_t generate_question(dns_packet_t* packet, uint16_t payload_cursor, dns_name_record_t * names,
	int* names_written) {
	char* name;
	int name_len, label_len, num_labels;
	
	label_len = round(rand_normal_bm(6, 1));		// Sample from N(6,1)
	num_labels = round(rand_normal_bm(3, 0.5));		// Sample from N(3,0.5)
	
	// Generate a name: num_labels, label_len each
	generate_name(&name, &name_len, num_labels, label_len);
	
	// Record that we wrote this name in the record table
	names[*names_written].offset_hbo = payload_cursor;
	names[*names_written].recur_depth = 0;	// No recursion starting here
	++(*names_written);
	
	// Copy the name into the target buffer and terminate it
	memcpy(packet->dns_payload + payload_cursor * sizeof(unsigned char), name, name_len);
	payload_cursor += name_len;
	(packet->dns_payload)[payload_cursor++] = (unsigned char) 0;	// End of name
	
	(packet->dns_payload)[payload_cursor++] = (unsigned char) 0;	// qtype=0x0001
	(packet->dns_payload)[payload_cursor++] = (unsigned char) 1;	//   qtype
	(packet->dns_payload)[payload_cursor++] = (unsigned char) 0;	// QCLASS=0x0001
	(packet->dns_payload)[payload_cursor++] = (unsigned char) 1;	//   qclass
		
	free(name);	// Dump the buffer that holds the constructed name
	
	return payload_cursor;
}

// rand_normal_bm function
// Implements rectangular form of the Box-Müller uniform-normal transformation.
// Uses random() for entropy.
double rand_normal_bm(double m, double s) {
	double x1, x2, y1;
	
	x1 = random() / (double) RAND_MAX;
	x2 = random() / (double) RAND_MAX;
	
	y1 = sqrt(-2*log(x1)) * cos(2*PI*x2);		// math.h's log() is log naturel...wtf
    //y2 = sqrt( - 2 ln(x1) ) sin( 2 pi x2 )
	
	return m + s*y1;		// N(0,1) -> N(m,s) (Thanks Prateek)
}

/* write_dns_header function
 * This puts the common header onto DNS packets...refactored out of generate_payload to make
 * that function less heinous
 */
uint16_t write_dns_header(dns_packet_t* packet, uint16_t payload_cursor, uint16_t flags_hbo,
	uint16_t num_questions_hbo, uint16_t answer_rrs_hbo, uint16_t authority_rrs_hbo,
	uint16_t additional_rrs_hbo)
	{
	
	uint16_t txn_id;
	
	// Write the txn_id into the request
	txn_id = htons((uint16_t) random());
	memcpy(packet->dns_payload + payload_cursor * sizeof(unsigned char), &txn_id,
		sizeof(uint16_t));
	payload_cursor += 2;
	
	// Write the flags
	flags_hbo = htons(flags_hbo);
	memcpy(packet->dns_payload + payload_cursor * sizeof(unsigned char), &flags_hbo,
		sizeof(uint16_t));
	payload_cursor += 2;
	
	// Question count
	num_questions_hbo = htons(num_questions_hbo);
	memcpy(packet->dns_payload + payload_cursor * sizeof(unsigned char), &num_questions_hbo,
		sizeof(uint16_t));
	payload_cursor += 2;
	
	// Answer RR count
	answer_rrs_hbo = htons(answer_rrs_hbo);
	memcpy(packet->dns_payload + payload_cursor * sizeof(unsigned char), &answer_rrs_hbo, 
		sizeof(uint16_t));
	payload_cursor += 2;
	
	// Authority RR count
	authority_rrs_hbo = htons(authority_rrs_hbo);
	memcpy(packet->dns_payload + payload_cursor * sizeof(unsigned char), &authority_rrs_hbo,
		sizeof(uint16_t));
	payload_cursor += 2;
	
	// Additional RR count
	additional_rrs_hbo = htons(additional_rrs_hbo);
	memcpy(packet->dns_payload + payload_cursor * sizeof(unsigned char), &additional_rrs_hbo,
		sizeof(uint16_t));
	payload_cursor += 2;
	
	return payload_cursor;
}

/* generate_name function
 * Takes a pointer to a char*, allocates enough memory to hold an entire label at the pointer,
 * and then fills it in with data.
 *
 * TODO: Maybe move randomization into here
 */
void generate_name(char** name, int* name_len, const int labels, const int label_len) {
	int on_label, on_char, name_cursor;
	unsigned char label_len_pack = (unsigned char) label_len;
	
	// One byte per label for its prefixed length
	*name_len = (label_len+1) * labels;

	if((*name = malloc( *name_len * sizeof(unsigned char))) == NULL) {
		printf("Error allocating memory in generate_name()\n");
		exit(1);
	}
	
	name_cursor = 0;
	
	for(on_label = 0; on_label < labels; ++on_label) {
		// Write label length into name buffer
		memcpy(*name + name_cursor*sizeof(unsigned char), &label_len_pack, sizeof(unsigned char));
		++name_cursor;
		
		for(on_char=0; on_char < label_len; ++on_char) {
			// Pick a random lowercase letter and write it into the name buffer
			(*name)[name_cursor] = (random() % 26) + 0x61; // 0x61 = 'a'
			++name_cursor;
		}
	}
}

/* udp_sum function
 * Write the correct UDP checksum into a packet
 * Adapted from http://www.netfor2.com/udpsum.htm
 */ 
uint16_t udp_sum(dns_packet_t* packet) {
	uint16_t prot_udp=17;
	uint16_t word16;
	uint32_t sum;
	
	int i;
	
	assert(UDP_HDR_LEN == 8);		// This not being true breaks a lot of stuff
	
	// Find out if the length of data is even or odd number. If odd,
	// add a padding byte = 0 at the end of the payload
	if((packet->payload_len + UDP_HDR_LEN) % 2 == 1) {
		packet->dns_payload[packet->payload_len] = 0;	// Padding byte 0
	}
	
	//initialize sum to zero
	sum=0;
	
	// make 16 bit words out of every two adjacent 8 bit words and 
	// calculate the sum of all 16 bit words (header first)
	for(i=0; i < UDP_HDR_LEN; i += 2) {
		word16 = (packet->udp_hdr[i] << 8) + packet->udp_hdr[i+1];
		sum = sum + (uint16_t) word16;
	}

	// ...next, payload
	for(i=0; i < packet->payload_len; i+=2) {
		word16 = (packet->dns_payload[i] << 8) + packet->dns_payload[i+1];
		sum = sum + (uint16_t) word16;
	}
	
	// add the UDP pseudo header which contains the IP source and destination addresses
	sum += ((ntohl(packet->saddr_nbo) & 0xff000000)>>16) + ((ntohl(packet->saddr_nbo) & 0x00ff0000)>>16);
	sum += (ntohl(packet->saddr_nbo) & 0x0000ff00) + (ntohl(packet->saddr_nbo) & 0x000000ff);

	sum += ((ntohl(packet->daddr_nbo) & 0xff000000)>>16) + ((ntohl(packet->daddr_nbo) & 0x00ff0000)>>16);
	sum += (ntohl(packet->daddr_nbo) & 0x0000ff00) + (ntohl(packet->daddr_nbo) & 0x000000ff);

	// the protocol number and the length of the UDP packet
	sum = sum + prot_udp + (packet->payload_len + UDP_HDR_LEN);

	// keep only the last 16 bits of the 32 bit calculated sum and add the carries
   	while (sum>>16) {
		sum = (sum & 0xFFFF) + (sum >> 16);
	}
		
	// Take the one's complement of sum
	sum = ~sum;

	return htons((uint16_t) sum);
}

/* ip_sum function
 * Write the IP header checksum into the header
 * Expects to have a pre-zeroed checksum field in the header
 */
uint16_t ip_sum(dns_packet_t* packet) {
	uint32_t sum;
	int i;
	
	assert(IP_HDR_LEN % 2 == 0);	// We don't pad the end of the IP header, so crash 
									// if the header has odd length
	sum = 0;
	for(i=0; i < IP_HDR_LEN; i+=2) {
		sum += (packet->ip_hdr[i] << 8) + packet->ip_hdr[i+1];
	}
	
	while(sum>>16) {
		sum = (sum & 0xffff) + (sum >> 16);
	}
	
	// One's comp
	sum = ~sum;
	
	return htons((uint16_t) sum);
}
