Moved PPP filter logic into its own class
[barry.git] / src / record.cc
blob7a9c524a99d184d1b4530ac0a36263c4b67a1c49
1 ///
2 /// \file record.cc
3 /// Blackberry database record classes. Help translate data
4 /// from data packets to useful structurs, and back.
5 /// This header provides the common types and classes
6 /// used by the general record parser classes in the
7 /// r_*.h files. Only application-safe API stuff goes in
8 /// here. Internal library types go in record-internal.h
9 ///
12 Copyright (C) 2005-2008, Net Direct Inc. (http://www.netdirect.ca/)
14 This program is free software; you can redistribute it and/or modify
15 it under the terms of the GNU General Public License as published by
16 the Free Software Foundation; either version 2 of the License, or
17 (at your option) any later version.
19 This program is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
23 See the GNU General Public License in the COPYING file at the
24 root directory of this project for more details.
27 #include "record.h"
28 #include "record-internal.h"
29 #include "protocol.h"
30 #include "protostructs.h"
31 #include "data.h"
32 #include "time.h"
33 #include "error.h"
34 #include "endian.h"
35 #include <ostream>
36 #include <iomanip>
37 #include <time.h>
38 #include <stdexcept>
40 #define __DEBUG_MODE__
41 #include "debug.h"
43 using namespace std;
44 using namespace Barry::Protocol;
46 namespace Barry {
48 //////////////////////////////////////////////////////////////////////////////
49 // Field builder helper functions
51 void BuildField1900(Data &data, size_t &size, uint8_t type, time_t t)
53 size_t timesize = COMMON_FIELD_MIN1900_SIZE;
54 size_t fieldsize = COMMON_FIELD_HEADER_SIZE + timesize;
55 unsigned char *pd = data.GetBuffer(size + fieldsize) + size;
56 CommonField *field = (CommonField *) pd;
58 field->size = htobs(timesize);
59 field->type = type;
60 field->u.min1900 = time2min(t);
62 size += fieldsize;
65 void BuildField(Data &data, size_t &size, uint8_t type, char c)
67 size_t strsize = 1;
68 size_t fieldsize = COMMON_FIELD_HEADER_SIZE + strsize;
69 unsigned char *pd = data.GetBuffer(size + fieldsize) + size;
70 CommonField *field = (CommonField *) pd;
72 field->size = htobs(strsize);
73 field->type = type;
74 memcpy(field->u.raw, &c, strsize);
76 size += fieldsize;
79 void BuildField(Data &data, size_t &size, uint8_t type, uint16_t value)
81 size_t strsize = 2;
82 size_t fieldsize = COMMON_FIELD_HEADER_SIZE + strsize;
83 unsigned char *pd = data.GetBuffer(size + fieldsize) + size;
84 CommonField *field = (CommonField *) pd;
86 field->size = htobs(strsize);
87 field->type = type;
89 uint16_t store = htobs(value);
90 memcpy(field->u.raw, &store, strsize);
92 size += fieldsize;
95 void BuildField(Data &data, size_t &size, uint8_t type, const std::string &str)
97 // include null terminator
98 BuildField(data, size, type, str.c_str(), str.size() + 1);
101 void BuildField(Data &data, size_t &size, uint8_t type,
102 const void *buf, size_t bufsize)
104 // include null terminator
105 size_t fieldsize = COMMON_FIELD_HEADER_SIZE + bufsize;
106 unsigned char *pd = data.GetBuffer(size + fieldsize) + size;
107 CommonField *field = (CommonField *) pd;
109 field->size = htobs(bufsize);
110 field->type = type;
111 memcpy(field->u.raw, buf, bufsize);
113 size += fieldsize;
116 void BuildField(Data &data, size_t &size, const Barry::UnknownField &field)
118 BuildField(data, size, field.type,
119 field.data.raw_data.data(), field.data.raw_data.size());
122 void BuildField(Data &data, size_t &size, uint8_t type, const Barry::Protocol::GroupLink &link)
124 size_t linksize = sizeof(Barry::Protocol::GroupLink);
125 size_t fieldsize = COMMON_FIELD_HEADER_SIZE + linksize;
126 unsigned char *pd = data.GetBuffer(size + fieldsize) + size;
127 CommonField *field = (CommonField *) pd;
129 field->size = htobs(linksize);
130 field->type = type;
131 field->u.link = link;
133 size += fieldsize;
136 std::string ParseFieldString(const Barry::Protocol::CommonField *field)
138 // make no assumptions here, and pass the full size in as
139 // the maxlen, even though 99% of the time, it will be a null...
140 // this function can be used by non-null terminated strings as well
141 return ParseFieldString(field->u.raw, btohs(field->size));
144 std::string ParseFieldString(const void *data, uint16_t maxlen)
146 const char *str = (const char *)data;
148 // find last non-null character, since some fields
149 // can have multiple null terminators
150 while( maxlen && str[maxlen-1] == 0 )
151 maxlen--;
153 return std::string(str, maxlen);
157 ///////////////////////////////////////////////////////////////////////////////
158 // CommandTable class
160 CommandTable::CommandTable()
164 CommandTable::~CommandTable()
168 const unsigned char* CommandTable::ParseField(const unsigned char *begin,
169 const unsigned char *end)
171 // check if there is enough data for a header
172 const unsigned char *headend = begin + sizeof(CommandTableField);
173 if( headend > end )
174 return headend;
176 const CommandTableField *field = (const CommandTableField *) begin;
178 // advance and check size
179 begin += COMMAND_FIELD_HEADER_SIZE + field->size; // size is byte
180 if( begin > end ) // if begin==end, we are ok
181 return begin;
183 if( !field->size ) // if field has no size, something's up
184 return begin;
186 Command command;
187 command.Code = field->code;
188 command.Name.assign((const char *)field->name, field->size);
189 Commands.push_back(command);
190 return begin;
193 void CommandTable::Parse(const Data &data, size_t offset)
195 if( offset >= data.GetSize() )
196 return;
198 const unsigned char *begin = data.GetData() + offset;
199 const unsigned char *end = data.GetData() + data.GetSize();
201 while( begin < end )
202 begin = ParseField(begin, end);
205 void CommandTable::Clear()
207 Commands.clear();
210 unsigned int CommandTable::GetCommand(const std::string &name) const
212 CommandArrayType::const_iterator b = Commands.begin();
213 for( ; b != Commands.end(); b++ )
214 if( b->Name == name )
215 return b->Code;
216 return 0;
219 void CommandTable::Dump(std::ostream &os) const
221 CommandArrayType::const_iterator b = Commands.begin();
222 os << "Command table:\n";
223 for( ; b != Commands.end(); b++ ) {
224 os << " Command: 0x" << setbase(16) << b->Code
225 << " '" << b->Name << "'\n";
231 ///////////////////////////////////////////////////////////////////////////////
232 // RecordStateTable class
234 RecordStateTable::RecordStateTable()
235 : m_LastNewRecordId(1)
239 RecordStateTable::~RecordStateTable()
243 const unsigned char* RecordStateTable::ParseField(const unsigned char *begin,
244 const unsigned char *end)
246 const RecordStateTableField *field = (const RecordStateTableField *) begin;
248 // advance and check size
249 begin += sizeof(RecordStateTableField);
250 if( begin > end ) // if begin==end, we are ok
251 return begin;
253 State state;
254 state.Index = btohs(field->index);
255 state.RecordId = btohl(field->uniqueId);
256 state.Dirty = (field->flags & BARRY_RSTF_DIRTY) != 0;
257 state.RecType = field->rectype;
258 state.Unknown2.assign((const char*)field->unknown2, sizeof(field->unknown2));
259 StateMap[state.Index] = state;
261 return begin;
264 void RecordStateTable::Parse(const Data &data)
266 size_t offset = 12; // skipping the unknown 2 bytes at start
268 if( offset >= data.GetSize() )
269 return;
271 const unsigned char *begin = data.GetData() + offset;
272 const unsigned char *end = data.GetData() + data.GetSize();
274 while( begin < end )
275 begin = ParseField(begin, end);
278 void RecordStateTable::Clear()
280 StateMap.clear();
281 m_LastNewRecordId = 1;
284 // Searches the StateMap table for RecordId, and returns the "index"
285 // in the map if found. Returns true if found, false if not.
286 // pFoundIndex can be null if only the existence of the index is desired
287 bool RecordStateTable::GetIndex(uint32_t RecordId, IndexType *pFoundIndex) const
289 StateMapType::const_iterator i = StateMap.begin();
290 for( ; i != StateMap.end(); ++i ) {
291 if( i->second.RecordId == RecordId ) {
292 if( pFoundIndex )
293 *pFoundIndex = i->first;
294 return true;
297 return false;
300 // Generate a new RecordId that is not in the state table.
301 // Starts at 1 and keeps incrementing until a free one is found.
302 uint32_t RecordStateTable::MakeNewRecordId() const
304 // start with next Id
305 m_LastNewRecordId++;
307 // make sure it doesn't already exist
308 StateMapType::const_iterator i = StateMap.begin();
309 while( i != StateMap.end() ) {
310 if( m_LastNewRecordId == i->second.RecordId ) {
311 m_LastNewRecordId++; // try again
312 i = StateMap.begin(); // start over
314 else {
315 ++i; // next State
318 return m_LastNewRecordId;
321 void RecordStateTable::Dump(std::ostream &os) const
323 ios::fmtflags oldflags = os.setf(ios::right);
324 char fill = os.fill(' ');
325 bool bPrintAscii = Data::PrintAscii();
326 Data::PrintAscii(false);
328 os << " Index RecordId Dirty RecType" << endl;
329 os << "------- ---------- ----- -------" << endl;
331 StateMapType::const_iterator b, e = StateMap.end();
332 for( b = StateMap.begin(); b != e ; ++b ) {
333 const State &state = b->second;
335 os.fill(' ');
336 os << setbase(10) << setw(7) << state.Index;
337 os << " 0x" << setbase(16) << setfill('0') << setw(8) << state.RecordId;
338 os << " " << setfill(' ') << setw(5) << (state.Dirty ? "yes" : "no");
339 os << " 0x" << setbase(16) << setfill('0') << setw(2) << state.RecType;
340 os << " " << Data(state.Unknown2.data(), state.Unknown2.size());
343 // cleanup the stream
344 os.flags(oldflags);
345 os.fill(fill);
346 Data::PrintAscii(bPrintAscii);
351 ///////////////////////////////////////////////////////////////////////////////
352 // DatabaseDatabase class
354 DatabaseDatabase::DatabaseDatabase()
358 DatabaseDatabase::~DatabaseDatabase()
362 template <class RecordType, class FieldType>
363 void DatabaseDatabase::ParseRec(const RecordType &rec, const unsigned char *end)
367 template <class FieldType>
368 const unsigned char* DatabaseDatabase::ParseField(const unsigned char *begin,
369 const unsigned char *end)
371 // check if there is enough data for a header
372 const unsigned char *headend = begin + sizeof(FieldType);
373 if( headend > end )
374 return headend;
376 // get our header
377 const FieldType *field = (const FieldType *) begin;
379 // advance and check size
380 begin += sizeof(FieldType) - sizeof(field->name) + ConvertHtoB(field->nameSize);
381 if( begin > end ) // if begin==end, we are ok
382 return begin;
384 if( !ConvertHtoB(field->nameSize) ) // if field has no size, something's up
385 return begin;
387 Database db;
388 db.Number = ConvertHtoB(field->dbNumber);
389 db.RecordCount = ConvertHtoB(field->dbRecordCount);
390 db.Name.assign((const char *)field->name, ConvertHtoB(field->nameSize) - 1);
391 Databases.push_back(db);
392 return begin;
395 void DatabaseDatabase::Parse(const Data &data)
397 // check size to make sure we have up to the DBAccess operation byte
398 if( data.GetSize() < (SB_PACKET_DBACCESS_HEADER_SIZE + 1) )
399 return;
401 MAKE_PACKET(pack, data);
402 const unsigned char *begin = 0;
403 const unsigned char *end = data.GetData() + data.GetSize();
405 switch( pack->u.db.u.response.operation )
407 case SB_DBOP_GET_DBDB:
408 // using the new protocol
409 if( data.GetSize() > SB_PACKET_DBDB_HEADER_SIZE ) {
410 begin = (const unsigned char *)
411 &pack->u.db.u.response.u.dbdb.field[0];
413 // this while check is ok, since ParseField checks
414 // for header size
415 while( begin < end )
416 begin = ParseField<DBDBField>(begin, end);
418 else
419 dout("DatabaseDatabase: not enough data for parsing");
420 break;
422 case SB_DBOP_OLD_GET_DBDB:
423 // using the old protocol
424 if( data.GetSize() > SB_PACKET_OLD_DBDB_HEADER_SIZE ) {
425 begin = (const unsigned char *)
426 &pack->u.db.u.response.u.old_dbdb.field[0];
428 // this while check is ok, since ParseField checks
429 // for header size
430 while( begin < end )
431 begin = ParseField<OldDBDBField>(begin, end);
433 else
434 dout("DatabaseDatabase: not enough data for parsing");
435 break;
437 default:
438 // unknown protocol
439 dout("Unknown protocol");
440 break;
446 void DatabaseDatabase::Clear()
448 Databases.clear();
451 bool DatabaseDatabase::GetDBNumber(const std::string &name,
452 unsigned int &number) const
454 DatabaseArrayType::const_iterator b = Databases.begin();
455 for( ; b != Databases.end(); b++ )
456 if( b->Name == name ) {
457 number = b->Number;
458 return true;
460 return false;
463 bool DatabaseDatabase::GetDBName(unsigned int number,
464 std::string &name) const
466 DatabaseArrayType::const_iterator b = Databases.begin();
467 for( ; b != Databases.end(); b++ )
468 if( b->Number == number ) {
469 name = b->Name;
470 return true;
472 return false;
475 void DatabaseDatabase::Dump(std::ostream &os) const
477 DatabaseArrayType::const_iterator b = Databases.begin();
478 os << "Database database:\n";
479 for( ; b != Databases.end(); b++ ) {
480 os << " Database: 0x" << setbase(16) << b->Number
481 << " '" << b->Name << "' (records: "
482 << setbase(10) << b->RecordCount << ")\n";
487 std::ostream& operator<< (std::ostream &os, const std::vector<UnknownField> &unknowns)
489 std::vector<UnknownField>::const_iterator
490 ub = unknowns.begin(), ue = unknowns.end();
491 if( ub != ue )
492 os << " Unknowns:\n";
493 for( ; ub != ue; ub++ ) {
494 os << " Type: 0x" << setbase(16)
495 << (unsigned int) ub->type
496 << " Data:\n" << Data(ub->data.data(), ub->data.size());
498 return os;
503 ///////////////////////////////////////////////////////////////////////////////
504 // EmailAddress class
506 std::ostream& operator<<(std::ostream &os, const EmailAddress &msga) {
507 os << msga.Name.c_str() << " <" << msga.Email.c_str() << ">";
508 return os;
512 ///////////////////////////////////////////////////////////////////////////////
513 // PostalAddress class
516 // GetLabel
518 /// Format a mailing address into a single string, handling missing fields.
520 std::string PostalAddress::GetLabel() const
522 std::string address = Address1;
523 if( Address2.size() ) {
524 if( address.size() )
525 address += "\n";
526 address += Address2;
528 if( Address3.size() ) {
529 if( address.size() )
530 address += "\n";
531 address += Address3;
533 if( address.size() )
534 address += "\n";
535 if( City.size() )
536 address += City + " ";
537 if( Province.size() )
538 address += Province + " ";
539 if( Country.size() )
540 address += Country;
541 if( address.size() )
542 address += "\n";
543 if( PostalCode.size() )
544 address += PostalCode;
546 return address;
549 void PostalAddress::Clear()
551 Address1.clear();
552 Address2.clear();
553 Address3.clear();
554 City.clear();
555 Province.clear();
556 PostalCode.clear();
557 Country.clear();
560 std::ostream& operator<<(std::ostream &os, const PostalAddress &post) {
561 os << post.GetLabel();
562 return os;
567 } // namespace Barry
570 #ifdef __TEST_MODE__
572 #include <iostream>
574 int main(int argc, char *argv[])
576 if( argc < 2 ) {
577 cerr << "Usage: test <datafile>" << endl;
578 return 1;
581 std::vector<Data> array;
582 if( !LoadDataArray(argv[1], array) ) {
583 cerr << "Unable to load file: " << argv[1] << endl;
584 return 1;
587 cout << "Loaded " << array.size() << " items" << endl;
589 for( std::vector<Data>::iterator b = array.begin(), e = array.end();
590 b != e; b++ )
592 Data &d = *b;
593 // cout << d << endl;
594 if( d.GetSize() > 13 && d.GetData()[6] == 0x4f ) {
595 Barry::Contact contact;
596 size_t size = 13;
597 contact.ParseFields(d, size);
598 cout << contact << endl;
599 contact.DumpLdif(cout, "ou=People,dc=example,dc=com");
601 else if( d.GetSize() > 13 && d.GetData()[6] == 0x44 ) {
602 Barry::Calendar cal;
603 size_t size = 13;
604 cal.ParseFields(d, size);
605 cout << cal << endl;
610 #endif