First commit
[anna.git] / source / comm / CompatCodec.cpp
1 // ANNA - Anna is Not 'N' Anymore
2 //
3 // (c) Copyright 2005-2014 Eduardo Ramos Testillano & Francisco Ruiz Rayo
4 //
5 // https://bitbucket.org/testillano/anna
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
9 // are met:
10 //
11 //     * Redistributions of source code must retain the above copyright
12 // notice, this list of conditions and the following disclaimer.
13 //     * Redistributions in binary form must reproduce the above
14 // copyright notice, this list of conditions and the following disclaimer
15 // in the documentation and/or other materials provided with the
16 // distribution.
17 //     * Neither the name of Google Inc. nor the names of its
18 // contributors may be used to endorse or promote products derived from
19 // this software without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 //
33 // Authors: eduardo.ramos.testillano@gmail.com
34 //          cisco.tierra@gmail.com
35
36
37 #include <stdlib.h>
38 #include <time.h>
39 #include <netinet/in.h>
40
41 #include <anna/core/RuntimeException.hpp>
42 #include <anna/config/defines.hpp>
43 #include <anna/core/tracing/Logger.hpp>
44 #include <anna/core/functions.hpp>
45 #include <anna/core/util/Second.hpp>
46 #include <anna/core/util/Millisecond.hpp>
47 #include <anna/core/util/Microsecond.hpp>
48
49 #include <anna/comm/CompatCodec.hpp>
50 #include <anna/comm/functions.hpp>
51 #include <anna/comm/Variable.hpp>
52
53 using namespace std;
54 using namespace anna;
55
56 bool comm::CompatCodec::st_initScramble(false);
57
58 // static
59 template <class T, class M> comm::Variable* insert(const char* name, const short int id, M& theVector, T& value)
60 throw(RuntimeException) {
61   comm::Variable* result = theVector.find(id);
62
63   if(result != NULL)
64     throw RuntimeException(functions::asString("Variable Id %d alreay used", id), ANNA_FILE_LOCATION);
65
66   theVector.add(result = new comm::Variable(id, name, value));
67   LOGDEBUG(
68     String msg("insert <T, M> | ");
69     msg <<  result->asString();
70     Logger::debug(msg, ANNA_FILE_LOCATION);
71   );
72   return result;
73 }
74
75 // static
76 template <class T, class M> comm::Variable* insertRef(const char* name, const short int id, M& theVector, T& value)
77 throw(RuntimeException) {
78   comm::Variable* result = theVector.find(id);
79
80   if(result != NULL)
81     throw RuntimeException(functions::asString("Variable Id %d alreay used", id), ANNA_FILE_LOCATION);
82
83   theVector.add(result = new comm::Variable(id, name, value.refValue()));
84   LOGDEBUG(
85     String msg("insert <T, M> | ");
86     msg <<  result->asString();
87     Logger::debug(msg, ANNA_FILE_LOCATION);
88   );
89   return result;
90 }
91
92 comm::CompatCodec::CompatCodec(const comm::CompatCodec::Type type, const bool scramble)  :
93   comm::Message(comm::Message::StatusCodeBuffer::None),
94   a_type(type),
95   a_scramble(scramble),
96   a_nullCounter(0) {
97   if(a_scramble == true && st_initScramble == false) {
98     st_initScramble = true;
99     srand(time(NULL));
100   }
101 }
102
103 comm::CompatCodec::~CompatCodec() {
104   for(iterator ii = begin(), maxii = end(); ii != maxii; ii ++)
105     delete variable(ii);
106
107   a_variables.clear();
108 }
109
110 const comm::Variable* comm::CompatCodec::attach(const char* name, const short int id, std::string& value)
111 throw(RuntimeException) {
112   return insert(name, id, a_variables, value);
113 }
114
115 const comm::Variable* comm::CompatCodec::attach(const char* name, const short int id, int& value)
116 throw(RuntimeException) {
117   const int backup(value);
118   const Variable* result = insert(name, id, a_variables, value);
119   value = backup;
120   return result;
121 }
122
123 const comm::Variable* comm::CompatCodec::attach(const char* name, const short int id, Integer64& value)
124 throw(RuntimeException) {
125   const Integer64 backup(value);
126   const Variable* result = insert(name, id, a_variables, value);
127   value = backup;
128   return result;
129 }
130
131 const comm::Variable* comm::CompatCodec::attach(const char* name, const short int id, bool& value)
132 throw(RuntimeException) {
133   const bool backup(value);
134   const Variable* result = insert(name, id, a_variables, value);
135   value = backup;
136   return result;
137 }
138
139 const comm::Variable* comm::CompatCodec::attach(const char* name, const short int id, DataBlock& value)
140 throw(RuntimeException) {
141   return insert(name, id, a_variables, value);
142 }
143
144 const comm::Variable* comm::CompatCodec::attach(const char* name, const short int id, float& value)
145 throw(RuntimeException) {
146   const float backup(value);
147   const Variable* result = insert(name, id, a_variables, value);
148   value = backup;
149   return result;
150 }
151
152 const comm::Variable* comm::CompatCodec::attach(const char* name, const short int id, double& value)
153 throw(RuntimeException) {
154   const double backup(value);
155   const Variable* result = insert(name, id, a_variables, value);
156   value = backup;
157   return result;
158 }
159
160 const comm::Variable* comm::CompatCodec::attach(const char* name, const short int id, Second& value)
161 throw(RuntimeException) {
162   const Second backup(value);
163   const Variable* result = insertRef(name, id, a_variables, value);
164   value = backup;
165   return result;
166 }
167
168 const comm::Variable* comm::CompatCodec::attach(const char* name, const short int id, Millisecond& value)
169 throw(RuntimeException) {
170   const Millisecond backup(value);
171   const Variable* result = insertRef(name, id, a_variables, value);
172   value = backup;
173   return result;
174 }
175
176 const comm::Variable* comm::CompatCodec::attach(const char* name, const short int id, Microsecond& value)
177 throw(RuntimeException) {
178   const Microsecond backup(value);
179   const Variable* result = insertRef(name, id, a_variables, value);
180   value = backup;
181   return result;
182 }
183
184 const comm::Variable* comm::CompatCodec::attach(const char* name, const short int id, comm::CompatCodec& value)
185 throw(RuntimeException) {
186   if(&value == this) {
187     String msg("comm::CompatCodec::attach | Variable: ");
188     msg << name << " | Can not link with itself";
189     throw RuntimeException(msg, ANNA_FILE_LOCATION);
190   }
191
192   return insert(name, id, a_variables, value);
193 }
194
195 void comm::CompatCodec::setNull(const short int id, const bool isNull)
196 throw(RuntimeException) {
197   Variable* variable = a_variables.find(id);
198
199   if(variable == NULL)
200     throw RuntimeException(functions::asString("Id %d is not defined", id), ANNA_FILE_LOCATION);
201
202   switch(variable->isNull()) {
203   case true:
204
205     if(isNull == false) a_nullCounter --;
206
207     break;
208   case false:
209
210     if(isNull == true) a_nullCounter ++;
211
212     break;
213   }
214
215   variable->setNull(isNull);
216 }
217
218 void comm::CompatCodec::setNull(const comm::Variable* variable, const bool isNull)
219 throw() {
220   switch(variable->isNull()) {
221   case true:
222
223     if(isNull == false) a_nullCounter --;
224
225     break;
226   case false:
227
228     if(isNull == true) a_nullCounter ++;
229
230     break;
231   }
232
233   const_cast <comm::Variable*>(variable)->setNull(isNull);
234 }
235
236 bool comm::CompatCodec::isNull(const short int id) const
237 throw(RuntimeException) {
238   const Variable* variable = a_variables.find(id);
239
240   if(variable == NULL)
241     throw RuntimeException(functions::asString("Id %d is not defined", id), ANNA_FILE_LOCATION);
242
243   return variable->isNull();
244 }
245
246 const comm::Variable& comm::CompatCodec::find(const short int id) const
247 throw(RuntimeException) {
248   const Variable* variable = a_variables.find(id);
249
250   if(variable == NULL)
251     throw RuntimeException(functions::asString("Id %d is not defined", id), ANNA_FILE_LOCATION);
252
253   return *variable;
254 }
255
256 //-----------------------------------------------------------------------
257 // Codificacin de los datos en un bloque de memoria.
258 //
259 // mensaje ::= <byte0><byte1><byte2> <dato> ... <dato>
260 //
261 // dato ::= <byteA><byteB><byteC> [<cadena | int | long | bloque>]
262 // cadena ::= <byteLSB><byteMSB> < contenido > </0>
263 // int ::= <byteLSB> <byte> <byte> <byteMSB>
264 // long ::= <byteLSB> <byte> <byte> <byteMSB>
265 // bloque ::= <int> < contenido >
266 //
267 // (0) byte0 = Si es distinto de cero => contiene la clave usada para codificar
268 //     el mensaje.
269 // (2) byte1 = Nmero de datos que contiene el mensaje.
270 //     Recordar que slo se transfieren a este bloque de memoria los datos que
271 //     tengan asignado algn value.
272 // (1) byte2 = Typeentificador del mensaje interno.
273 // (3) Los bytes A y B contienen del identificador del dato.
274 // (4) Los cuatro primeros bits del byteC indican el tipo de dato. En los
275 //     tipos de datos boolean su contenido se guarda en el bit mas significativo
276 //     de este byte.
277 // (5) En las cadenas los dos primeros byres indican la longitud de la cadena
278 //     incluyendo el indicador de fin de cadena, luego aparece el contenido
279 //     de la cadena y finalmente el 0. El cero se incluye para optimizar
280 //     la recogida de datos.
281 //-----------------------------------------------------------------------
282 const DataBlock& comm::CompatCodec::code()
283 throw(RuntimeException) {
284   register unsigned char c(0);
285   iterator ii;
286   iterator maxii(a_variables.end());
287   register Variable* variable;
288   int stringLen;
289   const char* string;
290   char aux [sizeof(Integer64)];
291
292   if(a_scramble == true)     // (1)
293     while(c == 0) c = rand() % 0xff;
294
295   DataBlock& self = *this;
296   self.clear();
297   self += c;
298   self += (char)(a_variables.size() - a_nullCounter);          // (2)
299   self += comm::CompatCodec::getType();                              // (1)
300
301   for(ii = begin(); ii != maxii; ii ++) {
302     variable = CompatCodec::variable(ii);
303
304     if(a_nullCounter > 0 && variable->isNull() == true)
305       continue;
306
307     switch(variable->getType()) {
308     case Variable::Type::String:
309       self += variable->codec();
310       stringLen = anna_strlen(string = variable->getStringValue()) + 1;
311       self.append(comm::functions::codeShort(aux, stringLen), sizeof(short int));
312       self.append(string, stringLen);
313       break;
314     case Variable::Type::Integer:
315       self += variable->codec();
316       self.append(comm::functions::codeInteger(aux, variable->getInteger()), sizeof(int));
317       break;
318     case Variable::Type::Integer64:
319       self += variable->codec();
320       self.append(comm::functions::codeInteger64(aux, variable->getInteger64()), sizeof(Integer64));
321       break;
322     case Variable::Type::Boolean:
323       self.append(comm::functions::codeShort(aux, variable->getId()), sizeof(short int));      // (3)
324       c = Variable::Type::Boolean;
325
326       if(variable->getBoolean() == true)    // (4)
327         c |= 0x80;
328
329       self += c;
330       break;
331     case Variable::Type::Block:
332       self += variable->codec();
333       self.append(comm::functions::codeInteger(aux, variable->getDataBlock().getSize()), sizeof(int));
334       self += variable->getDataBlock();
335       break;
336     case Variable::Type::Float:
337       self += variable->codec();
338       self.append(comm::functions::codeFloat(aux, variable->getFloat()), sizeof(float));
339       break;
340     case Variable::Type::Double:
341       self += variable->codec();
342       self.append(comm::functions::codeDouble(aux, variable->getDouble()), sizeof(float));
343       break;
344     case Variable::Type::Custom:
345       self += variable->codec();
346       {
347         const DataBlock& codec = reinterpret_cast <CompatCodec*>(variable->getCustom())->code();
348         self.append(comm::functions::codeInteger(aux, codec.getSize()), sizeof(int));
349         self += codec;
350       }
351       break;
352     }
353   }
354
355   LOGDEBUG(
356
357     // Se sacan aqui para evitar qtene que comprobar el estado de las trazas en cada paso
358     for(ii = begin(); ii != maxii; ii ++)
359     Logger::debug(CompatCodec::variable(ii)->asString(), ANNA_FILE_LOCATION);
360     Logger::write(Logger::Debug, "comm::CompatCodec::code", self, ANNA_FILE_LOCATION)
361   );
362
363   if(a_scramble == true) {
364     char* data = const_cast <char*>(self.getData());
365     int size = self.getSize();
366
367     for(register int i = 1, key = data [0]; i < size; i ++)
368       data [i] ^= key ++;
369   }
370
371   return self;
372 }
373
374 //-------------------------------------------------------------------------------------------
375 void comm::CompatCodec::decode(const DataBlock& dataBlock)
376 throw(RuntimeException) {
377   const char* data = dataBlock.getData();
378   const int size = dataBlock.getSize();
379
380   if(size < 1)
381     throw RuntimeException("Can not decode an empty DataBlock", ANNA_FILE_LOCATION);
382
383   if(data [0] != 0)
384     for(register int i = 1, key(data [0]); i < size; i ++)
385       const_cast <char*>(data)[i] ^= key ++;
386
387   LOGDEBUG(Logger::write(Logger::Debug, "comm::CompatCodec::decode", dataBlock, ANNA_FILE_LOCATION));
388   const int maxdata = data [1];
389
390   if(maxdata != a_variables.size())
391     normalDecode(data, size, maxdata);
392   else if(optimizedDecode(data, size) == false)
393     normalDecode(data, size, maxdata);
394
395   LOGDEBUG(
396
397     for(const_iterator ii = begin(), maxii = end(); ii != maxii; ii ++)
398     Logger::debug(CompatCodec::variable(ii)->asString(), ANNA_FILE_LOCATION);
399   );
400 }
401
402 //-------------------------------------------------------------------------------------------
403 // Decodifica los buffers que pueden contener variables nulas.
404 //-------------------------------------------------------------------------------------------
405 void comm::CompatCodec::normalDecode(const char* data, const int size, const int maxdata)
406 throw(RuntimeException) {
407   // Mientras no se demuestre lo contrario todas las variables son nulas
408   a_nullCounter = a_variables.size();
409
410   for(iterator ii = begin(), maxii = end(); ii != maxii; ii ++)
411     CompatCodec::variable(ii)->setNull(true);
412
413   short int id;
414   int nbytes;
415   Variable* variable;
416   bool hasError = false;
417   const char* top = data + size;
418   data += 3;
419
420   for(int ndata = 0; ndata < maxdata && data < top; ndata ++) {
421     id = comm::functions::decodeShort(data);
422     data += sizeof(short int);
423
424     if((variable = a_variables.find(id)) == NULL) {
425       hasError = true;
426       break;
427     }
428
429     a_nullCounter --;
430
431     switch(*data  & 0x7f) {
432     case Variable::Type::String:
433       nbytes = comm::functions::decodeShort(++ data);
434       variable->setValue(data += sizeof(short int));
435       data += nbytes;
436       break;
437     case Variable::Type::Integer:
438       variable->setInteger(comm::functions::decodeInteger(++ data));
439       data += sizeof(int);
440       break;
441     case Variable::Type::Integer64:
442       variable->setValue(comm::functions::decodeInteger64(++ data));
443       data += sizeof(Integer64);
444       break;
445     case  Variable::Type::Boolean:
446       variable->setBoolean((*data & 0x80) ? true : false);
447       data ++;
448       break;
449     case Variable::Type::Block:
450       nbytes = comm::functions::decodeInteger(++ data);
451       variable->setDataBlock(DataBlock(data += sizeof(int), nbytes, false));
452       data += nbytes;
453       break;
454     case Variable::Type::Float:
455       variable->setFloat(comm::functions::decodeFloat(++ data));
456       data += sizeof(float);
457       break;
458     case Variable::Type::Double:
459       variable->setDouble(comm::functions::decodeDouble(++ data));
460       data += sizeof(double);
461       break;
462     case Variable::Type::Custom:
463       nbytes = comm::functions::decodeInteger(++ data);
464       {
465         DataBlock dataBlock(data += sizeof(int), nbytes, false);
466         reinterpret_cast <CompatCodec*>(variable->getCustom())->decode(dataBlock);
467       }
468       data += nbytes;
469       break;
470     }
471   }
472
473   if(hasError == true) {
474     string msg("comm::CompatCodec::normalDecode | Buffer: ");
475     msg += functions::asString(DataBlock(data, size, false));
476     msg += functions::asText(" | Id is not defined: ", id);
477     Logger::error(msg, ANNA_FILE_LOCATION);
478   }
479
480   if(a_nullCounter < 0) {
481     a_nullCounter = 0;
482     LOGWARNING(
483       string msg("comm::CompatCodec::normalDecode | Buffer: ");
484       msg += functions::asString(DataBlock(data, size, false));
485       msg += " | NullCounter is less than zero";
486       Logger::warning(msg, ANNA_FILE_LOCATION);
487     );
488   }
489 }
490
491 //-------------------------------------------------------------------------------------------
492 // Decodifica optimizadamente buffers que no tengan variables nulas.
493 // (1) El codigo de la variable
494 //-------------------------------------------------------------------------------------------
495 bool comm::CompatCodec::optimizedDecode(const char* data, const int size)
496 throw(RuntimeException) {
497   int nbytes;
498   Variable* variable;
499   const char* top = data + size;
500   data += 3;
501
502   for(iterator ii = begin(), maxii = end(); ii != maxii && data < top; ii ++) {
503     data += sizeof(short int);
504     variable = CompatCodec::variable(ii);
505
506     switch(*data & 0x7f) {
507     case Variable::Type::String:
508       nbytes = comm::functions::decodeShort(++ data);
509       variable->setValue(data += sizeof(short int));
510       data += nbytes;
511       break;
512     case Variable::Type::Integer:
513       variable->setInteger(comm::functions::decodeInteger(++ data));
514       data += sizeof(int);
515       break;
516     case Variable::Type::Integer64:
517       variable->setValue(comm::functions::decodeInteger64(++ data));
518       data += sizeof(Integer64);
519       break;
520     case  Variable::Type::Boolean:
521       variable->setBoolean((*data & 0x80) ? true : false);
522       data ++;
523       break;
524     case Variable::Type::Block:
525       nbytes = comm::functions::decodeInteger(++ data);
526       variable->setDataBlock(DataBlock(data += sizeof(int), nbytes, false));
527       data += nbytes;
528       break;
529     case Variable::Type::Float:
530       variable->setFloat(comm::functions::decodeFloat(++ data));
531       data += sizeof(float);
532       break;
533     case Variable::Type::Double:
534       variable->setDouble(comm::functions::decodeDouble(++ data));
535       data += sizeof(double);
536       break;
537     case Variable::Type::Custom:
538       nbytes = comm::functions::decodeInteger(++ data);
539       {
540         DataBlock dataBlock(data += sizeof(int), nbytes, false);
541         reinterpret_cast <CompatCodec*>(variable->getCustom())->decode(dataBlock);
542       }
543       data += nbytes;
544       break;
545     }
546   }
547
548   return (data == top);
549 }
550
551 comm::CompatCodec::Type comm::CompatCodec::getType(const DataBlock& dataBlock)
552 throw(RuntimeException) {
553   const int size(dataBlock.getSize());
554
555   if(size <= 1)
556     throw RuntimeException("DataBlock is not valid", ANNA_FILE_LOCATION);
557
558   const char* data(dataBlock.getData());
559   comm::CompatCodec::Type result;
560   result = (data [0] != 0) ? ((data [0] + 1) ^ data [2]) : data [2];
561   LOGDEBUG(Logger::write(Logger::Debug, "comm::CompatCodec::getType", result, ANNA_FILE_LOCATION));
562   return result;
563 }
564
565 comm::CompatCodec::VariableContainer::VariableContainer() {
566   a_maxSize = 16;
567   a_size = 0;
568   a_variables = new Variable* [a_maxSize];
569   anna_memset(a_variables, 0, sizeof(Variable*) * a_maxSize);
570 }
571
572 void comm::CompatCodec::VariableContainer::add(comm::Variable* variable)
573 throw() {
574   if(a_size == a_maxSize) {
575     int maxSize = (a_maxSize << 1) - (a_maxSize >> 1);
576     Variable** variables = new Variable* [maxSize];
577     anna_memset(variables, 0, sizeof(Variable*) * maxSize);
578     anna_memcpy(variables, a_variables, sizeof(Variable*) * a_size);
579     delete a_variables;
580     a_variables = variables;
581     a_maxSize = maxSize;
582   }
583
584   a_variables [a_size ++] = variable;
585 }
586
587 comm::Variable* comm::CompatCodec::VariableContainer::find(const int id)
588 throw() {
589   for(register int ii = 0; ii < a_size; ii ++) {
590     if(a_variables [ii]->getId() == id)
591       return a_variables [ii];
592   }
593
594   return NULL;
595 }
596
597 const comm::Variable* comm::CompatCodec::VariableContainer::find(const int id) const
598 throw() {
599   for(register int ii = 0; ii < a_size; ii ++) {
600     if(a_variables [ii]->getId() == id)
601       return a_variables [ii];
602   }
603
604   return NULL;
605 }
606
607 void comm::CompatCodec::VariableContainer::clear()
608 throw() {
609   delete [] a_variables;
610   a_maxSize = a_size = 0;
611 }
612