reSIProcate
SIP stack, Dialog Usage Manager and related libraries
Loading...
Searching...
No Matches
PyRouteWorker.cxx
Go to the documentation of this file.
1
2#include <memory>
3
4/* Using the PyCXX API for C++ Python integration
5 * It is extremely convenient and avoids the need to write boilerplate
6 * code for handling the Python reference counts.
7 * It is licensed under BSD terms compatible with reSIProcate */
8#include <Python.h>
9#include <CXX/Objects.hxx>
10
11#include "rutil/Logger.hxx"
19#include "repro/Plugin.hxx"
20#include "repro/Processor.hxx"
24
26
27#include "PyRouteWorker.hxx"
28
29#define RESIPROCATE_SUBSYSTEM resip::Subsystem::REPRO
30
31using namespace repro;
32using namespace resip;
33
35 const resip::Data& tid,
36 resip::TransactionUser* passedtu,
37 resip::SipMessage& message)
38 : ProcessorMessage(proc,tid,passedtu),
39 mMessage(message),
40 mResponseCode(-1)
41{
42}
43
47
50{
51 return new PyRouteWork(*this);
52}
53
55PyRouteWork::encode(EncodeStream& ostr) const
56{
57 ostr << "PyRouteWork(tid="<<mTid<<")";
58 return ostr;
59}
60
62PyRouteWork::encodeBrief(EncodeStream& ostr) const
63{
64 return encode(ostr);
65}
66
68 : mMessage(dynamic_cast<resip::SipMessage*>(message.clone()))
69{
70}
71
75
78{
79 return new PyRouteMessageHandlerWork(*this);
80}
81
83PyRouteMessageHandlerWork::encode(EncodeStream& ostr) const
84{
85 ostr << "PyRouteMessageHandlerWork";
86 return ostr;
87}
88
91{
92 return encode(ostr);
93}
94
95PyRouteWorker::PyRouteWorker(PyExtensionBase& py, Py::Callable& action, Py::Callable& messageHandlerAction)
96 : mPy(py),
97 mAction(action),
98 mMessageHandlerAction(messageHandlerAction)
99{
100}
101
105
108{
110 return worker;
111}
112
113void
115{
116 DebugLog(<< "creating new PyThreadState");
118}
119
120bool
122{
123 PyRouteWork* work = dynamic_cast<PyRouteWork*>(msg);
124 if(work)
125 {
126 return processWork(work);
127 }
128 PyRouteMessageHandlerWork* messageHandlerWork = dynamic_cast<PyRouteMessageHandlerWork*>(msg);
129 if(messageHandlerWork)
130 {
131 return processMessageHandlerWork(messageHandlerWork);
132 }
133 WarningLog(<< "received unexpected message");
134 return false;
135}
136
137bool
139{
140 DebugLog(<<"handling a message");
141
142 resip::SipMessage& message = work->mMessage;
143
144 // Get the Global Interpreter Lock
145 StackLog(<< "getting lock...");
148
149 // arg 1: SIP method
150 Py::String reqMethod(getMethodName(message.header(resip::h_RequestLine).method()).c_str());
151
152 // arg 2: request URI
153 Py::String reqUri(message.header(resip::h_RequestLine).uri().toString().c_str());
154
155 // arg 3: a subset of the SIP headers
156 Py::Dict headers;
157 headers["DisplayName"] = Py::String(message.header(resip::h_From).displayName().c_str());
158 headers["From"] = Py::String(message.header(resip::h_From).uri().toString().c_str());
159 headers["To"] = Py::String(message.header(resip::h_To).uri().toString().c_str());
160 if(message.exists(resip::h_CallID))
161 {
162 headers["Call-ID"] = Py::String(message.header(resip::h_CallID).value().c_str());
163 }
164 if(message.exists(resip::h_ContentType))
165 {
166 const resip::HeaderFieldValue hfv = message.header(resip::h_ContentType).getHeaderField();
167 headers["Content-Type"] = Py::String(hfv.getBuffer(), hfv.getLength(), "utf8");
168 }
170 resip::SipMessage::UnknownHeaders::const_iterator it = unknowns.begin();
171 for( ; it != unknowns.end(); it++)
172 {
173 const resip::Data& name = it->first;
174 StackLog(<<"found unknown header: " << name);
175 resip::HeaderFieldValueList* hfvl = it->second;
176 if(!hfvl->empty())
177 {
178 resip::HeaderFieldValue* hfv = hfvl->front();
179 headers[name.c_str()] = Py::String(hfv->getBuffer(), hfv->getLength(), "utf8");
180 }
181 if(hfvl->size() > 1)
182 {
183 // TODO - if multiple values exist, put them in a Py::List
184 WarningLog(<<"ignoring additional values for header " << name);
185 }
186 }
187
188 // arg 4: transport type
189 Py::String transportType(
191
192 // arg 5: body
193 Py::String body("");
194 const resip::HeaderFieldValue& bodyHfv = message.getRawBody();
195 if(bodyHfv.getLength() > 0)
196 {
197 // FIXME: do we always need to copy the whole body?
198 // could we give Python a read-only pointer to the body data?
199 body = Py::String(bodyHfv.getBuffer(), bodyHfv.getLength(), "utf8");
200 }
201
202 // arg 6: cookies (if the message was received over a WebSocket transport)
203 const resip::CookieList& _cookies = message.getWsCookies();
204 Py::Dict cookies;
205 for(
206 resip::CookieList::const_iterator it = _cookies.begin();
207 it != _cookies.end();
208 it++)
209 {
210 ErrLog(<<"adding cookie: " << it->name());
211 cookies[Py::String(it->name().c_str())] = Py::String(it->value().c_str());
212 }
213
214 // arg 7: For new headers that come back from the script
215 Py::Dict newHeaders;
216
217 Py::Tuple args(7);
218 args[0] = reqMethod;
219 args[1] = reqUri;
220 args[2] = headers;
221 args[3] = transportType;
222 args[4] = body;
223 args[5] = cookies;
224 args[6] = newHeaders;
225 Py::Object response;
226 try
227 {
228 StackLog(<< "invoking mAction");
229 response = mAction.apply(args);
230 }
231 catch (const Py::Exception& ex)
232 {
233 WarningLog(<< "PyRoute mAction failed: " << Py::value(ex));
234 WarningLog(<< Py::trace(ex));
235 work->mResponseCode = 500;
236 return true;
237 }
238
239 // Did the script return a single numeric value?
240 // If so, it is a SIP response code, the default SIP error string
241 // will be selected by the stack
242 if(response.isNumeric())
243 {
244 Py::Long responseCode(response);
245 work->mResponseCode = responseCode;
246 work->mResponseMessage = "";
247 return true;
248 }
249
250 // Did the script return a tuple?
251 // If so, it is a SIP response code and optionally a response string
252 // to be used in the SIP response message
253 // If no response string is present in the tuple, the default SIP
254 // error string will be selected by the stack
255 if(response.isTuple())
256 {
257 // Error response
258 Py::Tuple err(response);
259 if(err.size() < 1)
260 {
261 ErrLog(<<"Incomplete response object from PyRoute script");
262 work->mResponseCode = 500;
263 return true;
264 }
265 if(err.size() > 2)
266 {
267 WarningLog(<<"Excessive values in response from PyRoute script");
268 }
269 if(!err[0].isNumeric())
270 {
271 ErrLog(<<"First value in response tuple must be numeric");
272 work->mResponseCode = 500;
273 return true;
274 }
275 Py::Long responseCode(err[0]);
276 Py::String responseMessage;
277 if(err.size() > 1)
278 {
279 if(!err[1].isString())
280 {
281 ErrLog(<<"Second value in response tuple must be a string, ignoring it");
282 }
283 responseMessage = err[1];
284 }
285 work->mResponseCode = responseCode;
286 work->mResponseMessage = resip::Data(responseMessage.as_std_string());
287 return true;
288 }
289
290 // If we get this far, the response should be a list of target URIs
291 if(!response.isList())
292 {
293 ErrLog(<<"Unexpected response object from PyRoute script");
294 work->mResponseCode = 500;
295 return true;
296 }
297
298 Py::List routes(response);
299 DebugLog(<< "got " << routes.size() << " result(s).");
300 for(
301 Py::Sequence::iterator i = routes.begin();
302 i != routes.end();
303 i++)
304 {
305 Py::String target(*i);
306 resip::Data target_s(target.as_std_string());
307 DebugLog(<< "processing result: " << target_s);
308 work->mTargets.push_back(target_s);
309 }
310
311 int newHeaderCount = newHeaders.size();
312 DebugLog(<<"got " << newHeaderCount << " new/changed header(s)");
313 for(Py::Dict::iterator it = newHeaders.begin();
314 it != newHeaders.end();
315 it++)
316 {
317 const Py::Dict::value_type vt(*it);
318 resip::Data headerName(vt.first.str());
319 resip::Data value(vt.second.str());
320 DebugLog(<<"processing a header: " << headerName << ": " << value);
321 resip::Headers::Type hType = resip::Headers::getType(headerName.data(), (int)headerName.size());
322 if(hType == resip::Headers::UNKNOWN)
323 {
324 resip::ExtensionHeader h_Tmp(headerName.c_str());
326 while(pc.begin() != pc.end())
327 {
328 pc.erase(pc.begin());
329 }
330 resip::StringCategory sc(value);
331 pc.push_back(sc);
332 }
333 else
334 {
335 WarningLog(<<"Discarding header '"<<headerName<<"' from pyroute script, only extension headers permitted");
336 }
337 }
338
339 return true;
340}
341
342bool
344{
345 DebugLog(<<"handling a message");
346
347 const resip::SipMessage& message = *work->mMessage;
348
349 // Get the Global Interpreter Lock
350 StackLog(<< "getting lock...");
353
354 // arg 1: SIP request / response line
355 Py::Dict statusLine;
356 if(message.isRequest())
357 {
358 statusLine["method"] = Py::String(getMethodName(message.header(resip::h_RequestLine).method()).c_str());
359 statusLine["request_uri"] = Py::String(message.header(resip::h_RequestLine).uri().toString().c_str());
360 }
361 else if(message.isResponse())
362 {
363 const StatusLine& sline = message.header(resip::h_StatusLine);
364 statusLine["response_code"] = Py::Long(sline.responseCode());
365 statusLine["reason_text"] = Py::String(sline.reason().c_str());
366 }
367 else
368 {
369 ErrLog(<<"neither request nor response");
370 return false;
371 }
372
373 // arg 2: a subset of the SIP headers
374 Py::Dict headers;
375 headers["DisplayName"] = Py::String(message.header(resip::h_From).displayName().c_str());
376 headers["From"] = Py::String(message.header(resip::h_From).uri().toString().c_str());
377 headers["To"] = Py::String(message.header(resip::h_To).uri().toString().c_str());
378 if(message.exists(resip::h_CallID))
379 {
380 headers["Call-ID"] = Py::String(message.header(resip::h_CallID).value().c_str());
381 }
382 if(message.exists(resip::h_ContentType))
383 {
384 std::ostringstream os;
385 os << message.header(resip::h_ContentType);
386 headers["Content-Type"] = Py::String(os.str());
387 //const resip::HeaderFieldValue hfv = message.header(resip::h_ContentType).getHeaderField();
388 //headers["Content-Type"] = Py::String(hfv.getBuffer(), hfv.getLength(), "utf8");
389 }
391 resip::SipMessage::UnknownHeaders::const_iterator it = unknowns.begin();
392 for( ; it != unknowns.end(); it++)
393 {
394 const resip::Data& name = it->first;
395 StackLog(<<"found unknown header: " << name);
396 resip::HeaderFieldValueList* hfvl = it->second;
397 if(!hfvl->empty())
398 {
399 resip::HeaderFieldValue* hfv = hfvl->front();
400 headers[name.c_str()] = Py::String(hfv->getBuffer(), hfv->getLength(), "utf8");
401 }
402 if(hfvl->size() > 1)
403 {
404 // TODO - if multiple values exist, put them in a Py::List
405 WarningLog(<<"ignoring additional values for header " << name);
406 }
407 }
408
409 // arg 3: transport type
410 Py::String transportType(
412
413 // arg 4: body
414 Py::String body("");
415 const resip::HeaderFieldValue& bodyHfv = message.getRawBody();
416 if(bodyHfv.getLength() > 0)
417 {
418 // FIXME: do we always need to copy the whole body?
419 // could we give Python a read-only pointer to the body data?
420 body = Py::String(bodyHfv.getBuffer(), bodyHfv.getLength(), "utf8");
421 }
422
423 // arg 5: cookies (if the message was received over a WebSocket transport)
424 const resip::CookieList& _cookies = message.getWsCookies();
425 Py::Dict cookies;
426 for(
427 resip::CookieList::const_iterator it = _cookies.begin();
428 it != _cookies.end();
429 it++)
430 {
431 ErrLog(<<"adding cookie: " << it->name());
432 cookies[Py::String(it->name().c_str())] = Py::String(it->value().c_str());
433 }
434
435 Py::Tuple args(5);
436 int arg = 0;
437 args[arg++] = statusLine;
438 args[arg++] = headers;
439 args[arg++] = transportType;
440 args[arg++] = body;
441 args[arg++] = cookies;
442 Py::Object response;
443 if(mMessageHandlerAction == Py::None())
444 {
445 ErrLog(<<"No on_message method in the Python script");
446 return false;
447 }
448 try
449 {
450 StackLog(<< "invoking mMessageHandlerAction");
451 response = mMessageHandlerAction.apply(args);
452 }
453 catch (const Py::Exception& ex)
454 {
455 WarningLog(<< "PyRoute mMessageHandlerAction failed: " << Py::value(ex));
456 WarningLog(<< Py::trace(ex));
457 return true;
458 }
459 return true;
460}
461
462
463/* ====================================================================
464 *
465 * Copyright (c) 2022, Software Freedom Institute https://softwarefreedom.institute
466 * Copyright (c) 2013-2022, Daniel Pocock https://danielpocock.com
467 *
468 * Redistribution and use in source and binary forms, with or without
469 * modification, are permitted provided that the following conditions
470 * are met:
471 *
472 * 1. Redistributions of source code must retain the above copyright
473 * notice, this list of conditions and the following disclaimer.
474 *
475 * 2. Redistributions in binary form must reproduce the above copyright
476 * notice, this list of conditions and the following disclaimer in
477 * the documentation and/or other materials provided with the
478 * distribution.
479 *
480 * 3. Neither the name of the author(s) nor the names of any contributors
481 * may be used to endorse or promote products derived from this software
482 * without specific prior written permission.
483 *
484 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND
485 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
486 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
487 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE
488 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
489 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
490 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
491 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
492 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
493 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
494 * SUCH DAMAGE.
495 *
496 * ====================================================================
497 *
498 *
499 */
500
#define DebugLog(args_)
Definition Logger.hxx:128
#define StackLog(args_)
Definition Logger.hxx:125
#define WarningLog(args_)
Definition Logger.hxx:134
#define ErrLog(args_)
Definition Logger.hxx:137
#define resip_assert(x)
This file needs to work for both C and C++ code.
virtual PyRouteMessageHandlerWork * clone() const
virtual EncodeStream & encode(EncodeStream &ostr) const
output the entire message to stream
std::shared_ptr< resip::SipMessage > mMessage
virtual EncodeStream & encodeBrief(EncodeStream &ostr) const
output a brief description to stream
PyRouteMessageHandlerWork(const resip::SipMessage &message)
resip::Data mResponseMessage
resip::SipMessage & mMessage
virtual PyRouteWork * clone() const
virtual EncodeStream & encodeBrief(EncodeStream &ostr) const
output a brief description to stream
PyRouteWork(Processor &proc, const resip::Data &tid, resip::TransactionUser *passedtu, resip::SipMessage &message)
std::vector< resip::Data > mTargets
virtual EncodeStream & encode(EncodeStream &ostr) const
output the entire message to stream
virtual bool process(resip::ApplicationMessage *msg) override
bool processWork(PyRouteWork *work)
bool processMessageHandlerWork(PyRouteMessageHandlerWork *work)
resip::PyExtensionBase & mPy
std::unique_ptr< resip::PyExternalUser > mPyUser
Py::Callable & mAction
virtual PyRouteWorker * clone() const override
PyRouteWorker(resip::PyExtensionBase &py, Py::Callable &action, Py::Callable &messageHandlerAction)
Py::Callable & mMessageHandlerAction
virtual void onStart() override
A base-class for messages used by applications.
An alternative to std::string, encapsulates an arbitrary buffer of bytes.
Definition Data.hxx:90
const char * c_str() const
Returns a null-terminated string representing.
Definition Data.cxx:1097
size_type size() const noexcept
Returns the number of bytes in this Data.
Definition Data.hxx:599
const char * data() const noexcept
Returns a pointer to the contents of this Data.
Definition Data.hxx:608
const char * getBuffer() const
static Type getType(const char *name, size_t len)
Definition Headers.cxx:351
HeaderFieldValue & getHeaderField()
Container class for ParserCategory, used by SipMessage to represent multi-valued headers (Contact,...
iterator end()
Returns an iterator pointing to the last header field value.
iterator erase(iterator i)
Erases the header field value pointed to by i.
iterator begin()
Returns an iterator pointing to the first header field value.
void push_back(const T &t)
Inserts a header field value at the back of this container.
std::unique_ptr< PyExternalUser > createPyExternalUser()
const Uri & uri() const
MethodTypes & method()
Represents a SIP message.
std::list< std::pair< Data, HeaderFieldValueList * >, StlPoolAllocator< std::pair< Data, HeaderFieldValueList * >, PoolBase > > UnknownHeaders
bool isRequest() const
Returns true if message is a request, false otherwise.
const Tuple & getReceivedTransportTuple() const
const UnknownHeaders & getRawUnknownHeaders() const noexcept
const RequestLine & header(const RequestLineType &l) const
Returns the RequestLine. This is only valid for request messages.
bool isResponse() const
Returns true if message is a response, false otherwise.
bool exists(const HeaderBase &headerType) const
Returns true if the given header field is present, false otherwise.
const HeaderFieldValue & getRawBody() const noexcept
Return the raw body string (if it exists).
const CookieList & getWsCookies() const
Represents the "Status-Line" element in the RFC 3261 grammar.
Represents the "header-value" element in the RFC 3261 grammar (ie, an unknown header field value).
The base-class for an RFC 3261 Transaction User.
TransportType getType() const
Definition Tuple.hxx:122
Data toString() const
Definition Uri.cxx:1678
This Class is used to implement a primitive form of RPC using loose XML formatting.
dcm! – add UnusedChecking(_enum) below;
StatusLineType h_StatusLine
Definition Headers.cxx:348
std::string getTransportNameFromType(const TransportType typeEnum)
Function which translates a transport enum value to its corrisponding name.
const Data & getMethodName(MethodTypes t)
RequestLineType h_RequestLine
Definition Headers.cxx:347
std::vector< Cookie > CookieList
Definition Cookie.hxx:35
#define EncodeStream