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
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.
28 #include "record-internal.h"
30 #include "protostructs.h"
41 #define __DEBUG_MODE__
45 using namespace Barry::Protocol
;
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
);
61 field
->u
.min1900
= time2min(t
);
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
)
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
);
80 memcpy(field
->u
.raw
, &c
, strsize
);
85 void BuildField(Data
&data
, size_t &size
, uint8_t type
, uint16_t value
)
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
);
95 uint16_t store
= htobs(value
);
96 memcpy(field
->u
.raw
, &store
, strsize
);
101 void BuildField(Data
&data
, size_t &size
, uint8_t type
, uint32_t value
)
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
);
111 uint32_t store
= htobs(value
);
112 memcpy(field
->u
.raw
, &store
, strsize
);
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
);
133 memcpy(field
->u
.raw
, buf
, bufsize
);
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
);
153 field
->u
.link
= link
;
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 )
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
);
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
205 if( !field
->size
) // if field has no size, something's up
209 command
.Code
= field
->code
;
210 command
.Name
.assign((const char *)field
->name
, field
->size
);
211 Commands
.push_back(command
);
215 void CommandTable::Parse(const Data
&data
, size_t offset
)
217 if( offset
>= data
.GetSize() )
220 const unsigned char *begin
= data
.GetData() + offset
;
221 const unsigned char *end
= data
.GetData() + data
.GetSize();
224 begin
= ParseField(begin
, end
);
227 void CommandTable::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
)
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
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
;
286 void RecordStateTable::Parse(const Data
&data
)
288 size_t offset
= 12; // skipping the unknown 2 bytes at start
290 if( offset
>= data
.GetSize() )
293 const unsigned char *begin
= data
.GetData() + offset
;
294 const unsigned char *end
= data
.GetData() + data
.GetSize();
297 begin
= ParseField(begin
, end
);
300 void RecordStateTable::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
) {
315 *pFoundIndex
= i
->first
;
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
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
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
;
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
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
);
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
406 if( !ConvertHtoB(field
->nameSize
) ) // if field has no size, something's up
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
);
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) )
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
438 begin
= ParseField
<DBDBField
>(begin
, end
);
441 dout("DatabaseDatabase: not enough data for parsing");
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
453 begin
= ParseField
<OldDBDBField
>(begin
, end
);
456 dout("DatabaseDatabase: not enough data for parsing");
461 dout("Unknown protocol");
468 void DatabaseDatabase::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
) {
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
) {
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();
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());
525 ///////////////////////////////////////////////////////////////////////////////
526 // EmailAddress class
528 std::ostream
& operator<<(std::ostream
&os
, const EmailAddress
&msga
) {
529 os
<< msga
.Name
<< " <" << msga
.Email
<< ">";
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() )
543 ///////////////////////////////////////////////////////////////////////////////
544 // PostalAddress class
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() ) {
559 if( Address3
.size() ) {
567 address
+= City
+ " ";
568 if( Province
.size() )
569 address
+= Province
+ " ";
574 if( PostalCode
.size() )
575 address
+= PostalCode
;
580 void PostalAddress::Clear()
591 std::ostream
& operator<<(std::ostream
&os
, const PostalAddress
&post
) {
592 os
<< post
.GetLabel();
598 ///////////////////////////////////////////////////////////////////////////////
601 Date::Date(const struct tm
*timep
)
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
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
643 bool Date::FromTm(const struct tm
*timep
)
645 Year
= timep
->tm_year
+ 1900;
646 Month
= timep
->tm_mon
;
647 Day
= timep
->tm_mday
;
651 bool Date::FromBBString(const std::string
&str
)
654 if( 3 == sscanf(str
.c_str(), "%d/%d/%d", &d
, &m
, &y
) ) {
663 bool Date::FromYYYYMMDD(const std::string
&str
)
666 if( 3 == sscanf(str
.c_str(), "%4d%2d%2d", &y
, &m
, &d
) ) {
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
;
692 int main(int argc
, char *argv
[])
695 cerr
<< "Usage: test <datafile>" << endl
;
699 std::vector
<Data
> array
;
700 if( !LoadDataArray(argv
[1], array
) ) {
701 cerr
<< "Unable to load file: " << argv
[1] << endl
;
705 cout
<< "Loaded " << array
.size() << " items" << endl
;
707 for( std::vector
<Data
>::iterator b
= array
.begin(), e
= array
.end();
711 // cout << d << endl;
712 if( d
.GetSize() > 13 && d
.GetData()[6] == 0x4f ) {
713 Barry::Contact contact
;
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 ) {
722 cal
.ParseFields(d
, size
);