Skip to content

Commit 3d79b5a

Browse files
committed
test FEATURE added other client testing interface
1 parent 2076041 commit 3d79b5a

22 files changed

+475
-35
lines changed

tests/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR})
3838
include_directories(${CMAKE_CURRENT_BINARY_DIR})
3939

4040
# base test source
41-
set(TEST_SRC "np2_test.c")
41+
set(TEST_SRC "np2_test.c" "np2_other_client.c")
4242

4343
# list of all the tests
4444
set(TESTS test_rpc test_edit test_filter test_subscribe_filter test_subscribe_param test_parallel_sessions

tests/np2_other_client.c

Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
/**
2+
* @file np2_other_client.c
3+
* @author Adam Piecek <piecek@cesnet.cz>
4+
* @brief An alternative test interface for communicating with the NETCONF server.
5+
*
6+
* @copyright
7+
* Copyright (c) 2019 - 2024 Deutsche Telekom AG.
8+
* Copyright (c) 2017 - 2024 CESNET, z.s.p.o.
9+
*
10+
* This source code is licensed under BSD 3-Clause License (the "License").
11+
* You may not use this file except in compliance with the License.
12+
* You may obtain a copy of the License at
13+
*
14+
* https://opensource.org/licenses/BSD-3-Clause
15+
*/
16+
17+
#define _GNU_SOURCE
18+
19+
#include <errno.h>
20+
#include <fcntl.h>
21+
#include <inttypes.h>
22+
#include <stdint.h>
23+
#include <stdio.h>
24+
#include <stdlib.h>
25+
#include <string.h>
26+
#include <sys/socket.h>
27+
#include <sys/un.h>
28+
#include <time.h>
29+
#include <unistd.h>
30+
31+
#include "libnetconf2/netconf.h"
32+
#include "np2_other_client.h"
33+
34+
#define OC_FAIL_LOG \
35+
fprintf(stderr, "Netconf client fail in %s:%d.\n", __FILE__, __LINE__)
36+
37+
/**
38+
* Full timeout of read or write in seconds.
39+
*/
40+
#define OC_TIMEOUT_SEC 60
41+
42+
/**
43+
* Microseconds after which tasks are repeated until the full timeout elapses.
44+
*/
45+
#define OC_TIMEOUT_STEP 100
46+
47+
/**
48+
* Check if the timeout has expired.
49+
*/
50+
#define OC_TIMEOUT(START_TIME) \
51+
(((double)(time(NULL) - START_TIME)) > OC_TIMEOUT_SEC)
52+
53+
/**
54+
* @brief Write message to socket.
55+
*
56+
* @param[in] oc_sess Client session.
57+
* @param[in] msg Message to send.
58+
* @param[in] msglen Length of @p msg.
59+
* @return 0 on success.
60+
* @return negative number on error.
61+
*/
62+
static int
63+
oc_write(struct np_other_client *oc_sess, const char *msg, uint64_t msglen)
64+
{
65+
uint64_t written = 0;
66+
int64_t cnt;
67+
int interrupted;
68+
69+
msglen = msglen ? msglen : strlen(msg);
70+
do {
71+
cnt = write(oc_sess->unixsock, msg + written, msglen - written);
72+
written += cnt;
73+
if ((cnt < 0) && (errno == EAGAIN)) {
74+
cnt = 0;
75+
} else if ((cnt < 0) && (errno == EINTR)) {
76+
cnt = 0;
77+
interrupted = 1;
78+
} else if (cnt < 0) {
79+
fprintf(stderr, "Socket error (%s).\n", strerror(errno));
80+
return -1;
81+
}
82+
if ((cnt == 0) && !interrupted) {
83+
/* we must wait */
84+
usleep(OC_TIMEOUT_STEP);
85+
}
86+
} while (written < msglen);
87+
88+
return 0;
89+
}
90+
91+
/**
92+
* @brief Reallocation of internal buffer in the struct np_other_client.
93+
*
94+
* @param[in] oc_sess Client session.
95+
* @return 0 on success.
96+
* @return negative number on error.
97+
*/
98+
static int
99+
oc_realloc(struct np_other_client *oc_sess)
100+
{
101+
void *tmp;
102+
103+
tmp = realloc(oc_sess->buf, oc_sess->bufsize * 2);
104+
if (!tmp) {
105+
fprintf(stderr, "Memory allocation error.\n");
106+
return -1;
107+
}
108+
oc_sess->buf = tmp;
109+
oc_sess->bufsize *= 2;
110+
111+
return 0;
112+
}
113+
114+
/**
115+
* @defgroup ocreadflags Flags for oc_read().
116+
* @{
117+
*/
118+
#define OC_READ_HELLO_MSG 0x1 /**< read the response to the hello message from the server */
119+
/** @} ocreadflags */
120+
121+
/**
122+
* @brief Read message from socket.
123+
*
124+
* @param[in] oc_sess Client session.
125+
* @param[in] flags Option for function (@ref ocreadflags).
126+
* @return positive number representing number of characters written into @p oc_sess buffer.
127+
* @return negative number on error.
128+
*/
129+
static int64_t
130+
oc_read(struct np_other_client *oc_sess, uint32_t flags)
131+
{
132+
int64_t rd, rdall = 0;
133+
time_t tm;
134+
int interrupted;
135+
const char *endtag;
136+
137+
tm = time(NULL);
138+
oc_sess->buf[0] = 0;
139+
140+
do {
141+
interrupted = 0;
142+
rd = read(oc_sess->unixsock, oc_sess->buf + rdall, oc_sess->bufsize - rdall);
143+
if (rd < 0) {
144+
if (errno == EAGAIN) {
145+
/* endtag not found */
146+
rd = 0;
147+
} else if (errno == EINTR) {
148+
rd = 0;
149+
interrupted = 1;
150+
break;
151+
} else {
152+
fprintf(stderr, "Reading from file descriptor (%d) failed (%s).\n", oc_sess->unixsock, strerror(errno));
153+
return -1;
154+
}
155+
} else if (rd == 0) {
156+
fprintf(stderr, "Communication file descriptor (%d) unexpectedly closed.\n", oc_sess->unixsock);
157+
return -1;
158+
}
159+
if (rd == 0) {
160+
/* nothing read */
161+
if (!interrupted) {
162+
usleep(OC_TIMEOUT_STEP);
163+
}
164+
if (OC_TIMEOUT(tm)) {
165+
/* waiting too long */
166+
fprintf(stderr, "Message took too long to read\n");
167+
return -1;
168+
}
169+
} else {
170+
/* something read */
171+
rdall += rd;
172+
}
173+
174+
if ((flags & OC_READ_HELLO_MSG) && (rdall > 5)) {
175+
/* check hello end tag, (strlen("]]>]]>") == 6) */
176+
endtag = (oc_sess->buf + rdall) - 6;
177+
if (!strncmp(endtag, "]]>]]>", 6)) {
178+
/* success */
179+
break;
180+
}
181+
} else if (rdall > 3) {
182+
/* check classic end tag, (strlen(\n##\n) == 4) */
183+
endtag = (oc_sess->buf + rdall) - 4;
184+
if (!strncmp(endtag, "\n##\n", 4)) {
185+
/* success */
186+
break;
187+
}
188+
}
189+
190+
if ((oc_sess->bufsize - rdall) == 0) {
191+
if (oc_realloc(oc_sess)) {
192+
return -1;
193+
}
194+
}
195+
} while (1);
196+
197+
return rdall;
198+
}
199+
200+
/**
201+
* @brief Establish NETCONF session.
202+
*
203+
* @param[in] oc_sess Client session.
204+
* @return 0 on success.
205+
*/
206+
static int
207+
oc_hello_handshake(struct np_other_client *oc_sess)
208+
{
209+
int rc;
210+
211+
const char *msg =
212+
"<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">"
213+
"<capabilities><capability>urn:ietf:params:netconf:base:1.0</capability>"
214+
"<capability>urn:ietf:params:netconf:base:1.1</capability></capabilities></hello>]]>]]>";
215+
216+
rc = oc_write(oc_sess, msg, 0);
217+
if (rc) {
218+
return rc;
219+
}
220+
221+
return (oc_read(oc_sess, OC_READ_HELLO_MSG) >= 0) ? 0 : -1;
222+
}
223+
224+
struct np_other_client *
225+
oc_connect_unix(const char *address)
226+
{
227+
struct sockaddr_un sun;
228+
struct np_other_client *oc_sess = NULL;
229+
int rc;
230+
231+
oc_sess = calloc(1, sizeof *oc_sess);
232+
if (!oc_sess) {
233+
OC_FAIL_LOG;
234+
return NULL;
235+
}
236+
237+
oc_sess->unixsock = socket(AF_UNIX, SOCK_STREAM, 0);
238+
if (oc_sess->unixsock < 0) {
239+
OC_FAIL_LOG;
240+
return NULL;
241+
}
242+
243+
memset(&sun, 0, sizeof(sun));
244+
sun.sun_family = AF_UNIX;
245+
snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", address);
246+
247+
if (connect(oc_sess->unixsock, (struct sockaddr *)&sun, sizeof(sun)) < 0) {
248+
OC_FAIL_LOG;
249+
return NULL;
250+
}
251+
252+
if (fcntl(oc_sess->unixsock, F_SETFL, O_NONBLOCK) < 0) {
253+
OC_FAIL_LOG;
254+
return NULL;
255+
}
256+
257+
oc_sess->buf = malloc(2048);
258+
if (!oc_sess->buf) {
259+
return NULL;
260+
}
261+
oc_sess->bufsize = 2048;
262+
263+
rc = oc_hello_handshake(oc_sess);
264+
if (rc) {
265+
return NULL;
266+
}
267+
268+
oc_sess->msgid = 1;
269+
270+
return oc_sess;
271+
}
272+
273+
int
274+
oc_send_msg(struct np_other_client *oc_sess, const char *msg)
275+
{
276+
int rc;
277+
char *starttag = NULL;
278+
uint64_t msglen;
279+
280+
/* increment message-id but do not increment after initial handshake */
281+
oc_sess->msgid = (oc_sess->msgid != 1) ? oc_sess->msgid + 1 : oc_sess->msgid;
282+
283+
msglen = strlen(msg);
284+
asprintf(&starttag, "\n#%" PRIu64 "\n", msglen);
285+
if (!starttag) {
286+
OC_FAIL_LOG;
287+
return -1;
288+
goto cleanup;
289+
}
290+
291+
rc = oc_write(oc_sess, starttag, 0);
292+
if (rc) {
293+
OC_FAIL_LOG;
294+
goto cleanup;
295+
}
296+
rc = oc_write(oc_sess, msg, msglen);
297+
if (rc) {
298+
OC_FAIL_LOG;
299+
goto cleanup;
300+
}
301+
rc = oc_write(oc_sess, "\n##\n", 0);
302+
if (rc) {
303+
OC_FAIL_LOG;
304+
goto cleanup;
305+
}
306+
307+
cleanup:
308+
free(starttag);
309+
return rc;
310+
}
311+
312+
int
313+
oc_recv_msg(struct np_other_client *oc_sess, char **msg)
314+
{
315+
int64_t len;
316+
char *endtag;
317+
318+
len = oc_read(oc_sess, 0);
319+
320+
if (len < 0) {
321+
return -1;
322+
} else if (len == (int64_t)oc_sess->bufsize) {
323+
/* unlikely, though no space for zero character */
324+
if (oc_realloc(oc_sess)) {
325+
return -1;
326+
}
327+
}
328+
329+
/* Delete end tag: \n##\n */
330+
endtag = (oc_sess->buf + len) - 4;
331+
*endtag = '\0';
332+
333+
/* Skip first start tag: \n##number\n */
334+
*msg = strchr(oc_sess->buf + 1, '\n');
335+
if (**msg == '\0') {
336+
return -1;
337+
}
338+
*msg = *msg + 1;
339+
340+
return 0;
341+
}
342+
343+
void
344+
oc_session_free(struct np_other_client *oc_sess)
345+
{
346+
if (!oc_sess) {
347+
return;
348+
}
349+
if (oc_sess->unixsock > 0) {
350+
close(oc_sess->unixsock);
351+
}
352+
free(oc_sess->buf);
353+
free(oc_sess);
354+
}

0 commit comments

Comments
 (0)