#!/usr/bin/octave-cli
## Paul Walko
## bluetooth-undergrads


### Variables
##############################################
global data            = -1;	# all data  array
global channel	       = -1;    # channal   int
global total_len_dec   = -1;	# total len int

### Preamble / Access Address vars
global preamble        = -1;	# preamble  int
global access_address  = -1;	# acc addr  int

### PDU / CRC vars # Holds raw dewhitened data
global pduCrcArray     = -1;    # pdu + crc array
global pduArray        = -1;    # pdu       array
global crcArray        = -1;    # crc       array
# PDU
global header_struct   = -1;    # header    Struct
global payload_struct  = -1;    # payload   Struct
# CRC
global crc_struct      = -1;    # crc       struct

## Functions
#############################################
## Helper Functions
# Flips every octect in array
function arr = flipOctects(arr)
	for i = 1:8:length(arr)
		arr(i:i + 7) = flip(arr(i:i + 7));
	endfor
endfunction
# Converts array of bits to hex string
function [hex_str, hex_len] = mat2hexStr(arr)
	hex_str  = '';
	nums = ceil(length(arr) / 52);
	for i = 1:nums
		start = (i - 1) * 52 + 1;
		if i == nums
			limit = length(arr);
		else
			limit = i * 52;
		endif
		arrStr = strrep(strrep(strrep(mat2str(arr(start:limit)), '[', ''), ']', ''), ' ', '');
		arrNum  = bin2dec(arrStr);
		outStr  = sprintf("%X", arrNum);
		hex_str = strcat(hex_str, outStr);
	endfor
	hex_len = length(hex_str);
endfunction
#############################################


# Loads data from file
function loadFile(dataName, channelName, fileName)
	global data;
	global channel;
	global total_len_dec;
	load(fileName);
	eval (strcat("data = ", dataName, ";"));
	eval (strcat("channel = ", channelName, ";"));
	total_len_dec = length(data);
endfunction

## Extracts preamble from data
function getPreamble()
	global data;
	global preamble;
	preamble = uint8(0);
	for i = 1:8
		preamble = bitset(preamble, i, data(i));
	endfor
endfunction

## Extracts Access Address
function getAccessAddress()
	global data;
	addrArray = data(9:40);
	global access_address;
	access_address = uint32(0);
	for i = 1:length(addrArray)
		access_address = bitset(access_address, i, addrArray(i));
	endfor
endfunction


## deWhiten PDU and split up pdu & crc arrays
function deWhitenPDUCRC()
	global data;
	global channel;
	global pduCrcArray;
	pduCrcArray = data(41:length(data));
	channel = uint8(channel);
	lfsr = uint8(1);
	for i = 1:6
		lfsr = bitset(lfsr, 8 - i, bitget(channel, i));
	endfor
	for i = 1:length(pduCrcArray)
		pduCrcArray(i) = bitxor(bitget(lfsr, 7), bitget(pduCrcArray(i), 1));
		lfsr = bitshift(lfsr, 1);
		lfsr = bitset(lfsr, 1, bitget(lfsr, 8));
		lfsr = bitset(lfsr, 5, bitxor(bitget(lfsr, 5), bitget(lfsr, 8)));
	endfor
	global pduArray;
	global crcArray;
	pduArray = pduCrcArray(1:length(pduCrcArray) - 24);
	crcArray = pduCrcArray(length(pduCrcArray) - 23:length(pduCrcArray));
endfunction

## Extract header and store values in header_struct
function getPDUHeader()
	global pduArray;
	global header_struct;
	header          = pduArray(1:16);
  	reserved_1      = header(5:6);
	tx_add          = uint8(0);
	rx_add          = uint8(0);
  	reserved_2      = header(15:16);
	payload_len_dec = uint8(0);
	pdutype_dec     = uint8(0);
	for i = 1:length(header)
		if i <= 4
			pdutype_dec = bitset(pdutype_dec, i, header(i));
		elseif i <= 6
			# Do Nothing; Already Assigned above
		elseif i <= 7
			tx_add = bitset(tx_add, i - 6, header(i));
		elseif i <= 8
			rx_add = bitset(rx_add, i - 7, header(i));
		elseif i <= 14
			payload_len_dec = bitset(payload_len_dec, i - 8, header(i));
		else
			# Do Nothing; Already Assigned above
		endif
	endfor
	if pdutype_dec == 0
		pdutype = "ADV_IND";
	elseif pdutype_dec == 1
		pdutype = "ADV_DIRECT_IND";
	elseif pdutype_dec == 2
		pdutype = "ADV_NONCONN_IND";
	elseif pdutype_dec == 3
		pdutype = "SCAN_REQ";
	elseif pdutype_dec == 4
		pdutype = "SCAN_RSP";
	elseif pdutype_dec == 5
		pdutype = "CONNECT_REQ";
	elseif pdutype_dec == 6
		pdutype = "ADV_SCAN_IND";
	else
		pdutype = "Reserved";
	endif
	# Set header values
	header_struct = struct("header", header, "pdutype", pdutype, "pdutype_dec", pdutype_dec, "reserved_1", reserved_1, "tx_add", tx_add, "rx_add", rx_add, "length", payload_len_dec, "reserved_2", reserved_2);
