Added EmailAddressList, in preparation for multi-address email
[barry.git] / src / record.cc
blob2258f83d8dd8e2bf96f431c683b1d9b487cfd774
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 <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 size_t strsize = 1;
69 size_t fieldsize = COMMON_FIELD_HEADER_SIZE + strsize;
70 unsigned char *pd = data.GetBuffer(size + fieldsize) + size;
71 CommonField *field = (CommonField *) pd;
73 field->size = htobs(strsize);
74 field->type = type;
75 memcpy(field->u.raw, &c, strsize);
77 size += fieldsize;
80 void BuildField(Data &data, size_t &size, uint8_t type, uint16_t value)
82 size_t strsize = 2;
83 size_t fieldsize = COMMON_FIELD_HEADER_SIZE + strsize;
84 unsigned char *pd = data.GetBuffer(size + fieldsize) + size;
85 CommonField *field = (CommonField *) pd;
87 field->size = htobs(strsize);
88 field->type = type;
90 uint16_t store = htobs(value);
91 memcpy(field->u.raw, &store, strsize);
93 size += fieldsize;
96 void BuildField(Data &data, size_t &size, uint8_t type, const std::string &str)
98 // include null terminator
99 BuildField(data, size, type, str.c_str(), str.size() + 1);
102 void BuildField(Data &data, size_t &size, uint8_t type,
103 const void *buf, size_t bufsize)
105 // include null terminator
106 size_t fieldsize = COMMON_FIELD_HEADER_SIZE + bufsize;
107 unsigned char *pd = data.GetBuffer(size + fieldsize) + size;
108 CommonField *field = (CommonField *) pd;
110 field->size = htobs(bufsize);
111 field->type = type;
112 memcpy(field->u.raw, buf, bufsize);
114 size += fieldsize;
117 void BuildField(Data &data, size_t &size, const Barry::UnknownField &field)
119 BuildField(data, size, field.type,
120 field.data.raw_data.data(), field.data.raw_data.size());
123 void BuildField(Data &data, size_t &size, uint8_t type, const Barry::Protocol::GroupLink &link)
125 size_t linksize = sizeof(Barry::Protocol::GroupLink);
126 size_t fieldsize = COMMON_FIELD_HEADER_SIZE + linksize;
127 unsigned char *pd = data.GetBuffer(size + fieldsize) + size;
128 CommonField *field = (CommonField *) pd;
130 field->size = htobs(linksize);
131 field->type = type;
132 field->u.link = link;
134 size += fieldsize;
137 std::string ParseFieldString(const Barry::Protocol::CommonField *field)
139 // make no assumptions here, and pass the full size in as
140 // the maxlen, even though 99% of the time, it will be a null...
141 // this function can be used by non-null terminated strings as well
142 return ParseFieldString(field->u.raw, btohs(field->size));
145 std::string ParseFieldString(const void *data, uint16_t maxlen)
147 const char *str = (const char *)data;
149 // find last non-null character, since some fields
150 // can have multiple null terminators
151 while( maxlen && str[maxlen-1] == 0 )
152 maxlen--;
154 return std::string(str, maxlen);
158 ///////////////////////////////////////////////////////////////////////////////
159 // CommandTable class
161 CommandTable::CommandTable()
165 CommandTable::~CommandTable()
169 const unsigned char* CommandTable::ParseField(const unsigned char *begin,
170 const unsigned char *end)
172 // check if there is enough data for a header
173 const unsigned char *headend = begin + sizeof(CommandTableField);
174 if( headend > end )
175 return headend;
177 const CommandTableField *field = (const CommandTableField *) begin;
179 // advance and check size
180 begin += COMMAND_FIELD_HEADER_SIZE + field->size; // size is byte
181 if( begin > end ) // if begin==end, we are ok
182 return begin;
184 if( !field->size ) // if field has no size, something's up
185 return begin;
187 Command command;
188 command.Code = field->code;
189 command.Name.assign((const char *)field->name, field->size);
190 Commands.push_back(command);
191 return begin;
194 void CommandTable::Parse(const Data &data, size_t offset)
196 if( offset >= data.GetSize() )
197 return;
199 const unsigned char *begin = data.GetData() + offset;
200 const unsigned char *end = data.GetData() + data.GetSize();
202 while( begin < end )
203 begin = ParseField(begin, end);
206 void CommandTable::Clear()
208 Commands.clear();
211 unsigned int CommandTable::GetCommand(const std::string &name) const
213 CommandArrayType::const_iterator b = Commands.begin();
214 for( ; b != Commands.end(); b++ )
215 if( b->Name == name )
216 return b->Code;
217 return 0;
220 void CommandTable::Dump(std::ostream &os) const
222 CommandArrayType::const_iterator b = Commands.begin();
223 os << "Command table:\n";
224 for( ; b != Commands.end(); b++ ) {
225 os << " Command: 0x" << setbase(16) << b->Code
226 << " '" << b->Name << "'\n";
232 ///////////////////////////////////////////////////////////////////////////////
233 // RecordStateTable class
235 RecordStateTable::RecordStateTable()
236 : m_LastNewRecordId(1)
240 RecordStateTable::~RecordStateTable()
244 const unsigned char* RecordStateTable::ParseField(const unsigned char *begin,
245 const unsigned char *end)
247 const RecordStateTableField *field = (const RecordStateTableField *) begin;
249 // advance and check size
250 begin += sizeof(RecordStateTableField);
251 if( begin > end ) // if begin==end, we are ok
252 return begin;
254 State state;
255 state.Index = btohs(field->index);
256 state.RecordId = btohl(field->uniqueId);
257 state.Dirty = (field->flags & BARRY_RSTF_DIRTY) != 0;
258 state.RecType = field->rectype;
259 state.Unknown2.assign((const char*)field->unknown2, sizeof(field->unknown2));
260 StateMap[state.Index] = state;
262 return begin;
265 void RecordStateTable::Parse(const Data &data)
267 size_t offset = 12; // skipping the unknown 2 bytes at start
269 if( offset >= data.GetSize() )
270 return;
272 const unsigned char *begin = data.GetData() + offset;
273 const unsigned char *end = data.GetData() + data.GetSize();
275 while( begin < end )
276 begin = ParseField(begin, end);
279 void RecordStateTable::Clear()
281 StateMap.clear();
282 m_LastNewRecordId = 1;
285 // Searches the StateMap table for RecordId, and returns the "index"
286 // in the map if found. Returns true if found, false if not.
287 // pFoundIndex can be null if only the existence of the index is desired
288 bool RecordStateTable::GetIndex(uint32_t RecordId, IndexType *pFoundIndex) const
290 StateMapType::const_iterator i = StateMap.begin();
291 for( ; i != StateMap.end(); ++i ) {
292 if( i->second.RecordId == RecordId ) {
293 if( pFoundIndex )
294 *pFoundIndex = i->first;
295 return true;
298 return false;
301 // Generate a new RecordId that is not in the state table.
302 // Starts at 1 and keeps incrementing until a free one is found.
303 uint32_t RecordStateTable::MakeNewRecordId() const
305 // start with next Id
306 m_LastNewRecordId++;
308 // make sure it doesn't already exist
309 StateMapType::const_iterator i = StateMap.begin();
310 while( i != StateMap.end() ) {
311 if( m_LastNewRecordId == i->second.RecordId ) {
312 m_LastNewRecordId++; // try again
313 i = StateMap.begin(); // start over
315 else {
316 ++i; // next State
319 return m_LastNewRecordId;
322 void RecordStateTable::Dump(std::ostream &os) const
324 ios::fmtflags oldflags = os.setf(ios::right);
325 char fill = os.fill(' ');
326 bool bPrintAscii = Data::PrintAscii();
327 Data::PrintAscii(false);
329 os << " Index RecordId Dirty RecType" << endl;
330 os << "------- ---------- ----- -------" << endl;
332 StateMapType::const_iterator b, e = StateMap.end();
333 for( b = StateMap.begin(); b != e ; ++b ) {
334 const State &state = b->second;
336 os.fill(' ');
337 os << setbase(10) << setw(7) << state.Index;
338 os << " 0x" << setbase(16) << setfill('0') << setw(8) << state.RecordId;
339 os << " " << setfill(' ') << setw(5) << (state.Dirty ? "yes" : "no");
340 os << " 0x" << setbase(16) << setfill('0') << setw(2) << state.RecType;
341 os << " " << Data(state.Unknown2.data(), state.Unknown2.size());
344 // cleanup the stream
345 os.flags(oldflags);
346 os.fill(fill);
347 Data::PrintAscii(bPrintAscii);
352 ///////////////////////////////////////////////////////////////////////////////
353 // DatabaseDatabase class
355 DatabaseDatabase::DatabaseDatabase()
359 DatabaseDatabase::~DatabaseDatabase()
363 template <class RecordType, class FieldType>
364 void DatabaseDatabase::ParseRec(const RecordType &rec, const unsigned char *end)
368 template <class FieldType>
369 const unsigned char* DatabaseDatabase::ParseField(const unsigned char *begin,
370 const unsigned char *end)
372 // check if there is enough data for a header
373 const unsigned char *headend = begin + sizeof(FieldType);
374 if( headend > end )
375 return headend;
377 // get our header
378 const FieldType *field = (const FieldType *) begin;
380 // advance and check size
381 begin += sizeof(FieldType) - sizeof(field->name) + ConvertHtoB(field->nameSize);
382 if( begin > end ) // if begin==end, we are ok
383 return begin;
385 if( !ConvertHtoB(field->nameSize) ) // if field has no size, something's up
386 return begin;
388 Database db;
389 db.Number = ConvertHtoB(field->dbNumber);
390 db.RecordCount = ConvertHtoB(field->dbRecordCount);
391 db.Name.assign((const char *)field->name, ConvertHtoB(field->nameSize) - 1);
392 Databases.push_back(db);
393 return begin;
396 void DatabaseDatabase::Parse(const Data &data)
398 // check size to make sure we have up to the DBAccess operation byte
399 if( data.GetSize() < (SB_PACKET_DBACCESS_HEADER_SIZE + 1) )
400 return;
402 MAKE_PACKET(pack, data);
403 const unsigned char *begin = 0;
404 const unsigned char *end = data.GetData() + data.GetSize();
406 switch( pack->u.db.u.response.operation )
408 case SB_DBOP_GET_DBDB:
409 // using the new protocol
410 if( data.GetSize() > SB_PACKET_DBDB_HEADER_SIZE ) {
411 begin = (const unsigned char *)
412 &pack->u.db.u.response.u.dbdb.field[0];
414 // this while check is ok, since ParseField checks
415 // for header size
416 while( begin < end )
417 begin = ParseField<DBDBField>(begin, end);
419 else
420 dout("DatabaseDatabase: not enough data for parsing");
421 break;
423 case SB_DBOP_OLD_GET_DBDB:
424 // using the old protocol
425 if( data.GetSize() > SB_PACKET_OLD_DBDB_HEADER_SIZE ) {
426 begin = (const unsigned char *)
427 &pack->u.db.u.response.u.old_dbdb.field[0];
429 // this while check is ok, since ParseField checks
430 // for header size
431 while( begin < end )
432 begin = ParseField<OldDBDBField>(begin, end);
434 else
435 dout("DatabaseDatabase: not enough data for parsing");
436 break;
438 default:
439 // unknown protocol
440 dout("Unknown protocol");
441 break;
447 void DatabaseDatabase::Clear()
449 Databases.clear();
452 bool DatabaseDatabase::GetDBNumber(const std::string &name,
453 unsigned int &number) const
455 DatabaseArrayType::const_iterator b = Databases.begin();
456 for( ; b != Databases.end(); b++ )
457 if( b->Name == name ) {
458 number = b->Number;
459 return true;
461 return false;
464 bool DatabaseDatabase::GetDBName(unsigned int number,
465 std::string &name) const
467 DatabaseArrayType::const_iterator b = Databases.begin();
468 for( ; b != Databases.end(); b++ )
469 if( b->Number == number ) {
470 name = b->Name;
471 return true;
473 return false;
476 void DatabaseDatabase::Dump(std::ostream &os) const
478 DatabaseArrayType::const_iterator b = Databases.begin();
479 os << "Database database:\n";
480 for( ; b != Databases.end(); b++ ) {
481 os << " Database: 0x" << setbase(16) << b->Number
482 << " '" << b->Name << "' (records: "
483 << setbase(10) << b->RecordCount << ")\n";
488 std::ostream& operator<< (std::ostream &os, const std::vector<UnknownField> &unknowns)
490 std::vector<UnknownField>::const_iterator
491 ub = unknowns.begin(), ue = unknowns.end();
492 if( ub != ue )
493 os << " Unknowns:\n";
494 for( ; ub != ue; ub++ ) {
495 os << " Type: 0x" << setbase(16)
496 << (unsigned int) ub->type
497 << " Data:\n" << Data(ub->data.data(), ub->data.size());
499 return os;
504 ///////////////////////////////////////////////////////////////////////////////
505 // EmailAddress class
507 std::ostream& operator<<(std::ostream &os, const EmailAddress &msga) {
508 os << msga.Name.c_str() << " <" << msga.Email.c_str() << ">";
509 return os;
512 std::ostream& operator<<(std::ostream &os, const EmailAddressList &elist) {
513 for( EmailAddressList::const_iterator i = elist.begin(); i != elist.end(); ++i ) {
514 if( i != elist.begin() )
515 os << ", ";
516 os << *i;
518 return os;
522 ///////////////////////////////////////////////////////////////////////////////
523 // PostalAddress class
526 // GetLabel
528 /// Format a mailing address into a single string, handling missing fields.
530 std::string PostalAddress::GetLabel() const
532 std::string address = Address1;
533 if( Address2.size() ) {
534 if( address.size() )
535 address += "\n";
536 address += Address2;
538 if( Address3.size() ) {
539 if( address.size() )
540 address += "\n";
541 address += Address3;
543 if( address.size() )
544 address += "\n";
545 if( City.size() )
546 address += City + " ";
547 if( Province.size() )
548 address += Province + " ";
549 if( Country.size() )
550 address += Country;
551 if( address.size() )
552 address += "\n";
553 if( PostalCode.size() )
554 address += PostalCode;
556 return address;
559 void PostalAddress::Clear()
561 Address1.clear();
562 Address2.clear();
563 Address3.clear();
564 City.clear();
565 Province.clear();
566 PostalCode.clear();
567 Country.clear();
570 std::ostream& operator<<(std::ostream &os, const PostalAddress &post) {
571 os << post.GetLabel();
572 return os;
577 ///////////////////////////////////////////////////////////////////////////////
578 // Date class
580 Date::Date(const struct tm *timep)
582 FromTm(timep);
585 void Date::Clear()
587 Month = Day = Year = 0;
590 void Date::ToTm(struct tm *timep) const
592 memset(timep, 0, sizeof(tm));
593 timep->tm_year = Year - 1900;
594 timep->tm_mon = Month;
595 timep->tm_mday = Day;
598 std::string Date::ToYYYYMMDD() const
600 std::ostringstream oss;
601 oss << setw(4) << Year
602 << setw(2) << Month + 1
603 << setw(2) << Day;
604 return oss.str();
608 // ToBBString
610 /// The Blackberry stores Birthday and Anniversary date fields
611 /// with the format: DD/MM/YYYY
613 std::string Date::ToBBString() const
615 std::ostringstream oss;
616 oss << setw(2) << Day
617 << Month + 1
618 << Year;
619 return oss.str();
622 bool Date::FromTm(const struct tm *timep)
624 Year = timep->tm_year + 1900;
625 Month = timep->tm_mon;
626 Day = timep->tm_mday;
627 return true;
630 bool Date::FromBBString(const std::string &str)
632 int m, d, y;
633 if( 3 == sscanf(str.c_str(), "%d/%d/%d", &d, &m, &y) ) {
634 Year = y;
635 Month = m - 1;
636 Day = d;
637 return true;
639 return false;
642 bool Date::FromYYYYMMDD(const std::string &str)
644 int m, d, y;
645 if( 3 == sscanf(str.c_str(), "%4d%2d%2d", &y, &m, &d) ) {
646 Year = y;
647 Month = m - 1;
648 Day = d;
649 return true;
651 return false;
654 std::ostream& operator<<(std::ostream &os, const Date &date)
656 os << setw(4) << date.Year << '/'
657 << setw(2) << date.Month << '/'
658 << setw(2) << date.Day;
659 return os;
664 } // namespace Barry
667 #ifdef __TEST_MODE__
669 #include <iostream>
671 int main(int argc, char *argv[])
673 if( argc < 2 ) {
674 cerr << "Usage: test <datafile>" << endl;
675 return 1;
678 std::vector<Data> array;
679 if( !LoadDataArray(argv[1], array) ) {
680 cerr << "Unable to load file: " << argv[1] << endl;
681 return 1;
684 cout << "Loaded " << array.size() << " items" << endl;
686 for( std::vector<Data>::iterator b = array.begin(), e = array.end();
687 b != e; b++ )
689 Data &d = *b;
690 // cout << d << endl;
691 if( d.GetSize() > 13 && d.GetData()[6] == 0x4f ) {
692 Barry::Contact contact;
693 size_t size = 13;
694 contact.ParseFields(d, size);
695 cout << contact << endl;
696 contact.DumpLdif(cout, "ou=People,dc=example,dc=com");
698 else if( d.GetSize() > 13 && d.GetData()[6] == 0x44 ) {
699 Barry::Calendar cal;
700 size_t size = 13;
701 cal.ParseFields(d, size);
702 cout << cal << endl;
707 #endif