Implement dynamic procedure at REST interface
[anna.git] / dynamic / launcher / gx / 00001 / Procedure.cpp
1 // ANNA - Anna is Not Nothingness Anymore                                                         //
2 //                                                                                                //
3 // (c) Copyright 2005-2015 Eduardo Ramos Testillano & Francisco Ruiz Rayo                         //
4 //                                                                                                //
5 // See project site at http://redmine.teslayout.com/projects/anna-suite                           //
6 // See accompanying file LICENSE or copy at http://www.teslayout.com/projects/public/anna.LICENSE //
7
8 // Standard
9 #include <iostream>
10 #include <string>
11
12 // Project
13 #include <anna/core/util/Tokenizer.hpp>
14 #include <anna/core/Exception.hpp>
15 #include <anna/testing/TestManager.hpp>
16 #include <anna/diameter/codec/Message.hpp>
17 #include <anna/diameter/codec/Avp.hpp>
18 #include <anna/diameter/helpers/dcca/functions.hpp>
19 #include <anna/diameter/helpers/dcca/functions.hpp>
20 #include <anna/diameter.comm/OriginHost.hpp>
21 #include <anna/diameter.comm/OriginHostManager.hpp>
22
23 // Local
24 #include "Procedure.hpp"
25
26 namespace {
27
28    void usage (std::string &response) {
29      response += "\n\nInvalid arguments. Provide these ones:";
30      response += "\n";
31      response += "\nSIGUSR2 Interface:";
32      response += "\n<initial sequence>|<final sequence>|<test timeout ms (0: no timeout step)>|<digits>|<CCR-I xml file>[|CCR-T xml file]";
33      response += "\n";
34      response += "\nREST Interface:";
35      response += "\n{";
36      response += "\n   \"seqI\":\"<initial sequence>\"";
37      response += "\n   ,\"seqF\":\"<final sequence>\"";
38      response += "\n   ,\"msecsTimeout\":\"<test timeout ms (0: no timeout step)>\"";
39      response += "\n   ,\"digits\":\"<digits>\"";
40      response += "\n   ,\"ccrI\":\"<CCR-I xml file>\"";
41      response += "\n   [,\"ccrT\":\"<CCR-T xml file>\"]";
42      response += "\n}";
43      response += "\n";
44      response += "\nSequences are parsed when needed, over AVPs or internal values:";
45      response += "\n";
46      response += "\nSession-Id: <DiameterIdentity>;<high 32 bits>;<low 32 bits>[;<optional value>]";
47      response += "\n            \\_fixed_/\\_digits_/                            \\_fixed_/\\_digits_/";
48      response += "\n";
49      response += "\nFor example, imagine a 13-digits diameter identity, and a 10-digits optional part.";
50      response += "\nThis procedure will sequence the range for 8 digits in this way:";
51      response += "\n";
52      response += "\n111111<7-digit-sequence>;BAT004;esmdx0900.gxrel10plusrealm.com;901<7-digit-sequence>";
53      response += "\n";
54      response += "\nThe same will be done in MSISDN and IMSI (Subscription-Data AVPs).";
55      response += "\nFramed-IP-Address will be sequenced with a direct correspondence to hex value.";
56      response += "\n";
57      response += "\nThen, you could provide these arguments: \"2000000|9000000|5000|7|CCR-I.xml|CCR-T.xml\"";
58      response += "\n";
59    }
60 }
61
62 void Procedure::execute(const std::string &args, std::string &response)  throw(anna::RuntimeException) {
63
64   response = "Dynamic procedure failed to process '"; response += args; response += "': ";
65
66   anna::Tokenizer targs;
67   targs.apply(args, "|");
68
69   if (targs.size() < 5) {
70     usage(response);
71     return;
72   }
73
74   anna::Tokenizer::const_iterator tok_it = targs.begin();
75
76   std::string seq_i = anna::Tokenizer::data(tok_it); tok_it++;
77   std::string seq_f = anna::Tokenizer::data(tok_it); tok_it++;
78   std::string timeout = anna::Tokenizer::data(tok_it); tok_it++;
79   std::string digits = anna::Tokenizer::data(tok_it); tok_it++;
80   std::string ccr_i = anna::Tokenizer::data(tok_it); tok_it++;
81   std::string ccr_t = ((tok_it != targs.end()) ? anna::Tokenizer::data(tok_it):"");
82   bool haveTermination = (ccr_t != "");
83
84   // Test cases cycles:
85   int i_timeout = std::atoi(timeout.c_str());
86   unsigned int ll_seq_i = std::atol(seq_i.c_str());
87   unsigned int ll_seq_f = std::atol(seq_f.c_str());
88   unsigned int ll_seq_size = ll_seq_f - ll_seq_i + 1;
89   int i_digits = std::atoi(digits.c_str());
90
91   if (ll_seq_i > ll_seq_f) {
92     response += "<final sequence> must be greater or equal than <initial sequence>";
93     return;
94   }
95
96   if (seq_f.size() > i_digits) {
97     response += "<final sequence> must be lesser than number of <digits>";
98     return;
99   }
100
101   unsigned int ll_seq, ll_index;
102   anna::Millisecond timeoutMS(i_timeout);
103
104   // Load xml messages:
105   anna::diameter::codec::Message ccri, ccrt;
106   anna::diameter::codec::Avp *ccri_sessionId, *ccrt_sessionId, *ccri_framedIPAddress, *ccrt_framedIPAddress, *ccri_msisdn, *ccri_imsi;
107   anna::diameter::codec::Avp *si1, *si2, *sidata1, *sidata2, *sitype1;
108   anna::diameter::codec::Avp *ccri_originHost;
109
110   ///////// CCR-Initial:
111   ccri.loadXMLFile(ccr_i);
112
113   // Session-Id & Framed-Ip-Address AVPs
114   ccri_sessionId = ccri.getAvp("Session-Id");
115   ccri_framedIPAddress = ccri.getAvp("Framed-IP-Address");
116   ccri_originHost = ccri.getAvp("Origin-Host");
117
118   // Subscription-Id AVPs
119   if (ccri.countAvp("Subscription-Id") != 2) {
120     response += "Both Subscription-Id MSISDN & IMSI Avps must be present in the CCR-Initial provided !";
121     return;
122   }
123
124   si1 = ccri.getAvp("Subscription-Id", 1, anna::Exception::Mode::Ignore);
125   si2 = ccri.getAvp("Subscription-Id", 2, anna::Exception::Mode::Ignore);
126
127   if (!si1 || !si2) {
128     response += "Cannot found Subscription-Id MSISDN & IMSI Avps !" ;
129     return;
130   }
131
132   sidata1 = si1->getAvp("Subscription-Id-Data");
133   sidata2 = si2->getAvp("Subscription-Id-Data");
134   sitype1 = si1->getAvp("Subscription-Id-Type");
135   //sitype2 = si2->getAvp("Subscription-Id-Type");
136
137   if (sitype1->getEnumerated()->getValue() == anna::diameter::helpers::dcca::AVPVALUES__Subscription_Id_Type::END_USER_E164) {
138     ccri_msisdn = sidata1;
139     ccri_imsi = sidata2;
140   }
141   else {
142     ccri_msisdn = sidata2;
143     ccri_imsi = sidata1;
144   }
145
146   ///////// CCR-Termination:
147   if (haveTermination) {
148     ccrt.loadXMLFile(ccr_t);
149
150     // Session-Id & Framed-Ip-Address AVPs
151     ccrt_sessionId = ccrt.getAvp("Session-Id");
152     ccrt_framedIPAddress = ccrt.getAvp("Framed-IP-Address");
153   }
154
155
156   // Prepare session-id string:
157   std::string sessionId = ccri_sessionId->getUTF8String()->getValue();
158   std::size_t last_semicolon = sessionId.rfind(";");
159   anna::Tokenizer tsessionid;
160   tsessionid.apply(sessionId, ";");
161
162   if (tsessionid.size() < 4) {
163     response += "Session-Id must be in form '<a>;<b>;<c>;<d>'.\n\n";
164     usage(response);
165     return;
166   }
167
168   tok_it = tsessionid.begin();
169   std::string d_identity = anna::Tokenizer::data(tok_it); tok_it++; tok_it++; tok_it++;
170   std::string o_part = anna::Tokenizer::data(tok_it);
171   int d_identity_len = d_identity.size();
172   int left_di = d_identity_len - i_digits;
173   if (left_di < 0) {
174     response += "Session-Id diameter identity length is lower than selected number of digits ";
175     response += anna::functions::asString("(%d < %d).\n\n", d_identity_len, i_digits);
176     usage(response);
177     return;
178   }
179   int o_part_len = o_part.size();
180   int left_op = o_part_len - i_digits;
181   if (left_op < 0) {
182     response += "Session-Id optional part length is lower than selected number of digits ";
183     response += anna::functions::asString("(%d < %d).\n\n", o_part_len, i_digits);
184     usage(response);
185     return;
186   }
187
188   // Idem for MSISDN & IMSI Subscription-Data:
189   std::string msisdn = ccri_msisdn->getUTF8String()->getValue();
190   int msisdn_len = msisdn.size();
191   int left_msisdn = msisdn_len - i_digits;
192   if (left_msisdn < 0) {
193     response += "MSISDN Subscription-Data length is lower than selected number of digits ";
194     response += anna::functions::asString("(%d < %d).\n\n", left_msisdn, i_digits);
195     usage(response);
196     return;
197   }
198   std::string imsi = ccri_imsi->getUTF8String()->getValue();
199   int imsi_len = imsi.size();
200   int left_imsi = imsi_len - i_digits;
201   if (left_imsi < 0) {
202     response += "IMSI Subscription-Data length is lower than selected number of digits ";
203     response += anna::functions::asString("(%d < %d).\n\n", left_imsi, i_digits);
204     usage(response);
205     return;
206   }
207
208
209   // Format strings:
210   char ndigit_format[32];
211   sprintf(ndigit_format, "%s%d%s", "%0", i_digits, "u");
212
213   //response += "\nSession-Id original: "; response += sessionId;
214   //response += "\nMSISDN original:     "; response += msisdn;
215   //response += "\nIMSI original:       "; response += imsi;
216
217   // TestManager:
218   anna::testing::TestManager &testManager = anna::testing::TestManager::instantiate();
219   char cad_aux[16];
220   char cad_framed[16];
221   anna::testing::TestCase *tc;
222
223   // Origin host manager:
224   anna::diameter::comm::OriginHostManager &ohm = anna::diameter::comm::OriginHostManager::instantiate();
225   // Assume the oh name from CCR-I (same as CCR-T):
226   std::string originHostName = ccri_originHost->getDiameterIdentity()->getValue();
227   anna::diameter::comm::OriginHost *originHost = ohm.getOriginHost(originHostName);
228
229   for (ll_index = 0; ll_index < ll_seq_size; ll_index++) {
230
231     // Calculate next values ////////////////////////////////////////////////////////////
232     ll_seq = ll_seq_i + ll_index;
233     sprintf(cad_aux, ndigit_format, ll_seq);
234     sprintf(cad_framed, "%08x", ll_seq);
235
236     // Activity indicator:
237     if (ll_seq % 10000 == 0) std::cout << ".";
238
239     sessionId.replace(left_di, i_digits, cad_aux);
240     sessionId.replace(last_semicolon + left_op + 1, i_digits, cad_aux);
241
242     msisdn.replace(left_msisdn, i_digits, cad_aux);
243
244     imsi.replace(left_imsi, i_digits, cad_aux);
245
246     //response += "\nSession-Id modified: "; response += sessionId;
247     //response += "\nMSISDN modified:     "; response += msisdn;
248     //response += "\nIMSI modified:       "; response += imsi;
249     //response += "\nFramedIP modified:   "; response += cad_framed;
250
251     // Update diameter messages /////////////////////////////////////////////////////////
252     ccri_sessionId->getUTF8String()->setValue(sessionId);
253     ccri_framedIPAddress->getOctetString()->fromHexString(cad_framed);
254     ccri_msisdn->getUTF8String()->setValue(msisdn);
255     ccri_imsi->getUTF8String()->setValue(imsi);
256
257     if (haveTermination) {
258       ccrt_sessionId->getUTF8String()->setValue(sessionId);
259       ccrt_framedIPAddress->getOctetString()->fromHexString(cad_framed);
260     }
261
262     // Create testcase:
263     tc = testManager.getTestCase(ll_index+1);
264     //    test|__TESTID__|timeout|6000
265     //    test|__TESTID__|sendxml2e|ccr-i.xml
266     //    test|__TESTID__|waitfe|272|0|||11111__SEQ8__;BAT004;esmdx0900.gxrel10plusrealm.com;90__SEQ8__|2001
267     //    test|__TESTID__|sendxml2e|ccr-t.xml
268     //    test|__TESTID__|waitfe|272|0|||11111__SEQ8__;BAT004;esmdx0900.gxrel10plusrealm.com;90__SEQ8__|2001
269
270     if (i_timeout != 0) {
271       // Step 1: timeout 5000 ms:
272       tc->addTimeout(timeoutMS);
273     }
274
275     // Step 2: sendxml2e: CCR-Initial
276     tc->addSendDiameterXml2e(ccri.code(), originHost, -1 /* 'wait for request' step number for answers */);
277
278     // Step 3: waitfe: CCA with same session id
279     // PARAM: 1     2            3      4          5           6             7           8          9       10         11
280     //             wait<fe/fc>|[code]|[bitR]|[hopByHop]|[applicationId]|[sessionId]|[resultCode]|[msisdn]|[imsi]|[serviceContextId]
281     tc->addWaitDiameter(true /* from entity */, "272", "0", "", "", sessionId, "2001", "", "", "");
282
283     if (haveTermination) {
284       // Step 4: sendxml2e: CCR-Termination
285       tc->addSendDiameterXml2e(ccrt.code(), originHost, -1 /* 'wait for request' step number for answers */);
286
287       // Step 5: waitfe: CCA with same session id
288       tc->addWaitDiameter(true /* from entity */, "272", "0", "", "", sessionId, "2001", "", "", "");
289     }
290   } // loop
291
292   response = "Completed provision: range [";
293   response += seq_i; response += ", "; response += seq_f; response += "]; scenary: ";
294   response += "CCR-Initial"; if (haveTermination) response += " + CCR-Termination";
295 }
296
297 void Procedure::execute(const nlohmann::json &args, std::string &response)  throw(anna::RuntimeException) {
298
299   // Build the arguments string and call the previous centralized logic procedure execution:
300   //   <initial sequence>|<final sequence>|<test timeout ms (0: no timeout step)>|<digits>|<CCR-I xml file>[|CCR-T xml file]
301   const char *arg_names[6] = { "seqI", "seqF", "msecsTimeout", "digits", "ccrI", "ccrT" };
302   std::string args_string, arg, pipe("|");
303
304   for (int i = 0; i < 6; i++)
305   {
306     auto it = args.find(arg_names[i]);
307     arg = (it != args.end() && it->is_string()) ? *it : "";
308     if (arg != "") args_string += arg + pipe;
309   }
310
311   // Remove last 'pipe':
312   if (args_string != "")
313     args_string = args_string.substr(0, args_string.size()-1);
314
315   execute(args_string, response);
316 }
317
318