Skip to content

Commit 740951e

Browse files
authoredAug 14, 2024··
Merge pull request #35 from boschglobal/app/hello-world
Added a hello-world example for Open1722
2 parents 161c3b5 + c96f77d commit 740951e

File tree

4 files changed

+519
-0
lines changed

4 files changed

+519
-0
lines changed
 

‎CMakeLists.txt

+10
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,16 @@ add_executable(cvf-listener "examples/cvf/cvf-listener.c")
8282
target_include_directories(cvf-listener PRIVATE "examples" "include")
8383
target_link_libraries(cvf-listener open1722 open1722examples)
8484

85+
# Hello-world talker app
86+
add_executable(hello-world-talker "examples/hello-world/hello-world-talker.c")
87+
target_include_directories(hello-world-talker PRIVATE "examples" "include")
88+
target_link_libraries(hello-world-talker open1722 open1722examples)
89+
90+
# Hello-world listener app
91+
add_executable(hello-world-listener "examples/hello-world/hello-world-listener.c")
92+
target_include_directories(hello-world-listener PRIVATE "examples" "include")
93+
target_link_libraries(hello-world-listener open1722 open1722examples)
94+
8595
#### Tests ####################################################################
8696

8797
enable_testing()

‎examples/hello-world/README.md

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Hello World!
2+
3+
The hello-world applications provide an exemplary set of listener and talker based on the IEEE 1722 protocol.
4+
These applications are based on the Genereal Purpose Control (GPC) subtype of the AVTP Control Formats.
5+
6+
## hello-world-talker
7+
_hello-world-talker_ sends the string "Hello World!" packed in a GPC frame of AVTP Control Format.
8+
The string being sent can be changed using the ```--message``` option.
9+
Options are available to send the IEEE 1722 frame as layer 2 frame or as a UDP segment.
10+
11+
To run over Ethernet
12+
```
13+
$ ./hello-world-talker <Ethernet interface name> <Destination MAC address>
14+
```
15+
16+
To run over UDP
17+
```
18+
$ ./hello-world-talker --udp <destination IP>:<Port>
19+
```
20+
21+
By running wireshark on an appropriate interface, IEEE 1722 frames can be observed.
22+
23+
24+
## hello-world-listener
25+
_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.
26+
27+
To run over Ethernet
28+
```
29+
$ ./hello-world-listener <Ethernet interface name>
30+
```
31+
32+
To run over UDP
33+
```
34+
$ ./hello-world-talker --udp <--port=<UDP port>
35+
```
+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/*
2+
* Copyright (c) 2024, COVESA
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are met:
6+
*
7+
* * Redistributions of source code must retain the above copyright notice,
8+
* this list of conditions and the following disclaimer.
9+
* * Redistributions in binary form must reproduce the above copyright
10+
* notice, this list of conditions and the following disclaimer in the
11+
* documentation and/or other materials provided with the distribution.
12+
* * Neither the name of COVESA nor the names of its contributors may be
13+
* used to endorse or promote products derived from this software without
14+
* specific prior written permission.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26+
*
27+
* SPDX-License-Identifier: BSD-3-Clause
28+
*/
29+
30+
#include <argp.h>
31+
#include <stdlib.h>
32+
#include <linux/if_ether.h>
33+
#include <linux/if_packet.h>
34+
#include <linux/if.h>
35+
#include <stdint.h>
36+
#include <string.h>
37+
#include <unistd.h>
38+
#include <inttypes.h>
39+
40+
#include "common/common.h"
41+
#include "avtp/Udp.h"
42+
#include "avtp/acf/Ntscf.h"
43+
#include "avtp/acf/Tscf.h"
44+
#include "avtp/acf/Common.h"
45+
#include "avtp/acf/Gpc.h"
46+
#include "avtp/CommonHeader.h"
47+
48+
#define MAX_PDU_SIZE 1500
49+
#define MAX_MSG_SIZE 100
50+
51+
static char ifname[IFNAMSIZ];
52+
static uint8_t macaddr[ETH_ALEN];
53+
static uint8_t use_udp;
54+
static uint32_t udp_port = 17220;
55+
56+
static struct argp_option options[] = {
57+
{"port", 'p', "UDP_PORT", 0, "UDP Port to listen on if UDP enabled"},
58+
{"udp", 'u', 0, 0, "Use UDP"},
59+
{"dst-mac-address", 0, 0, OPTION_DOC, "Stream destination MAC address (If Ethernet)"},
60+
{"ifname", 0, 0, OPTION_DOC, "Network interface (If Ethernet)" },
61+
{ 0 }
62+
};
63+
64+
static error_t parser(int key, char *arg, struct argp_state *state)
65+
{
66+
int res;
67+
68+
switch (key) {
69+
case 'p':
70+
udp_port = atoi(arg);
71+
break;
72+
case 'u':
73+
use_udp = 1;
74+
break;
75+
76+
case ARGP_KEY_NO_ARGS:
77+
break;
78+
79+
case ARGP_KEY_ARG:
80+
81+
if(state->argc < 2){
82+
argp_usage(state);
83+
}
84+
85+
if(!use_udp){
86+
87+
strncpy(ifname, arg, sizeof(ifname) - 1);
88+
89+
if(state->next < state->argc)
90+
{
91+
res = sscanf(state->argv[state->next], "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
92+
&macaddr[0], &macaddr[1], &macaddr[2],
93+
&macaddr[3], &macaddr[4], &macaddr[5]);
94+
if (res != 6) {
95+
fprintf(stderr, "Invalid MAC address\n\n");
96+
argp_usage(state);
97+
}
98+
state->next += 1;
99+
}
100+
}
101+
102+
break;
103+
}
104+
105+
return 0;
106+
}
107+
108+
static struct argp argp = { options, parser, 0, 0};
109+
110+
int main(int argc, char *argv[])
111+
{
112+
int sk_fd, res;
113+
uint64_t msg_length, proc_bytes = 0, msg_proc_bytes = 0;
114+
uint64_t udp_seq_num, subtype, flag, acf_type, pdu_length;
115+
uint8_t pdu[MAX_PDU_SIZE];
116+
uint8_t *cf_pdu, *acf_pdu, *udp_pdu;
117+
uint64_t gpc_code;
118+
char *recd_msg;
119+
120+
argp_parse(&argp, argc, argv, 0, NULL, NULL);
121+
122+
if (use_udp) {
123+
sk_fd = create_listener_socket_udp(udp_port);
124+
} else {
125+
sk_fd = create_listener_socket(ifname, macaddr, ETH_P_TSN);
126+
}
127+
128+
if (sk_fd < 0)
129+
return 1;
130+
131+
while (1) {
132+
proc_bytes = 0;
133+
134+
res = recv(sk_fd, pdu, MAX_PDU_SIZE, 0);
135+
if (res < 0 || res > MAX_PDU_SIZE) {
136+
perror("Failed to receive data");
137+
goto err;
138+
}
139+
140+
// If UDP is used the packets starts with an encapsulation number
141+
if (use_udp) {
142+
udp_pdu = pdu;
143+
Avtp_Udp_GetField((Avtp_Udp_t *)udp_pdu, AVTP_UDP_FIELD_ENCAPSULATION_SEQ_NO, &udp_seq_num);
144+
cf_pdu = pdu + AVTP_UDP_HEADER_LEN;
145+
proc_bytes += AVTP_UDP_HEADER_LEN;
146+
} else {
147+
cf_pdu = pdu;
148+
}
149+
150+
// Check if the packet is a control format packet (i.e. NTSCF or TSCF)
151+
res = Avtp_CommonHeader_GetField((Avtp_CommonHeader_t*)cf_pdu, AVTP_COMMON_HEADER_FIELD_SUBTYPE, &subtype);
152+
if (res < 0) {
153+
fprintf(stderr, "Failed to get subtype field: %d\n", res);
154+
goto err;
155+
}
156+
if(subtype == AVTP_SUBTYPE_TSCF){
157+
proc_bytes += AVTP_TSCF_HEADER_LEN;
158+
Avtp_Tscf_GetField((Avtp_Tscf_t*)cf_pdu, AVTP_TSCF_FIELD_STREAM_DATA_LENGTH, (uint64_t *) &msg_length);
159+
}else{
160+
proc_bytes += AVTP_NTSCF_HEADER_LEN;
161+
Avtp_Ntscf_GetField((Avtp_Ntscf_t*)cf_pdu, AVTP_NTSCF_FIELD_NTSCF_DATA_LENGTH, (uint64_t *) &msg_length);
162+
}
163+
164+
// Check if the control packet payload is a ACF GPC.
165+
acf_pdu = &pdu[proc_bytes];
166+
Avtp_AcfCommon_GetField((Avtp_AcfCommon_t*)acf_pdu, AVTP_ACF_FIELD_ACF_MSG_TYPE, &acf_type);
167+
if (acf_type != AVTP_ACF_TYPE_GPC) {
168+
fprintf(stderr, "ACF type mismatch: expected %u, got %lu\n",
169+
AVTP_ACF_TYPE_GPC, acf_type);
170+
continue;
171+
}
172+
173+
// Parse the GPC Packet and print contents on the STDOUT
174+
Avtp_Gpc_GetField((Avtp_Gpc_t*)acf_pdu, AVTP_GPC_FIELD_GPC_MSG_ID, &gpc_code);
175+
Avtp_Gpc_GetField((Avtp_Gpc_t*)acf_pdu, AVTP_GPC_FIELD_ACF_MSG_LENGTH, &pdu_length);
176+
if (pdu_length * 4 <= MAX_MSG_SIZE) {
177+
recd_msg = acf_pdu + AVTP_GPC_HEADER_LEN;
178+
printf("%s : GPC Code %ld\n", recd_msg, gpc_code);
179+
}
180+
}
181+
182+
return 0;
183+
184+
err:
185+
close(sk_fd);
186+
return 1;
187+
188+
}
+286
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
/*
2+
* Copyright (c) 2024, COVESA
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are met:
6+
*
7+
* * Redistributions of source code must retain the above copyright notice,
8+
* this list of conditions and the following disclaimer.
9+
* * Redistributions in binary form must reproduce the above copyright
10+
* notice, this list of conditions and the following disclaimer in the
11+
* documentation and/or other materials provided with the distribution.
12+
* * Neither the name of COVESA nor the names of its contributors may be
13+
* used to endorse or promote products derived from this software without
14+
* specific prior written permission.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26+
*
27+
* SPDX-License-Identifier: BSD-3-Clause
28+
*/
29+
30+
#include <linux/if_packet.h>
31+
#include <linux/if.h>
32+
#include <linux/if_ether.h>
33+
#include <linux/can.h>
34+
#include <linux/can/raw.h>
35+
#include <sys/ioctl.h>
36+
#include <time.h>
37+
38+
#include <arpa/inet.h>
39+
#include <stdlib.h>
40+
#include <argp.h>
41+
#include <string.h>
42+
#include <unistd.h>
43+
#include <stdio.h>
44+
45+
#include "common/common.h"
46+
#include "avtp/Udp.h"
47+
#include "avtp/acf/Ntscf.h"
48+
#include "avtp/acf/Tscf.h"
49+
#include "avtp/acf/Gpc.h"
50+
#include "avtp/CommonHeader.h"
51+
52+
#define MAX_PDU_SIZE 1500
53+
#define MAX_MSG_SIZE 100
54+
#define STREAM_ID 0xAABBCCDDEEFF0001
55+
#define ARGPARSE_OPTION_MSG 500
56+
#define GPC_CODE 256
57+
58+
static char ifname[IFNAMSIZ];
59+
static uint8_t macaddr[ETH_ALEN];
60+
static char message_string[MAX_MSG_SIZE] = "Hello World!\0";
61+
static uint8_t ip_addr[sizeof(struct in_addr)];
62+
static uint32_t udp_port=17220;
63+
static int priority = -1;
64+
static uint8_t seq_num = 0;
65+
static uint32_t udp_seq_num = 0;
66+
static uint8_t use_tscf;
67+
static uint8_t use_udp;
68+
69+
static error_t parser(int key, char *arg, struct argp_state *state)
70+
{
71+
int res;
72+
char ip_addr_str[100];
73+
74+
switch (key) {
75+
case 't':
76+
use_tscf = 1;
77+
break;
78+
case 'u':
79+
use_udp = 1;
80+
break;
81+
case ARGPARSE_OPTION_MSG:
82+
strncpy(message_string, arg, MAX_MSG_SIZE);
83+
break;
84+
case ARGP_KEY_NO_ARGS:
85+
argp_usage(state);
86+
87+
case ARGP_KEY_ARG:
88+
if(state->argc < 2){
89+
argp_usage(state);
90+
}
91+
if(!use_udp){
92+
93+
strncpy(ifname, arg, sizeof(ifname) - 1);
94+
95+
if(state->next < state->argc)
96+
{
97+
res = sscanf(state->argv[state->next], "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
98+
&macaddr[0], &macaddr[1], &macaddr[2],
99+
&macaddr[3], &macaddr[4], &macaddr[5]);
100+
if (res != 6) {
101+
fprintf(stderr, "Invalid MAC address\n\n");
102+
argp_usage(state);
103+
}
104+
state->next += 1;
105+
}
106+
107+
} else {
108+
res = sscanf(arg, "%[^:]:%d", ip_addr_str, &udp_port);
109+
if (!res) {
110+
fprintf(stderr, "Invalid IP address or port\n\n");
111+
argp_usage(state);
112+
}
113+
res = inet_pton(AF_INET, ip_addr_str, ip_addr);
114+
if (!res) {
115+
fprintf(stderr, "Invalid IP address\n\n");
116+
argp_usage(state);
117+
}
118+
}
119+
120+
break;
121+
}
122+
123+
return 0;
124+
}
125+
126+
static struct argp_option options[] = {
127+
{"tscf", 't', 0, 0, "Use TSCF"},
128+
{"udp", 'u', 0, 0, "Use UDP" },
129+
{"message", ARGPARSE_OPTION_MSG, "MSG_STR", 0, "String message to send over IEEE 1722"},
130+
{"ifname", 0, 0, OPTION_DOC, "Network interface (If Ethernet)"},
131+
{"dst-mac-address", 0, 0, OPTION_DOC, "Stream destination MAC address (If Ethernet)"},
132+
{"dst-nw-address:port", 0, 0, OPTION_DOC, "Stream destination network address and port (If UDP)"},
133+
{ 0 }
134+
};
135+
136+
static struct argp argp = { options, parser, 0, 0};
137+
138+
static int init_cf_pdu(uint8_t* pdu)
139+
{
140+
int res;
141+
if (use_tscf) {
142+
Avtp_Tscf_t* tscf_pdu = (Avtp_Tscf_t*) pdu;
143+
memset(tscf_pdu, 0, AVTP_TSCF_HEADER_LEN);
144+
Avtp_Tscf_Init(tscf_pdu);
145+
Avtp_Tscf_SetField(tscf_pdu, AVTP_TSCF_FIELD_TU, 0U);
146+
Avtp_Tscf_SetField(tscf_pdu, AVTP_TSCF_FIELD_SEQUENCE_NUM, seq_num++);
147+
Avtp_Tscf_SetField(tscf_pdu, AVTP_TSCF_FIELD_STREAM_ID, STREAM_ID);
148+
res = AVTP_TSCF_HEADER_LEN;
149+
} else {
150+
Avtp_Ntscf_t* ntscf_pdu = (Avtp_Ntscf_t*) pdu;
151+
memset(ntscf_pdu, 0, AVTP_NTSCF_HEADER_LEN);
152+
Avtp_Ntscf_Init(ntscf_pdu);
153+
Avtp_Ntscf_SetField(ntscf_pdu, AVTP_NTSCF_FIELD_SEQUENCE_NUM, seq_num++);
154+
Avtp_Ntscf_SetField(ntscf_pdu, AVTP_NTSCF_FIELD_STREAM_ID, STREAM_ID);
155+
res = AVTP_NTSCF_HEADER_LEN;
156+
}
157+
return res;
158+
}
159+
160+
static int update_pdu_length(uint8_t* pdu, uint64_t length)
161+
{
162+
if (use_tscf) {
163+
uint64_t payloadLen = length - AVTP_TSCF_HEADER_LEN;
164+
Avtp_Tscf_SetField((Avtp_Tscf_t*)pdu, AVTP_TSCF_FIELD_STREAM_DATA_LENGTH, payloadLen);
165+
} else {
166+
uint64_t payloadLen = length - AVTP_NTSCF_HEADER_LEN;
167+
Avtp_Ntscf_SetField((Avtp_Ntscf_t*)pdu, AVTP_NTSCF_FIELD_NTSCF_DATA_LENGTH, payloadLen);
168+
}
169+
return 0;
170+
}
171+
172+
static int prepare_acf_packet(uint8_t* acf_pdu, uint64_t gpc_code,
173+
uint8_t* payload, uint16_t length) {
174+
175+
Avtp_Gpc_t* pdu = (Avtp_Gpc_t*) acf_pdu;
176+
177+
// Clear bits
178+
memset(pdu, 0, AVTP_GPC_HEADER_LEN);
179+
uint8_t acf_length = (AVTP_GPC_HEADER_LEN + length)/4;
180+
if (length % 4) acf_length++;
181+
182+
// Prepare ACF PDU for CAN
183+
Avtp_Gpc_Init(pdu);
184+
Avtp_Gpc_SetField(pdu, AVTP_GPC_FIELD_GPC_MSG_ID, gpc_code);
185+
Avtp_Gpc_SetField(pdu, AVTP_GPC_FIELD_ACF_MSG_LENGTH, acf_length);
186+
memcpy(acf_pdu+AVTP_GPC_HEADER_LEN, payload, length);
187+
memset(acf_pdu+AVTP_GPC_HEADER_LEN+length, 0, acf_length*4 - length);
188+
189+
return acf_length*4;
190+
}
191+
192+
static int update_cf_length(uint8_t* cf_pdu, uint64_t length)
193+
{
194+
if (use_tscf) {
195+
uint64_t payloadLen = length - AVTP_TSCF_HEADER_LEN;
196+
Avtp_Tscf_SetField((Avtp_Tscf_t*)cf_pdu, AVTP_TSCF_FIELD_STREAM_DATA_LENGTH, payloadLen);
197+
} else {
198+
uint64_t payloadLen = length - AVTP_NTSCF_HEADER_LEN;
199+
Avtp_Ntscf_SetField((Avtp_Ntscf_t*)cf_pdu, AVTP_NTSCF_FIELD_NTSCF_DATA_LENGTH, payloadLen);
200+
}
201+
return 0;
202+
}
203+
204+
int main(int argc, char *argv[])
205+
{
206+
207+
int fd, res;
208+
struct sockaddr_ll sk_ll_addr;
209+
struct sockaddr_in sk_udp_addr;
210+
uint8_t pdu[MAX_PDU_SIZE];
211+
uint16_t pdu_length, cf_length;
212+
uint64_t gpc_code = 0;
213+
214+
argp_parse(&argp, argc, argv, 0, NULL, NULL);
215+
216+
// Create an appropriate talker socket: UDP or Ethernet raw
217+
// Setup the socket for sending to the destination
218+
if (use_udp) {
219+
fd = create_talker_socket_udp(priority);
220+
if (fd < 0) return fd;
221+
222+
res = setup_udp_socket_address((struct in_addr*) ip_addr,
223+
udp_port, &sk_udp_addr);
224+
} else {
225+
fd = create_talker_socket(priority);
226+
if (fd < 0) return fd;
227+
res = setup_socket_address(fd, ifname, macaddr,
228+
ETH_P_TSN, &sk_ll_addr);
229+
}
230+
if (res < 0) goto err;
231+
232+
// Sending loop
233+
for(;;) {
234+
235+
// Pack into control formats
236+
uint8_t *cf_pdu;
237+
pdu_length = 0;
238+
cf_length = 0;
239+
240+
// Usage of UDP means the PDU needs an encapsulation number
241+
if (use_udp) {
242+
Avtp_Udp_t *udp_pdu = (Avtp_Udp_t *) pdu;
243+
Avtp_Udp_SetField(udp_pdu, AVTP_UDP_FIELD_ENCAPSULATION_SEQ_NO,
244+
udp_seq_num++);
245+
pdu_length += sizeof(Avtp_Udp_t);
246+
}
247+
248+
// Create the CF packet first
249+
cf_pdu = pdu + pdu_length;
250+
res = init_cf_pdu(cf_pdu);
251+
if (res < 0)
252+
goto err;
253+
pdu_length += res;
254+
cf_length += res;
255+
256+
// Creation of the ACF Packet
257+
uint8_t* acf_pdu = pdu + pdu_length;
258+
res = prepare_acf_packet(acf_pdu, gpc_code++, (uint8_t*)message_string,
259+
strlen((char *)message_string));
260+
if (res < 0) goto err;
261+
pdu_length += res;
262+
cf_length += res;
263+
264+
res = update_cf_length(cf_pdu, cf_length);
265+
if (res < 0)
266+
goto err;
267+
268+
if (use_udp) {
269+
res = sendto(fd, pdu, pdu_length, 0,
270+
(struct sockaddr *) &sk_udp_addr, sizeof(sk_udp_addr));
271+
} else {
272+
res = sendto(fd, pdu, pdu_length, 0,
273+
(struct sockaddr *) &sk_ll_addr, sizeof(sk_ll_addr));
274+
}
275+
if (res < 0) {
276+
perror("Failed to send data");
277+
goto err;
278+
}
279+
sleep(1);
280+
}
281+
282+
err:
283+
close(fd);
284+
return 1;
285+
286+
}

0 commit comments

Comments
 (0)
Please sign in to comment.