Added clarifying comment to CodFileBuilder
[barry.git] / src / record.cc
blob3a491be359e8adff38d03e537f227686bf806fc0
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-2009, 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 <sstream>
36 #include <iomanip>
37 #include <time.h>
38 #include <string.h>
39 #include <stdexcept>
41 #define __DEBUG_MODE__
42 #include "debug.h"
44 using namespace std;
45 using namespace Barry::Protocol;
47 namespace Barry {
49 //////////////////////////////////////////////////////////////////////////////
50 // Field builder helper functions
52 void BuildField1900(Data &data, size_t &size, uint8_t type, time_t t)
54 size_t timesize = COMMON_FIELD_MIN1900_SIZE;
55 size_t fieldsize = COMMON_FIELD_HEADER_SIZE + timesize;
56 unsigned char *pd = data.GetBuffer(size + fieldsize) + size;
57 CommonField *field = (CommonField *) pd;
59 field->size = htobs(timesize);
60 field->type = type;
61 field->u.min1900 = time2min(t);
63 size += fieldsize;
66 void BuildField(Data &data, size_t &size, uint8_t type, char c)
68 BuildField(data, size, type, (uint8_t)c);
71 void BuildField(Data &data, size_t &size, uint8_t type, uint8_t c)
73 size_t strsize = 1;
74 size_t fieldsize = COMMON_FIELD_HEADER_SIZE + strsize;
75 unsigned char *pd = data.GetBuffer(size + fieldsize) + size;
76 CommonField *field = (CommonField *) pd;
78 field->size = htobs(strsize);
79 field->type = type;
80 memcpy(field->u.raw, &c, strsize);
82 size += fieldsize;
85 void BuildField(Data &data, size_t &size, uint8_t type, uint16_t value)
87 size_t strsize = 2;
88 size_t fieldsize = COMMON_FIELD_HEADER_SIZE + strsize;
89 unsigned char *pd = data.GetBuffer(size + fieldsize) + size;
90 CommonField *field = (CommonField *) pd;
92 field->size = htobs(strsize);
93 field->type = type;
95 uint16_t store = htobs(value);
96 memcpy(field->u.raw, &store, strsize);
98 size += fieldsize;
101 void BuildField(Data &data, size_t &size, uint8_t type, uint32_t value)
103 size_t strsize = 4;
104 size_t fieldsize = COMMON_FIELD_HEADER_SIZE + strsize;
105 unsigned char *pd = data.GetBuffer(size + fieldsize) + size;
106 CommonField *field = (CommonField *) pd;
108 field->size = htobl(strsize);
109 field->type = type;
111 uint32_t store = htobs(value);
112 memcpy(field->u.raw, &store, strsize);
114 size += fieldsize;
117 void BuildField(Data &data, size_t &size, uint8_t type, const std::string &str)
119 // include null terminator
120 BuildField(data, size, type, str.c_str(), str.size() + 1);
123 void BuildField(Data &data, size_t &size, uint8_t type,
124 const void *buf, size_t bufsize)
126 // include null terminator
127 size_t fieldsize = COMMON_FIELD_HEADER_SIZE + bufsize;
128 unsigned char *pd = data.GetBuffer(size + fieldsize) + size;
129 CommonField *field = (CommonField *) pd;
131 field->size = htobs(bufsize);
132 field->type = type;
133 memcpy(field->u.raw, buf, bufsize);
135 size += fieldsize;
138 void BuildField(Data &data, size_t &size, const Barry::UnknownField &field)
140 BuildField(data, size, field.type,
141 field.data.raw_data.data(), field.data.raw_data.size());
144 void BuildField(Data &data, size_t &size, uint8_t type, const Barry::Protocol::GroupLink &link)
146 size_t linksize = sizeof(Barry::Protocol::GroupLink);
147 size_t fieldsize = COMMON_FIELD_HEADER_SIZE + linksize;
148 unsigned char *pd = data.GetBuffer(size + fieldsize) + size;
149 CommonField *field = (CommonField *) pd;
151 field->size = htobs(linksize);
152 field->type = type;
153 field->u.link = link;
155 size += fieldsize;
158 std::string ParseFieldString(const Barry::Protocol::CommonField *field)
160 // make no assumptions here, and pass the full size in as
161 // the maxlen, even though 99% of the time, it will be a null...
162 // this function can be used by non-null terminated strings as well
163 return ParseFieldString(field->u.raw, btohs(field->size));
166 std::string ParseFieldString(const void *data, uint16_t maxlen)
168 const char *str = (const char *)data;
170 // find last non-null character, since some fields
171 // can have multiple null terminators
172 while( maxlen && str[maxlen-1] == 0 )
173 maxlen--;
175 return std::string(str, maxlen);
179 ///////////////////////////////////////////////////////////////////////////////
180 // CommandTable class
182 CommandTable::CommandTable()
186 CommandTable::~CommandTable()
190 const unsigned char* CommandTable::ParseField(const unsigned char *begin,
191 const unsigned char *end)
193 // check if there is enough data for a header
194 const unsigned char *headend = begin + sizeof(CommandTableField);
195 if( headend > end )
196 return headend;
198 const CommandTableField *field = (const CommandTableField *) begin;
200 // advance and check size
201 begin += COMMAND_FIELD_HEADER_SIZE + field->size; // size is byte
202 if( begin > end ) // if begin==end, we are ok
203 return begin;
205 if( !field->size ) // if field has no size, something's up
206 return begin;
208 Command command;
209 command.Code = field->code;
210 command.Name.assign((const char *)field->name, field->size);
211 Commands.push_back(command);
212 return begin;
215 void CommandTable::Parse(const Data &data, size_t offset)
217 if( offset >= data.GetSize() )
218 return;
220 const unsigned char *begin = data.GetData() + offset;
221 const unsigned char *end = data.GetData() + data.GetSize();
223 while( begin < end )
224 begin = ParseField(begin, end);
227 void CommandTable::Clear()
229 Commands.clear();
232 unsigned int CommandTable::GetCommand(const std::string &name) const
234 CommandArrayType::const_iterator b = Commands.begin();
235 for( ; b != Commands.end(); b++ )
236 if( b->Name == name )
237 return b->Code;
238 return 0;
241 void CommandTable::Dump(std::ostream &os) const
243 CommandArrayType::const_iterator b = Commands.begin();
244 os << "Command table:\n";
245 for( ; b != Commands.end(); b++ ) {
246 os << " Command: 0x" << setbase(16) << b->Code
247 << " '" << b->Name << "'\n";
253 ///////////////////////////////////////////////////////////////////////////////
254 // RecordStateTable class
256 RecordStateTable::RecordStateTable()
257 : m_LastNewRecordId(1)
261 RecordStateTable::~RecordStateTable()
265 const unsigned char* RecordStateTable::ParseField(const unsigned char *begin,
266 const unsigned char *end)
268 const RecordStateTableField *field = (const RecordStateTableField *) begin;
270 // advance and check size
271 begin += sizeof(RecordStateTableField);
272 if( begin > end ) // if begin==end, we are ok
273 return begin;
275 State state;
276 state.Index = btohs(field->index);
277 state.RecordId = btohl(field->uniqueId);
278 state.Dirty = (field->flags & BARRY_RSTF_DIRTY) != 0;
279 state.RecType = field->rectype;
280 state.Unknown2.assign((const char*)field->unknown2, sizeof(field->unknown2));
281 StateMap[state.Index] = state;
283 return begin;
286 void RecordStateTable::Parse(const Data &data)
288 size_t offset = 12; // skipping the unknown 2 bytes at start
290 if( offset >= data.GetSize() )
291 return;
293 const unsigned char *begin = data.GetData() + offset;
294 const unsigned char *end = data.GetData() + data.GetSize();
296 while( begin < end )
297 begin = ParseField(begin, end);
300 void RecordStateTable::Clear()
302 StateMap.clear();
303 m_LastNewRecordId = 1;
306 // Searches the StateMap table for RecordId, and returns the "index"
307 // in the map if found. Returns true if found, false if not.
308 // pFoundIndex can be null if only the existence of the index is desired
309 bool RecordStateTable::GetIndex(uint32_t RecordId, IndexType *pFoundIndex) const
311 StateMapType::const_iterator i = StateMap.begin();
312 for( ; i != StateMap.end(); ++i ) {
313 if( i->second.RecordId == RecordId ) {
314 if( pFoundIndex )
315 *pFoundIndex = i->first;
316 return true;
319 return false;
322 // Generate a new RecordId that is not in the state table.
323 // Starts at 1 and keeps incrementing until a free one is found.
324 uint32_t RecordStateTable::MakeNewRecordId() const
326 // start with next Id
327 m_LastNewRecordId++;
329 // make sure it doesn't already exist
330 StateMapType::const_iterator i = StateMap.begin();
331 while( i != StateMap.end() ) {
332 if( m_LastNewRecordId == i->second.RecordId ) {
333 m_LastNewRecordId++; // try again
334 i = StateMap.begin(); // start over
336 else {
337 ++i; // next State
340 return m_LastNewRecordId;
343 void RecordStateTable::Dump(std::ostream &os) const
345 ios::fmtflags oldflags = os.setf(ios::right);
346 char fill = os.fill(' ');
347 bool bPrintAscii = Data::PrintAscii();
348 Data::PrintAscii(false);
350 os << " Index RecordId Dirty RecType" << endl;
351 os << "------- ---------- ----- -------" << endl;
353 StateMapType::const_iterator b, e = StateMap.end();
354 for( b = StateMap.begin(); b != e ; ++b ) {
355 const State &state = b->second;
357 os.fill(' ');
358 os << setbase(10) << setw(7) << state.Index;
359 os << " 0x" << setbase(16) << setfill('0') << setw(8) << state.RecordId;
360 os << " " << setfill(' ') << setw(5) << (state.Dirty ? "yes" : "no");
361 os << " 0x" << setbase(16) << setfill('0') << setw(2) << state.RecType;
362 os << " " << Data(state.Unknown2.data(), state.Unknown2.size());
365 // cleanup the stream
366 os.flags(oldflags);
367 os.fill(fill);
368 Data::PrintAscii(bPrintAscii);
373 ///////////////////////////////////////////////////////////////////////////////
374 // DatabaseDatabase class
376 DatabaseDatabase::DatabaseDatabase()
380 DatabaseDatabase::~DatabaseDatabase()
384 template <class RecordType, class FieldType>
385 void DatabaseDatabase::ParseRec(const RecordType &rec, const unsigned char *end)
389 template <class FieldType>
390 const unsigned char* DatabaseDatabase::ParseField(const unsigned char *begin,
391 const unsigned char *end)
393 // check if there is enough data for a header
394 const unsigned char *headend = begin + sizeof(FieldType);
395 if( headend > end )
396 return headend;
398 // get our header
399 const FieldType *field = (const FieldType *) begin;
401 // advance and check size
402 begin += sizeof(FieldType) - sizeof(field->name) + ConvertHtoB(field->nameSize);
403 if( begin > end ) // if begin==end, we are ok
404 return begin;
406 if( !ConvertHtoB(field->nameSize) ) // if field has no size, something's up
407 return begin;
409 Database db;
410 db.Number = ConvertHtoB(field->dbNumber);
411 db.RecordCount = ConvertHtoB(field->dbRecordCount);
412 db.Name.assign((const char *)field->name, ConvertHtoB(field->nameSize) - 1);
413 Databases.push_back(db);
414 return begin;
417 void DatabaseDatabase::Parse(const Data &data)
419 // check size to make sure we have up to the DBAccess operation byte
420 if( data.GetSize() < (SB_PACKET_DBACCESS_HEADER_SIZE + 1) )
421 return;
423 MAKE_PACKET(pack, data);
424 const unsigned char *begin = 0;
425 const unsigned char *end = data.GetData() + data.GetSize();
427 switch( pack->u.db.u.response.operation )
429 case SB_DBOP_GET_DBDB:
430 // using the new protocol
431 if( data.GetSize() > SB_PACKET_DBDB_HEADER_SIZE ) {
432 begin = (const unsigned char *)
433 &pack->u.db.u.response.u.dbdb.field[0];
435 // this while check is ok, since ParseField checks
436 // for header size
437 while( begin < end )
438 begin = ParseField<DBDBField>(begin, end);
440 else
441 dout("DatabaseDatabase: not enough data for parsing");
442 break;
444 case SB_DBOP_OLD_GET_DBDB:
445 // using the old protocol
446 if( data.GetSize() > SB_PACKET_OLD_DBDB_HEADER_SIZE ) {
447 begin = (const unsigned char *)
448 &pack->u.db.u.response.u.old_dbdb.field[0];
450 // this while check is ok, since ParseField checks
451 // for header size
452 while( begin < end )
453 begin = ParseField<OldDBDBField>(begin, end);
455 else
456 dout("DatabaseDatabase: not enough data for parsing");
457 break;
459 default:
460 // unknown protocol
461 dout("Unknown protocol");
462 break;
468 void DatabaseDatabase::Clear()
470 Databases.clear();
473 bool DatabaseDatabase::GetDBNumber(const std::string &name,
474 unsigned int &number) const
476 DatabaseArrayType::const_iterator b = Databases.begin();
477 for( ; b != Databases.end(); b++ )
478 if( b->Name == name ) {
479 number = b->Number;
480 return true;
482 return false;
485 bool DatabaseDatabase::GetDBName(unsigned int number,
486 std::string &name) const
488 DatabaseArrayType::const_iterator b = Databases.begin();
489 for( ; b != Databases.end(); b++ )
490 if( b->Number == number ) {
491 name = b->Name;
492 return true;
494 return false;
497 void DatabaseDatabase::Dump(std::ostream &os) const
499 DatabaseArrayType::const_iterator b = Databases.begin();
500 os << "Database database:\n";
501 for( ; b != Databases.end(); b++ ) {
502 os << " Database: 0x" << setbase(16) << b->Number
503 << " '" << b->Name << "' (records: "
504 << setbase(10) << b->RecordCount << ")\n";
509 std::ostream& operator<< (std::ostream &os, const std::vector<UnknownField> &unknowns)
511 std::vector<UnknownField>::const_iterator
512 ub = unknowns.begin(), ue = unknowns.end();
513 if( ub != ue )
514 os << " Unknowns:\n";
515 for( ; ub != ue; ub++ ) {
516 os << " Type: 0x" << setbase(16)
517 << (unsigned int) ub->type
518 << " Data:\n" << Data(ub->data.data(), ub->data.size());
520 return os;
525 ///////////////////////////////////////////////////////////////////////////////
526 // EmailAddress class
528 std::ostream& operator<<(std::ostream &os, const EmailAddress &msga) {
529 os << msga.Name << " <" << msga.Email << ">";
530 return os;
533 std::ostream& operator<<(std::ostream &os, const EmailAddressList &elist) {
534 for( EmailAddressList::const_iterator i = elist.begin(); i != elist.end(); ++i ) {
535 if( i != elist.begin() )
536 os << ", ";
537 os << *i;
539 return os;
543 ///////////////////////////////////////////////////////////////////////////////
544 // PostalAddress class
547 // GetLabel
549 /// Format a mailing address into a single string, handling missing fields.
551 std::string PostalAddress::GetLabel() const
553 std::string address = Address1;
554 if( Address2.size() ) {
555 if( address.size() )
556 address += "\n";
557 address += Address2;
559 if( Address3.size() ) {
560 if( address.size() )
561 address += "\n";
562 address += Address3;
564 if( address.size() )
565 address += "\n";
566 if( City.size() )
567 address += City + " ";
568 if( Province.size() )
569 address += Province + " ";
570 if( Country.size() )
571 address += Country;
572 if( address.size() )
573 address += "\n";
574 if( PostalCode.size() )
575 address += PostalCode;
577 return address;
580 void PostalAddress::Clear()
582 Address1.clear();
583 Address2.clear();
584 Address3.clear();
585 City.clear();
586 Province.clear();
587 PostalCode.clear();
588 Country.clear();
591 std::ostream& operator<<(std::ostream &os, const PostalAddress &post) {
592 os << post.GetLabel();
593 return os;
598 ///////////////////////////////////////////////////////////////////////////////
599 // Date class
601 Date::Date(const struct tm *timep)
603 FromTm(timep);
606 void Date::Clear()
608 Month = Day = Year = 0;
611 void Date::ToTm(struct tm *timep) const
613 memset(timep, 0, sizeof(tm));
614 timep->tm_year = Year - 1900;
615 timep->tm_mon = Month;
616 timep->tm_mday = Day;
619 std::string Date::ToYYYYMMDD() const
621 std::ostringstream oss;
622 oss << setw(4) << Year
623 << setw(2) << Month + 1
624 << setw(2) << Day;
625 return oss.str();
629 // ToBBString
631 /// The Blackberry stores Birthday and Anniversary date fields
632 /// with the format: DD/MM/YYYY
634 std::string Date::ToBBString() const
636 std::ostringstream oss;
637 oss << setw(2) << Day
638 << Month + 1
639 << Year;
640 return oss.str();
643 bool Date::FromTm(const struct tm *timep)
645 Year = timep->tm_year + 1900;
646 Month = timep->tm_mon;
647 Day = timep->tm_mday;
648 return true;
651 bool Date::FromBBString(const std::string &str)
653 int m, d, y;
654 if( 3 == sscanf(str.c_str(), "%d/%d/%d", &d, &m, &y) ) {
655 Year = y;
656 Month = m - 1;
657 Day = d;
658 return true;
660 return false;
663 bool Date::FromYYYYMMDD(const std::string &str)
665 int m, d, y;
666 if( 3 == sscanf(str.c_str(), "%4d%2d%2d", &y, &m, &d) ) {
667 Year = y;
668 Month = m - 1;
669 Day = d;
670 return true;
672 return false;
675 std::ostream& operator<<(std::ostream &os, const Date &date)
677 os << setw(4) << date.Year << '/'
678 << setw(2) << date.Month << '/'
679 << setw(2) << date.Day;
680 return os;
685 } // namespace Barry
688 #ifdef __TEST_MODE__
690 #include <iostream>
692 int main(int argc, char *argv[])
694 if( argc < 2 ) {
695 cerr << "Usage: test <datafile>" << endl;
696 return 1;
699 std::vector<Data> array;
700 if( !LoadDataArray(argv[1], array) ) {
701 cerr << "Unable to load file: " << argv[1] << endl;
702 return 1;
705 cout << "Loaded " << array.size() << " items" << endl;
707 for( std::vector<Data>::iterator b = array.begin(), e = array.end();
708 b != e; b++ )
710 Data &d = *b;
711 // cout << d << endl;
712 if( d.GetSize() > 13 && d.GetData()[6] == 0x4f ) {
713 Barry::Contact contact;
714 size_t size = 13;
715 contact.ParseFields(d, size);
716 cout << contact << endl;
717 contact.DumpLdif(cout, "ou=People,dc=example,dc=com");
719 else if( d.GetSize() > 13 && d.GetData()[6] == 0x44 ) {
720 Barry::Calendar cal;
721 size_t size = 13;
722 cal.ParseFields(d, size);
723 cout << cal << endl;
728 #endif