1 /**
2  * Copyright © 2019 IBM Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <assert.h>
18 #include <stdint.h>
19 #include <stdio.h>
20 
21 #define read __read
22 #include "config.c"
23 #include "console-socket.c"
24 #define main __main
25 #include "console-client.c"
26 #undef read
27 #undef main
28 
29 struct test {
30 	enum esc_type	esc_type;
31 	union {
32 		struct ssh_esc_state ssh;
33 		struct str_esc_state str;
34 	} esc_state;
35 	const char	*in[4];
36 	int		n_in;
37 	const char	*exp_out;
38 	int		exp_rc;
39 };
40 
41 struct test_ctx {
42 	struct console_client	client;
43 	struct test		*test;
44 	uint8_t			out[4096];
45 	int			cur_in;
46 	int			cur_out;
47 };
48 
49 
50 struct test tests[] = {
51 	{
52 		/* no escape code */
53 		.esc_type	= ESC_TYPE_SSH,
54 		.in		= {"a"},
55 		.n_in		= 1,
56 		.exp_out	= "a",
57 		.exp_rc		= PROCESS_EXIT,
58 	},
59 	{
60 		/* no escape code, multiple reads */
61 		.esc_type	= ESC_TYPE_SSH,
62 		.in		= {"a", "b"},
63 		.n_in		= 2,
64 		.exp_out	= "ab",
65 		.exp_rc		= PROCESS_EXIT,
66 	},
67 	{
68 		/* ssh escape in one read */
69 		.esc_type	= ESC_TYPE_SSH,
70 		.in		= {"a\r~."},
71 		.n_in		= 1,
72 		.exp_out	= "a\r",
73 		.exp_rc		= PROCESS_ESC,
74 	},
75 	{
76 		/* ssh escape, partial ~ is not output. */
77 		.esc_type	= ESC_TYPE_SSH,
78 		.in		= {"a\r~"},
79 		.n_in		= 1,
80 		.exp_out	= "a\r",
81 		.exp_rc		= PROCESS_EXIT,
82 	},
83 	{
84 		/* ssh escape split into individual reads */
85 		.esc_type	= ESC_TYPE_SSH,
86 		.in		= {"a", "\r", "~", "."},
87 		.n_in		= 4,
88 		.exp_out	= "a\r",
89 		.exp_rc		= PROCESS_ESC,
90 	},
91 	{
92 		/* ssh escape, escaped. */
93 		.esc_type	= ESC_TYPE_SSH,
94 		.in		= {"a\r~~."},
95 		.n_in		= 1,
96 		.exp_out	= "a\r~.",
97 		.exp_rc		= PROCESS_EXIT,
98 	},
99 	{
100 		/* ssh escape, escaped ~, and not completed. */
101 		.esc_type	= ESC_TYPE_SSH,
102 		.in		= {"a\r~~"},
103 		.n_in		= 1,
104 		.exp_out	= "a\r~",
105 		.exp_rc		= PROCESS_EXIT,
106 	},
107 	{
108 		/* str escape, no match */
109 		.esc_type	= ESC_TYPE_STR,
110 		.esc_state	= { .str = { .str = (const uint8_t *)"c" } },
111 		.in		= {"ab"},
112 		.n_in		= 1,
113 		.exp_out	= "ab",
114 		.exp_rc		= PROCESS_EXIT,
115 	},
116 	{
117 		/* str escape, one byte, as one read */
118 		.esc_type	= ESC_TYPE_STR,
119 		.esc_state	= { .str = { .str = (const uint8_t *)"b" } },
120 		.in		= {"abc"},
121 		.n_in		= 1,
122 		.exp_out	= "ab",
123 		.exp_rc		= PROCESS_ESC,
124 	},
125 	{
126 		/* str escape, multiple bytes, as one read */
127 		.esc_type	= ESC_TYPE_STR,
128 		.esc_state	= { .str = { .str = (const uint8_t *)"bc" } },
129 		.in		= {"abcd"},
130 		.n_in		= 1,
131 		.exp_out	= "abc",
132 		.exp_rc		= PROCESS_ESC,
133 	},
134 	{
135 		/* str escape, multiple bytes, split over reads */
136 		.esc_type	= ESC_TYPE_STR,
137 		.esc_state	= { .str = { .str = (const uint8_t *)"bc" } },
138 		.in		= {"ab", "cd"},
139 		.n_in		= 2,
140 		.exp_out	= "abc",
141 		.exp_rc		= PROCESS_ESC,
142 	},
143 	{
144 		/* str escape, not matched due to intermediate data */
145 		.esc_type	= ESC_TYPE_STR,
146 		.esc_state	= { .str = { .str = (const uint8_t *)"ab" } },
147 		.in		= {"acb"},
148 		.n_in		= 1,
149 		.exp_out	= "acb",
150 		.exp_rc		= PROCESS_EXIT,
151 	},
152 };
153 
154 #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
155 
156 struct test_ctx ctxs[ARRAY_SIZE(tests)];
157 
158 int write_buf_to_fd(int fd, const uint8_t *buf, size_t len)
159 {
160 	struct test_ctx *ctx = &ctxs[fd];
161 
162 	assert(ctx->cur_out + len <= sizeof(ctx->out));
163 	memcpy(ctx->out + ctx->cur_out, buf, len);
164 	ctx->cur_out += len;
165 
166 	return 0;
167 }
168 
169 ssize_t __read(int fd, void *buf, size_t len)
170 {
171 	struct test_ctx *ctx = &ctxs[fd];
172 	const char *inbuf;
173 	size_t inlen;
174 
175 	if (ctx->cur_in >= ctx->test->n_in)
176 		return 0;
177 
178 	inbuf = ctx->test->in[ctx->cur_in];
179 	inlen = strlen(inbuf);
180 	assert(inlen <= len);
181 	memcpy(buf, inbuf, inlen);
182 	ctx->cur_in++;
183 	return inlen;
184 }
185 
186 void run_one_test(int idx, struct test *test, struct test_ctx *ctx)
187 {
188 	size_t exp_out_len;
189 	int rc;
190 
191 	/* we store the index into the context array as a FD, so we
192 	 * can refer to it through the read & write callbacks.
193 	 */
194 	ctx->client.console_sd = idx;
195 	ctx->client.fd_in = idx;
196 	ctx->client.esc_type = test->esc_type;
197 	memcpy(&ctx->client.esc_state, &test->esc_state,
198 			sizeof(test->esc_state));
199 	ctx->test = test;
200 
201 	for (;;) {
202 		rc = process_tty(&ctx->client);
203 		if (rc != PROCESS_OK)
204 			break;
205 	}
206 
207 	exp_out_len = strlen(test->exp_out);
208 
209 #ifdef DEBUG
210 	printf("got: rc %d %s(%d), exp: rc %d %s(%ld)\n",
211 			rc, ctx->out, ctx->cur_out,
212 			test->exp_rc, test->exp_out, exp_out_len);
213 	fflush(stdout);
214 #endif
215 	assert(rc == test->exp_rc);
216 	assert(exp_out_len == ctx->cur_out);
217 	assert(!memcmp(ctx->out, test->exp_out, exp_out_len));
218 }
219 
220 int main(void)
221 {
222 	int i;
223 
224 	for (i = 0; i < ARRAY_SIZE(tests); i++)
225 		run_one_test(i, &tests[i], &ctxs[i]);
226 
227 	return EXIT_SUCCESS;
228 }
229