]> git.sesse.net Git - casparcg/blob - protocol/amcp/AMCPProtocolStrategy.cpp
* Don't show base path in FLS. Use INFO PATHS to get that.
[casparcg] / protocol / amcp / AMCPProtocolStrategy.cpp
1 /*
2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
3 *
4 * This file is part of CasparCG (www.casparcg.com).
5 *
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * Author: Nicklas P Andersson
20 */
21
22  
23 #include "../StdAfx.h"
24
25 #include "AMCPProtocolStrategy.h"
26 #include "amcp_shared.h"
27 #include "AMCPCommand.h"
28 #include "AMCPCommandQueue.h"
29 #include "amcp_command_repository.h"
30
31 #include <stdio.h>
32 #include <string.h>
33 #include <algorithm>
34 #include <cctype>
35 #include <future>
36
37 #include <core/help/help_repository.h>
38 #include <core/help/help_sink.h>
39
40 #include <boost/algorithm/string/trim.hpp>
41 #include <boost/algorithm/string/split.hpp>
42 #include <boost/algorithm/string/replace.hpp>
43 #include <boost/lexical_cast.hpp>
44
45 #if defined(_MSC_VER)
46 #pragma warning (push, 1) // TODO: Legacy code, just disable warnings
47 #endif
48
49 namespace caspar { namespace protocol { namespace amcp {
50
51 using IO::ClientInfoPtr;
52
53 template <typename Out, typename In>
54 bool try_lexical_cast(const In& input, Out& result)
55 {
56         Out saved = result;
57         bool success = boost::conversion::detail::try_lexical_convert(input, result);
58
59         if (!success)
60                 result = saved; // Needed because of how try_lexical_convert is implemented.
61
62         return success;
63 }
64
65 struct AMCPProtocolStrategy::impl
66 {
67 private:
68         std::vector<AMCPCommandQueue::ptr_type>         commandQueues_;
69         spl::shared_ptr<amcp_command_repository>        repo_;
70
71 public:
72         impl(const std::wstring& name, const spl::shared_ptr<amcp_command_repository>& repo)
73                 : repo_(repo)
74         {
75                 commandQueues_.push_back(spl::make_shared<AMCPCommandQueue>(L"General Queue for " + name));
76
77                 for (int i = 0; i < repo_->channels().size(); ++i)
78                 {
79                         commandQueues_.push_back(spl::make_shared<AMCPCommandQueue>(
80                                         L"Channel " + boost::lexical_cast<std::wstring>(i + 1) + L" for " + name));
81                 }
82         }
83
84         ~impl() {}
85
86         enum class error_state {
87                 no_error = 0,
88                 command_error,
89                 channel_error,
90                 parameters_error,
91                 unknown_error,
92                 access_error
93         };
94
95         struct command_interpreter_result
96         {
97                 std::shared_ptr<caspar::IO::lock_container>     lock;
98                 std::wstring                                                            command_name;
99                 AMCPCommand::ptr_type                                           command;
100                 error_state                                                                     error                   = error_state::no_error;
101                 std::shared_ptr<AMCPCommandQueue>                       queue;
102         };
103
104         //The paser method expects message to be complete messages with the delimiter stripped away.
105         //Thesefore the AMCPProtocolStrategy should be decorated with a delimiter_based_chunking_strategy
106         void Parse(const std::wstring& message, ClientInfoPtr client)
107         {
108                 CASPAR_LOG(info) << L"Received message from " << client->address() << ": " << message << L"\\r\\n";
109         
110                 command_interpreter_result result;
111                 if(interpret_command_string(message, result, client))
112                 {
113                         if(result.lock && !result.lock->check_access(client))
114                                 result.error = error_state::access_error;
115                         else
116                                 result.queue->AddCommand(result.command);
117                 }
118                 
119                 if (result.error != error_state::no_error)
120                 {
121                         std::wstringstream answer;
122
123                         switch(result.error)
124                         {
125                         case error_state::command_error:
126                                 answer << L"400 ERROR\r\n" << message << "\r\n";
127                                 break;
128                         case error_state::channel_error:
129                                 answer << L"401 " << result.command_name << " ERROR\r\n";
130                                 break;
131                         case error_state::parameters_error:
132                                 answer << L"402 " << result.command_name << " ERROR\r\n";
133                                 break;
134                         case error_state::access_error:
135                                 answer << L"503 " << result.command_name << " FAILED\r\n";
136                                 break;
137                         default:
138                                 answer << L"500 FAILED\r\n";
139                                 break;
140                         }
141                         client->send(answer.str());
142                 }
143         }
144
145 private:
146         bool interpret_command_string(const std::wstring& message, command_interpreter_result& result, ClientInfoPtr client)
147         {
148                 try
149                 {
150                         std::list<std::wstring> tokens;
151                         tokenize(message, tokens);
152
153                         // Discard GetSwitch
154                         if (!tokens.empty() && tokens.front().at(0) == L'/')
155                                 tokens.pop_front();
156
157                         // Fail if no more tokens.
158                         if (tokens.empty())
159                         {
160                                 result.error = error_state::command_error;
161                                 return false;
162                         }
163
164                         // Consume command name
165                         result.command_name = boost::to_upper_copy(tokens.front());
166                         tokens.pop_front();
167
168                         // Determine whether the next parameter is a channel spec or not
169                         int channel_index = -1;
170                         int layer_index = -1;
171                         std::wstring channel_spec;
172
173                         if (!tokens.empty())
174                         {
175                                 channel_spec = tokens.front();
176                                 std::wstring channelid_str = boost::trim_copy(channel_spec);
177                                 std::vector<std::wstring> split;
178                                 boost::split(split, channelid_str, boost::is_any_of("-"));
179
180                                 // Use non_throwing lexical cast to not hit exception break point all the time.
181                                 if (try_lexical_cast(split[0], channel_index))
182                                 {
183                                         --channel_index;
184
185                                         if (split.size() > 1)
186                                                 try_lexical_cast(split[1], layer_index);
187
188                                         // Consume channel-spec
189                                         tokens.pop_front();
190                                 }
191                         }
192
193                         bool is_channel_command = channel_index != -1;
194
195                         // Create command instance
196                         if (is_channel_command)
197                         {
198                                 result.command = repo_->create_channel_command(result.command_name, client, channel_index, layer_index, tokens);
199
200                                 if (result.command)
201                                 {
202                                         result.lock = repo_->channels().at(channel_index).lock;
203                                         result.queue = commandQueues_.at(channel_index + 1);
204                                 }
205                                 else // Might be a non channel command, although the first argument is numeric
206                                 {
207                                         // Restore backed up channel spec string.
208                                         tokens.push_front(channel_spec);
209                                         result.command = repo_->create_command(result.command_name, client, tokens);
210
211                                         if (result.command)
212                                                 result.queue = commandQueues_.at(0);
213                                 }
214                         }
215                         else
216                         {
217                                 result.command = repo_->create_command(result.command_name, client, tokens);
218
219                                 if (result.command)
220                                         result.queue = commandQueues_.at(0);
221                         }
222
223                         if (!result.command)
224                                 result.error = error_state::command_error;
225                         else
226                         {
227                                 std::vector<std::wstring> parameters(tokens.begin(), tokens.end());
228
229                                 result.command->parameters() = std::move(parameters);
230
231                                 if (result.command->parameters().size() < result.command->minimum_parameters())
232                                         result.error = error_state::parameters_error;
233                         }
234                 }
235                 catch(...)
236                 {
237                         CASPAR_LOG_CURRENT_EXCEPTION();
238                         result.error = error_state::unknown_error;
239                 }
240
241                 return result.error == error_state::no_error;
242         }
243
244         template<typename C>
245         std::size_t tokenize(const std::wstring& message, C& pTokenVector)
246         {
247                 //split on whitespace but keep strings within quotationmarks
248                 //treat \ as the start of an escape-sequence: the following char will indicate what to actually put in the string
249
250                 std::wstring currentToken;
251
252                 bool inQuote = false;
253                 bool getSpecialCode = false;
254
255                 for(unsigned int charIndex=0; charIndex<message.size(); ++charIndex)
256                 {
257                         if(getSpecialCode)
258                         {
259                                 //insert code-handling here
260                                 switch(message[charIndex])
261                                 {
262                                 case L'\\':
263                                         currentToken += L"\\";
264                                         break;
265                                 case L'\"':
266                                         currentToken += L"\"";
267                                         break;
268                                 case L'n':
269                                         currentToken += L"\n";
270                                         break;
271                                 default:
272                                         break;
273                                 };
274                                 getSpecialCode = false;
275                                 continue;
276                         }
277
278                         if(message[charIndex]==L'\\')
279                         {
280                                 getSpecialCode = true;
281                                 continue;
282                         }
283
284                         if(message[charIndex]==L' ' && inQuote==false)
285                         {
286                                 if(!currentToken.empty())
287                                 {
288                                         pTokenVector.push_back(currentToken);
289                                         currentToken.clear();
290                                 }
291                                 continue;
292                         }
293
294                         if(message[charIndex]==L'\"')
295                         {
296                                 inQuote = !inQuote;
297
298                                 if(!currentToken.empty() || !inQuote)
299                                 {
300                                         pTokenVector.push_back(currentToken);
301                                         currentToken.clear();
302                                 }
303                                 continue;
304                         }
305
306                         currentToken += message[charIndex];
307                 }
308
309                 if(!currentToken.empty())
310                 {
311                         pTokenVector.push_back(currentToken);
312                         currentToken.clear();
313                 }
314
315                 return pTokenVector.size();
316         }
317 };
318
319 AMCPProtocolStrategy::AMCPProtocolStrategy(const std::wstring& name, const spl::shared_ptr<amcp_command_repository>& repo)
320         : impl_(spl::make_unique<impl>(name, repo))
321 {
322 }
323 AMCPProtocolStrategy::~AMCPProtocolStrategy() {}
324 void AMCPProtocolStrategy::Parse(const std::wstring& msg, IO::ClientInfoPtr pClientInfo) { impl_->Parse(msg, pClientInfo); }
325
326
327 }       //namespace amcp
328 }}      //namespace caspar