-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
39 changed files
with
8,841 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
*.o | ||
*.swp | ||
*.gcov | ||
.directory | ||
/.sconsign.dblite | ||
/tags | ||
/build | ||
/docs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# Doxyfile 1.8.3.1 | ||
|
||
PROJECT_NAME = "MPack" | ||
PROJECT_BRIEF = "A C encoding/decoding library for the MessagePack serialization format." | ||
|
||
# This is the only place where the version number is defined. | ||
# The packaging script pulls the version number from here. | ||
PROJECT_NUMBER = 0.1 | ||
|
||
INPUT = \ | ||
README.md \ | ||
src/mpack/mpack-platform.h \ | ||
src/mpack/mpack-common.h \ | ||
src/mpack/mpack-reader.h \ | ||
src/mpack/mpack-writer.h \ | ||
src/mpack/mpack-expect.h \ | ||
src/mpack/mpack-node.h \ | ||
src/mpack/mpack.h | ||
|
||
USE_MDFILE_AS_MAINPAGE = README.md | ||
HTML_OUTPUT = docs | ||
GENERATE_LATEX = no | ||
STRIP_FROM_PATH = . ./src | ||
|
||
PREDEFINED = \ | ||
MPACK_READER=1 \ | ||
MPACK_WRITER=1 \ | ||
MPACK_EXPECT=1 \ | ||
MPACK_NODE=1 \ | ||
MPACK_STDLIB=1 \ | ||
MPACK_STDIO=1 \ | ||
MPACK_SETJMP=1 \ | ||
MPACK_TRACKING=1 \ | ||
MPACK_MALLOC=malloc \ | ||
MPACK_DEBUG=1 | ||
|
||
MARKDOWN_SUPPORT = YES | ||
JAVADOC_AUTOBRIEF = YES | ||
ALWAYS_DETAILED_SEC = YES | ||
SORT_BRIEF_DOCS = YES | ||
|
||
OPTIMIZE_OUTPUT_FOR_C = YES | ||
INLINE_SIMPLE_STRUCTS = YES | ||
TYPEDEF_HIDES_STRUCT = YES | ||
HIDE_UNDOC_MEMBERS = YES | ||
EXTRACT_STATIC = YES | ||
|
||
QUIET = YES | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,149 @@ | ||
# mpack | ||
MPack - A C encoder/decoder for the MessagePack serialization format | ||
|
||
## Introduction | ||
|
||
MPack is a C implementation of an encoder and decoder for the [MessagePack](http://msgpack.org/) serialization format. It is intended to be: | ||
|
||
* Simple and easy to use | ||
* Secure against untrusted data | ||
* Lightweight, suitable for embedded | ||
* Helpful for debugging | ||
* Extensively documented | ||
|
||
The core of MPack contains a buffered reader and writer with a custom callback to fill or flush the buffer. Helper functions can be enabled to read values of expected type, to work with files, to allocate strings automatically, to check UTF-8 encoding, and more. The MPack featureset can be configured at compile-time to set which features, components and debug checks are compiled, and what dependencies are available. | ||
|
||
The MPack code is small enough to be embedded directly into your codebase. The easiest way to use it is to download the amalgamation package and insert the source files directly into your project. Copy `mpack.h` and `mpack.c` into to your codebase, and copy `mpack-config.h.sample` as `mpack-config.h`. You can use the defaults or edit it if you'd like to customize the MPack featureset. | ||
|
||
MPack is written in the portable intersection of C99 and C++. In other words, it's written in C99, but if you are stuck using a certain popular compiler from a certain unpopular vendor that refuses to support C99, you can compile it as C++ instead. (The headers should also be C89-clean, provided compatible definitions of `bool`, `inline`, `stdint.h` and friends are available, and `const` is defined away.) | ||
|
||
*NOTE: MPack is beta software under development. There are still some TODOs in the codebase, some security issues to fix, some MessagePack 1.0/1.1 compatibility and interoperability issues to sort out, some test suite portability issues to fix, and there is only around 45% unit test coverage. The API for initializing custom I/O readers and writers is likely to change as well.* | ||
|
||
## The Node Reader API | ||
|
||
The Node API parses a chunk of MessagePack data into an immutable tree of dynamically-typed nodes. A series of helper functions can be used to extract data of specific types from each node. | ||
|
||
// parse a data buffer into a node tree | ||
mpack_tree_t tree; | ||
mpack_tree_init(&tree, buffer, count); | ||
mpack_node_t* root = mpack_tree_root(&tree); | ||
|
||
// extract the example data on the msgpack homepage | ||
bool compact = mpack_node_bool(mpack_node_map_cstr(root, "compact")); | ||
int schema = mpack_node_i32(mpack_node_map_cstr(root, "schema")); | ||
|
||
// clean up and check for errors | ||
if (mpack_tree_destroy(tree) != mpack_ok) { | ||
fprintf(stderr, "An error occurred decoding the data!\n"); | ||
return; | ||
} | ||
|
||
Note that no additional error handling is needed in the above code. If map keys are missing or if nodes are not in the expected types, special "nil" nodes and false/zero values are returned and the tree is placed in an error state. An error check is only needed before using the data. Alternatively, the tree can be configured to longjmp in such cases if a handler is set. | ||
|
||
## The Static Write API | ||
|
||
The MPack Write API encodes structured data of a fixed (hardcoded) schema to MessagePack. | ||
|
||
// encode to memory buffer | ||
char buffer[256]; | ||
mpack_writer_t writer; | ||
mpack_writer_init_buffer(&writer, buffer, sizeof(buffer)); | ||
|
||
// write the example on the msgpack homepage | ||
mpack_start_map(&writer, 2); | ||
mpack_write_cstr(&writer, "compact"); | ||
mpack_write_bool(&writer, true); | ||
mpack_write_cstr(&writer, "schema"); | ||
mpack_write_uint(&writer, 0); | ||
mpack_finish_map(&writer); | ||
|
||
// clean up | ||
size_t count = mpack_writer_buffer_used(&writer); | ||
if (mpack_writer_destroy(&writer) != mpack_ok) { | ||
fprintf(stderr, "An error occurred encoding the data!\n"); | ||
return; | ||
} | ||
|
||
In the above example, we encode only to an in-memory buffer. The writer can optionally be provided with a flush function (such as a file or socket write function) to call when the buffer is full or when writing is done. | ||
|
||
If any error occurs, the writer is placed in an error state and can optionally longjmp if a handler is set. The writer will flag an error if too much data is written, if the wrong number of elements are written, if the data could not be flushed, etc. | ||
|
||
The MPack writer API is imperative in nature. Since it's easy to incorrectly compose structured data with an imperative API, MPack provides a debug mode which tracks reads and writes of compound types to ensure that the correct number of elements and bytes were read and written. This allows for rapid development of high-performance applications that use MessagePack. In release mode, these checks are eliminated for maximum performance. | ||
|
||
## The Expect API | ||
|
||
The Expect API is used to imperatively parse data of a fixed (hardcoded) schema. It is useful when parsing very large mpack files or parsing in memory-constrained environments. The below example demonstrates shows reading from a file and handling errors with longjmp. | ||
|
||
// open a file on disk | ||
FILE* file = fopen("example.mp"); | ||
if (file == NULL) { | ||
fprintf(stderr, "An error occurred opening the file!\n"); | ||
return; | ||
} | ||
|
||
// initialize the reader using fread() and a stack-allocated buffer | ||
mpack_reader_t reader; | ||
mpack_reader_init_stack(&reader, mpack_fread, file); | ||
|
||
// catch errors with longjmp | ||
if (MPACK_READER_SETJMP(&reader)) { | ||
fprintf(stderr, "An error occurred decoding the file!\n"); | ||
mpack_reader_destroy(&reader); | ||
fclose(file); | ||
return; | ||
} | ||
|
||
// we expect the file to contain the example on the msgpack homepage | ||
mpack_expect_map_match(&reader, 2); | ||
mpack_expect_cstr_match(&reader, "compact"); | ||
bool compact = mpack_expect_bool(&reader); | ||
mpack_expect_cstr_match(&reader, "schema"); | ||
int schema = mpack_expect_int(&reader); | ||
mpack_done_map(&reader); | ||
|
||
// clean up | ||
mpack_reader_destroy(&reader); | ||
fclose(file); | ||
|
||
// verify and use the data | ||
if (!compact || schema != 0) { | ||
fprintf(stderr, "The file data is invalid!\n"); | ||
return; | ||
} | ||
|
||
The example expects the map elements to be laid out in a specific order. If you expect map keys to be re-arranged (for example if the data is converted from JSON or encoded from an unordered map), something like this may be more appropriate: | ||
|
||
bool compact = false; | ||
int schema = -1; | ||
|
||
for (int i = mpack_expect_map(&reader); i > 0; --i) { | ||
char key[20]; | ||
mpack_expect_cstr(&reader, key, sizeof(key)); | ||
|
||
if (strcmp(key, "compact") == 0) | ||
compact = mpack_expect_bool(&reader); | ||
else if (strcmp(key, "schema") == 0) | ||
schema = mpack_expect_int(&reader); | ||
else | ||
mpack_reader_flag_error(&reader, mpack_error_data); | ||
} | ||
mpack_done_map(&reader); | ||
|
||
## Why Not Just Use JSON? | ||
|
||
Conceptually, MessagePack stores data similarly to JSON: they are both composed of simple values such as numbers and strings, stored hierarchically in maps and arrays. So why not just use JSON instead? The main reason is that JSON is designed to be human-readable, so it is not as efficient as a binary serialization format: | ||
|
||
- Compound types such as strings, maps and arrays are delimited, so appropriate storage cannot be allocated upfront. The whole object must be parsed to determine its size. | ||
|
||
- Strings are not stored in their native encoding. They cannot contain quotes or special characters, so they must be escaped when written and converted back when read. | ||
|
||
- Numbers are particularly inefficient (especially when parsing back floats), making JSON inappropriate as a base format for structured data that contains lots of numbers. | ||
|
||
The above issues greatly increase the complexity of the decoder. Full-featured JSON decoders are quite large, and minimal decoders tend to leave out such features as string unescaping and float parsing, instead leaving these up to the user or platform. This can lead to hard-to-find and/or platform-specific bugs. This also significantly decreases performance, making JSON unattractive for use in applications such as mobile games. | ||
|
||
While the space inefficiencies of JSON can be partially mitigated through minification and compression, the performance inefficiencies cannot. More importantly, if you are minifying and compressing the data, then why use a human-readable format in the first place? | ||
|
||
## Running the Unit Tests | ||
|
||
The MPack build process does not build MPack into a library; it is used to build and run the unit tests. You do not need to build MPack or the unit testing suite to use MPack. The test suite currently only supports Linux. | ||
|
||
The test suite uses SCons and requires Valgrind, and can be run in the repository or in the amalgamation package. SCons will build and run the library and unit testing suite in both debug and release for both 32-bit and 64-bit architectures (if available), so it will take a minute or two to build and run. If you are on 64-bit, you will also need support for running 32-bit binaries with 64-bit Valgrind (`libc6-dbg:i386` on Ubuntu or `valgrind-multilib` on Arch.) Just run `scons` to build and run all tests. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
Import('env', 'CPPFLAGS', 'LINKFLAGS') | ||
|
||
srcs = env.Object(env.Glob('src/mpack/*.c') + env.Glob('test/*.c'), | ||
CPPFLAGS=env['CPPFLAGS'] + CPPFLAGS) | ||
|
||
prog = env.Program("mpack-test", srcs, | ||
LINKFLAGS=env['LINKFLAGS'] + LINKFLAGS, | ||
LINK=(("-xc++" in CPPFLAGS) and 'g++' or 'gcc')) | ||
|
||
env.Default(env.AlwaysBuild(env.Alias("test", | ||
[prog], | ||
"valgrind --leak-check=full " + Dir('.').path + "/mpack-test"))) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import platform | ||
|
||
env = Environment() | ||
env.Append(CPPFLAGS = [ | ||
"-Wall", "-Wextra", "-Werror", | ||
"-Isrc", "-Itest", | ||
"-g", | ||
"-fprofile-arcs", "-ftest-coverage" | ||
]) | ||
env.Append(LINKFLAGS = [ | ||
"-g", | ||
"-fprofile-arcs", "-ftest-coverage" | ||
]) | ||
|
||
debugflags = ["-DMPACK_DEBUG=1", "-DMPACK_TRACKING=1", "-O0"] | ||
releaseflags = ["-Os"] | ||
cflags = ["-std=c99", "-Wc++-compat"] | ||
cxxflags = ["-xc++", "-std=c++98"] | ||
|
||
def AddBuild(variant_dir, cppflags, linkflags): | ||
env.SConscript("SConscript", | ||
variant_dir=variant_dir, | ||
src="../..", | ||
exports={'env': env, 'CPPFLAGS': cppflags, 'LINKFLAGS': linkflags}, | ||
duplicate=0) | ||
|
||
AddBuild("build/debug", debugflags + cflags, []) | ||
if not ARGUMENTS.get('dev'): | ||
AddBuild("build/release", releaseflags + cflags, []) | ||
AddBuild("build/debug-cxx", debugflags + cxxflags, []) | ||
AddBuild("build/release-cxx", releaseflags + cxxflags, []) | ||
if '64' in platform.architecture()[0]: | ||
AddBuild("build/debug-32", debugflags + cflags + ["-m32"], ["-m32"]) | ||
AddBuild("build/release-32", releaseflags + cflags + ["-m32"], ["-m32"]) | ||
|
Oops, something went wrong.