When asked to design an API (Application Programming Interface), most people will approach this different to an internal interface between modules. Both should be treated in a similar manner to ensure that validation is more manageable, and also to ensure that, should the internal interface be accidentally or intentionally exposed, it doesn’t cause a mass panic about making it more “resilient”.

An external API if done well, will:

  • Be consistent. Each function in the API will feel part of the API. Each data structure will feel part of the API. Familiarity with one function or structure should help familiarity with another function or structure.
  • Have a good “unit test” harness. I will cover unit testing in a future blog, but suffice to say, it is good practice to write unit tests before the function being tested. The unit test should precisely check for error cases as well as functionality. The unit test is the first line of defense against incomplete bug fixes as well as ensuring functionality is correct.
  • Make no assumptions about implementation. For example, the implementation may be aware that the platform can only handle up to 64 CPU’s, thus store CPU affinity via a bitmap. However should that very same 64-bit bitmap be exposed via the API? Most likely not. Flags and internal structures are almost always a sore point here, as not only is it making assumptions about the underlying platform, but it can also lead to lazyness on how those values are passed through, with no imposed sanity checking.
  • Get a handle on handles. How you treat these may depend on the types of consumers of the handles, some thoughts about this in another blog.
  • Be careful about API dependencies of the caller. Take for example a C interface that must return a variable-length structure. One approach entertained may be to have that function return a pointer to memory, that the caller must later release by calling “free()” or “LocalFree” or “GlobalFree” or… something. This in general (not just for memory allocation) is not a good idea.
  • Ensure parameters are appropriately checked. Bounds checked. Context checked. This is for both security and robustness. An example security check is bounds checking. An example robustness checking that should not be confused with security checking is looking for a “magic value” within a structure to return an error if the pointer does not point to the correct structure.
  • Version resilience. Look at potential migration of the interface 2-3 iterations away. Structures may change… make then extensible (e.g. version, size, tag). Functions may require additional information, or may return different types of errors. Define in advance what that will look like.

Now given these design constraints for an external API, is there a good reason to dismiss them for an internal API? The biggest excuse is performance. In rare circumstances this may be true. However, consider the following points:

  • An internal function may be made external. Don’t believe me? Look for all the “Nt” prefix functions on MSDN.
  • An internal function may expose a security hole due to the way that internal function is (incorrectly) called. Some of the cases I’ve seen this is where a structure is read from disk, and not bounds checked or format checked. This structure is then retrieved from an untrusted source. The correct code to validate the structure in a version-proof manner is the code that understands and interprets the structure. Do the validation in the correct locality and don’t throw security checking onto the caller. Look at MSDN, and you can see how “strcpy” “strcat” etc. have been depreciated for this reason.
  • Detection of errors. Appropriate robustness checking and validation at multiple levels in the code catch errors sooner, and makes for better code.

Conclusion

Determine your interfaces and treat them in the same manner as external API.