2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
4 * This file is part of CasparCG (www.casparcg.com).
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.
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.
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/>.
19 * Author: Nicklas P Andersson
23 #include "../StdAfx.h"
25 #include "AMCPProtocolStrategy.h"
27 #include "../util/AsyncEventServer.h"
28 #include "AMCPCommandsImpl.h"
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>
42 #pragma warning (push, 1) // TODO: Legacy code, just disable warnings
45 namespace caspar { namespace protocol { namespace amcp {
47 using IO::ClientInfoPtr;
49 const std::wstring AMCPProtocolStrategy::MessageDelimiter = TEXT("\r\n");
51 inline std::shared_ptr<core::video_channel> GetChannelSafe(unsigned int index, const std::vector<spl::shared_ptr<core::video_channel>>& channels)
53 return index < channels.size() ? std::shared_ptr<core::video_channel>(channels[index]) : nullptr;
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);
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 ");
68 //HACK: Perform real conversion from int to string
69 TCHAR num = TEXT('1')+static_cast<TCHAR>(index);
72 commandQueues_.push_back(pChannelCommandQueue);
76 AMCPProtocolStrategy::~AMCPProtocolStrategy() {
79 void AMCPProtocolStrategy::Parse(const TCHAR* pData, int charCount, ClientInfoPtr pClientInfo)
82 std::wstring recvData(pData, charCount);
83 std::wstring availibleData = (pClientInfo != nullptr ? pClientInfo->currentMessage_ : L"") + recvData;
86 pos = availibleData.find(MessageDelimiter);
87 if(pos != std::wstring::npos)
89 std::wstring message = availibleData.substr(0,pos);
91 //This is where a complete message gets taken care of
92 if(message.length() > 0) {
93 ProcessMessage(message, pClientInfo);
96 std::size_t nextStartPos = pos + MessageDelimiter.length();
97 if(nextStartPos < availibleData.length())
98 availibleData = availibleData.substr(nextStartPos);
100 availibleData.clear();
110 pClientInfo->currentMessage_ = availibleData;
113 void AMCPProtocolStrategy::ProcessMessage(const std::wstring& message, ClientInfoPtr& pClientInfo)
115 CASPAR_LOG(info) << L"Received message from " << pClientInfo->print() << ": " << message << L"\\r\\n";
118 MessageParserState state = New;
120 AMCPCommandPtr pCommand;
122 pCommand = InterpretCommandString(message, &state);
125 pCommand->SetClientInfo(pClientInfo);
126 if(QueueCommand(pCommand))
133 std::wstringstream answer;
137 answer << TEXT("400 ERROR\r\n") + message << "\r\n";
140 answer << TEXT("401 ERROR\r\n");
143 answer << TEXT("402 ERROR\r\n");
146 answer << TEXT("500 FAILED\r\n");
149 pClientInfo->Send(answer.str());
153 AMCPCommandPtr AMCPProtocolStrategy::InterpretCommandString(const std::wstring& message, MessageParserState* pOutState)
155 std::vector<std::wstring> tokens;
156 unsigned int currentToken = 0;
157 std::wstring commandSwitch;
159 AMCPCommandPtr pCommand;
160 MessageParserState state = New;
162 std::size_t tokensInMessage = TokenizeMessage(message, &tokens);
164 //parse the message one token at the time
165 while(currentToken < tokensInMessage)
170 if(tokens[currentToken][0] == TEXT('/'))
177 commandSwitch = tokens[currentToken];
183 pCommand = CommandFactory(tokens[currentToken]);
189 pCommand->SetChannels(channels_);
191 if(commandSwitch.size() > 0) {
192 transform(commandSwitch.begin(), commandSwitch.end(), commandSwitch.begin(), toupper);
194 //if(commandSwitch == TEXT("/APP"))
195 // pCommand->SetScheduling(AddToQueue);
196 //else if(commandSwitch == TEXT("/IMMF"))
197 // pCommand->SetScheduling(ImmediatelyAndClear);
200 if(pCommand->NeedChannel())
203 state = GetParameters;
210 _ASSERTE(pCommand != 0);
211 int parameterCount=0;
212 while(currentToken<tokensInMessage)
214 pCommand->AddParameter(tokens[currentToken++]);
218 if(parameterCount < pCommand->GetMinimumParameters()) {
228 // assert(pCommand != 0);
230 std::wstring str = boost::trim_copy(tokens[currentToken]);
231 std::vector<std::wstring> split;
232 boost::split(split, str, boost::is_any_of("-"));
234 int channelIndex = -1;
238 channelIndex = boost::lexical_cast<int>(split[0]) - 1;
241 layerIndex = boost::lexical_cast<int>(split[1]);
248 std::shared_ptr<core::video_channel> pChannel = GetChannelSafe(channelIndex, channels_);
253 pCommand->SetChannel(pChannel);
254 pCommand->SetChannels(channels_);
255 pCommand->SetChannelIndex(channelIndex);
256 pCommand->SetLayerIntex(layerIndex);
258 state = GetParameters;
263 default: //Done and unexpected
269 if(state == GetParameters && pCommand->GetMinimumParameters()==0)
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);
293 commandQueues_[0]->AddCommand(pCommand);
298 AMCPCommandPtr AMCPProtocolStrategy::CommandFactory(const std::wstring& str)
300 std::wstring s = str;
301 transform(s.begin(), s.end(), s.begin(), toupper);
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"))
329 // result = AMCPCommandPtr(new MonitorCommand());
331 //else if(s == TEXT("KILL"))
333 // result = AMCPCommandPtr(new KillCommand());
338 std::size_t AMCPProtocolStrategy::TokenizeMessage(const std::wstring& message, std::vector<std::wstring>* pTokenVector)
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
343 std::wstring currentToken;
346 bool getSpecialCode = false;
348 for(unsigned int charIndex=0; charIndex<message.size(); ++charIndex)
352 //insert code-handling here
353 switch(message[charIndex])
356 currentToken += TEXT("\\");
359 currentToken += TEXT("\"");
362 currentToken += TEXT("\n");
367 getSpecialCode = false;
371 if(message[charIndex]==TEXT('\\'))
373 getSpecialCode = true;
377 if(message[charIndex]==' ' && inQuote==false)
379 if(currentToken.size()>0)
381 pTokenVector->push_back(currentToken);
382 currentToken.clear();
387 if(message[charIndex]==TEXT('\"'))
391 if(currentToken.size()>0)
393 pTokenVector->push_back(currentToken);
394 currentToken.clear();
399 currentToken += message[charIndex];
402 if(currentToken.size()>0)
404 pTokenVector->push_back(currentToken);
405 currentToken.clear();
408 return pTokenVector->size();
412 }} //namespace caspar