-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmessage_serialization.cpp
More file actions
160 lines (141 loc) · 6.79 KB
/
Copy pathmessage_serialization.cpp
File metadata and controls
160 lines (141 loc) · 6.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/*
* Implementation of the MessageSerialization namespace.
* Provides functions for encoding and decoding Message objects.
*
* CSF Assignment 5
*
* Ifrah Attar - iattar1@jh.edu
* Morgan Huberty - mhubert3@jh.edu
*
*/
#include <utility>
#include <sstream>
#include <cassert>
#include <map>
#include "exceptions.h"
#include "message_serialization.h"
namespace {
// This anonymous namespace holds constants and helper functions local to this file.
// Primary map from MessageType enum to its string command. This is the
// source for the string representation of each message type.
const std::map<MessageType, std::string> message_type_to_string = {
{ MessageType::LOGIN, "LOGIN" }, { MessageType::CREATE, "CREATE" },
{ MessageType::PUSH, "PUSH" }, { MessageType::POP, "POP" },
{ MessageType::TOP, "TOP" }, { MessageType::SET, "SET" },
{ MessageType::GET, "GET" }, { MessageType::ADD, "ADD" },
{ MessageType::SUB, "SUB" }, { MessageType::MUL, "MUL" },
{ MessageType::DIV, "DIV" }, { MessageType::BEGIN, "BEGIN" },
{ MessageType::COMMIT, "COMMIT" }, { MessageType::BYE, "BYE" },
{ MessageType::OK, "OK" }, { MessageType::FAILED, "FAILED" },
{ MessageType::ERROR, "ERROR" }, { MessageType::DATA, "DATA" },
};
/**
* @brief Creates the reverse mapping from string command to MessageType.
* This function is called once to initialize the reverse map, ensuring that
* it is always in sync with the primary map. This prevents bugs where one
* map could be updated without the other.
* @return A map from std::string to MessageType.
*/
std::map<std::string, MessageType> create_reverse_map() {
std::map<std::string, MessageType> reverse_map;
for (const auto& pair : message_type_to_string) {
reverse_map[pair.second] = pair.first;
}
return reverse_map;
}
// Reverse map, generated automatically to ensure it stays in sync. This is used
// during decoding to find the MessageType from a command string.
const std::map<std::string, MessageType> string_to_message_type = create_reverse_map();
}
void MessageSerialization::encode( const Message &msg, std::string &encoded_msg )
{
// Use a stringstream for efficient string construction.
std::stringstream ss;
// Append the command string (e.g., "LOGIN") based on the message type.
ss << message_type_to_string.at(msg.get_message_type());
// Append each argument, separated by a space.
for (unsigned i = 0; i < msg.get_num_args(); ++i) {
ss << " ";
// Special case: FAILED and ERROR messages require their text to be in quotes.
if (msg.get_message_type() == MessageType::FAILED || msg.get_message_type() == MessageType::ERROR) {
ss << "\"" << msg.get_arg(i) << "\"";
} else {
ss << msg.get_arg(i);
}
}
// Terminate the message with a newline character as required by protocol.
ss << "\n";
encoded_msg = ss.str();
// Ensure the final encoded message does not exceed the maximum allowed length.
if (encoded_msg.length() > Message::MAX_ENCODED_LEN) {
throw InvalidMessage("Encoded message exceeds maximum length");
}
}
void MessageSerialization::decode( const std::string &encoded_msg_, Message &msg )
{
// Perform initial validation checks on the raw incoming string.
if (encoded_msg_.length() > Message::MAX_ENCODED_LEN) {
throw InvalidMessage("Encoded message exceeds maximum length");
}
if (encoded_msg_.empty() || encoded_msg_.back() != '\n') {
throw InvalidMessage("Encoded message is not terminated by a newline character");
}
// Create a temporary message object. Only modify the output parameter 'msg'
// at the very end if the entire decoding process is successful.
Message temp_msg;
// Make a mutable copy of the input string to work with.
std::string encoded_msg = encoded_msg_;
// Find the first non-whitespace character to locate the start of the command.
size_t first = encoded_msg.find_first_not_of(" \t\r\v\f");
if (first == std::string::npos) {
// The message is empty or contains only whitespace.
throw InvalidMessage("Message is empty or contains only whitespace");
}
// Extract command token by finding the next whitespace or newline.
size_t cmd_end = encoded_msg.find_first_of(" \t\r\v\f\n", first);
std::string command = encoded_msg.substr(first, cmd_end - first);
// Look up the command string in our reverse map to determine the MessageType.
auto it = string_to_message_type.find(command);
if (it == string_to_message_type.end()) {
// If the command isn't in the map, it's not part of protocol.
throw InvalidMessage("Unknown command: " + command);
}
temp_msg.set_message_type(it->second);
// Start parsing arguments from where the command ended.
size_t current_pos = cmd_end;
while(current_pos < encoded_msg.length() && encoded_msg[current_pos] != '\n') {
// Find the beginning of the next argument, skipping any intermediate whitespace.
size_t arg_start = encoded_msg.find_first_not_of(" \t\r\v\f", current_pos);
if (arg_start == std::string::npos || encoded_msg[arg_start] == '\n') {
// No more arguments found before the end of the line.
break;
}
if (encoded_msg[arg_start] == '"') {
// This is a quoted_text argument. Find the closing quote.
size_t arg_end = encoded_msg.find('"', arg_start + 1);
if (arg_end == std::string::npos) {
// No closing quote was found.
throw InvalidMessage("Unterminated quoted string");
}
// Add the content inside the quotes as a single argument.
temp_msg.push_arg(encoded_msg.substr(arg_start + 1, arg_end - (arg_start + 1)));
// Continue parsing after the closing quote.
current_pos = arg_end + 1;
} else {
// This is a regular, non-quoted argument. Find its end.
size_t arg_end = encoded_msg.find_first_of(" \t\r\v\f\n", arg_start);
// Add the argument to the message.
temp_msg.push_arg(encoded_msg.substr(arg_start, arg_end - arg_start));
// Continue parsing from the end of this argument.
current_pos = arg_end;
}
}
// After parsing, use the built-in validator to ensure the message is
// protocol-compliant (correct number and type of arguments).
if (!temp_msg.is_valid()) {
throw InvalidMessage("Decoded message is invalid");
}
// If everything is successful, assign the fully-formed temporary message
// to the output parameter.
msg = temp_msg;
}