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