endfunction

## Sets pdu struct to correct vars and store payload, and update pduArray with flipped values
function setPayloadFields()
	global pduArray;
	global header_struct;
	global payload_struct;

	field2_len  = header_struct.length - 6;
	pdutype_dec = header_struct.pdutype_dec;
	payload_len = length(pduArray) - 16;
	payload     = pduArray(17:length(pduArray));

	# ADV_IND, ADV_NONNCONN_IND, ADV_SCAN_IND
	if pdutype_dec == 0 || pdutype_dec == 2 || pdutype_dec == 6
		payload_struct = struct("payload", {payload_len, payload}, "advA",  {6, -1}, "advA_hexStr",  {-1, -1}, "advData",     {field2_len, -1}, "advData_hexStr",     {-1, -1});
		setPDUField_026();
	# ADV_DIRECT_IND
	elseif pdutype_dec == 1
		payload_struct = struct("payload", {payload_len, payload}, "advA",  {6, -1}, "advA_hexStr",  {-1, -1}, "initA",       {6,          -1}, "initA_hexStr",       {-1, -1});
		setPDUField_1();
	# SCAN_REQ
	elseif pdutype_dec == 3
		payload_struct = struct("payload", {payload_len, payload}, "scanA", {6, -1}, "scanA_hexStr", {-1, -1}, "advA",        {6,          -1}, "advA_hexStr",        {-1, -1});
		setPDUField_3();
	# SCAN_RSP
	elseif pdutype_dec == 4
		payload_struct = struct("payload", {payload_len, payload}, "advA",  {6, -1}, "advA_hexStr",  {-1, -1}, "scanRspData", {field2_len, -1}, "scanRspData_hexStr", {-1, -1});
		setPDUField_4();
	# CONNECT_REQ
	elseif pdutype_dec == 5
		payload_struct = struct("payload", {payload_len, payload}, "initA", {6, -1}, "initA_hexStr", {-1, -1}, "advA",        {6,          -1}, "advA_hexStr",        {-1, -1}, "llData", {22, -1}, "llData_hexStr", {-1, -1});
		setPDUField_5();
	endif
endfunction

## Specific payload flipping functions
# ADV_IND, ADV_NONNCONN_IND, ADV_SCAN_IND
function setPDUField_026()
	global payload_struct;
	tS1 = payload_struct(1);
	tS2 = payload_struct(2);
	
	payload = tS2.payload;

	advA_len    = 8 * tS1.advA;
	advData_len = 8 * tS1.advData;
	tS2.advA    = flip(payload(1:advA_len));
	tS2.advData = flipOctects(payload(advA_len + 1:advA_len + advData_len));
	tS2.payload = cat(2, tS2.advA, tS2.advData);
	
	[tS2.advA_hexStr,    tS1.advA_hexStr]    = mat2hexStr(tS2.advA);
	[tS2.advData_hexStr, tS1.advData_hexStr] = mat2hexStr(tS2.advData);

	payload_struct(1) = tS1;
	payload_struct(2) = tS2;
endfunction

# ADV_DIRECT_IND
function setPDUField_1()
	global payload_struct;
	tS1 = payload_struct(1);
	tS2 = payload_strcut(2);

	payload = tS2.payload;

	advA_len  = 8 * tS1.advA;
	initA_len = 8 * tS1.initA;
	tS2.advA    = flip(payload(1:advA_len));
	tS2.initA   = flipOctects(payload(advA_len + 1:advA_len + initA_len));
	tS2.payload = cat(2, tS2.advA, tS2.initA);

	[tS2.advA_hexStr,  tS1.advA_hexStr]  = mat2hexStr(tS2.advA);
	[tS2.initA_hexStr, tS1.initA_hexStr] = mat2hexStr(tS2.initA);

	payload_struct(1) = tS1;
	payload_struct(2) = tS2;
endfunction

# SCAN_REQ
function setPDUField_3()
	global payload_struct;
	tS1 = payload_struct(1);
	tS2 = payload_struct(2);

	payload = tS2.payload;

	scanA_len = 8 * tS1.scanA;
	advA_len  = 8 * tS1.advA;
	tS2.scanA   = flip(payload(1:scanA_len));
	tS2.advA    = flipOctects(payload(scanA_len + 1:scanA_len + advA_len));
	tS2.payload = cat(2, tS2.scanA, tS2.advA);

	[tS2.scanA_hexStr, tS1.scanA_hexStr] = mat2hexStr(tS2.scanA);
	[tS2.advA_hexStr,  tS1.advA_hexStr]  = mat2hexStr(tS2.advA);

	payload_struct(1) = tS1;
	payload_struct(2) = tS2;
endfunction

