]> git.sesse.net Git - casparcg/blob - protocol/amcp/AMCPProtocolStrategy.cpp
Add call tracing at selected placed in the code to help debug deadlocks in OpenGL.
[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 "amcp_shared.h"
27 #include "AMCPCommand.h"
28 #include "AMCPCommandQueue.h"
29 #include "amcp_command_repository.h"
30
31 #include <stdio.h>
32 #include <string.h>
33 #include <algorithm>
34 #include <cctype>
35 #include <future>
36
37 #include <core/help/help_repository.h>
38 #include <core/help/help_sink.h>
39
40 #include <boost/algorithm/string/trim.hpp>
41 #include <boost/algorithm/string/split.hpp>
42 #include <boost/algorithm/string/replace.hpp>
43 #include <boost/lexical_cast.hpp>
44
45 #if defined(_MSC_VER)
46 #pragma warning (push, 1) // TODO: Legacy code, just disable warnings
47 #endif
48
49 namespace caspar { namespace protocol { namespace amcp {
50
51 using IO::ClientInfoPtr;
52
53 template <typename Out, typename In>
54 bool try_lexical_cast(const In& input, Out& result)
55 {
56         Out saved = result;
57         bool success = boost::conversion::detail::try_lexical_convert(input, result);
58
59         if (!success)
60                 result = saved; // Needed because of how try_lexical_convert is implemented.
61
62         return success;
63 }
64
65 struct AMCPProtocolStrategy::impl
66 {
67 private:
68         std::vector<AMCPCommandQueue::ptr_type>         commandQueues_;
69         spl::shared_ptr<amcp_command_repository>        repo_;
70
71 public:
72         impl(const std::wstring& name, const spl::shared_ptr<amcp_command_repository>& repo)
73                 : repo_(repo)
74         {
75                 commandQueues_.push_back(spl::make_shared<AMCPCommandQueue>(L"General Queue for " + name));
76
77                 for (int i = 0; i < repo_->channels().size(); ++i)
78                 {
79                         commandQueues_.push_back(spl::make_shared<AMCPCommandQueue>(
80                                         L"Channel " + boost::lexical_cast<std::wstring>(i + 1) + L" for " + name));
81                 }
82         }
83
84         ~impl() {}
85
86         enum class error_state {
87                 no_error = 0,
88                 command_error,
89                 channel_error,
90                 parameters_error,
91                 unknown_error,
92                 access_error
93         };
94
95         struct command_interpreter_result
96         {
97                 std::shared_ptr<caspar::IO::lock_container>     lock;
98                 std::wstring                                                            command_name;
99                 AMCPCommand::ptr_type                                           command;
100                 error_state                                                                     error                   = error_state::no_error;
101                 std::shared_ptr<AMCPCommandQueue>                       queue;
102         };
103
104         //The paser method expects message to be complete messages with the delimiter stripped away.
105         //Thesefore the AMCPProtocolStrategy should be decorated with a delimiter_based_chunking_strategy
106         void Parse(const std::wstring& message, ClientInfoPtr client)
107         {
108                 CASPAR_LOG_COMMUNICATION(info) << L"Received message from " << client->address() << ": " << message << L"\\r\\n";
109         
110                 command_interpreter_result result;
111                 if(interpret_command_string(message, result, client))
112                 {
113                         if(result.lock && !result.lock->check_access(client))
114                                 result.error = error_state::access_error;
115                         else
116                                 result.queue->AddCommand(result.command);
117                 }
118                 
119                 if (result.error != error_state::no_error)
120                 {
121                         std::wstringstream answer;
122
123                         switch(result.error)
124                         {
125                         case error_state::command_error:
126                                 answer << L"400 ERROR\r\n" << message << "\r\n";
127                                 break;
128                         case error_state::channel_error:
129                                 answer << L"401 " << result.command_name << " ERROR\r\n";
130                                 break;
131                         case error_state::parameters_error:
132                                 answer << L"402 " << result.command_name << " ERROR\r\n";
133                                 break;
134                         case error_state::access_error:
135                                 answer << L"503 " << result.command_name << " FAILED\r\n";
136                                 break;
137                         case error_state::unknown_error:
138                                 answer << L"500 FAILED\r\n";
139                                 break;
140                         default:
141                                 CASPAR_THROW_EXCEPTION(programming_error()
142                                                 << msg_info(L"Unhandled error_state enum constant " + boost::lexical_cast<std::wstring>(static_cast<int>(result.error))));
143                         }
144                         client->send(answer.str());
145                 }
146         }
147
148 private:
149         bool interpret_command_string(const std::wstring& message, command_interpreter_result& result, ClientInfoPtr client)
150         {
151                 try
152                 {
153                         std::list<std::wstring> tokens;
154                         tokenize(message, tokens);
155
156                         // Discard GetSwitch
157                         if (!tokens.empty() && tokens.front().at(0) == L'/')
158                                 tokens.pop_front();
159
160                         // Fail if no more tokens.
161                         if (tokens.empty())
162                         {
163                                 result.error = error_state::command_error;
164                                 return false;
165                         }
166
167                         // Consume command name
168                         result.command_name = boost::to_upper_copy(tokens.front());
169                         tokens.pop_front();
170
171                         // Determine whether the next parameter is a channel spec or not
172                         int channel_index = -1;
173                         int layer_index = -1;
174                         std::wstring channel_spec;
175
176                         if (!tokens.empty())
177                         {
178                                 channel_spec = tokens.front();
179                                 std::wstring channelid_str = boost::trim_copy(channel_spec);
180                                 std::vector<std::wstring> split;
181                                 boost::split(split, channelid_str, boost::is_any_of("-"));
182
183                                 // Use non_throwing lexical cast to not hit exception break point all the time.
184                                 if (try_lexical_cast(split[0], channel_index))
185                                 {
186                                         --channel_index;
187
188                                         if (split.size() > 1)
189                                                 try_lexical_cast(split[1], layer_index);
190
191                                         // Consume channel-spec
192                                         tokens.pop_front();
193                                 }
194                         }
195
196                         bool is_channel_command = channel_index != -1;
197
198                         // Create command instance
199                         if (is_channel_command)
200                         {
201                                 result.command = repo_->create_channel_command(result.command_name, client, channel_index, layer_index, tokens);
202
203                                 if (result.command)
204                                 {
205                                         result.lock = repo_->channels().at(channel_index).lock;
206                                         result.queue = commandQueues_.at(channel_index + 1);
207                                 }
208                                 else // Might be a non channel command, although the first argument is numeric
209                                 {
210                                         // Restore backed up channel spec string.
211                                         tokens.push_front(channel_spec);
212                                         result.command = repo_->create_command(result.command_name, client, tokens);
213
214                                         if (result.command)
215                                                 result.queue = commandQueues_.at(0);
216                                 }
217                         }
218                         else
219                         {
220                                 result.command = repo_->create_command(result.command_name, client, tokens);
221
222                                 if (result.command)
223                                         result.queue = commandQueues_.at(0);
224                         }
225
226                         if (!result.command)
227                                 result.error = error_state::command_error;
228                         else
229                         {
230                                 std::vector<std::wstring> parameters(tokens.begin(), tokens.end());
231
232                                 result.command->parameters() = std::move(parameters);
233
234                                 if (result.command->parameters().size() < result.command->minimum_parameters())
235                                         result.error = error_state::parameters_error;
236                         }
237                 }
238                 catch (std::out_of_range&)
239                 {
240                         CASPAR_LOG(error) << "Invalid channel specified.";
241                         result.error = error_state::channel_error;
242                 }
243                 catch (...)
244                 {
245                         CASPAR_LOG_CURRENT_EXCEPTION();
246                         result.error = error_state::unknown_error;
247                 }
248
249                 return result.error == error_state::no_error;
250         }
251
252         template<typename C>
253         std::size_t tokenize(const std::wstring& message, C& pTokenVector)
254         {
255                 //split on whitespace but keep strings within quotationmarks
256                 //treat \ as the start of an escape-sequence: the following char will indicate what to actually put in the string
257
258                 std::wstring currentToken;
259
260                 bool inQuote = false;
261                 bool getSpecialCode = false;
262
263                 for(unsigned int charIndex=0; charIndex<message.size(); ++charIndex)
264                 {
265                         if(getSpecialCode)
266                         {
267                                 //insert code-handling here
268                                 switch(message[charIndex])
269                                 {
270                                 case L'\\':
271                                         currentToken += L"\\";
272                                         break;
273                                 case L'\"':
274                                         currentToken += L"\"";
275                                         break;
276                                 case L'n':
277                                         currentToken += L"\n";
278                                         break;
279                                 default:
280                                         break;
281                                 };
282                                 getSpecialCode = false;
283                                 continue;
284                         }
285
286                         if(message[charIndex]==L'\\')
287                         {
288                                 getSpecialCode = true;
289                                 continue;
290                         }
291
292                         if(message[charIndex]==L' ' && inQuote==false)
293                         {
294                                 if(!currentToken.empty())
295                                 {
296                                         pTokenVector.push_back(currentToken);
297                                         currentToken.clear();
298                                 }
299                                 continue;
300                         }
301
302                         if(message[charIndex]==L'\"')
303                         {
304                                 inQuote = !inQuote;
305
306                                 if(!currentToken.empty() || !inQuote)
307                                 {
308                                         pTokenVector.push_back(currentToken);
309                                         currentToken.clear();
310                                 }
311                                 continue;
312                         }
313
314                         currentToken += message[charIndex];
315                 }
316
317                 if(!currentToken.empty())
318                 {
319                         pTokenVector.push_back(currentToken);
320                         currentToken.clear();
321                 }
322
323                 return pTokenVector.size();
324         }
325 };
326
327 AMCPProtocolStrategy::AMCPProtocolStrategy(const std::wstring& name, const spl::shared_ptr<amcp_command_repository>& repo)
328         : impl_(spl::make_unique<impl>(name, repo))
329 {
330 }
331 AMCPProtocolStrategy::~AMCPProtocolStrategy() {}
332 void AMCPProtocolStrategy::Parse(const std::wstring& msg, IO::ClientInfoPtr pClientInfo) { impl_->Parse(msg, pClientInfo); }
333
334
335 }       //namespace amcp
336 }}      //namespace caspar