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 <errno.h>
19 #include <limits.h>
20 #include <stdint.h>
21 #include <stdio.h>
22 #include <unistd.h>
23
24 #include "util.h"
25
26 static ssize_t __read(int fd, void *buf, size_t len);
27 #define read __read
28 #include "config.c"
29 #include "console-socket.c"
30 #define main __main
31 #include "console-client.c"
32 #undef read
33 #undef main
34
35 struct test {
36 enum esc_type esc_type;
37 union {
38 struct ssh_esc_state ssh;
39 struct str_esc_state str;
40 } esc_state;
41 const char *in[4];
42 size_t n_in;
43 const char *exp_out;
44 int exp_rc;
45 };
46
47 struct test_ctx {
48 struct console_client client;
49 struct test *test;
50 uint8_t out[4096];
51 size_t cur_in;
52 size_t cur_out;
53 };
54
55 struct test tests[] = {
56 {
57 /* no escape code */
58 .esc_type = ESC_TYPE_SSH,
59 .in = { "a" },
60 .n_in = 1,
61 .exp_out = "a",
62 .exp_rc = PROCESS_EXIT,
63 },
64 {
65 /* no escape code, multiple reads */
66 .esc_type = ESC_TYPE_SSH,
67 .in = { "a", "b" },
68 .n_in = 2,
69 .exp_out = "ab",
70 .exp_rc = PROCESS_EXIT,
71 },
72 {
73 /* ssh escape in one read */
74 .esc_type = ESC_TYPE_SSH,
75 .in = { "a\r~." },
76 .n_in = 1,
77 .exp_out = "a\r",
78 .exp_rc = PROCESS_ESC,
79 },
80 {
81 /* ssh escape, partial ~ is not output. */
82 .esc_type = ESC_TYPE_SSH,
83 .in = { "a\r~" },
84 .n_in = 1,
85 .exp_out = "a\r",
86 .exp_rc = PROCESS_EXIT,
87 },
88 {
89 /* ssh escape split into individual reads */
90 .esc_type = ESC_TYPE_SSH,
91 .in = { "a", "\r", "~", "." },
92 .n_in = 4,
93 .exp_out = "a\r",
94 .exp_rc = PROCESS_ESC,
95 },
96 {
97 /* ssh escape, escaped. */
98 .esc_type = ESC_TYPE_SSH,
99 .in = { "a\r~~." },
100 .n_in = 1,
101 .exp_out = "a\r~.",
102 .exp_rc = PROCESS_EXIT,
103 },
104 {
105 /* ssh escape, escaped ~, and not completed. */
106 .esc_type = ESC_TYPE_SSH,
107 .in = { "a\r~~" },
108 .n_in = 1,
109 .exp_out = "a\r~",
110 .exp_rc = PROCESS_EXIT,
111 },
112 {
113 /* str escape, no match */
114 .esc_type = ESC_TYPE_STR,
115 .esc_state = { .str = { .str = (const uint8_t *)"c" } },
116 .in = { "ab" },
117 .n_in = 1,
118 .exp_out = "ab",
119 .exp_rc = PROCESS_EXIT,
120 },
121 {
122 /* str escape, one byte, as one read */
123 .esc_type = ESC_TYPE_STR,
124 .esc_state = { .str = { .str = (const uint8_t *)"b" } },
125 .in = { "abc" },
126 .n_in = 1,
127 .exp_out = "ab",
128 .exp_rc = PROCESS_ESC,
129 },
130 {
131 /* str escape, multiple bytes, as one read */
132 .esc_type = ESC_TYPE_STR,
133 .esc_state = { .str = { .str = (const uint8_t *)"bc" } },
134 .in = { "abcd" },
135 .n_in = 1,
136 .exp_out = "abc",
137 .exp_rc = PROCESS_ESC,
138 },
139 {
140 /* str escape, multiple bytes, split over reads */
141 .esc_type = ESC_TYPE_STR,
142 .esc_state = { .str = { .str = (const uint8_t *)"bc" } },
143 .in = { "ab", "cd" },
144 .n_in = 2,
145 .exp_out = "abc",
146 .exp_rc = PROCESS_ESC,
147 },
148 {
149 /* str escape, not matched due to intermediate data */
150 .esc_type = ESC_TYPE_STR,
151 .esc_state = { .str = { .str = (const uint8_t *)"ab" } },
152 .in = { "acb" },
153 .n_in = 1,
154 .exp_out = "acb",
155 .exp_rc = PROCESS_EXIT,
156 },
157 };
158
159 struct test_ctx ctxs[ARRAY_SIZE(tests)];
160
write_buf_to_fd(int fd,const uint8_t * buf,size_t len)161 int write_buf_to_fd(int fd, const uint8_t *buf, size_t len)
162 {
163 struct test_ctx *ctx = &ctxs[fd];
164
165 assert(ctx->cur_out + len <= sizeof(ctx->out));
166 memcpy(ctx->out + ctx->cur_out, buf, len);
167 ctx->cur_out += len;
168
169 return 0;
170 }
171
__read(int fd,void * buf,size_t len)172 static ssize_t __read(int fd, void *buf, size_t len)
173 {
174 struct test_ctx *ctx = &ctxs[fd];
175 const char *inbuf;
176 size_t inlen;
177
178 if (len > SSIZE_MAX) {
179 errno = EINVAL;
180 return -1;
181 }
182
183 if (ctx->cur_in >= ctx->test->n_in) {
184 return 0;
185 }
186
187 inbuf = ctx->test->in[ctx->cur_in];
188 inlen = strlen(inbuf);
189 assert(inlen <= len);
190 memcpy(buf, inbuf, inlen);
191 ctx->cur_in++;
192 return (ssize_t)inlen;
193 }
194
run_one_test(size_t idx,struct test * test,struct test_ctx * ctx)195 void run_one_test(size_t idx, struct test *test, struct test_ctx *ctx)
196 {
197 size_t exp_out_len;
198 int rc;
199
200 /* we store the index into the context array as a FD, so we
201 * can refer to it through the read & write callbacks.
202 */
203 assert(idx < INT_MAX);
204 ctx->client.console_sd = (int)idx;
205 ctx->client.fd_in = (int)idx;
206 ctx->client.esc_type = test->esc_type;
207 memcpy(&ctx->client.esc_state, &test->esc_state,
208 sizeof(test->esc_state));
209 ctx->test = test;
210
211 for (;;) {
212 rc = process_tty(&ctx->client);
213 if (rc != PROCESS_OK) {
214 break;
215 }
216 }
217
218 exp_out_len = strlen(test->exp_out);
219
220 #ifdef DEBUG
221 printf("got: rc %d %s(%d), exp: rc %d %s(%ld)\n", rc, ctx->out,
222 ctx->cur_out, test->exp_rc, test->exp_out, exp_out_len);
223 fflush(stdout);
224 #endif
225 assert(rc == test->exp_rc);
226 assert(exp_out_len == ctx->cur_out);
227 assert(!memcmp(ctx->out, test->exp_out, exp_out_len));
228 }
229
main(void)230 int main(void)
231 {
232 size_t i;
233
234 for (i = 0; i < ARRAY_SIZE(tests); i++) {
235 run_one_test(i, &tests[i], &ctxs[i]);
236 }
237
238 return EXIT_SUCCESS;
239 }
240