Search code examples
cinterfaceopensslcopydeep-copy

How to Provide a Consistent Interface: Deep-Copy OpenSSL structs or Document Distinct Cases?


I am developing an application that performs performs cryptographic tasks. It uses OpenSSL for those tasks and the interface is similar to that of libcurl. If unfamiliar, it looks like this:

conf_func(handle, ENUM_OPTION, value);

Thus setting the option represented by ENUM_OPTION to value on the instance handle. Reading through the documentation of libcurl it says that it copies all values (except if stated otherwise) and thus you free what you allocate and it frees what it allocates.

I thought this might be a good idea to do with my application. Though it will raise the memory requirement, it certainly makes usage easier. However, I am faced with a problem here. Suppose this code:

X509 *cert = X509_new();
X509 *cert_copy = malloc(sizeof(X509));
memcpy(cert_copy, cert, sizeof(X509));

This will of course create a problem: It is only a shallow copy and freeing will get problematic. After searching, I decided that deep copy is not provided natively by OpenSSL and it may not even be desireable (memory usage).

Thus, I took a different approach: I don't copy but just store the pointer I get passed:

X509 *cert = X509_new();
X509 *cert_copy = cert;
X509_free(cert_copy);

This will reduce the memory footprint and will ease development. On the other hand, this will raise documentation need and special usage awareness: You allocate, but I free for you.

So I thought maybe I should provide this behaviour consistently? But then I get a problem with strings:

char *s = "Some text";
char *s_copy = s;
free(s_copy);

This will not work as s is constant and was not allocated on the heap.

So there's my dilemma: I will have to copy strings but not copy OpenSSL structs. How do I solve this cleanly? Do I document it and rely on the user to correctly use my interface? Or is there something totally obvious I am missing?

Note: Me freeing means that I have a generic destruction function at the end of usage. It will just check if a pointer is NULL and free otherwise.


Solution

  • As you have discovered, there is no correct way to "deep copy" opaque data objects belonging to a library unless that library exposes an API for performing the copying. You have several options:

    1. Don't have the caller pass in objects belonging to the library, but instead just pass the information/parameters needed to create said objects. Then, your code is free to call the library to create the object.

    2. Design your interface contract such that your code is the "owner" of objects passed to it, and the caller must no longer use them after passing them in.

    3. Design your interface contract so that there's a clear sharing of ownership between the caller and your code. This could for example mean the caller is not to modify or free the object while your code still has a reference to it, but that after your code is finished using it, the caller is free to use it again and is responsible for freeing it.

    Which approach is best depends a lot on your application's requirements. Unless memory or performance are critical issues, I would choose the design that wins on simplicity.