422 lines
13 KiB
C
422 lines
13 KiB
C
/*
|
|
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership.
|
|
* The OpenAirInterface Software Alliance licenses this file to You under
|
|
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
|
|
* except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.openairinterface.org/?page_id=698
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*-------------------------------------------------------------------------------
|
|
* For more information about the OpenAirInterface (OAI) Software Alliance:
|
|
* contact@openairinterface.org
|
|
*/
|
|
|
|
/*
|
|
* PDCP test code
|
|
*
|
|
* Author: Baris Demiray <baris.demiray@eurecom.fr>
|
|
*/
|
|
|
|
#include "MAC_INTERFACE/vars.h"
|
|
#include "LAYER2/MAC/vars.h"
|
|
#include "UTIL/LOG/log.h"
|
|
#include "test_util.h"
|
|
#include "test_pdcp.h"
|
|
|
|
/*
|
|
* These are the PDCP entities that will be utilised
|
|
* throughout the test
|
|
*
|
|
* For pdcp_data_req() and pdcp_data_ind() these
|
|
* are passed and used
|
|
*/
|
|
pdcp_t pdcp_array[2];
|
|
|
|
/*
|
|
* TX list is the one we use to receive PDUs created by pdcp_data_req() and
|
|
* RX list is the one we use to pass these PDUs to pdcp_data_ind(). In test_pdcp_data_req()
|
|
* method every PDU created by pdcp_data_req() are first validated and then added to RX
|
|
* list after it's removed from TX list
|
|
*/
|
|
list_t test_pdu_tx_list;
|
|
list_t test_pdu_rx_list;
|
|
|
|
/*
|
|
* This is used by a number of methods to determine how
|
|
* many times a packet should be generated/a test run
|
|
*/
|
|
#define NUMBER_OF_TEST_PACKETS 10000
|
|
/*
|
|
* This is determined by the size of Sequence Number
|
|
* field and used to check sequence number synchronisation
|
|
*/
|
|
#define WINDOW_SIZE 4096 // 12-bit SN
|
|
|
|
/*
|
|
* Test configuration is STATEFUL. If you want to run DATA_REQUEST after TX_WINDOW
|
|
* then you have to reset/reinitialize PDCP entity state!
|
|
*
|
|
* TEST_RX_AND_TX_WINDOW Tests TX window code by repetitively asking for new TX
|
|
* sequence numbers without generating any packets
|
|
* And tests RX window code by supplying sequenced RX sequence
|
|
* numbers and asking RX window code to validate, without
|
|
* generating any packets
|
|
*/
|
|
#define TEST_RX_AND_TX_WINDOW 0
|
|
/*
|
|
* TEST_PDCP_DATA_REQUEST_AND_INDICATION Tests pdcp_data_req() method by repetitively asking it
|
|
* to create a PDCP PDU out of supplied SDU and parsing
|
|
* particular fields of relevant PDU lateron
|
|
* Afterwards, those PDUs created by pdcp_data_req() are
|
|
* passed to pdcp_data_ind()
|
|
*/
|
|
#define TEST_PDCP_DATA_REQUEST_AND_INDICATION 1
|
|
|
|
/*
|
|
* Buffer content used for testing purposes
|
|
*/
|
|
#define DUMMY_BUFFER ((unsigned char*)"123456789")
|
|
#define DUMMY_BUFFER_SIZE 10
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
unsigned char index = 0;
|
|
unsigned char test_result = 0;
|
|
|
|
/*
|
|
* Initialize memory allocator, list_t for test PDUs, and log generator
|
|
*/
|
|
pool_buffer_init();
|
|
list_init(&test_pdu_tx_list, NULL);
|
|
list_init(&test_pdu_rx_list, NULL);
|
|
logInit();
|
|
|
|
if (init_pdcp_entity(&pdcp_array[0]) == true && init_pdcp_entity(&pdcp_array[1]) == true)
|
|
msg("[TEST] PDCP entity initialization OK\n");
|
|
else {
|
|
msg("[TEST] Cannot initialize PDCP entities!\n");
|
|
return 1;
|
|
}
|
|
|
|
/* Initialize PDCP state variables */
|
|
for (index = 0; index < 2; ++index) {
|
|
if (pdcp_init_seq_numbers(&pdcp_array[index]) == false) {
|
|
msg("[TEST] Cannot initialize sequence numbers of PDCP entity %d!\n", index);
|
|
exit(1);
|
|
} else {
|
|
msg("[TEST] Sequence number state of PDCP entity %d is initialized\n", index);
|
|
}
|
|
}
|
|
|
|
#if TEST_RX_AND_TX_WINDOW
|
|
|
|
/* Test TX window */
|
|
if (test_tx_window() == false)
|
|
test_result = 1;
|
|
|
|
/* Test RX window */
|
|
if (test_rx_window() == false)
|
|
test_result = 1;
|
|
|
|
#endif
|
|
|
|
#if TEST_PDCP_DATA_REQUEST_AND_INDICATION
|
|
|
|
/* Test pdcp_data_req() method in pdcp.c */
|
|
if (test_pdcp_data_req() == false)
|
|
test_result = 1;
|
|
|
|
/* Test pdcp_data_ind() method in pdcp.c */
|
|
if (test_pdcp_data_ind() == false)
|
|
test_result = 1;
|
|
|
|
#endif
|
|
|
|
if (test_result) {
|
|
msg("\n\nOne or more tests failed!\n");
|
|
} else {
|
|
msg("\n\nAll tests are successfull!\n");
|
|
}
|
|
|
|
return test_result;
|
|
}
|
|
|
|
bool init_pdcp_entity(pdcp_t *pdcp_entity)
|
|
{
|
|
if (pdcp_entity == NULL)
|
|
return false;
|
|
|
|
/*
|
|
* Initialize sequence number state variables of relevant PDCP entity
|
|
*/
|
|
pdcp_entity->next_pdcp_tx_sn = 0;
|
|
pdcp_entity->next_pdcp_rx_sn = 0;
|
|
pdcp_entity->tx_hfn = 0;
|
|
pdcp_entity->rx_hfn = 0;
|
|
/* SN of the last PDCP SDU delivered to upper layers */
|
|
pdcp_entity->last_submitted_pdcp_rx_sn = 4095;
|
|
pdcp_entity->seq_num_size = 12;
|
|
|
|
msg("PDCP entity is initialized: Next TX: %d, Next Rx: %d, TX HFN: %d, RX HFN: %d, " \
|
|
"Last Submitted RX: %d, Sequence Number Size: %d\n", pdcp_entity->next_pdcp_tx_sn, \
|
|
pdcp_entity->next_pdcp_rx_sn, pdcp_entity->tx_hfn, pdcp_entity->rx_hfn, \
|
|
pdcp_entity->last_submitted_pdcp_rx_sn, pdcp_entity->seq_num_size);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool test_tx_window(void)
|
|
{
|
|
unsigned long index = 0;
|
|
|
|
for (index = 0; index < NUMBER_OF_TEST_PACKETS; ++index) {
|
|
u16 pseudo_tx_sn = pdcp_get_next_tx_seq_number(&pdcp_array[0]);
|
|
|
|
if (pseudo_tx_sn == index % WINDOW_SIZE)
|
|
msg("TX packet # %07lu seq # %04d hfn # %04d\n", index, pseudo_tx_sn, pdcp_array[0].tx_hfn);
|
|
else {
|
|
msg("TX packet is out-of-window!\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool test_rx_window(void)
|
|
{
|
|
unsigned long index = 0;
|
|
|
|
for (index = 0; index < NUMBER_OF_TEST_PACKETS; ++index) {
|
|
u16 pseudo_rx_sn = (index == 0) ? 0 : index % WINDOW_SIZE;
|
|
|
|
if (pdcp_is_rx_seq_number_valid(pseudo_rx_sn, &pdcp_array[1]) == true) {
|
|
msg("RX packet # %07lu seq # %04d last-submitted # %04d hfn # %04d\n", \
|
|
index, pdcp_array[1].next_pdcp_rx_sn, pdcp_array[1].last_submitted_pdcp_rx_sn, pdcp_array[1].rx_hfn);
|
|
} else {
|
|
msg("RX packet seq # %04lu is not valid!\n", index);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool test_pdcp_data_req(void)
|
|
{
|
|
unsigned char* pdcp_test_pdu_buffer = NULL;
|
|
unsigned char pdcp_test_pdu_buffer_size = DUMMY_BUFFER_SIZE + PDCP_USER_PLANE_DATA_PDU_LONG_SN_HEADER_SIZE;
|
|
unsigned int index = 0;
|
|
|
|
/*
|
|
* Create an unsigned char buffer out of uint8_t
|
|
*/
|
|
pdcp_test_pdu_buffer = (unsigned char*) calloc(1, pdcp_test_pdu_buffer_size);
|
|
|
|
if (pdcp_test_pdu_buffer == NULL) {
|
|
msg("Cannot allocate a buffer for test!\n");
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Ask PDCP to handle a number of data requests
|
|
*/
|
|
for (index = 0; index < NUMBER_OF_TEST_PACKETS; ++index) {
|
|
msg("\n\nAsking PDCP to send %d/%d SDU...\n", index+1, NUMBER_OF_TEST_PACKETS);
|
|
|
|
/*
|
|
* Reset test pdu buffer for every run
|
|
*/
|
|
memset(pdcp_test_pdu_buffer, 0x00, pdcp_test_pdu_buffer_size);
|
|
|
|
/*
|
|
* Ask PDCP to create a PDU with given buffer and enqueue it to `test_pdu_tx_list`
|
|
*/
|
|
if (pdcp_data_req(0, 0, 10, DUMMY_BUFFER, &pdcp_array[0], &test_pdu_tx_list) == true) {
|
|
msg("[TEST] Starting to dissect PDU created by PDCP...\n");
|
|
|
|
/*
|
|
* XXX uint8_t doesn't hold buffer size, how do we keep the size
|
|
* information if we pass uint8_ts via a linked list?
|
|
*/
|
|
|
|
if (pdcp_test_pdu_buffer_size == 0 ) {
|
|
msg("[TEST] PDU created by pdcp_data_req() is invalid!\n");
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Serialize incoming uint8_t into an unsigned character array
|
|
* and add removed PDU to RX list in order to use it in the next test
|
|
* (test_pdcp_data_ind())
|
|
*/
|
|
uint8_t* pdcp_test_pdu = list_remove_head(&test_pdu_tx_list);
|
|
memcpy(pdcp_test_pdu_buffer, pdcp_test_pdu->data, pdcp_test_pdu_buffer_size);
|
|
list_add_tail_eurecom(pdcp_test_pdu, &test_pdu_rx_list);
|
|
|
|
/*
|
|
* Verify that this is a data packet by checking
|
|
* if the first bit is 0x00 (PDCP_DATA_PDU)
|
|
*/
|
|
if (pdcp_test_pdu_buffer[0] & 0x80) {
|
|
msg("[TEST] First bit is not 0, which means this is not a Data PDU!\n");
|
|
return false;
|
|
} else {
|
|
msg("[TEST] First bit is 0 so this is a Data PDU, OK\n");
|
|
}
|
|
|
|
/*
|
|
* Verify that all three reserved bits are 0
|
|
*/
|
|
if ((pdcp_test_pdu_buffer[0] & 0x70) != 0) {
|
|
msg("[TEST] Reserved bits are not 0!\n");
|
|
return false;
|
|
} else {
|
|
msg("[TEST] Reserved bits are all 0, OK\n");
|
|
}
|
|
|
|
/*
|
|
* Parse and verify sequence number
|
|
*/
|
|
u16 sequence_number = pdcp_get_sequence_number_of_pdu_with_long_sn(pdcp_test_pdu_buffer);
|
|
msg("[TEST] Parsed sequence number is %04d\n", sequence_number);
|
|
|
|
if (sequence_number != index % WINDOW_SIZE) {
|
|
msg("[TEST] Sequence numbers are out-of-order!\n");
|
|
return false;
|
|
} else {
|
|
msg("[TEST] Sequence number is correct\n");
|
|
}
|
|
|
|
} else {
|
|
msg("[TEST] pdcp_data_req() returned FALSE!\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool test_pdcp_data_ind(void)
|
|
{
|
|
/*
|
|
* This is the list that pdcp_data_ind() takes to put pdcp_data_ind_header_t
|
|
* packets after it receives/validates PDUs and preprends them with pdcp_data_ind_header_t
|
|
* structure. Here, after this list is filled by pdcp_data_ind() we parse/validate
|
|
* every single element in the list
|
|
*/
|
|
list_t test_pdu_indication_list;
|
|
uint8_t* test_sdu = NULL;
|
|
/*
|
|
* pdcp_data_req() method prepended PDU header in front of DUMMY_BUFFER so
|
|
* the size should be 12 bytes
|
|
*/
|
|
unsigned char test_sdu_size = PDCP_USER_PLANE_DATA_PDU_LONG_SN_HEADER_SIZE + DUMMY_BUFFER_SIZE;
|
|
unsigned int index = 0;
|
|
|
|
/*
|
|
* Initialize linked list
|
|
*/
|
|
list_init(&test_pdu_indication_list, NULL);
|
|
|
|
/*
|
|
* Ask PDCP to handle a number of data indications
|
|
*/
|
|
msg("There are %d PDUs in the list\n", test_pdu_rx_list.nb_elements);
|
|
|
|
/*
|
|
* Traverse PDU list and pass each one of them to pdcp_data_ind()
|
|
*/
|
|
while (list_get_head(&test_pdu_rx_list) != NULL) {
|
|
msg("\n\nAsking PDCP to receive %d. SDU...\n", 1 + index++);
|
|
|
|
test_sdu = list_remove_head(&test_pdu_rx_list);
|
|
|
|
if (pdcp_data_ind(0, 0, test_sdu_size, test_sdu, &pdcp_array[0], &test_pdu_indication_list) == false) {
|
|
msg("[TEST] pdcp_data_ind() failed to handle data indication!\n");
|
|
return false;
|
|
} else {
|
|
msg("[TEST] pdcp_data_ind() succcessfuly handled data indication\n");
|
|
}
|
|
|
|
/*
|
|
* Parse/validate fields of SDU
|
|
*/
|
|
msg("[TEST] Starting to dissect SDU created by PDCP...\n");
|
|
/*
|
|
* Get pdcp_data_ind_header_t added by pdcp_data_ind()
|
|
*/
|
|
uint8_t* test_data_ind_header = list_remove_head(&test_pdu_indication_list);
|
|
|
|
if (test_data_ind_header == NULL) {
|
|
msg("[TEST] Data indication header is not valid!\n");
|
|
return false;
|
|
} else {
|
|
pdcp_data_ind_header_t* indication_header = (pdcp_data_ind_header_t*)test_data_ind_header->data;
|
|
|
|
/*
|
|
* Verify that radio bearer ID is correct (0)
|
|
*/
|
|
if (indication_header->rb_id == 0) {
|
|
msg("[TEST] Radio bearer ID is correct\n");
|
|
} else {
|
|
msg("[TEST] Radio bearer ID is not correct! (expected: 0, parsed: %d)\n", indication_header->rb_id);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Verify that SDU size is correct (DUMMY_BUFFER_SIZE)
|
|
*/
|
|
if (indication_header->data_size == DUMMY_BUFFER_SIZE + PDCP_USER_PLANE_DATA_PDU_LONG_SN_HEADER_SIZE) {
|
|
msg("[TEST] SDU size is correct\n");
|
|
} else {
|
|
msg("[TEST] SDU size is not correct! (expected: %d, parsed: %d)\n", DUMMY_BUFFER_SIZE, indication_header->data_size);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* XXX Verify `indication_header->inst` when you know what it is
|
|
*/
|
|
}
|
|
|
|
/*
|
|
* XXX PDCP PDU header should also be checked here
|
|
*/
|
|
|
|
/*
|
|
* Verify that serialised data is the stream we've supplied
|
|
*
|
|
* Data comes after `pdcp_data_ind_header_t` and `pdcp_user_plane_data_pdu_header_with_long_sn`
|
|
*/
|
|
unsigned char data_index = sizeof(pdcp_data_ind_header_t) + PDCP_USER_PLANE_DATA_PDU_LONG_SN_HEADER_SIZE;
|
|
|
|
if (memcmp(DUMMY_BUFFER, (unsigned char*)&(test_data_ind_header->data[data_index]), DUMMY_BUFFER_SIZE) == 0) {
|
|
msg("[TEST] Data payload of pdcp_data_ind_header_t matches with the stream we sent\n");
|
|
|
|
} else {
|
|
msg("[TEST] Data payload of pdcp_data_ind_header_t does not match with the stream we sent!\n");
|
|
|
|
/*
|
|
* Print octets of both streams
|
|
* XXX This could be a method in test_util.h
|
|
*/
|
|
print_byte_stream("[TEST] TXed data: ", DUMMY_BUFFER, DUMMY_BUFFER_SIZE);
|
|
print_byte_stream("[TEST] RXed data: ", (unsigned char*)&(test_data_ind_header->data[data_index]), DUMMY_BUFFER_SIZE);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|