Removed unneeded call to .c_str() in EmailAddress stream operator
[barry.git] / src / record.cc
blobe20468f138c9efd9d628220b7186905b4415e05b
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, const std::string &str)
103 // include null terminator
104 BuildField(data, size, type, str.c_str(), str.size() + 1);
107 void BuildField(Data &data, size_t &size, uint8_t type,
108 const void *buf, size_t bufsize)
110 // include null terminator
111 size_t fieldsize = COMMON_FIELD_HEADER_SIZE + bufsize;
112 unsigned char *pd = data.GetBuffer(size + fieldsize) + size;
113 CommonField *field = (CommonField *) pd;
115 field->size = htobs(bufsize);
116 field->type = type;
117 memcpy(field->u.raw, buf, bufsize);
119 size += fieldsize;
122 void BuildField(Data &data, size_t &size, const Barry::UnknownField &field)
124 BuildField(data, size, field.type,
125 field.data.raw_data.data(), field.data.raw_data.size());
128 void BuildField(Data &data, size_t &size, uint8_t type, const Barry::Protocol::GroupLink &link)
130 size_t linksize = sizeof(Barry::Protocol::GroupLink);
131 size_t fieldsize = COMMON_FIELD_HEADER_SIZE + linksize;
132 unsigned char *pd = data.GetBuffer(size + fieldsize) + size;
133 CommonField *field = (CommonField *) pd;
135 field->size = htobs(linksize);
136 field->type = type;
137 field->u.link = link;
139 size += fieldsize;
142 std::string ParseFieldString(const Barry::Protocol::CommonField *field)
144 // make no assumptions here, and pass the full size in as
145 // the maxlen, even though 99% of the time, it will be a null...
146 // this function can be used by non-null terminated strings as well
147 return ParseFieldString(field->u.raw, btohs(field->size));
150 std::string ParseFieldString(const void *data, uint16_t maxlen)
152 const char *str = (const char *)data;
154 // find last non-null character, since some fields
155 // can have multiple null terminators
156 while( maxlen && str[maxlen-1] == 0 )
157 maxlen--;
159 return std::string(str, maxlen);
163 ///////////////////////////////////////////////////////////////////////////////
164 // CommandTable class
166 CommandTable::CommandTable()
170 CommandTable::~CommandTable()
174 const unsigned char* CommandTable::ParseField(const unsigned char *begin,
175 const unsigned char *end)
177 // check if there is enough data for a header
178 const unsigned char *headend = begin + sizeof(CommandTableField);
179 if( headend > end )
180 return headend;
182 const CommandTableField *field = (const CommandTableField *) begin;
184 // advance and check size
185 begin += COMMAND_FIELD_HEADER_SIZE + field->size; // size is byte
186 if( begin > end ) // if begin==end, we are ok
187 return begin;
189 if( !field->size ) // if field has no size, something's up
190 return begin;
192 Command command;
193 command.Code = field->code;
194 command.Name.assign((const char *)field->name, field->size);
195 Commands.push_back(command);
196 return begin;
199 void CommandTable::Parse(const Data &data, size_t offset)
201 if( offset >= data.GetSize() )
202 return;
204 const unsigned char *begin = data.GetData() + offset;
205 const unsigned char *end = data.GetData() + data.GetSize();
207 while( begin < end )
208 begin = ParseField(begin, end);
211 void CommandTable::Clear()
213 Commands.clear();
216 unsigned int CommandTable::GetCommand(const std::string &name) const
218 CommandArrayType::const_iterator b = Commands.begin();
219 for( ; b != Commands.end(); b++ )
220 if( b->Name == name )
221 return b->Code;
222 return 0;
225 void CommandTable::Dump(std::ostream &os) const
227 CommandArrayType::const_iterator b = Commands.begin();
228 os << "Command table:\n";
229 for( ; b != Commands.end(); b++ ) {
230 os << " Command: 0x" << setbase(16) << b->Code
231 << " '" << b->Name << "'\n";
237 ///////////////////////////////////////////////////////////////////////////////
238 // RecordStateTable class
240 RecordStateTable::RecordStateTable()
241 : m_LastNewRecordId(1)
245 RecordStateTable::~RecordStateTable()
249 const unsigned char* RecordStateTable::ParseField(const unsigned char *begin,
250 const unsigned char *end)
252 const RecordStateTableField *field = (const RecordStateTableField *) begin;
254 // advance and check size
255 begin += sizeof(RecordStateTableField);
256 if( begin > end ) // if begin==end, we are ok
257 return begin;
259 State state;
260 state.Index = btohs(field->index);
261 state.RecordId = btohl(field->uniqueId);
262 state.Dirty = (field->flags & BARRY_RSTF_DIRTY) != 0;
263 state.RecType = field->rectype;
264 state.Unknown2.assign((const char*)field->unknown2, sizeof(field->unknown2));
265 StateMap[state.Index] = state;
267 return begin;
270 void RecordStateTable::Parse(const Data &data)
272 size_t offset = 12; // skipping the unknown 2 bytes at start
274 if( offset >= data.GetSize() )
275 return;
277 const unsigned char *begin = data.GetData() + offset;
278 const unsigned char *end = data.GetData() + data.GetSize();
280 while( begin < end )
281 begin = ParseField(begin, end);
284 void RecordStateTable::Clear()
286 StateMap.clear();
287 m_LastNewRecordId = 1;
290 // Searches the StateMap table for RecordId, and returns the "index"
291 // in the map if found. Returns true if found, false if not.
292 // pFoundIndex can be null if only the existence of the index is desired
293 bool RecordStateTable::GetIndex(uint32_t RecordId, IndexType *pFoundIndex) const
295 StateMapType::const_iterator i = StateMap.begin();
296 for( ; i != StateMap.end(); ++i ) {
297 if( i->second.RecordId == RecordId ) {
298 if( pFoundIndex )
299 *pFoundIndex = i->first;
300 return true;
303 return false;
306 // Generate a new RecordId that is not in the state table.
307 // Starts at 1 and keeps incrementing until a free one is found.
308 uint32_t RecordStateTable::MakeNewRecordId() const
310 // start with next Id
311 m_LastNewRecordId++;
313 // make sure it doesn't already exist
314 StateMapType::const_iterator i = StateMap.begin();
315 while( i != StateMap.end() ) {
316 if( m_LastNewRecordId == i->second.RecordId ) {
317 m_LastNewRecordId++; // try again
318 i = StateMap.begin(); // start over
320 else {
321 ++i; // next State
324 return m_LastNewRecordId;
327 void RecordStateTable::Dump(std::ostream &os) const
329 ios::fmtflags oldflags = os.setf(ios::right);
330 char fill = os.fill(' ');
331 bool bPrintAscii = Data::PrintAscii();
332 Data::PrintAscii(false);
334 os << " Index RecordId Dirty RecType" << endl;
335 os << "------- ---------- ----- -------" << endl;
337 StateMapType::const_iterator b, e = StateMap.end();
338 for( b = StateMap.begin(); b != e ; ++b ) {
339 const State &state = b->second;
341 os.fill(' ');
342 os << setbase(10) << setw(7) << state.Index;
343 os << " 0x" << setbase(16) << setfill('0') << setw(8) << state.RecordId;
344 os << " " << setfill(' ') << setw(5) << (state.Dirty ? "yes" : "no");
345 os << " 0x" << setbase(16) << setfill('0') << setw(2) << state.RecType;
346 os << " " << Data(state.Unknown2.data(), state.Unknown2.size());
349 // cleanup the stream
350 os.flags(oldflags);
351 os.fill(fill);
352 Data::PrintAscii(bPrintAscii);
357 ///////////////////////////////////////////////////////////////////////////////
358 // DatabaseDatabase class
360 DatabaseDatabase::DatabaseDatabase()
364 DatabaseDatabase::~DatabaseDatabase()
368 template <class RecordType, class FieldType>
369 void DatabaseDatabase::ParseRec(const RecordType &rec, const unsigned char *end)
373 template <class FieldType>
374 const unsigned char* DatabaseDatabase::ParseField(const unsigned char *begin,
375 const unsigned char *end)
377 // check if there is enough data for a header
378 const unsigned char *headend = begin + sizeof(FieldType);
379 if( headend > end )
380 return headend;
382 // get our header
383 const FieldType *field = (const FieldType *) begin;
385 // advance and check size
386 begin += sizeof(FieldType) - sizeof(field->name) + ConvertHtoB(field->nameSize);
387 if( begin > end ) // if begin==end, we are ok
388 return begin;
390 if( !ConvertHtoB(field->nameSize) ) // if field has no size, something's up
391 return begin;
393 Database db;
394 db.Number = ConvertHtoB(field->dbNumber);
395 db.RecordCount = ConvertHtoB(field->dbRecordCount);
396 db.Name.assign((const char *)field->name, ConvertHtoB(field->nameSize) - 1);
397 Databases.push_back(db);
398 return begin;
401 void DatabaseDatabase::Parse(const Data &data)
403 // check size to make sure we have up to the DBAccess operation byte
404 if( data.GetSize() < (SB_PACKET_DBACCESS_HEADER_SIZE + 1) )
405 return;
407 MAKE_PACKET(pack, data);
408 const unsigned char *begin = 0;
409 const unsigned char *end = data.GetData() + data.GetSize();
411 switch( pack->u.db.u.response.operation )
413 case SB_DBOP_GET_DBDB:
414 // using the new protocol
415 if( data.GetSize() > SB_PACKET_DBDB_HEADER_SIZE ) {
416 begin = (const unsigned char *)
417 &pack->u.db.u.response.u.dbdb.field[0];
419 // this while check is ok, since ParseField checks
420 // for header size
421 while( begin < end )
422 begin = ParseField<DBDBField>(begin, end);
424 else
425 dout("DatabaseDatabase: not enough data for parsing");
426 break;
428 case SB_DBOP_OLD_GET_DBDB:
429 // using the old protocol
430 if( data.GetSize() > SB_PACKET_OLD_DBDB_HEADER_SIZE ) {
431 begin = (const unsigned char *)
432 &pack->u.db.u.response.u.old_dbdb.field[0];
434 // this while check is ok, since ParseField checks
435 // for header size
436 while( begin < end )
437 begin = ParseField<OldDBDBField>(begin, end);
439 else
440 dout("DatabaseDatabase: not enough data for parsing");
441 break;
443 default:
444 // unknown protocol
445 dout("Unknown protocol");
446 break;
452 void DatabaseDatabase::Clear()
454 Databases.clear();
457 bool DatabaseDatabase::GetDBNumber(const std::string &name,
458 unsigned int &number) const
460 DatabaseArrayType::const_iterator b = Databases.begin();
461 for( ; b != Databases.end(); b++ )
462 if( b->Name == name ) {
463 number = b->Number;
464 return true;
466 return false;
469 bool DatabaseDatabase::GetDBName(unsigned int number,
470 std::string &name) const
472 DatabaseArrayType::const_iterator b = Databases.begin();
473 for( ; b != Databases.end(); b++ )
474 if( b->Number == number ) {
475 name = b->Name;
476 return true;
478 return false;
481 void DatabaseDatabase::Dump(std::ostream &os) const
483 DatabaseArrayType::const_iterator b = Databases.begin();
484 os << "Database database:\n";
485 for( ; b != Databases.end(); b++ ) {
486 os << " Database: 0x" << setbase(16) << b->Number
487 << " '" << b->Name << "' (records: "
488 << setbase(10) << b->RecordCount << ")\n";
493 std::ostream& operator<< (std::ostream &os, const std::vector<UnknownField> &unknowns)
495 std::vector<UnknownField>::const_iterator
496 ub = unknowns.begin(), ue = unknowns.end();
497 if( ub != ue )
498 os << " Unknowns:\n";
499 for( ; ub != ue; ub++ ) {
500 os << " Type: 0x" << setbase(16)
501 << (unsigned int) ub->type
502 << " Data:\n" << Data(ub->data.data(), ub->data.size());
504 return os;
509 ///////////////////////////////////////////////////////////////////////////////
510 // EmailAddress class
512 std::ostream& operator<<(std::ostream &os, const EmailAddress &msga) {
513 os << msga.Name << " <" << msga.Email << ">";
514 return os;
517 std::ostream& operator<<(std::ostream &os, const EmailAddressList &elist) {
518 for( EmailAddressList::const_iterator i = elist.begin(); i != elist.end(); ++i ) {
519 if( i != elist.begin() )
520 os << ", ";
521 os << *i;
523 return os;
527 ///////////////////////////////////////////////////////////////////////////////
528 // PostalAddress class
531 // GetLabel
533 /// Format a mailing address into a single string, handling missing fields.
535 std::string PostalAddress::GetLabel() const
537 std::string address = Address1;
538 if( Address2.size() ) {
539 if( address.size() )
540 address += "\n";
541 address += Address2;
543 if( Address3.size() ) {
544 if( address.size() )
545 address += "\n";
546 address += Address3;
548 if( address.size() )
549 address += "\n";
550 if( City.size() )
551 address += City + " ";
552 if( Province.size() )
553 address += Province + " ";
554 if( Country.size() )
555 address += Country;
556 if( address.size() )
557 address += "\n";
558 if( PostalCode.size() )
559 address += PostalCode;
561 return address;
564 void PostalAddress::Clear()
566 Address1.clear();
567 Address2.clear();
568 Address3.clear();
569 City.clear();
570 Province.clear();
571 PostalCode.clear();
572 Country.clear();
575 std::ostream& operator<<(std::ostream &os, const PostalAddress &post) {
576 os << post.GetLabel();
577 return os;
582 ///////////////////////////////////////////////////////////////////////////////
583 // Date class
585 Date::Date(const struct tm *timep)
587 FromTm(timep);
590 void Date::Clear()
592 Month = Day = Year = 0;
595 void Date::ToTm(struct tm *timep) const
597 memset(timep, 0, sizeof(tm));
598 timep->tm_year = Year - 1900;
599 timep->tm_mon = Month;
600 timep->tm_mday = Day;
603 std::string Date::ToYYYYMMDD() const
605 std::ostringstream oss;
606 oss << setw(4) << Year
607 << setw(2) << Month + 1
608 << setw(2) << Day;
609 return oss.str();
613 // ToBBString
615 /// The Blackberry stores Birthday and Anniversary date fields
616 /// with the format: DD/MM/YYYY
618 std::string Date::ToBBString() const
620 std::ostringstream oss;
621 oss << setw(2) << Day
622 << Month + 1
623 << Year;
624 return oss.str();
627 bool Date::FromTm(const struct tm *timep)
629 Year = timep->tm_year + 1900;
630 Month = timep->tm_mon;
631 Day = timep->tm_mday;
632 return true;
635 bool Date::FromBBString(const std::string &str)
637 int m, d, y;
638 if( 3 == sscanf(str.c_str(), "%d/%d/%d", &d, &m, &y) ) {
639 Year = y;
640 Month = m - 1;
641 Day = d;
642 return true;
644 return false;
647 bool Date::FromYYYYMMDD(const std::string &str)
649 int m, d, y;
650 if( 3 == sscanf(str.c_str(), "%4d%2d%2d", &y, &m, &d) ) {
651 Year = y;
652 Month = m - 1;
653 Day = d;
654 return true;
656 return false;
659 std::ostream& operator<<(std::ostream &os, const Date &date)
661 os << setw(4) << date.Year << '/'
662 << setw(2) << date.Month << '/'
663 << setw(2) << date.Day;
664 return os;
669 } // namespace Barry
672 #ifdef __TEST_MODE__
674 #include <iostream>
676 int main(int argc, char *argv[])
678 if( argc < 2 ) {
679 cerr << "Usage: test <datafile>" << endl;
680 return 1;
683 std::vector<Data> array;
684 if( !LoadDataArray(argv[1], array) ) {
685 cerr << "Unable to load file: " << argv[1] << endl;
686 return 1;
689 cout << "Loaded " << array.size() << " items" << endl;
691 for( std::vector<Data>::iterator b = array.begin(), e = array.end();
692 b != e; b++ )
694 Data &d = *b;
695 // cout << d << endl;
696 if( d.GetSize() > 13 && d.GetData()[6] == 0x4f ) {
697 Barry::Contact contact;
698 size_t size = 13;
699 contact.ParseFields(d, size);
700 cout << contact << endl;
701 contact.DumpLdif(cout, "ou=People,dc=example,dc=com");
703 else if( d.GetSize() > 13 && d.GetData()[6] == 0x44 ) {
704 Barry::Calendar cal;
705 size_t size = 13;
706 cal.ParseFields(d, size);
707 cout << cal << endl;
712 #endif