[PATCH] Revisions requested

Jef Driesen jef at libdivecomputer.org
Mon Nov 17 03:14:13 PST 2014


On 2014-11-17 01:17, John Van Ostrand wrote:
> On Sunday, November 16, 2014, Jef Driesen <jef at libdivecomputer.org> 
> wrote:
>> There is a problem with how you pass the data to the dive callback
>> function. I didn't notice this the first time, but you pass a pointer 
>> to
>> the cochran_data_t structure to the callback function. That's not how 
>> this
>> is supposed to work. You need to extract the raw binary data of each 
>> dive,
>> and then pass that to the callback function. What you do now is pass 
>> the
>> data of all dives, with a couple of fields pointing to the dive of
>> interest. That's wrong and needs to change.
>> 
>> The cochran_data_t alone is already a problem here, because it 
>> contains
>> pointers. The dive data is supposed to be a simple byte array which 
>> can be
>> serialized as-is. Remember, libdivecomputer has absolutely no control 
>> over
>> how or when the application uses the data. For example the application 
>> can
>> copy the dives, and only parse them after the download has finished. 
>> In
>> that case the device handle will already have been closed when parsing 
>> the
>> data. Thus anything in your structure pointing to data in that handle 
>> will
>> have been freed, and unexpected things will happen. With a self 
>> contained
>> blob of binary data that's not a problem. You can copy it, write it to
>> disk, etc with no problems.
> 
> I'm a little confused by what is expected. I understand that an 
> application
> may choose to save the dive data and not parse so I can fix that. The 
> only
> way that makes sense to me is to create a blob in malloc'ed space and 
> hope
> the application frees it. That doesn't really seem to make sense to me. 
> The
> blob will have to contain a size, offset into the data (there is 
> pre-dive
> data) and some information about the type of data it is so that the 
> common
> parser code can deal with it properly. The app won't necessarily know 
> the
> structure of the blob or its length so what it can do with it will be
> limited.
> 
> Subsurface hasn't complained about freeing memory so there's one
> application that would leak memory.

I'll explain the principle with some trivial examples. A typical 
implementation of the foreach function will look like this:

dc_status_t
xxx_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, 
void *userdata)
{
     unsigned char dive[SIZE_DIVE];
     unsigned char fingerprint[SIZE_FP];

     for (i = 0; i < ndives; ++i) {
         /* Download the i'th dive here and pass it to the callback 
function .*/
         callback (dive, sizeof(dive), fingerprint, sizeof(fingerprint), 
userdata))
     }
}

Of course this is overly simplified. In practice, dives are not fixed 
length, and the dive buffer will be dynamically allocated, but that 
doesn't matter here. What does matter, is that the memory buffer is 
clearly owned by the foreach function. As long as you're inside the 
callback function, the application can freely access the data. But once 
the callback function return, you can no longer assume the data is still 
accessible. The implementation of the foreach function may re-use the 
same buffer for the other dives, or even free the buffer.

So if you want to use the data after the callback function has returned, 
then you HAVE to copy the data. For example an application might not 
parse the dives immediately, but store them in a list, and only parse 
them after the download has finished:

static int
dive_cb (const unsigned char *data, unsigned int size, const unsigned 
char *fingerprint, unsigned int fsize, void *userdata)
{
     list *dives = userdata;

     /* Copy the dive data into a new buffer. */
     dc_buffer_t *buffer = NULL;
     dc_buffer_new(&buffer, size);
     dc_buffer_append(buffer, data, size);

     /* Append the dive to the list. */
     dives.add(buffer);
}

int
main (int argc, char *argv[])
{
     list *dives = ...;

     /* Download the dives. */
     dc_device_t *device = NULL;
     dc_device_open (&device, ...);
     dc_device_foreach(device, dive_cb, dives);
     dc_device_close(device);

     /* Parse all dives. */
     for (i = 0; i < dives.count; ++i){
         /* Do something with the i'th dive here. */
     }
}

With a simple byte array, this will work just fine, because copying a 
byte array is well defined. But for structures containing pointers, 
that's not the case. If you copy the structure, the pointers will still 
point to their original data. But that data may no longer exist.

The important message here is that the data is owned by the foreach 
function. The application should certainly not try to free it. If the 
data is needed after the callback function returns, then the application 
needs to copy it.

The reason why you don't notice any problems with subsurface, is because 
it parses the dive inside the callback function.



Regarding your question about the data format of the blob. The 
application doesn't really need to know anything about the format. The 
parser will take care of that, by translating the raw data into 
something more practical.

Based on your documentation in the libdivecomputer wiki (which is 
excellent btw! You did a really nice job there!), each dive has a fixed 
size (256 bytes) header containing the logbook summary, and a variable 
length part with the sample data. That's exactly the same structure as 
used in the Oceanics. There, I simply concatenate those two parts, and 
use that as the raw data format for the dives. I suggest you do the 
same. The header is fixed size, so you don't even need to include a 
length.

The fact that the logbook header and profile data are stored in separate 
memory areas is just an implementation detail. The dive computer could 
equally well store it as one single blob. I assume that the reason 
behind using two different memory areas is the following. The length of 
the dive profiles is variable, and depends on many factors, like the 
sample rate, dive duration, etc. Thus the amount of profiles that can be 
stored isn't fixed. By storing the logbook summary separately, the dive 
computer can always display basic information for a fixed pre-defined 
number of dives. Even if the profile data for those dives has already 
been overwritten. (If you download such a dive where the profile data is 
no longer available, you just pass only the logbook data to the callback 
function.)


If the data format is different for the different models supported by 
the backend, and there is no identification data stored in the dive data 
itself, then you proceed as follows. First of all, extend the 
cochran_commander_parser_create() function with an extra parameter to 
pass the model number. Store the model number internally into the parser 
structure, and then you can use it when needed, just like you do now. 
Next, you need some way to pass the model number from the device to the 
parser. That is done as follows. In your xxx_device_foreach 
implementation, you emit a DC_EVENT_DEVINFO event containing the model 
number (and serial number if available). This info gets cached 
internally (see the device_event_emit() function), and in the 
dc_parser_new() function you can pass the model number to your 
cochran_commander_parser_create() function.

It looks like you have only two variants (e.g. commander and emc). If 
there is no model number in the data, you just assign artificial model 
numbers (e.g. commander=0 and emc=1). For completeness, you use the same 
numbers in the table in the descriptor.c file, like this:

     {"Cochran", "Commander",    DC_FAMILY_COCHRAN_COMMANDER, 0},
     {"Cochran", "EMC-16",       DC_FAMILY_COCHRAN_COMMANDER, 1},
     {"Cochran", "EMC-20H",      DC_FAMILY_COCHRAN_COMMANDER, 1},

Note that it's not a problem to list multiple models with the same model 
number, as long as the EMC-16 and EMC-20H share the same data format. If 
you want to be able to tell apart the individual models, then you'll 
need to assign different numbers to each of them. You can use the upper 
16 bits for the main model, and the the lower 16bits for the individual 
models, as you already did with the COCHRAN_MODEL_XXX constants. As long 
as the same numbers are used everywhere, you can use whatever number 
work best. They are just artificial numbers :-) (For model numbers that 
are embedded in the data, that's of course another story.)

If you have further questions, just ask! I'll try to help where I can.

Jef


More information about the devel mailing list