]> git.sesse.net Git - casparcg/blob - protocol/amcp/AMCPProtocolStrategy.cpp
Merged from trunk
[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<spl::shared_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(const std::vector<spl::shared_ptr<core::video_channel>>& channels) : channels_(channels) {\r
57         AMCPCommandQueuePtr pGeneralCommandQueue(new AMCPCommandQueue());\r
58         commandQueues_.push_back(pGeneralCommandQueue);\r
59 \r
60 \r
61         std::shared_ptr<core::video_channel> pChannel;\r
62         unsigned int index = -1;\r
63         //Create a commandpump for each video_channel\r
64         while((pChannel = GetChannelSafe(++index, channels_)) != 0) {\r
65                 AMCPCommandQueuePtr pChannelCommandQueue(new AMCPCommandQueue());\r
66                 std::wstring title = TEXT("video_channel ");\r
67 \r
68                 //HACK: Perform real conversion from int to string\r
69                 TCHAR num = TEXT('1')+static_cast<TCHAR>(index);\r
70                 title += num;\r
71                 \r
72                 commandQueues_.push_back(pChannelCommandQueue);\r
73         }\r
74 }\r
75 \r
76 AMCPProtocolStrategy::~AMCPProtocolStrategy() {\r
77 }\r
78 \r
79 void AMCPProtocolStrategy::Parse(const TCHAR* pData, int charCount, ClientInfoPtr pClientInfo)\r
80 {\r
81         size_t pos;\r
82         std::wstring recvData(pData, charCount);\r
83         std::wstring availibleData = (pClientInfo != nullptr ? pClientInfo->currentMessage_ : L"") + recvData;\r
84 \r
85         while(true) {\r
86                 pos = availibleData.find(MessageDelimiter);\r
87                 if(pos != std::wstring::npos)\r
88                 {\r
89                         std::wstring message = availibleData.substr(0,pos);\r
90 \r
91                         //This is where a complete message gets taken care of\r
92                         if(message.length() > 0) {\r
93                                 ProcessMessage(message, pClientInfo);\r
94                         }\r
95 \r
96                         std::size_t nextStartPos = pos + MessageDelimiter.length();\r
97                         if(nextStartPos < availibleData.length())\r
98                                 availibleData = availibleData.substr(nextStartPos);\r
99                         else {\r
100                                 availibleData.clear();\r
101                                 break;\r
102                         }\r
103                 }\r
104                 else\r
105                 {\r
106                         break;\r
107                 }\r
108         }\r
109         if(pClientInfo)\r
110                 pClientInfo->currentMessage_ = availibleData;\r
111 }\r
112 \r
113 void AMCPProtocolStrategy::ProcessMessage(const std::wstring& message, ClientInfoPtr& pClientInfo)\r
114 {       \r
115         CASPAR_LOG(info) << L"Received message from " << pClientInfo->print() << ": " << message << L"\\r\\n";\r
116         \r
117         bool bError = true;\r
118         MessageParserState state = New;\r
119 \r
120         AMCPCommandPtr pCommand;\r
121 \r
122         pCommand = InterpretCommandString(message, &state);\r
123 \r
124         if(pCommand != 0) {\r
125                 pCommand->SetClientInfo(pClientInfo);   \r
126                 if(QueueCommand(pCommand))\r
127                         bError = false;\r
128                 else\r
129                         state = GetChannel;\r
130         }\r
131 \r
132         if(bError == true) {\r
133                 std::wstringstream answer;\r
134                 switch(state)\r
135                 {\r
136                 case GetCommand:\r
137                         answer << TEXT("400 ERROR\r\n") + message << "\r\n";\r
138                         break;\r
139                 case GetChannel:\r
140                         answer << TEXT("401 ERROR\r\n");\r
141                         break;\r
142                 case GetParameters:\r
143                         answer << TEXT("402 ERROR\r\n");\r
144                         break;\r
145                 default:\r
146                         answer << TEXT("500 FAILED\r\n");\r
147                         break;\r
148                 }\r
149                 pClientInfo->Send(answer.str());\r
150         }\r
151 }\r
152 \r
153 AMCPCommandPtr AMCPProtocolStrategy::InterpretCommandString(const std::wstring& message, MessageParserState* pOutState)\r
154 {\r
155         std::vector<std::wstring> tokens;\r
156         unsigned int currentToken = 0;\r
157         std::wstring commandSwitch;\r
158 \r
159         AMCPCommandPtr pCommand;\r
160         MessageParserState state = New;\r
161 \r
162         std::size_t tokensInMessage = TokenizeMessage(message, &tokens);\r
163 \r
164         //parse the message one token at the time\r
165         while(currentToken < tokensInMessage)\r
166         {\r
167                 switch(state)\r
168                 {\r
169                 case New:\r
170                         if(tokens[currentToken][0] == TEXT('/'))\r
171                                 state = GetSwitch;\r
172                         else\r
173                                 state = GetCommand;\r
174                         break;\r
175 \r
176                 case GetSwitch:\r
177                         commandSwitch = tokens[currentToken];\r
178                         state = GetCommand;\r
179                         ++currentToken;\r
180                         break;\r
181 \r
182                 case GetCommand:\r
183                         pCommand = CommandFactory(tokens[currentToken]);\r
184                         if(pCommand == 0) {\r
185                                 goto ParseFinnished;\r
186                         }\r
187                         else\r
188                         {\r
189                                 pCommand->SetChannels(channels_);\r
190                                 //Set scheduling\r
191                                 if(commandSwitch.size() > 0) {\r
192                                         transform(commandSwitch.begin(), commandSwitch.end(), commandSwitch.begin(), toupper);\r
193 \r
194                                         //if(commandSwitch == TEXT("/APP"))\r
195                                         //      pCommand->SetScheduling(AddToQueue);\r
196                                         //else if(commandSwitch  == TEXT("/IMMF"))\r
197                                         //      pCommand->SetScheduling(ImmediatelyAndClear);\r
198                                 }\r
199 \r
200                                 if(pCommand->NeedChannel())\r
201                                         state = GetChannel;\r
202                                 else\r
203                                         state = GetParameters;\r
204                         }\r
205                         ++currentToken;\r
206                         break;\r
207 \r
208                 case GetParameters:\r
209                         {\r
210                                 _ASSERTE(pCommand != 0);\r
211                                 int parameterCount=0;\r
212                                 while(currentToken<tokensInMessage)\r
213                                 {\r
214                                         pCommand->AddParameter(tokens[currentToken++]);\r
215                                         ++parameterCount;\r
216                                 }\r
217 \r
218                                 if(parameterCount < pCommand->GetMinimumParameters()) {\r
219                                         goto ParseFinnished;\r
220                                 }\r
221 \r
222                                 state = Done;\r
223                                 break;\r
224                         }\r
225 \r
226                 case GetChannel:\r
227                         {\r
228 //                              assert(pCommand != 0);\r
229 \r
230                                 std::wstring str = boost::trim_copy(tokens[currentToken]);\r
231                                 std::vector<std::wstring> split;\r
232                                 boost::split(split, str, boost::is_any_of("-"));\r
233                                         \r
234                                 int channelIndex = -1;\r
235                                 int layerIndex = -1;\r
236                                 try\r
237                                 {\r
238                                         channelIndex = boost::lexical_cast<int>(split[0]) - 1;\r
239 \r
240                                         if(split.size() > 1)\r
241                                                 layerIndex = boost::lexical_cast<int>(split[1]);\r
242                                 }\r
243                                 catch(...)\r
244                                 {\r
245                                         goto ParseFinnished;\r
246                                 }\r
247 \r
248                                 std::shared_ptr<core::video_channel> pChannel = GetChannelSafe(channelIndex, channels_);\r
249                                 if(pChannel == 0) {\r
250                                         goto ParseFinnished;\r
251                                 }\r
252 \r
253                                 pCommand->SetChannel(pChannel);\r
254                                 pCommand->SetChannels(channels_);\r
255                                 pCommand->SetChannelIndex(channelIndex);\r
256                                 pCommand->SetLayerIntex(layerIndex);\r
257 \r
258                                 state = GetParameters;\r
259                                 ++currentToken;\r
260                                 break;\r
261                         }\r
262 \r
263                 default:        //Done and unexpected\r
264                         goto ParseFinnished;\r
265                 }\r
266         }\r
267 \r
268 ParseFinnished:\r
269         if(state == GetParameters && pCommand->GetMinimumParameters()==0)\r
270                 state = Done;\r
271 \r
272         if(state != Done) {\r
273                 pCommand.reset();\r
274         }\r
275 \r
276         if(pOutState != 0) {\r
277                 *pOutState = state;\r
278         }\r
279 \r
280         return pCommand;\r
281 }\r
282 \r
283 bool AMCPProtocolStrategy::QueueCommand(AMCPCommandPtr pCommand) {\r
284         if(pCommand->NeedChannel()) {\r
285                 unsigned int channelIndex = pCommand->GetChannelIndex() + 1;\r
286                 if(commandQueues_.size() > channelIndex) {\r
287                         commandQueues_[channelIndex]->AddCommand(pCommand);\r
288                 }\r
289                 else\r
290                         return false;\r
291         }\r
292         else {\r
293                 commandQueues_[0]->AddCommand(pCommand);\r
294         }\r
295         return true;\r
296 }\r
297 \r
298 AMCPCommandPtr AMCPProtocolStrategy::CommandFactory(const std::wstring& str)\r
299 {\r
300         std::wstring s = str;\r
301         transform(s.begin(), s.end(), s.begin(), toupper);\r
302         \r
303         if         (s == TEXT("MIXER"))                 return std::make_shared<MixerCommand>();\r
304         else if(s == TEXT("DIAG"))                      return std::make_shared<DiagnosticsCommand>();\r
305         else if(s == TEXT("CHANNEL_GRID"))      return std::make_shared<ChannelGridCommand>();\r
306         else if(s == TEXT("CALL"))                      return std::make_shared<CallCommand>();\r
307         else if(s == TEXT("SWAP"))                      return std::make_shared<SwapCommand>();\r
308         else if(s == TEXT("LOAD"))                      return std::make_shared<LoadCommand>();\r
309         else if(s == TEXT("LOADBG"))            return std::make_shared<LoadbgCommand>();\r
310         else if(s == TEXT("ADD"))                       return std::make_shared<AddCommand>();\r
311         else if(s == TEXT("REMOVE"))            return std::make_shared<RemoveCommand>();\r
312         else if(s == TEXT("PAUSE"))                     return std::make_shared<PauseCommand>();\r
313         else if(s == TEXT("PLAY"))                      return std::make_shared<PlayCommand>();\r
314         else if(s == TEXT("STOP"))                      return std::make_shared<StopCommand>();\r
315         else if(s == TEXT("CLEAR"))                     return std::make_shared<ClearCommand>();\r
316         else if(s == TEXT("PRINT"))                     return std::make_shared<PrintCommand>();\r
317         else if(s == TEXT("LOG"))                       return std::make_shared<LogCommand>();\r
318         else if(s == TEXT("CG"))                        return std::make_shared<CGCommand>();\r
319         else if(s == TEXT("DATA"))                      return std::make_shared<DataCommand>();\r
320         else if(s == TEXT("CINF"))                      return std::make_shared<CinfCommand>();\r
321         else if(s == TEXT("INFO"))                      return std::make_shared<InfoCommand>(channels_);\r
322         else if(s == TEXT("CLS"))                       return std::make_shared<ClsCommand>();\r
323         else if(s == TEXT("TLS"))                       return std::make_shared<TlsCommand>();\r
324         else if(s == TEXT("VERSION"))           return std::make_shared<VersionCommand>();\r
325         else if(s == TEXT("BYE"))                       return std::make_shared<ByeCommand>();\r
326         else if(s == TEXT("SET"))                       return std::make_shared<SetCommand>();\r
327         //else if(s == TEXT("MONITOR"))\r
328         //{\r
329         //      result = AMCPCommandPtr(new MonitorCommand());\r
330         //}\r
331         //else if(s == TEXT("KILL"))\r
332         //{\r
333         //      result = AMCPCommandPtr(new KillCommand());\r
334         //}\r
335         return nullptr;\r
336 }\r
337 \r
338 std::size_t AMCPProtocolStrategy::TokenizeMessage(const std::wstring& message, std::vector<std::wstring>* pTokenVector)\r
339 {\r
340         //split on whitespace but keep strings within quotationmarks\r
341         //treat \ as the start of an escape-sequence: the following char will indicate what to actually put in the string\r
342 \r
343         std::wstring currentToken;\r
344 \r
345         char inQuote = 0;\r
346         bool getSpecialCode = false;\r
347 \r
348         for(unsigned int charIndex=0; charIndex<message.size(); ++charIndex)\r
349         {\r
350                 if(getSpecialCode)\r
351                 {\r
352                         //insert code-handling here\r
353                         switch(message[charIndex])\r
354                         {\r
355                         case TEXT('\\'):\r
356                                 currentToken += TEXT("\\");\r
357                                 break;\r
358                         case TEXT('\"'):\r
359                                 currentToken += TEXT("\"");\r
360                                 break;\r
361                         case TEXT('n'):\r
362                                 currentToken += TEXT("\n");\r
363                                 break;\r
364                         default:\r
365                                 break;\r
366                         };\r
367                         getSpecialCode = false;\r
368                         continue;\r
369                 }\r
370 \r
371                 if(message[charIndex]==TEXT('\\'))\r
372                 {\r
373                         getSpecialCode = true;\r
374                         continue;\r
375                 }\r
376 \r
377                 if(message[charIndex]==' ' && inQuote==false)\r
378                 {\r
379                         if(currentToken.size()>0)\r
380                         {\r
381                                 pTokenVector->push_back(currentToken);\r
382                                 currentToken.clear();\r
383                         }\r
384                         continue;\r
385                 }\r
386 \r
387                 if(message[charIndex]==TEXT('\"'))\r
388                 {\r
389                         inQuote ^= 1;\r
390 \r
391                         if(currentToken.size()>0)\r
392                         {\r
393                                 pTokenVector->push_back(currentToken);\r
394                                 currentToken.clear();\r
395                         }\r
396                         continue;\r
397                 }\r
398 \r
399                 currentToken += message[charIndex];\r
400         }\r
401 \r
402         if(currentToken.size()>0)\r
403         {\r
404                 pTokenVector->push_back(currentToken);\r
405                 currentToken.clear();\r
406         }\r
407 \r
408         return pTokenVector->size();\r
409 }\r
410 \r
411 }       //namespace amcp\r
412 }}      //namespace caspar