diff --git a/CMakeLists.txt b/CMakeLists.txt index d2de517..f2fe63e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,6 +82,16 @@ add_executable(cvf-listener "examples/cvf/cvf-listener.c") target_include_directories(cvf-listener PRIVATE "examples" "include") target_link_libraries(cvf-listener open1722 open1722examples) +# Hello-world talker app +add_executable(hello-world-talker "examples/hello-world/hello-world-talker.c") +target_include_directories(hello-world-talker PRIVATE "examples" "include") +target_link_libraries(hello-world-talker open1722 open1722examples) + +# Hello-world listener app +add_executable(hello-world-listener "examples/hello-world/hello-world-listener.c") +target_include_directories(hello-world-listener PRIVATE "examples" "include") +target_link_libraries(hello-world-listener open1722 open1722examples) + #### Tests #################################################################### enable_testing() diff --git a/examples/hello-world/README.md b/examples/hello-world/README.md new file mode 100644 index 0000000..4b0b333 --- /dev/null +++ b/examples/hello-world/README.md @@ -0,0 +1,35 @@ +# Hello World! + +The hello-world applications provide an exemplary set of listener and talker based on the IEEE 1722 protocol. +These applications are based on the Genereal Purpose Control (GPC) subtype of the AVTP Control Formats. + +## hello-world-talker +_hello-world-talker_ sends the string "Hello World!" packed in a GPC frame of AVTP Control Format. +The string being sent can be changed using the ```--message``` option. +Options are available to send the IEEE 1722 frame as layer 2 frame or as a UDP segment. + +To run over Ethernet +``` +$ ./hello-world-talker +``` + +To run over UDP +``` +$ ./hello-world-talker --udp : +``` + +By running wireshark on an appropriate interface, IEEE 1722 frames can be observed. + + +## hello-world-listener +_hello-world-listener_ receives the message string sent by the talker and prints it out on the console. The listener shall be operated in the same configuration (e.g. layer 2 or UDP) as the talker to ensure compatability. + +To run over Ethernet +``` +$ ./hello-world-listener +``` + +To run over UDP +``` +$ ./hello-world-talker --udp <--port= +``` \ No newline at end of file diff --git a/examples/hello-world/hello-world-listener.c b/examples/hello-world/hello-world-listener.c new file mode 100644 index 0000000..e33c27a --- /dev/null +++ b/examples/hello-world/hello-world-listener.c @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2024, COVESA + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of COVESA nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/common.h" +#include "avtp/Udp.h" +#include "avtp/acf/Ntscf.h" +#include "avtp/acf/Tscf.h" +#include "avtp/acf/Common.h" +#include "avtp/acf/Gpc.h" +#include "avtp/CommonHeader.h" + +#define MAX_PDU_SIZE 1500 +#define MAX_MSG_SIZE 100 + +static char ifname[IFNAMSIZ]; +static uint8_t macaddr[ETH_ALEN]; +static uint8_t use_udp; +static uint32_t udp_port = 17220; + +static struct argp_option options[] = { + {"port", 'p', "UDP_PORT", 0, "UDP Port to listen on if UDP enabled"}, + {"udp", 'u', 0, 0, "Use UDP"}, + {"dst-mac-address", 0, 0, OPTION_DOC, "Stream destination MAC address (If Ethernet)"}, + {"ifname", 0, 0, OPTION_DOC, "Network interface (If Ethernet)" }, + { 0 } +}; + +static error_t parser(int key, char *arg, struct argp_state *state) +{ + int res; + + switch (key) { + case 'p': + udp_port = atoi(arg); + break; + case 'u': + use_udp = 1; + break; + + case ARGP_KEY_NO_ARGS: + break; + + case ARGP_KEY_ARG: + + if(state->argc < 2){ + argp_usage(state); + } + + if(!use_udp){ + + strncpy(ifname, arg, sizeof(ifname) - 1); + + if(state->next < state->argc) + { + res = sscanf(state->argv[state->next], "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &macaddr[0], &macaddr[1], &macaddr[2], + &macaddr[3], &macaddr[4], &macaddr[5]); + if (res != 6) { + fprintf(stderr, "Invalid MAC address\n\n"); + argp_usage(state); + } + state->next += 1; + } + } + + break; + } + + return 0; +} + +static struct argp argp = { options, parser, 0, 0}; + +int main(int argc, char *argv[]) +{ + int sk_fd, res; + uint64_t msg_length, proc_bytes = 0, msg_proc_bytes = 0; + uint64_t udp_seq_num, subtype, flag, acf_type, pdu_length; + uint8_t pdu[MAX_PDU_SIZE]; + uint8_t *cf_pdu, *acf_pdu, *udp_pdu; + uint64_t gpc_code; + char *recd_msg; + + argp_parse(&argp, argc, argv, 0, NULL, NULL); + + if (use_udp) { + sk_fd = create_listener_socket_udp(udp_port); + } else { + sk_fd = create_listener_socket(ifname, macaddr, ETH_P_TSN); + } + + if (sk_fd < 0) + return 1; + + while (1) { + proc_bytes = 0; + + res = recv(sk_fd, pdu, MAX_PDU_SIZE, 0); + if (res < 0 || res > MAX_PDU_SIZE) { + perror("Failed to receive data"); + goto err; + } + + // If UDP is used the packets starts with an encapsulation number + if (use_udp) { + udp_pdu = pdu; + Avtp_Udp_GetField((Avtp_Udp_t *)udp_pdu, AVTP_UDP_FIELD_ENCAPSULATION_SEQ_NO, &udp_seq_num); + cf_pdu = pdu + AVTP_UDP_HEADER_LEN; + proc_bytes += AVTP_UDP_HEADER_LEN; + } else { + cf_pdu = pdu; + } + + // Check if the packet is a control format packet (i.e. NTSCF or TSCF) + res = Avtp_CommonHeader_GetField((Avtp_CommonHeader_t*)cf_pdu, AVTP_COMMON_HEADER_FIELD_SUBTYPE, &subtype); + if (res < 0) { + fprintf(stderr, "Failed to get subtype field: %d\n", res); + goto err; + } + if(subtype == AVTP_SUBTYPE_TSCF){ + proc_bytes += AVTP_TSCF_HEADER_LEN; + Avtp_Tscf_GetField((Avtp_Tscf_t*)cf_pdu, AVTP_TSCF_FIELD_STREAM_DATA_LENGTH, (uint64_t *) &msg_length); + }else{ + proc_bytes += AVTP_NTSCF_HEADER_LEN; + Avtp_Ntscf_GetField((Avtp_Ntscf_t*)cf_pdu, AVTP_NTSCF_FIELD_NTSCF_DATA_LENGTH, (uint64_t *) &msg_length); + } + + // Check if the control packet payload is a ACF GPC. + acf_pdu = &pdu[proc_bytes]; + Avtp_AcfCommon_GetField((Avtp_AcfCommon_t*)acf_pdu, AVTP_ACF_FIELD_ACF_MSG_TYPE, &acf_type); + if (acf_type != AVTP_ACF_TYPE_GPC) { + fprintf(stderr, "ACF type mismatch: expected %u, got %lu\n", + AVTP_ACF_TYPE_GPC, acf_type); + continue; + } + + // Parse the GPC Packet and print contents on the STDOUT + Avtp_Gpc_GetField((Avtp_Gpc_t*)acf_pdu, AVTP_GPC_FIELD_GPC_MSG_ID, &gpc_code); + Avtp_Gpc_GetField((Avtp_Gpc_t*)acf_pdu, AVTP_GPC_FIELD_ACF_MSG_LENGTH, &pdu_length); + if (pdu_length * 4 <= MAX_MSG_SIZE) { + recd_msg = acf_pdu + AVTP_GPC_HEADER_LEN; + printf("%s : GPC Code %ld\n", recd_msg, gpc_code); + } + } + + return 0; + +err: + close(sk_fd); + return 1; + +} \ No newline at end of file diff --git a/examples/hello-world/hello-world-talker.c b/examples/hello-world/hello-world-talker.c new file mode 100644 index 0000000..cf7a9b8 --- /dev/null +++ b/examples/hello-world/hello-world-talker.c @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2024, COVESA + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of COVESA nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "common/common.h" +#include "avtp/Udp.h" +#include "avtp/acf/Ntscf.h" +#include "avtp/acf/Tscf.h" +#include "avtp/acf/Gpc.h" +#include "avtp/CommonHeader.h" + +#define MAX_PDU_SIZE 1500 +#define MAX_MSG_SIZE 100 +#define STREAM_ID 0xAABBCCDDEEFF0001 +#define ARGPARSE_OPTION_MSG 500 +#define GPC_CODE 256 + +static char ifname[IFNAMSIZ]; +static uint8_t macaddr[ETH_ALEN]; +static char message_string[MAX_MSG_SIZE] = "Hello World!\0"; +static uint8_t ip_addr[sizeof(struct in_addr)]; +static uint32_t udp_port=17220; +static int priority = -1; +static uint8_t seq_num = 0; +static uint32_t udp_seq_num = 0; +static uint8_t use_tscf; +static uint8_t use_udp; + +static error_t parser(int key, char *arg, struct argp_state *state) +{ + int res; + char ip_addr_str[100]; + + switch (key) { + case 't': + use_tscf = 1; + break; + case 'u': + use_udp = 1; + break; + case ARGPARSE_OPTION_MSG: + strncpy(message_string, arg, MAX_MSG_SIZE); + break; + case ARGP_KEY_NO_ARGS: + argp_usage(state); + + case ARGP_KEY_ARG: + if(state->argc < 2){ + argp_usage(state); + } + if(!use_udp){ + + strncpy(ifname, arg, sizeof(ifname) - 1); + + if(state->next < state->argc) + { + res = sscanf(state->argv[state->next], "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &macaddr[0], &macaddr[1], &macaddr[2], + &macaddr[3], &macaddr[4], &macaddr[5]); + if (res != 6) { + fprintf(stderr, "Invalid MAC address\n\n"); + argp_usage(state); + } + state->next += 1; + } + + } else { + res = sscanf(arg, "%[^:]:%d", ip_addr_str, &udp_port); + if (!res) { + fprintf(stderr, "Invalid IP address or port\n\n"); + argp_usage(state); + } + res = inet_pton(AF_INET, ip_addr_str, ip_addr); + if (!res) { + fprintf(stderr, "Invalid IP address\n\n"); + argp_usage(state); + } + } + + break; + } + + return 0; +} + +static struct argp_option options[] = { + {"tscf", 't', 0, 0, "Use TSCF"}, + {"udp", 'u', 0, 0, "Use UDP" }, + {"message", ARGPARSE_OPTION_MSG, "MSG_STR", 0, "String message to send over IEEE 1722"}, + {"ifname", 0, 0, OPTION_DOC, "Network interface (If Ethernet)"}, + {"dst-mac-address", 0, 0, OPTION_DOC, "Stream destination MAC address (If Ethernet)"}, + {"dst-nw-address:port", 0, 0, OPTION_DOC, "Stream destination network address and port (If UDP)"}, + { 0 } +}; + +static struct argp argp = { options, parser, 0, 0}; + +static int init_cf_pdu(uint8_t* pdu) +{ + int res; + if (use_tscf) { + Avtp_Tscf_t* tscf_pdu = (Avtp_Tscf_t*) pdu; + memset(tscf_pdu, 0, AVTP_TSCF_HEADER_LEN); + Avtp_Tscf_Init(tscf_pdu); + Avtp_Tscf_SetField(tscf_pdu, AVTP_TSCF_FIELD_TU, 0U); + Avtp_Tscf_SetField(tscf_pdu, AVTP_TSCF_FIELD_SEQUENCE_NUM, seq_num++); + Avtp_Tscf_SetField(tscf_pdu, AVTP_TSCF_FIELD_STREAM_ID, STREAM_ID); + res = AVTP_TSCF_HEADER_LEN; + } else { + Avtp_Ntscf_t* ntscf_pdu = (Avtp_Ntscf_t*) pdu; + memset(ntscf_pdu, 0, AVTP_NTSCF_HEADER_LEN); + Avtp_Ntscf_Init(ntscf_pdu); + Avtp_Ntscf_SetField(ntscf_pdu, AVTP_NTSCF_FIELD_SEQUENCE_NUM, seq_num++); + Avtp_Ntscf_SetField(ntscf_pdu, AVTP_NTSCF_FIELD_STREAM_ID, STREAM_ID); + res = AVTP_NTSCF_HEADER_LEN; + } + return res; +} + +static int update_pdu_length(uint8_t* pdu, uint64_t length) +{ + if (use_tscf) { + uint64_t payloadLen = length - AVTP_TSCF_HEADER_LEN; + Avtp_Tscf_SetField((Avtp_Tscf_t*)pdu, AVTP_TSCF_FIELD_STREAM_DATA_LENGTH, payloadLen); + } else { + uint64_t payloadLen = length - AVTP_NTSCF_HEADER_LEN; + Avtp_Ntscf_SetField((Avtp_Ntscf_t*)pdu, AVTP_NTSCF_FIELD_NTSCF_DATA_LENGTH, payloadLen); + } + return 0; +} + +static int prepare_acf_packet(uint8_t* acf_pdu, uint64_t gpc_code, + uint8_t* payload, uint16_t length) { + + Avtp_Gpc_t* pdu = (Avtp_Gpc_t*) acf_pdu; + + // Clear bits + memset(pdu, 0, AVTP_GPC_HEADER_LEN); + uint8_t acf_length = (AVTP_GPC_HEADER_LEN + length)/4; + if (length % 4) acf_length++; + + // Prepare ACF PDU for CAN + Avtp_Gpc_Init(pdu); + Avtp_Gpc_SetField(pdu, AVTP_GPC_FIELD_GPC_MSG_ID, gpc_code); + Avtp_Gpc_SetField(pdu, AVTP_GPC_FIELD_ACF_MSG_LENGTH, acf_length); + memcpy(acf_pdu+AVTP_GPC_HEADER_LEN, payload, length); + memset(acf_pdu+AVTP_GPC_HEADER_LEN+length, 0, acf_length*4 - length); + + return acf_length*4; +} + +static int update_cf_length(uint8_t* cf_pdu, uint64_t length) +{ + if (use_tscf) { + uint64_t payloadLen = length - AVTP_TSCF_HEADER_LEN; + Avtp_Tscf_SetField((Avtp_Tscf_t*)cf_pdu, AVTP_TSCF_FIELD_STREAM_DATA_LENGTH, payloadLen); + } else { + uint64_t payloadLen = length - AVTP_NTSCF_HEADER_LEN; + Avtp_Ntscf_SetField((Avtp_Ntscf_t*)cf_pdu, AVTP_NTSCF_FIELD_NTSCF_DATA_LENGTH, payloadLen); + } + return 0; +} + +int main(int argc, char *argv[]) +{ + + int fd, res; + struct sockaddr_ll sk_ll_addr; + struct sockaddr_in sk_udp_addr; + uint8_t pdu[MAX_PDU_SIZE]; + uint16_t pdu_length, cf_length; + uint64_t gpc_code = 0; + + argp_parse(&argp, argc, argv, 0, NULL, NULL); + + // Create an appropriate talker socket: UDP or Ethernet raw + // Setup the socket for sending to the destination + if (use_udp) { + fd = create_talker_socket_udp(priority); + if (fd < 0) return fd; + + res = setup_udp_socket_address((struct in_addr*) ip_addr, + udp_port, &sk_udp_addr); + } else { + fd = create_talker_socket(priority); + if (fd < 0) return fd; + res = setup_socket_address(fd, ifname, macaddr, + ETH_P_TSN, &sk_ll_addr); + } + if (res < 0) goto err; + + // Sending loop + for(;;) { + + // Pack into control formats + uint8_t *cf_pdu; + pdu_length = 0; + cf_length = 0; + + // Usage of UDP means the PDU needs an encapsulation number + if (use_udp) { + Avtp_Udp_t *udp_pdu = (Avtp_Udp_t *) pdu; + Avtp_Udp_SetField(udp_pdu, AVTP_UDP_FIELD_ENCAPSULATION_SEQ_NO, + udp_seq_num++); + pdu_length += sizeof(Avtp_Udp_t); + } + + // Create the CF packet first + cf_pdu = pdu + pdu_length; + res = init_cf_pdu(cf_pdu); + if (res < 0) + goto err; + pdu_length += res; + cf_length += res; + + // Creation of the ACF Packet + uint8_t* acf_pdu = pdu + pdu_length; + res = prepare_acf_packet(acf_pdu, gpc_code++, (uint8_t*)message_string, + strlen((char *)message_string)); + if (res < 0) goto err; + pdu_length += res; + cf_length += res; + + res = update_cf_length(cf_pdu, cf_length); + if (res < 0) + goto err; + + if (use_udp) { + res = sendto(fd, pdu, pdu_length, 0, + (struct sockaddr *) &sk_udp_addr, sizeof(sk_udp_addr)); + } else { + res = sendto(fd, pdu, pdu_length, 0, + (struct sockaddr *) &sk_ll_addr, sizeof(sk_ll_addr)); + } + if (res < 0) { + perror("Failed to send data"); + goto err; + } + sleep(1); + } + +err: + close(fd); + return 1; + +} \ No newline at end of file