1 You want to help? Excellent!
2 -----------------------------
3 Barry currently only supports about 3 database record formats. A
4 database dump reveals over 50 different databases types, and you can help
7 Use the following command to dump the Database Database to stdout:
11 Pick a database you are interested in, and run the following command to
12 dump the protocol to stdout:
17 Protocol Documentation:
18 -----------------------
19 Once you have the raw protocol dump, you need to reverse engineer it.
20 There are a number of places you can find documentation on this.
22 The original source that I started with was the Cassis project docs.
23 You can find them here:
25 http://off.net/cassis/protocol-description.html
27 While these docs are quite helpful, they only cover the serial protocol.
29 The USB protocol is more data-block based. There are no checksums, since
30 USB provides a reliable data transfer. The common protocol headers are
31 struct based, until you get to the variable sized data. (This is also
32 struct based, but requires pointer arithmetic.)
34 See the following Barry source file for protocol structure information:
41 These structures are all low level in nature, and MUST be packed. You
42 will notice that every struct in this header is flagged with the gcc
43 extension "__attribute__ ((packed))", which makes it useful to apply
44 structure to raw protocol data.
46 You can build / deconstruct a protocol packet in this manner:
48 Imagine you have a packet of raw data in the following buffer:
52 You can apply the Packet struct to it directly:
54 struct Packet *packet = (struct Packet *) buff;
56 Looking at the structure, this gives you a lot of fixed-position data:
60 ///////////////////////////////////////////////////////////////////////////////
65 uint16_t socket; // socket ID... 0 is always there
66 uint16_t size; // total size of data packet
73 SequenceCommand sequence;
74 ModeSelectCommand mode;
78 } __attribute__ ((packed)) u;
79 } __attribute__ ((packed));
83 In the case of database records, you are interested in the DBAccess union
89 ///////////////////////////////////////////////////////////////////////////////
90 // Database access command structure
92 // even fragmented packets have a tableCmd
100 OldDBResponse old_response;
101 UploadCommand upload;
102 CommandTableField table[1];
103 OldDBDBRecord old_dbdb;
109 } __attribute__ ((packed)) u;
110 } __attribute__ ((packed));
114 When downloading database records, they all arrive in either a DBResponse
115 or OldDBResponse, depending on the database access command you use.
116 Barry uses the old, serial-compatible command, so you're interested in:
125 } __attribute__ ((packed));
129 This is enough information to get you to the data you need to reverse
130 engineer. That data will be located at:
132 packet->u.db.u.old_response.data[0]
135 So, taking an Address Book record as an example:
137 00000000: 06 00 37 00 40 03 44 00 08 00 80 82 b1 22 00 06 ..7.@.D......"..
138 00000010: 00 20 43 68 72 69 73 00 05 00 20 46 72 65 79 00 . Chris... Frey.
139 00000020: 14 00 01 63 64 66 72 65 79 40 6e 65 74 64 69 72 ...cdfrey@netdir
140 00000030: 65 63 74 2e 63 61 00 ect.ca.
144 06 00 - packet->socket
147 03 - packet->u.db.tableCmd
148 44 - packet->u.db.u.old_response.operation
149 00 - packet->u.db.u.old_response.unknown
150 08 00 - packet->u.db.u.old_response.count
151 80 82 b1 22 - packet->u.db.u.old_response.data[0]...
152 first bytes of record data
154 In the case of Address Book records, the struct is:
156 struct OldContactRecord
160 CommonField field[1];
161 } __attribute__ ((packed));
165 uint16_t size; // including null terminator
175 } __attribute__ ((packed)) u;
176 } __attribute__ ((packed));
179 Which gives the following code:
181 struct OldContactRecord *con = (struct OldContactRecord *)
182 &packet->u.db.u.old_response.data[0];
184 80 82 b1 22 - con->uniqueId
186 06 00 - con->field[0].size
187 20 - con->field[0].type
188 43 68 72... - con->field[0].raw ("Chris..."
192 This is a common theme in the database records. There is a static header
193 (eg. OldContactRecord) followed by a number of fields, which each contains
194 a common field header (eg. CommonField), followed by a variable sized block
195 of data (CommonField's u.raw field). The next field starts size bytes later,
196 so you _cannot_ use con->field[1], con->field[2], etc. You must advance using
197 the size of the data.
201 This should give you enough information to begin hacking on additional
202 database records. Once you have a "Record" header struct, you are ready
203 to begin parsing into an API class.
210 Record API Classes are intended to be consistent, C++-friendly structures
211 for storing the parsed data. You can find them in:
215 These classes may be broken out into multiple files someday.
217 The requirements of these classes are as follows:
219 - no virtual functions
220 - must be valid STL container items
221 - generally contain all public data
222 - provide member functions for:
224 - parsing downloaded data blocks from the BlackBerry
225 - building data blocks for the BlackBerry for uploads
226 - must have absolutely no low level contamination
228 The final point deserves some commentary. When using gcc-specific extensions
229 like __attribute__, and when using pointer arithmetic and pointer casts
230 across types, as is necessary in some of the low level parsing code,
231 special compiler flags are required.
233 Specifically, g++ requires -fno-strict-aliasing to stop it from optimizing
234 away some of the pointer casts and data accesses. This is not desireable
235 in an end-user application, so care must be taken to hide all low level
236 parsing in the library, while exposing the record class structures and
237 member function APIs to the application.
239 Therefore src/record.h (or any new record header you add) must not include
240 src/protostructs.h. Your .cc code may include protostructs.h, but not
241 the header, which will be used by the application.
244 Good luck, and happy hacking!