]> git.sesse.net Git - casparcg/blob - protocol/amcp/AMCPProtocolStrategy.cpp
Subtree merge of old SVN "docs" folder into the "master" git branch. You can see...
[casparcg] / protocol / amcp / AMCPProtocolStrategy.cpp
1 /*\r
2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>\r
3 *\r
4 * This file is part of CasparCG (www.casparcg.com).\r
5 *\r
6 * CasparCG is free software: you can redistribute it and/or modify\r
7 * it under the terms of the GNU General Public License as published by\r
8 * the Free Software Foundation, either version 3 of the License, or\r
9 * (at your option) any later version.\r
10 *\r
11 * CasparCG is distributed in the hope that it will be useful,\r
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
14 * GNU General Public License for more details.\r
15 *\r
16 * You should have received a copy of the GNU General Public License\r
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.\r
18 *\r
19 * Author: Nicklas P Andersson\r
20 */\r
21 \r
22  \r
23 #include "../StdAfx.h"\r
24 \r
25 #include "AMCPProtocolStrategy.h"\r
26 \r
27 #include "../util/AsyncEventServer.h"\r
28 #include "AMCPCommandsImpl.h"\r
29 \r
30 #include <stdio.h>\r
31 #include <crtdbg.h>\r
32 #include <string.h>\r
33 #include <algorithm>\r
34 #include <cctype>\r
35 \r
36 #include <boost/algorithm/string/trim.hpp>\r
37 #include <boost/algorithm/string/split.hpp>\r
38 #include <boost/algorithm/string/replace.hpp>\r
39 #include <boost/lexical_cast.hpp>\r
40 \r
41 #if defined(_MSC_VER)\r
42 #pragma warning (push, 1) // TODO: Legacy code, just disable warnings\r
43 #endif\r
44 \r
45 namespace caspar { namespace protocol { namespace amcp {\r
46 \r
47 using IO::ClientInfoPtr;\r
48 \r
49 const std::wstring AMCPProtocolStrategy::MessageDelimiter = TEXT("\r\n");\r
50 \r
51 inline std::shared_ptr<core::video_channel> GetChannelSafe(unsigned int index, const std::vector<safe_ptr<core::video_channel>>& channels)\r
52 {\r
53         return index < channels.size() ? std::shared_ptr<core::video_channel>(channels[index]) : nullptr;\r
54 }\r
55 \r
56 AMCPProtocolStrategy::AMCPProtocolStrategy(\r
57                 const std::vector<safe_ptr<core::video_channel>>& channels,\r
58                 const std::shared_ptr<core::thumbnail_generator>& thumb_gen,\r
59                 boost::promise<bool>& shutdown_server_now)\r
60         : channels_(channels)\r
61         , thumb_gen_(thumb_gen)\r
62         , shutdown_server_now_(shutdown_server_now)\r
63 {\r
64         AMCPCommandQueuePtr pGeneralCommandQueue(new AMCPCommandQueue());\r
65         commandQueues_.push_back(pGeneralCommandQueue);\r
66 \r
67 \r
68         std::shared_ptr<core::video_channel> pChannel;\r
69         unsigned int index = -1;\r
70         //Create a commandpump for each video_channel\r
71         while((pChannel = GetChannelSafe(++index, channels_)) != 0) {\r
72                 AMCPCommandQueuePtr pChannelCommandQueue(new AMCPCommandQueue());\r
73                 std::wstring title = TEXT("video_channel ");\r
74 \r
75                 //HACK: Perform real conversion from int to string\r
76                 TCHAR num = TEXT('1')+static_cast<TCHAR>(index);\r
77                 title += num;\r
78                 \r
79                 commandQueues_.push_back(pChannelCommandQueue);\r
80         }\r
81 }\r
82 \r
83 AMCPProtocolStrategy::~AMCPProtocolStrategy() {\r
84 }\r
85 \r
86 void AMCPProtocolStrategy::Parse(const TCHAR* pData, int charCount, ClientInfoPtr pClientInfo)\r
87 {\r
88         size_t pos;\r
89         std::wstring recvData(pData, charCount);\r
90         std::wstring availibleData = (pClientInfo != nullptr ? pClientInfo->currentMessage_ : L"") + recvData;\r
91 \r
92         while(true) {\r
93                 pos = availibleData.find(MessageDelimiter);\r
94                 if(pos != std::wstring::npos)\r
95                 {\r
96                         std::wstring message = availibleData.substr(0,pos);\r
97 \r
98                         //This is where a complete message gets taken care of\r
99                         if(message.length() > 0) {\r
100                                 ProcessMessage(message, pClientInfo);\r
101                         }\r
102 \r
103                         std::size_t nextStartPos = pos + MessageDelimiter.length();\r
104                         if(nextStartPos < availibleData.length())\r
105                                 availibleData = availibleData.substr(nextStartPos);\r
106                         else {\r
107                                 availibleData.clear();\r
108                                 break;\r
109                         }\r
110                 }\r
111                 else\r
112                 {\r
113                         break;\r
114                 }\r
115         }\r
116         if(pClientInfo)\r
117                 pClientInfo->currentMessage_ = availibleData;\r
118 }\r
119 \r
120 void AMCPProtocolStrategy::ProcessMessage(const std::wstring& message, ClientInfoPtr& pClientInfo)\r
121 {       \r
122         CASPAR_LOG(info) << L"Received message from " << pClientInfo->print() << ": " << message << L"\\r\\n";\r
123         \r
124         bool bError = true;\r
125         MessageParserState state = New;\r
126 \r
127         AMCPCommandPtr pCommand;\r
128 \r
129         pCommand = InterpretCommandString(message, &state);\r
130 \r
131         if(pCommand != 0) {\r
132                 pCommand->SetClientInfo(pClientInfo);   \r
133                 if(QueueCommand(pCommand))\r
134                         bError = false;\r
135                 else\r
136                         state = GetChannel;\r
137         }\r
138 \r
139         if(bError == true) {\r
140                 std::wstringstream answer;\r
141                 switch(state)\r
142                 {\r
143                 case GetCommand:\r
144                         answer << TEXT("400 ERROR\r\n") + message << "\r\n";\r
145                         break;\r
146                 case GetChannel:\r
147                         answer << TEXT("401 ERROR\r\n");\r
148                         break;\r
149                 case GetParameters:\r
150                         answer << TEXT("402 ERROR\r\n");\r
151                         break;\r
152                 default:\r
153                         answer << TEXT("500 FAILED\r\n");\r
154                         break;\r
155                 }\r
156                 pClientInfo->Send(answer.str());\r
157         }\r
158 }\r
159 \r
160 AMCPCommandPtr AMCPProtocolStrategy::InterpretCommandString(const std::wstring& message, MessageParserState* pOutState)\r
161 {\r
162         std::vector<std::wstring> tokens;\r
163         unsigned int currentToken = 0;\r
164         std::wstring commandSwitch;\r
165 \r
166         AMCPCommandPtr pCommand;\r
167         MessageParserState state = New;\r
168 \r
169         std::size_t tokensInMessage = TokenizeMessage(message, &tokens);\r
170 \r
171         //parse the message one token at the time\r
172         while(currentToken < tokensInMessage)\r
173         {\r
174                 switch(state)\r
175                 {\r
176                 case New:\r
177                         if(tokens[currentToken][0] == TEXT('/'))\r
178                                 state = GetSwitch;\r
179                         else\r
180                                 state = GetCommand;\r
181                         break;\r
182 \r
183                 case GetSwitch:\r
184                         commandSwitch = tokens[currentToken];\r
185                         state = GetCommand;\r
186                         ++currentToken;\r
187                         break;\r
188 \r
189                 case GetCommand:\r
190                         pCommand = CommandFactory(tokens[currentToken]);\r
191                         if(pCommand == 0) {\r
192                                 goto ParseFinnished;\r
193                         }\r
194                         else\r
195                         {\r
196                                 pCommand->SetChannels(channels_);\r
197                                 pCommand->SetThumbGenerator(thumb_gen_);\r
198                                 pCommand->SetShutdownServerNow(shutdown_server_now_);\r
199                                 //Set scheduling\r
200                                 if(commandSwitch.size() > 0) {\r
201                                         transform(commandSwitch.begin(), commandSwitch.end(), commandSwitch.begin(), toupper);\r
202 \r
203                                         if(commandSwitch == TEXT("/APP"))\r
204                                                 pCommand->SetScheduling(AddToQueue);\r
205                                         else if(commandSwitch  == TEXT("/IMMF"))\r
206                                                 pCommand->SetScheduling(ImmediatelyAndClear);\r
207                                 }\r
208 \r
209                                 if(pCommand->NeedChannel())\r
210                                         state = GetChannel;\r
211                                 else\r
212                                         state = GetParameters;\r
213                         }\r
214                         ++currentToken;\r
215                         break;\r
216 \r
217                 case GetParameters:\r
218                         {\r
219                                 _ASSERTE(pCommand != 0);\r
220                                 int parameterCount=0;\r
221                                 while(currentToken<tokensInMessage)\r
222                                 {\r
223                                         pCommand->AddParameter(tokens[currentToken++]);\r
224                                         ++parameterCount;\r
225                                 }\r
226 \r
227                                 if(parameterCount < pCommand->GetMinimumParameters()) {\r
228                                         goto ParseFinnished;\r
229                                 }\r
230 \r
231                                 state = Done;\r
232                                 break;\r
233                         }\r
234 \r
235                 case GetChannel:\r
236                         {\r
237 //                              assert(pCommand != 0);\r
238 \r
239                                 std::wstring str = boost::trim_copy(tokens[currentToken]);\r
240                                 std::vector<std::wstring> split;\r
241                                 boost::split(split, str, boost::is_any_of("-"));\r
242                                         \r
243                                 int channelIndex = -1;\r
244                                 int layerIndex = -1;\r
245                                 try\r
246                                 {\r
247                                         channelIndex = boost::lexical_cast<int>(split[0]) - 1;\r
248 \r
249                                         if(split.size() > 1)\r
250                                                 layerIndex = boost::lexical_cast<int>(split[1]);\r
251                                 }\r
252                                 catch(...)\r
253                                 {\r
254                                         goto ParseFinnished;\r
255                                 }\r
256 \r
257                                 std::shared_ptr<core::video_channel> pChannel = GetChannelSafe(channelIndex, channels_);\r
258                                 if(pChannel == 0) {\r
259                                         goto ParseFinnished;\r
260                                 }\r
261 \r
262                                 pCommand->SetChannel(pChannel);\r
263                                 pCommand->SetChannelIndex(channelIndex);\r
264                                 pCommand->SetLayerIntex(layerIndex);\r
265 \r
266                                 state = GetParameters;\r
267                                 ++currentToken;\r
268                                 break;\r
269                         }\r
270 \r
271                 default:        //Done and unexpected\r
272                         goto ParseFinnished;\r
273                 }\r
274         }\r
275 \r
276 ParseFinnished:\r
277         if(state == GetParameters && pCommand->GetMinimumParameters()==0)\r
278                 state = Done;\r
279 \r
280         if(state != Done) {\r
281                 pCommand.reset();\r
282         }\r
283 \r
284         if(pOutState != 0) {\r
285                 *pOutState = state;\r
286         }\r
287 \r
288         return pCommand;\r
289 }\r
290 \r
291 bool AMCPProtocolStrategy::QueueCommand(AMCPCommandPtr pCommand) {\r
292         if(pCommand->NeedChannel()) {\r
293                 unsigned int channelIndex = pCommand->GetChannelIndex() + 1;\r
294                 if(commandQueues_.size() > channelIndex) {\r
295                         commandQueues_[channelIndex]->AddCommand(pCommand);\r
296                 }\r
297                 else\r
298                         return false;\r
299         }\r
300         else {\r
301                 commandQueues_[0]->AddCommand(pCommand);\r
302         }\r
303         return true;\r
304 }\r
305 \r
306 AMCPCommandPtr AMCPProtocolStrategy::CommandFactory(const std::wstring& str)\r
307 {\r
308         std::wstring s = str;\r
309         transform(s.begin(), s.end(), s.begin(), toupper);\r
310         \r
311         if         (s == TEXT("MIXER"))                 return std::make_shared<MixerCommand>();\r
312         else if(s == TEXT("DIAG"))                      return std::make_shared<DiagnosticsCommand>();\r
313         else if(s == TEXT("CHANNEL_GRID"))      return std::make_shared<ChannelGridCommand>();\r
314         else if(s == TEXT("CALL"))                      return std::make_shared<CallCommand>();\r
315         else if(s == TEXT("SWAP"))                      return std::make_shared<SwapCommand>();\r
316         else if(s == TEXT("LOAD"))                      return std::make_shared<LoadCommand>();\r
317         else if(s == TEXT("LOADBG"))            return std::make_shared<LoadbgCommand>();\r
318         else if(s == TEXT("ADD"))                       return std::make_shared<AddCommand>();\r
319         else if(s == TEXT("REMOVE"))            return std::make_shared<RemoveCommand>();\r
320         else if(s == TEXT("PAUSE"))                     return std::make_shared<PauseCommand>();\r
321         else if(s == TEXT("PLAY"))                      return std::make_shared<PlayCommand>();\r
322         else if(s == TEXT("STOP"))                      return std::make_shared<StopCommand>();\r
323         else if(s == TEXT("CLEAR"))                     return std::make_shared<ClearCommand>();\r
324         else if(s == TEXT("PRINT"))                     return std::make_shared<PrintCommand>();\r
325         else if(s == TEXT("LOG"))                       return std::make_shared<LogCommand>();\r
326         else if(s == TEXT("CG"))                        return std::make_shared<CGCommand>();\r
327         else if(s == TEXT("DATA"))                      return std::make_shared<DataCommand>();\r
328         else if(s == TEXT("CINF"))                      return std::make_shared<CinfCommand>();\r
329         else if(s == TEXT("INFO"))                      return std::make_shared<InfoCommand>(channels_);\r
330         else if(s == TEXT("CLS"))                       return std::make_shared<ClsCommand>();\r
331         else if(s == TEXT("TLS"))                       return std::make_shared<TlsCommand>();\r
332         else if(s == TEXT("VERSION"))           return std::make_shared<VersionCommand>();\r
333         else if(s == TEXT("BYE"))                       return std::make_shared<ByeCommand>();\r
334         else if(s == TEXT("SET"))                       return std::make_shared<SetCommand>();\r
335         else if(s == TEXT("THUMBNAIL"))         return std::make_shared<ThumbnailCommand>();\r
336         //else if(s == TEXT("MONITOR"))\r
337         //{\r
338         //      result = AMCPCommandPtr(new MonitorCommand());\r
339         //}\r
340         else if(s == TEXT("KILL"))                      return std::make_shared<KillCommand>();\r
341         return nullptr;\r
342 }\r
343 \r
344 std::size_t AMCPProtocolStrategy::TokenizeMessage(const std::wstring& message, std::vector<std::wstring>* pTokenVector)\r
345 {\r
346         //split on whitespace but keep strings within quotationmarks\r
347         //treat \ as the start of an escape-sequence: the following char will indicate what to actually put in the string\r
348 \r
349         std::wstring currentToken;\r
350 \r
351         char inQuote = 0;\r
352         bool getSpecialCode = false;\r
353 \r
354         for(unsigned int charIndex=0; charIndex<message.size(); ++charIndex)\r
355         {\r
356                 if(getSpecialCode)\r
357                 {\r
358                         //insert code-handling here\r
359                         switch(message[charIndex])\r
360                         {\r
361                         case TEXT('\\'):\r
362                                 currentToken += TEXT("\\");\r
363                                 break;\r
364                         case TEXT('\"'):\r
365                                 currentToken += TEXT("\"");\r
366                                 break;\r
367                         case TEXT('n'):\r
368                                 currentToken += TEXT("\n");\r
369                                 break;\r
370                         default:\r
371                                 break;\r
372                         };\r
373                         getSpecialCode = false;\r
374                         continue;\r
375                 }\r
376 \r
377                 if(message[charIndex]==TEXT('\\'))\r
378                 {\r
379                         getSpecialCode = true;\r
380                         continue;\r
381                 }\r
382 \r
383                 if(message[charIndex]==' ' && inQuote==false)\r
384                 {\r
385                         if(currentToken.size()>0)\r
386                         {\r
387                                 pTokenVector->push_back(currentToken);\r
388                                 currentToken.clear();\r
389                         }\r
390                         continue;\r
391                 }\r
392 \r
393                 if(message[charIndex]==TEXT('\"'))\r
394                 {\r
395                         inQuote ^= 1;\r
396 \r
397                         if(currentToken.size()>0)\r
398                         {\r
399                                 pTokenVector->push_back(currentToken);\r
400                                 currentToken.clear();\r
401                         }\r
402                         continue;\r
403                 }\r
404 \r
405                 currentToken += message[charIndex];\r
406         }\r
407 \r
408         if(currentToken.size()>0)\r
409         {\r
410                 pTokenVector->push_back(currentToken);\r
411                 currentToken.clear();\r
412         }\r
413 \r
414         return pTokenVector->size();\r
415 }\r
416 \r
417 }       //namespace amcp\r
418 }}      //namespace caspar