Skip to content

Commit 84594b8

Browse files
committed
added chAT
1 parent 75f7ff4 commit 84594b8

File tree

3 files changed

+755
-0
lines changed

3 files changed

+755
-0
lines changed
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
This file is part of chAT.
3+
Copyright (C) 2022-2023 Reimu NotMoe <[email protected]>
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU Affero General Public License as
7+
published by the Free Software Foundation, either version 3 of the
8+
License, or (at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU Affero General Public License for more details.
14+
15+
You should have received a copy of the GNU Affero General Public License
16+
along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
#pragma once
20+
21+
#include <tuple>
22+
#include <variant>
23+
#include <vector>
24+
#include <deque>
25+
#include <string>
26+
#include <functional>
27+
#include <memory>
28+
#include <optional>
29+
30+
#include <cstdio>
31+
#include <cstring>
32+
#include <cinttypes>
33+
34+
namespace SudoMaker::chAT {
35+
enum class CommandMode : unsigned int {
36+
Run, Write, Read, Test
37+
};
38+
39+
class ATParser {
40+
public:
41+
enum class ParseState : unsigned int {
42+
Keyword, Command, Argument, Done, Malformed
43+
};
44+
45+
bool malformed;
46+
ParseState state;
47+
CommandMode cmd_mode;
48+
size_t keyword_count;
49+
std::string command;
50+
std::vector<std::string> args;
51+
bool args_quote;
52+
size_t args_escape_count;
53+
#ifdef CHAT_STRICT_CRLF
54+
uint8_t last_data[2];
55+
#endif
56+
size_t bytes_parsed;
57+
58+
ATParser();
59+
60+
void reset();
61+
void show();
62+
size_t parse(const uint8_t *buf, size_t len);
63+
};
64+
65+
struct io_interface {
66+
typedef std::function<ssize_t(uint8_t *, size_t)> io_callback_t;
67+
68+
io_callback_t callback_io_read, callback_io_write;
69+
};
70+
71+
enum class CommandStatus {
72+
OK, ERROR, CUSTOM
73+
};
74+
75+
class Server;
76+
class ServerImpl;
77+
78+
typedef std::function<CommandStatus(Server& srv, const std::string& command)> command_callback_t;
79+
80+
class Server {
81+
private:
82+
std::unique_ptr<ServerImpl> pimpl;
83+
public:
84+
enum RunStatus : unsigned {
85+
OK = 0, WantRead = 0x1, WantWrite = 0x2,
86+
};
87+
88+
Server();
89+
~Server();
90+
91+
ATParser &parser() noexcept;
92+
93+
void set_command_callback(command_callback_t cmd_cb);
94+
void set_io_callback(io_interface io_cbs);
95+
const io_interface& get_io_callback() noexcept;
96+
void set_nonblocking_mode(bool v) noexcept;
97+
void set_parser_debugging(bool v) noexcept;
98+
void set_write_buffer_size_limit(size_t l = 16384) noexcept;
99+
100+
std::vector<uint8_t> inhibit_read(size_t raw_data_len);
101+
void continue_read() noexcept;
102+
103+
void write_data(const void *buf, size_t len);
104+
void write_cstr(const char *buf, ssize_t len = -1);
105+
void write_str(std::string str);
106+
void write_vec8(std::vector<uint8_t> vec8);
107+
108+
void write_response(std::string str);
109+
void write_response(const char *buf, ssize_t len = -1);
110+
void write_error_reason(std::string str);
111+
void write_error_reason(const char *buf, ssize_t len = -1);
112+
void write_error();
113+
void write_ok();
114+
115+
void write_response_prompt();
116+
void write_error_prompt();
117+
void write_line_end();
118+
119+
RunStatus run();
120+
};
121+
122+
inline Server::RunStatus operator|(Server::RunStatus a, Server::RunStatus b) {
123+
return static_cast<Server::RunStatus>(static_cast<unsigned>(a) | static_cast<unsigned>(b));
124+
}
125+
126+
inline Server::RunStatus operator&(Server::RunStatus a, Server::RunStatus b) {
127+
return static_cast<Server::RunStatus>(static_cast<unsigned>(a) & static_cast<unsigned>(b));
128+
}
129+
130+
inline Server::RunStatus& operator|=(Server::RunStatus& a, Server::RunStatus b) {
131+
a = a | b;
132+
return a;
133+
}
134+
135+
inline Server::RunStatus& operator&=(Server::RunStatus& a, Server::RunStatus b) {
136+
a = a & b;
137+
return a;
138+
}
139+
}
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/*
2+
This file is part of chAT.
3+
Copyright (C) 2022-2023 Reimu NotMoe <[email protected]>
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU Affero General Public License as
7+
published by the Free Software Foundation, either version 3 of the
8+
License, or (at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU Affero General Public License for more details.
14+
15+
You should have received a copy of the GNU Affero General Public License
16+
along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
#include "chAT.hpp"
20+
21+
namespace SudoMaker::chAT {
22+
ATParser::ATParser() {
23+
reset();
24+
}
25+
26+
void ATParser::reset() {
27+
malformed = false;
28+
state = ParseState::Keyword;
29+
cmd_mode = CommandMode::Run;
30+
keyword_count = 0;
31+
command.clear();
32+
args.clear();
33+
args_quote = false;
34+
args_escape_count = 0;
35+
#ifdef CHAT_STRICT_CRLF
36+
last_data[0] = 0;
37+
last_data[1] = 0;
38+
#endif
39+
bytes_parsed = 0;
40+
}
41+
42+
void ATParser::show() {
43+
const char *mode_str = nullptr;
44+
45+
switch (cmd_mode) {
46+
case CommandMode::Run:
47+
mode_str = "run";
48+
break;
49+
case CommandMode::Write:
50+
mode_str = "write";
51+
break;
52+
case CommandMode::Read:
53+
mode_str = "read";
54+
break;
55+
case CommandMode::Test:
56+
mode_str = "test";
57+
break;
58+
}
59+
60+
printf("[parser] malformed: %s, mode: %s, args_count: %zu\n", malformed ? "true" : "false", mode_str, args.size());
61+
62+
if (!command.empty()) {
63+
printf("[parser] command: %s\n", command.c_str());
64+
}
65+
66+
if (!args.empty()) {
67+
printf("[parser] args: ");
68+
69+
for (size_t i=0; i<args.size(); i++) {
70+
printf("[%zu]=%s ", i, args[i].c_str());
71+
}
72+
73+
printf("\n");
74+
75+
}
76+
}
77+
78+
size_t ATParser::parse(const uint8_t *buf, size_t len) {
79+
static const uint8_t keyword[2] = {'A', 'T'};
80+
81+
for (size_t i = 0; i < len; i++) {
82+
uint8_t c = buf[i];
83+
84+
#ifdef CHAT_STRICT_CRLF
85+
last_data[0] = last_data[1];
86+
last_data[1] = c;
87+
88+
if (last_data[0] == '\r' && last_data[1] == '\n') {
89+
#else
90+
if (c == '\n') {
91+
#endif
92+
if (state == ParseState::Malformed) {
93+
malformed = true;
94+
}
95+
state = ParseState::Done;
96+
bytes_parsed += (i+1);
97+
return i+1;
98+
}
99+
100+
switch (state) {
101+
case ParseState::Keyword: {
102+
uint8_t kc = keyword[keyword_count];
103+
104+
if (c == kc) {
105+
keyword_count++;
106+
} else {
107+
state = ParseState::Malformed;
108+
}
109+
110+
if (keyword_count == sizeof(keyword)) {
111+
state = ParseState::Command;
112+
}
113+
114+
break;
115+
}
116+
117+
case ParseState::Command: {
118+
switch (c) {
119+
case '?':
120+
if (cmd_mode == CommandMode::Run) {
121+
cmd_mode = CommandMode::Read;
122+
} else if (cmd_mode == CommandMode::Write) {
123+
cmd_mode = CommandMode::Test;
124+
} else {
125+
state = ParseState::Malformed;
126+
}
127+
break;
128+
case '=':
129+
if (cmd_mode == CommandMode::Run) {
130+
cmd_mode = CommandMode::Write;
131+
} else {
132+
state = ParseState::Malformed;
133+
}
134+
break;
135+
case '\r':
136+
case '\n':
137+
break;
138+
default:
139+
if (cmd_mode == CommandMode::Run) {
140+
command.push_back((char) c);
141+
} else {
142+
args.emplace_back();
143+
state = ParseState::Argument;
144+
i--;
145+
}
146+
break;
147+
}
148+
break;
149+
}
150+
151+
case ParseState::Argument: {
152+
auto &cur_arg = args.back();
153+
if (args_escape_count) {
154+
cur_arg.push_back((char)c);
155+
args_escape_count--;
156+
} else {
157+
switch (c) {
158+
case '\\':
159+
args_escape_count++;
160+
break;
161+
case '"':
162+
args_quote = !args_quote;
163+
break;
164+
case ',':
165+
if (args_quote) {
166+
cur_arg.push_back(',');
167+
} else {
168+
args.emplace_back();
169+
}
170+
break;
171+
case '\r':
172+
case '\n':
173+
break;
174+
default:
175+
cur_arg.push_back((char)c);
176+
break;
177+
}
178+
}
179+
break;
180+
}
181+
182+
default:
183+
break;
184+
}
185+
186+
}
187+
188+
bytes_parsed += len;
189+
return len;
190+
}
191+
}

0 commit comments

Comments
 (0)