Improve xml parsing on diameter codec
[anna.git] / source / diameter / codec / functions.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
9 // Local
10 #include <anna/diameter/codec/functions.hpp>
11 #include <anna/diameter/codec/Message.hpp>
12 #include <anna/diameter/codec/Avp.hpp>
13 #include <anna/diameter/defines.hpp>
14 #include <anna/diameter/functions.hpp>
15 #include <anna/config/defines.hpp> // general types, decoding helpers (DECODE[2/3/4]BYTES_INDX_VALUETYPE), etc.
16
17 #include <anna/core/DataBlock.hpp>
18 #include <anna/core/functions.hpp>
19 #include <anna/core/tracing/Logger.hpp>
20
21 // STL
22 #include <string>
23
24
25
26 namespace anna {
27 namespace diameter {
28 namespace codec {
29 // Preloaded MemoryDTD for function helpers:
30 anna::xml::DTDMemory MessageDTDMemory(MessageDTD);
31 }
32 }
33 }
34
35
36 // Parent struct helper /////////////////////////////////////////////////////////////////////////////
37 void anna::diameter::codec::parent::setMessage(const anna::diameter::CommandId & mid, const char *mname) throw() {
38   MessageId = mid;
39   if (mname) {
40     MessageName = mname;
41   }
42   else {
43     MessageName = "Message";
44     MessageName += anna::diameter::functions::commandIdAsPairString(mid);
45   }
46 }
47
48 void anna::diameter::codec::parent::addAvp(const anna::diameter::AvpId & aid, const char *aname) throw() {
49   AvpsId.push_back(aid);
50   std::string name;
51   if (aname) {
52     name = aname;
53   }
54   else {
55     name = "Avp";
56     name += anna::diameter::functions::avpIdAsPairString(aid);
57   }
58   AvpsName.push_back(name);
59 }
60
61 std::string anna::diameter::codec::parent::asString() const throw() { // "<command><avp 1>-><avp 2>->...-><avp N>"
62   std::string result = MessageName;
63   for (std::vector<std::string>::const_iterator it = AvpsName.begin(); it != AvpsName.end(); it++) {
64     result += "->";
65     result += (*it);
66   }
67
68   return result;
69 }
70 /////////////////////////////////////////////////////////////////////////////////////////////////////
71
72
73 // getters
74 anna::diameter::CommandId anna::diameter::codec::functions::getCommandId(const anna::DataBlock & db) throw(anna::RuntimeException) {
75   if(db.getSize() < Message::HeaderLength)
76     throw anna::RuntimeException("Not enough bytes to cover command header length", ANNA_FILE_LOCATION);
77
78   const char * data = db.getData();
79   U8 flags =  data[4];
80   U24 code = DECODE3BYTES_INDX_VALUETYPE(data, 5, U24);
81   //   U24 code = (((U24)data[5] << 16) & 0xFF0000) +
82   //              (((U24)data[6] << 8)  & 0x00FF00) +
83   //              (((U24)data[7]) & 0x0000FF);
84   return (anna::diameter::CommandId(code, (flags & Message::RBitMask) != 0x00));
85 }
86
87 bool anna::diameter::codec::functions::requestBit(const anna::DataBlock & db) throw(anna::RuntimeException) {
88   if(db.getSize() < Message::HeaderLength)
89     throw anna::RuntimeException("Not enough bytes to cover command header length", ANNA_FILE_LOCATION);
90
91   return (((db.getData())[4] & Message::RBitMask) != 0x00);
92 }
93
94 bool anna::diameter::codec::functions::proxiableBit(const anna::DataBlock & db) throw(anna::RuntimeException) {
95   if(db.getSize() < Message::HeaderLength)
96     throw anna::RuntimeException("Not enough bytes to cover command header length", ANNA_FILE_LOCATION);
97
98   return (((db.getData())[4] & Message::PBitMask) != 0x00);
99 }
100
101 bool anna::diameter::codec::functions::errorBit(const anna::DataBlock & db) throw(anna::RuntimeException) {
102   if(db.getSize() < Message::HeaderLength)
103     throw anna::RuntimeException("Not enough bytes to cover command header length", ANNA_FILE_LOCATION);
104
105   return (((db.getData())[4] & Message::EBitMask) != 0x00);
106 }
107
108 bool anna::diameter::codec::functions::potentiallyReTransmittedMessageBit(const anna::DataBlock & db) throw(anna::RuntimeException) {
109   if(db.getSize() < Message::HeaderLength)
110     throw anna::RuntimeException("Not enough bytes to cover command header length", ANNA_FILE_LOCATION);
111
112   return (((db.getData())[4] & Message::TBitMask) != 0x00);
113 }
114
115 anna::diameter::ApplicationId anna::diameter::codec::functions::getApplicationId(const anna::DataBlock & db) throw(anna::RuntimeException) {
116   if(db.getSize() < Message::HeaderLength)
117     throw anna::RuntimeException("Not enough bytes to cover command header length", ANNA_FILE_LOCATION);
118
119   const char * appidPtr = db.getData() + 8;
120   anna::diameter::ApplicationId result = DECODE4BYTES_INDX_VALUETYPE(appidPtr, 0, U32);
121   //   anna::diameter::ApplicationId result = (((U32)appidPtr[0] << 24) & 0xFF000000) +
122   //                               (((U32)appidPtr[1] << 16) & 0x00FF0000) +
123   //                               (((U32)appidPtr[2] << 8) & 0x0000FF00) +
124   //                               (((U32)appidPtr[3]) & 0x000000FF);
125   return result;
126 }
127
128 anna::diameter::HopByHop anna::diameter::codec::functions::getHopByHop(const anna::DataBlock & db) throw(anna::RuntimeException) {
129   if(db.getSize() < Message::HeaderLength)
130     throw anna::RuntimeException("Not enough bytes to cover command header length", ANNA_FILE_LOCATION);
131
132   const char * hbhPtr = db.getData() + 12;
133   anna::diameter::HopByHop result = DECODE4BYTES_INDX_VALUETYPE(hbhPtr, 0, U32);
134   //   anna::diameter::HopByHop result = (((U32)hbhPtr[0] << 24) & 0xFF000000) +
135   //                               (((U32)hbhPtr[1] << 16) & 0x00FF0000) +
136   //                               (((U32)hbhPtr[2] << 8) & 0x0000FF00) +
137   //                               (((U32)hbhPtr[3]) & 0x000000FF);
138   return result;
139 }
140
141 anna::diameter::EndToEnd anna::diameter::codec::functions::getEndToEnd(const anna::DataBlock & db) throw(anna::RuntimeException) {
142   if(db.getSize() < Message::HeaderLength)
143     throw anna::RuntimeException("Not enough bytes to cover command header length", ANNA_FILE_LOCATION);
144
145   const char * etePtr = db.getData() + 16;
146   anna::diameter::EndToEnd result = DECODE4BYTES_INDX_VALUETYPE(etePtr, 0, U32);
147   //   anna::diameter::EndToEnd result = (((U32)etePtr[0] << 24) & 0xFF000000) +
148   //                               (((U32)etePtr[1] << 16) & 0x00FF0000) +
149   //                               (((U32)etePtr[2] << 8) & 0x0000FF00) +
150   //                               (((U32)etePtr[3]) & 0x000000FF);
151   return result;
152 }
153
154 void anna::diameter::codec::functions::decodeCommandHeader(const char *start, char & version, U24 & length, char & flags, diameter::CommandId & id, int & appId, int & hbh, int & ete) throw(anna::RuntimeException) {
155   if(start == NULL)
156     throw anna::RuntimeException("NULL provided start pointer", ANNA_FILE_LOCATION);
157
158   //      0                   1                   2                   3
159   //      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
160   //      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
161   //      |    Version    |                 Message Length                |
162   //      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
163   //      | command flags |                  Command-Code                 |
164   //      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
165   //      |                         Application-ID                        |
166   //      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
167   //      |                      Hop-by-Hop Identifier                    |
168   //      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
169   //      |                      End-to-End Identifier                    |
170   //      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
171   //      |  AVPs ...
172   //      +-+-+-+-+-+-+-+-+-+-+-+-+-
173   version = start[0]; // Version
174   //length = (start[1] << 16) + (start[2] << 8) + start[3]; // Message Length
175   length = DECODE3BYTES_INDX_VALUETYPE(start, 1, U24);
176   flags = start[4]; // command flags
177   //id.first = (start[5] << 16) + (start[6] << 8) + start[7]; // Command-Code
178   id.first = DECODE3BYTES_INDX_VALUETYPE(start, 5, U24);
179   id.second = ((flags & Message::RBitMask) != 0x00); // Command-Code is a request
180   //appId = (start[8] << 24) + (start[9] << 16) + (start[10] << 8) + start[11]; // Application-ID
181   appId = DECODE4BYTES_INDX_VALUETYPE(start, 8, U32);
182   //hbh = (start[12] << 24) + (start[13] << 16) + (start[14] << 8) + start[15]; // Hop-by-Hop Identifier
183   hbh = DECODE4BYTES_INDX_VALUETYPE(start, 12, U32);
184   //ete = (start[16] << 24) + (start[17] << 16) + (start[18] << 8) + start[19]; // End-to-End Identifier
185   ete = DECODE4BYTES_INDX_VALUETYPE(start, 16, U32);
186 }
187
188 void anna::diameter::codec::functions::decodeAVP(const char *start, diameter::AvpId & id, char & flags, int & length, std::string & data) throw(anna::RuntimeException) {
189   if(start == NULL)
190     throw anna::RuntimeException("NULL provided start pointer", ANNA_FILE_LOCATION);
191
192   //       0                   1                   2                   3
193   //       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
194   //      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
195   //      |                           AVP Code                            |
196   //      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
197   //      |   AVP Flags   |                  AVP Length                   |
198   //      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
199   //      |                        Vendor-ID (opt)                        |
200   //      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
201   //      |    Data ...
202   //      +-+-+-+-+-+-+-+-+
203   //id.first = (start[0] << 24) + (start[1] << 16) + (start[2] << 8) + start[3]; // AVP Code
204   id.first = DECODE4BYTES_INDX_VALUETYPE(start, 0, S32);
205   flags = start[4]; // AVP Flags
206   //length = (start[5] << 16) + (start[6] << 8) + start[7]; // AVP Length
207   length = DECODE3BYTES_INDX_VALUETYPE(start, 5, U24);
208   bool vendorSpecific = ((flags & Avp::VBitMask) != 0x00); // AVP Code is vendor-specific
209   //id.second = (vendorSpecific ? ((start[8] << 24) + (start[9] << 16) + (start[10] << 8) + start[11]) : 0 /* IETF */); // Vendor-ID (opt)
210   id.second = (vendorSpecific ? (DECODE4BYTES_INDX_VALUETYPE(start, 8, S32)) : 0 /* IETF */); // Vendor-ID (opt)
211   int dataLength = (vendorSpecific ? (length - 12) : (length - 8)); // avp data part length
212   const char *dataPointer = (vendorSpecific ? (start + 12) : (start + 8)); // pointer to data part
213   data.assign(dataPointer, dataLength);
214   LOGLOCAL3(
215       std::string msg = anna::functions::asString("decodedAVP id (%d,%d), length %d, data length %d, data part 0x%s",
216           id.first, id.second, length, dataLength, anna::functions::asHexString(anna::DataBlock(dataPointer, dataLength)).c_str());
217   anna::Logger::write(anna::Logger::Local3, msg, ANNA_FILE_LOCATION);
218   );
219 }
220
221 const char * anna::diameter::codec::functions::nextAVP(const char *avpsDB, int avpsLen, const char *start) throw(anna::RuntimeException) {
222   if(start == NULL)
223     throw anna::RuntimeException("NULL provided start pointer", ANNA_FILE_LOCATION);
224   if(avpsDB == NULL)
225     throw anna::RuntimeException("NULL provided avpsDB pointer", ANNA_FILE_LOCATION);
226
227   const char *result;
228   //   LOGDEBUG(
229   //      std::string msg("DataBlock provided to 'nextAVP'");
230   //      msg += avpsDB.asString();
231   //      anna::Logger::debug(msg, ANNA_FILE_LOCATION);
232   //   );
233   //int avpLength = (start[5] << 16) + (start[6] << 8) + start[7]; // AVP Length
234   int avpLength = DECODE3BYTES_INDX_VALUETYPE(start, 5, int);
235   result = start + 4 * REQUIRED_WORDS(avpLength);
236   int offset = (result - avpsDB);
237
238   if(offset > (avpsLen - 1))
239     //throw anna::RuntimeException("Start pointer out of boundaries for block(avpsDB, avpsLen)", ANNA_FILE_LOCATION);
240     return NULL; // (*)
241
242   return result;
243 }
244
245 //const char * functions::nextAVP(const anna::DataBlock & avpsDB, const char *start) throw(anna::RuntimeException) {
246 //  return nextAVP(avpsDB.getData(), avpsDB.getSize(), start);
247 //}
248
249 const char * anna::diameter::codec::functions::findAVP(const char *avpsDB, int avpsLen, const diameter::AvpId & id, int n) throw(anna::RuntimeException) {
250   const char *result = avpsDB; // first avp
251   int positives = 0;
252   // Decoded avp information:
253   diameter::AvpId _id;
254   char _flags;
255   int _length;
256   std::string _data;
257   decodeAVP(result, _id, _flags, _length, _data);
258
259   if(_id == id) positives++;
260
261   while((_id != id) || (positives != n)) {  // next search if not found or not ocurrence number reached
262     result = nextAVP(avpsDB, avpsLen, result);
263
264     if(result == NULL) {  // (*)
265       LOGDEBUG(
266           std::string msg = "AVP ";
267       msg += anna::diameter::functions::avpIdAsPairString(id);
268       msg += " not found at DataBlock";
269       anna::Logger::debug(msg, ANNA_FILE_LOCATION);
270       );
271       return NULL;
272     }
273
274     decodeAVP(result, _id, _flags, _length, _data);
275
276     if(_id == id) positives++;
277   }
278
279   return result;
280 }
281
282 //const char * functions::findAVP(const anna::DataBlock & avpsDB, const diameter::AvpId & id, int n) throw(anna::RuntimeException) {
283 //  return findAVP(avpsDB.getData(), avpsDB.getSize(), id, n);
284 //}
285
286 // modifiers
287 void anna::diameter::codec::functions::setHopByHop(anna::DataBlock & db, diameter::HopByHop hbh) throw(anna::RuntimeException) {
288   if(db.getSize() < Message::HeaderLength) {
289     throw anna::RuntimeException("Not enough bytes to cover command header length", ANNA_FILE_LOCATION);
290   }
291
292   char source[4];
293   source[0] = (char)(hbh >> 24);
294   source[1] = (char)(hbh >> 16);
295   source[2] = (char)(hbh >> 8);
296   source[3] = (char)hbh;
297   memcpy((char*)(db.getData() + 12), source, 4);
298 }
299
300
301 void anna::diameter::codec::functions::setEndToEnd(anna::DataBlock & db, diameter::EndToEnd ete) throw(anna::RuntimeException) {
302   if(db.getSize() < Message::HeaderLength) {
303     throw anna::RuntimeException("Not enough bytes to cover command header length", ANNA_FILE_LOCATION);
304   }
305
306   char source[4];
307   source[0] = (char)(ete >> 24);
308   source[1] = (char)(ete >> 16);
309   source[2] = (char)(ete >> 8);
310   source[3] = (char)ete;
311   memcpy((char *)(db.getData() + 16), source, 4);
312 }
313
314 void anna::diameter::codec::functions::setPotentiallyReTransmittedMessageBit(const anna::DataBlock & db, bool activate) throw(anna::RuntimeException) {
315   if(db.getSize() < Message::HeaderLength) {
316     throw anna::RuntimeException("Not enough bytes to cover command header length", ANNA_FILE_LOCATION);
317   }
318
319   char flags[1];
320   flags[0] = *(db.getData() + 4);
321   if(activate) flags[0] |= Message::TBitMask; else flags[0] &= (~Message::TBitMask);
322   memcpy((char *)(db.getData() + 4), flags, 1);
323 }
324
325 // XML parsers for diameter messages ///////////////////////////////////////////////////////////////////////////
326 void anna::diameter::codec::functions::messageXmlDocumentFromXmlFile(anna::xml::DocumentFile &xmlDocument, const std::string & xmlPathFile) throw(anna::RuntimeException) {
327   LOGDEBUG(anna::Logger::debug(anna::functions::asString("Parsing diameter message from xml file '%s' into xml document", xmlPathFile.c_str()), ANNA_FILE_LOCATION));
328   xmlDocument.initialize(xmlPathFile.c_str()); // fail here is i/o error
329   const anna::xml::Node *rootNode = xmlDocument.parse(MessageDTDMemory); // Parsing: fail here if xml violates dtd
330   LOGDEBUG(
331       std::string trace = "Parsing OK from XML file '";
332   trace += xmlPathFile;
333   trace += "':\n";
334   trace += anna::xml::Compiler().apply(rootNode);
335   anna::Logger::debug(trace, ANNA_FILE_LOCATION);
336   );
337 }
338
339 void anna::diameter::codec::functions::messageXmlDocumentFromXmlString(anna::xml::DocumentMemory &xmlDocument, const std::string &xmlString) throw(anna::RuntimeException) {
340   LOGDEBUG(anna::Logger::debug("Parsing diameter message from xml string representation into xml document", ANNA_FILE_LOCATION));
341   xmlDocument.initialize(xmlString.c_str());
342   const anna::xml::Node *rootNode = xmlDocument.parse(MessageDTDMemory); // Parsing: fail here if xml violates dtd
343   LOGDEBUG(
344       std::string trace = "Parsing OK from XML string representation '";
345   trace += xmlString;
346   trace += "':\n";
347   trace += anna::xml::Compiler().apply(rootNode);
348   anna::Logger::debug(trace, ANNA_FILE_LOCATION);
349   );
350 }