]> git.sesse.net Git - casparcg/blob - protocol/amcp/AMCPProtocolStrategy.cpp
d95fd9133626a081fce994fdc9ff5ddcefb0c061
[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 "AMCPCommandsImpl.h"
27 #include "amcp_shared.h"
28 #include "AMCPCommand.h"
29 #include "AMCPCommandQueue.h"
30
31 #include <stdio.h>
32 #include <crtdbg.h>
33 #include <string.h>
34 #include <algorithm>
35 #include <cctype>
36
37 #include <boost/algorithm/string/trim.hpp>
38 #include <boost/algorithm/string/split.hpp>
39 #include <boost/algorithm/string/replace.hpp>
40 #include <boost/lexical_cast.hpp>
41
42 #if defined(_MSC_VER)
43 #pragma warning (push, 1) // TODO: Legacy code, just disable warnings
44 #endif
45
46 namespace caspar { namespace protocol { namespace amcp {
47
48 using IO::ClientInfoPtr;
49
50 struct AMCPProtocolStrategy::impl
51 {
52 private:
53         std::vector<channel_context>                            channels_;
54         std::vector<AMCPCommandQueue::ptr_type>         commandQueues_;
55         std::shared_ptr<core::thumbnail_generator>      thumb_gen_;
56         boost::promise<bool>&                                           shutdown_server_now_;
57
58 public:
59         impl(const std::vector<spl::shared_ptr<core::video_channel>>& channels, const std::shared_ptr<core::thumbnail_generator>& thumb_gen, boost::promise<bool>& shutdown_server_now) : thumb_gen_(thumb_gen), shutdown_server_now_(shutdown_server_now)
60         {
61                 commandQueues_.push_back(std::make_shared<AMCPCommandQueue>());
62
63                 int index = 0;
64                 BOOST_FOREACH(const spl::shared_ptr<core::video_channel>& channel,  channels)
65                 {
66                         std::wstring lifecycle_key = L"lock" + boost::lexical_cast<std::wstring>(index);
67                         channels_.push_back(channel_context(channel, lifecycle_key));
68                         auto queue(std::make_shared<AMCPCommandQueue>());
69                         commandQueues_.push_back(queue);
70                         ++index;
71                 }
72         }
73
74         ~impl() {}
75
76         enum parser_state {
77                 New = 0,
78                 GetSwitch,
79                 GetCommand,
80                 GetParameters
81         };
82         enum error_state {
83                 no_error = 0,
84                 command_error,
85                 channel_error,
86                 parameters_error,
87                 unknown_error,
88                 access_error
89         };
90
91         struct command_interpreter_result
92         {
93                 command_interpreter_result() : error(no_error) {}
94
95                 std::shared_ptr<caspar::IO::lock_container>     lock;
96                 std::wstring                                                            command_name;
97                 AMCPCommand::ptr_type                                           command;
98                 error_state                                                                     error;
99                 AMCPCommandQueue::ptr_type                                      queue;
100         };
101
102         //The paser method expects message to be complete messages with the delimiter stripped away.
103         //Thesefore the AMCPProtocolStrategy should be decorated with a delimiter_based_chunking_strategy
104         void Parse(const std::wstring& message, ClientInfoPtr client)
105         {
106                 CASPAR_LOG(info) << L"Received message from " << client->print() << ": " << message << L"\\r\\n";
107         
108                 command_interpreter_result result;
109                 if(interpret_command_string(message, result, client))
110                 {
111                         if(result.lock && !result.lock->check_access(client))
112                                 result.error = access_error;
113                         else
114                                 result.queue->AddCommand(result.command);
115                 }
116                 
117                 if(result.error != no_error)
118                 {
119                         std::wstringstream answer;
120                         boost::to_upper(result.command_name);
121
122                         switch(result.error)
123                         {
124                         case command_error:
125                                 answer << L"400 ERROR\r\n" << message << "\r\n";
126                                 break;
127                         case channel_error:
128                                 answer << L"401 " << result.command_name << " ERROR\r\n";
129                                 break;
130                         case parameters_error:
131                                 answer << L"402 " << result.command_name << " ERROR\r\n";
132                                 break;
133                         case access_error:
134                                 answer << L"503 " << result.command_name << " FAILED\r\n";
135                                 break;
136                         default:
137                                 answer << L"500 FAILED\r\n";
138                                 break;
139                         }
140                         client->send(answer.str());
141                 }
142         }
143
144 private:
145         friend class AMCPCommand;
146
147         bool interpret_command_string(const std::wstring& message, command_interpreter_result& result, ClientInfoPtr client)
148         {
149                 try
150                 {
151                         std::vector<std::wstring> tokens;
152                         parser_state state = New;
153
154                         tokenize(message, &tokens);
155
156                         //parse the message one token at the time
157                         auto end = tokens.end();
158                         auto it = tokens.begin();
159                         while(it != end && result.error == no_error)
160                         {
161                                 switch(state)
162                                 {
163                                 case New:
164                                         if((*it)[0] == TEXT('/'))
165                                                 state = GetSwitch;
166                                         else
167                                                 state = GetCommand;
168                                         break;
169
170                                 case GetSwitch:
171                                         //command_switch = (*it);       //we dont care for the switch anymore
172                                         state = GetCommand;
173                                         ++it;
174                                         break;
175
176                                 case GetCommand:
177                                         {
178                                                 result.command_name = (*it);
179                                                 result.command = create_command(result.command_name, client);
180                                                 if(result.command)      //the command doesn't need a channel
181                                                 {
182                                                         result.queue = commandQueues_[0];
183                                                         state = GetParameters;
184                                                 }
185                                                 else
186                                                 {
187                                                         //get channel index from next token
188                                                         int channel_index = -1;
189                                                         int layer_index = -1;
190
191                                                         ++it;
192                                                         if(it == end)
193                                                         {
194                                                                 if(create_channel_command(result.command_name, client, channels_.at(0), 0, 0))  //check if there is a command like this
195                                                                         result.error = channel_error;
196                                                                 else
197                                                                         result.error = command_error;
198
199                                                                 break;
200                                                         }
201
202                                                         {       //parse channel/layer token
203                                                                 try
204                                                                 {
205                                                                         std::wstring channelid_str = boost::trim_copy(*it);
206                                                                         std::vector<std::wstring> split;
207                                                                         boost::split(split, channelid_str, boost::is_any_of("-"));
208
209                                                                         channel_index = boost::lexical_cast<int>(split[0]) - 1;
210                                                                         if(split.size() > 1)
211                                                                                 layer_index = boost::lexical_cast<int>(split[1]);
212                                                                 }
213                                                                 catch(...)
214                                                                 {
215                                                                         result.error = channel_error;
216                                                                         break;
217                                                                 }
218                                                         }
219                                                 
220                                                         if(channel_index >= 0 && channel_index < channels_.size())
221                                                         {
222                                                                 result.command = create_channel_command(result.command_name, client, channels_.at(channel_index), channel_index, layer_index);
223                                                                 if(result.command)
224                                                                 {
225                                                                         result.lock = channels_.at(channel_index).lock;
226                                                                         result.queue = commandQueues_[channel_index + 1];
227                                                                 }
228                                                                 else
229                                                                 {
230                                                                         result.error = command_error;
231                                                                         break;
232                                                                 }
233                                                         }
234                                                         else
235                                                         {
236                                                                 result.error = channel_error;
237                                                                 break;
238                                                         }
239                                                 }
240
241                                                 state = GetParameters;
242                                                 ++it;
243                                         }
244                                         break;
245
246                                 case GetParameters:
247                                         {
248                                                 int parameterCount=0;
249                                                 while(it != end)
250                                                 {
251                                                         result.command->parameters().push_back((*it));
252                                                         ++it;
253                                                         ++parameterCount;
254                                                 }
255                                         }
256                                         break;
257                                 }
258                         }
259
260                         if(result.command && result.error == no_error && result.command->parameters().size() < result.command->minimum_parameters()) {
261                                 result.error = parameters_error;
262                         }
263                 }
264                 catch(...)
265                 {
266                         CASPAR_LOG_CURRENT_EXCEPTION();
267                         result.error = unknown_error;
268                 }
269
270                 return result.error == no_error;
271         }
272
273         std::size_t tokenize(const std::wstring& message, std::vector<std::wstring>* pTokenVector)
274         {
275                 //split on whitespace but keep strings within quotationmarks
276                 //treat \ as the start of an escape-sequence: the following char will indicate what to actually put in the string
277
278                 std::wstring currentToken;
279
280                 bool inQuote = false;
281                 bool getSpecialCode = false;
282
283                 for(unsigned int charIndex=0; charIndex<message.size(); ++charIndex)
284                 {
285                         if(getSpecialCode)
286                         {
287                                 //insert code-handling here
288                                 switch(message[charIndex])
289                                 {
290                                 case TEXT('\\'):
291                                         currentToken += TEXT("\\");
292                                         break;
293                                 case TEXT('\"'):
294                                         currentToken += TEXT("\"");
295                                         break;
296                                 case TEXT('n'):
297                                         currentToken += TEXT("\n");
298                                         break;
299                                 default:
300                                         break;
301                                 };
302                                 getSpecialCode = false;
303                                 continue;
304                         }
305
306                         if(message[charIndex]==TEXT('\\'))
307                         {
308                                 getSpecialCode = true;
309                                 continue;
310                         }
311
312                         if(message[charIndex]==' ' && inQuote==false)
313                         {
314                                 if(currentToken.size()>0)
315                                 {
316                                         pTokenVector->push_back(currentToken);
317                                         currentToken.clear();
318                                 }
319                                 continue;
320                         }
321
322                         if(message[charIndex]==TEXT('\"'))
323                         {
324                                 inQuote = !inQuote;
325
326                                 if(currentToken.size()>0 || !inQuote)
327                                 {
328                                         pTokenVector->push_back(currentToken);
329                                         currentToken.clear();
330                                 }
331                                 continue;
332                         }
333
334                         currentToken += message[charIndex];
335                 }
336
337                 if(currentToken.size()>0)
338                 {
339                         pTokenVector->push_back(currentToken);
340                         currentToken.clear();
341                 }
342
343                 return pTokenVector->size();
344         }
345
346         AMCPCommand::ptr_type create_command(const std::wstring& str, ClientInfoPtr client)
347         {
348                 std::wstring s = boost::to_upper_copy(str);
349                 if(s == TEXT("DIAG"))                           return std::make_shared<DiagnosticsCommand>(client);
350                 else if(s == TEXT("CHANNEL_GRID"))      return std::make_shared<ChannelGridCommand>(client, channels_);
351                 else if(s == TEXT("DATA"))                      return std::make_shared<DataCommand>(client);
352                 else if(s == TEXT("CINF"))                      return std::make_shared<CinfCommand>(client);
353                 else if(s == TEXT("INFO"))                      return std::make_shared<InfoCommand>(client, channels_);
354                 else if(s == TEXT("CLS"))                       return std::make_shared<ClsCommand>(client);
355                 else if(s == TEXT("TLS"))                       return std::make_shared<TlsCommand>(client);
356                 else if(s == TEXT("VERSION"))           return std::make_shared<VersionCommand>(client);
357                 else if(s == TEXT("BYE"))                       return std::make_shared<ByeCommand>(client);
358                 else if(s == TEXT("LOCK"))                      return std::make_shared<LockCommand>(client, channels_);
359                 else if(s == TEXT("LOG"))                       return std::make_shared<LogCommand>(client);
360                 else if(s == TEXT("THUMBNAIL"))         return std::make_shared<ThumbnailCommand>(client, thumb_gen_);
361                 else if(s == TEXT("KILL"))                      return std::make_shared<KillCommand>(client, shutdown_server_now_);
362                 else if(s == TEXT("RESTART"))           return std::make_shared<RestartCommand>(client, shutdown_server_now_);
363
364                 return nullptr;
365         }
366
367         AMCPCommand::ptr_type create_channel_command(const std::wstring& str, ClientInfoPtr client, const channel_context& channel, unsigned int channel_index, int layer_index)
368         {
369                 std::wstring s = boost::to_upper_copy(str);
370         
371                 if         (s == TEXT("MIXER"))                 return std::make_shared<MixerCommand>(client, channel, channel_index, layer_index);
372                 else if(s == TEXT("CALL"))                      return std::make_shared<CallCommand>(client, channel, channel_index, layer_index);
373                 else if(s == TEXT("SWAP"))                      return std::make_shared<SwapCommand>(client, channel, channel_index, layer_index, channels_);
374                 else if(s == TEXT("LOAD"))                      return std::make_shared<LoadCommand>(client, channel, channel_index, layer_index);
375                 else if(s == TEXT("LOADBG"))            return std::make_shared<LoadbgCommand>(client, channel, channel_index, layer_index, channels_);
376                 else if(s == TEXT("ADD"))                       return std::make_shared<AddCommand>(client, channel, channel_index, layer_index);
377                 else if(s == TEXT("REMOVE"))            return std::make_shared<RemoveCommand>(client, channel, channel_index, layer_index);
378                 else if(s == TEXT("PAUSE"))                     return std::make_shared<PauseCommand>(client, channel, channel_index, layer_index);
379                 else if(s == TEXT("PLAY"))                      return std::make_shared<PlayCommand>(client, channel, channel_index, layer_index, channels_);
380                 else if(s == TEXT("STOP"))                      return std::make_shared<StopCommand>(client, channel, channel_index, layer_index);
381                 else if(s == TEXT("CLEAR"))                     return std::make_shared<ClearCommand>(client, channel, channel_index, layer_index);
382                 else if(s == TEXT("PRINT"))                     return std::make_shared<PrintCommand>(client, channel, channel_index, layer_index);
383                 else if(s == TEXT("CG"))                        return std::make_shared<CGCommand>(client, channel, channel_index, layer_index);
384                 else if(s == TEXT("SET"))                       return std::make_shared<SetCommand>(client, channel, channel_index, layer_index);
385
386                 return nullptr;
387         }
388 };
389
390
391 AMCPProtocolStrategy::AMCPProtocolStrategy(const std::vector<spl::shared_ptr<core::video_channel>>& channels, const std::shared_ptr<core::thumbnail_generator>& thumb_gen, boost::promise<bool>& shutdown_server_now) : impl_(spl::make_unique<impl>(channels, thumb_gen, shutdown_server_now)) {}
392 AMCPProtocolStrategy::~AMCPProtocolStrategy() {}
393 void AMCPProtocolStrategy::Parse(const std::wstring& msg, IO::ClientInfoPtr pClientInfo) { impl_->Parse(msg, pClientInfo); }
394
395
396 }       //namespace amcp
397 }}      //namespace caspar