# SCAN_RSP
function setPDUField_4()
	global payload_struct;
	tS1 = payload_struct(1);
	tS2 = payload_strcut(2);

	payload = tS2.payload;

	advA_len        = 8 * tS1.advA;
	scanRspData_len = 8 * tS1.scanRspData;
	tS2.advA        = flip(payload(1:advA_len));
	tS2.scanRspData = flipOctects(payload(advA_len + 1:advA_len + scanRspData_len));
	tS2.payload     = cat(2, tS2.advA, tS2.scanRspData);

	[tS2.advA_hexStr,        tS1.advA_hexStr]        = mat2hexStr(tS2.advA);
	[tS2.scanRspData_hexStr, tS1.scapRspData_hexStr] = mat2hexStr(tS2.scanRspData);

	payload_struct(1) = tS1;
	payload_struct(2) = tS2
endfunction

# CONNECT_REQ
function setPDUField_5()
	global payload_struct;
	tS1 = payload_struct(1);
	tS2 = payload_strcut(2);

	payload = payload_struct(2).payload;

	initA_len  = 8 * tS1.initA;
	advA_len   = 8 * tS1.advA;
	llData_len = 8 * tS1.llData;
	tS2.initA   = flip(payload(1:initA_len));
	tS2.advA    = flipOctects(payload(initA_len + 1:initA_len + advA_len));
	tS2.llData  = flipOctects(payload(initA_len + advA_len + 1:initA_len + advA_len + llData_len));
	tS2.payload = cat(2, tS2.initA, tS2.advA, tS2.llData);

	[tS2.initA_hexStr,  tS1.initA_hexStr]  = mat2hexStr(tS2.initA);
	[tS2.advA_hexStr,   tS1.advA_hexStr]   = mat2hexStr(tS2.advA);
	[tS2.llData_hexStr, tS1.llData_hexStr] = mat2hexStr(tS2.llData);

	payload_struct(1) = tS1;
	payload_struct(2) = tS2
endfunction

## Calculates crc & sets up struct
# crc_dec is calculated crc, crc_rx_dec is transmitted
function setCRC()
	global pduArray;
	global crcArray;
	global crc_struct;
	
	crc_rx     = flipOctects(crcArray);
	x55        = [0 1 0 1 0 1 0 1];
	crc        = cat(2, x55, x55, x55);
	crc_rx_dec = uint32(0);
	crc_dec    = uint32(0);
	crc_flag   = 0;

	for i = 1:length(pduArray)
		newBit  = bitxor(crc(1), pduArray(i));
		crc     = shift(crc, -1);
		crc(24) = newBit;
		crc(23) = bitxor(crc(23), newBit);
		crc(21) = bitxor(crc(21), newBit);
		crc(20) = bitxor(crc(20), newBit);
		crc(18) = bitxor(crc(18), newBit);
		crc(15) = bitxor(crc(15), newBit);
		crc(14) = bitxor(crc(14), newBit);
	endfor
	crc = flipOctects(crc);
	
	for i = 1:24
		crc_rx_dec = bitset(crc_rx_dec, 25 - i, crc_rx(i));
		crc_dec    = bitset(crc_dec,    25 - i, crc(i));
	endfor
	if crc_rx_dec == crc_dec
		crc_flag = 1;
	endif
	crc_struct = struct("crc", crc, "crc_rx", crc_rx, "crc_dec", crc_dec, "crc_rx_dec", crc_rx_dec, "crc_flag", crc_flag);
endfunction

# Print Everything
function printAll()
	global total_len_dec;
	global preamble;
	global access_address;
	global header_struct;
	global payload_struct;
	global crc_struct;

	printf("Preamble:         0x%X\n", preamble);
	printf("Access Address:   0x%X\n", access_address);

       	# Print header fields (Too much variety to use a for loop)
	printf("PDU Type:         %s\n",   header_struct.pdutype);
	printf("PDU Dec:          %d\n",   header_struct.pdutype_dec);
	printf("Reserved 1: ");
	disp(header_struct.reserved_1);
	printf("Tx_Add:           %d\n",   header_struct.tx_add);
	printf("Rx_Add:           %d\n",   header_struct.rx_add);
	printf("Payload Length:   %d\n",   header_struct.length);
	printf("Reserved 2: ");
	disp(header_struct.reserved_2);

	# Print payload fields
	for [val, key] = payload_struct(2)
		if index(key, "_hexStr") != 0
			printf("%s: 0x%s\n", key, val);
		endif
	endfor
	
	# Print crc fields
	for [val, key] = crc_struct(1)
		if index(key, "dec") != 0 || strcmp(key, "crc_flag") == 1
			printf("%s: 0x%X\n", key, val);
		endif
	endfor

	# Total Length
	printf("total_len_dec: %d\n", total_len_dec);
endfunction

## Actually call functions
#############################################
function main(dataName = "advertising_data_unknown", channelName = "channel", fileName = "frames.mat")
	arg_list = argv();
	if length(arg_list) > 0
		dataName = arg_list{1};
	endif
	if length(arg_list) > 1
		channelName = arg_list{2};
	endif
	if length(arg_list) > 2
		fileName = arg_list{3};
	endif
	loadFile(dataName, channelName, fileName);
	getPreamble();
	getAccessAddress();
	deWhitenPDUCRC();
	getPDUHeader();
	setPayloadFields();
	setCRC();
	printAll();
endfunction

main();
