├── .formatter.exs ├── .gitignore ├── .vscode └── c_cpp_properties.json ├── Makefile ├── README.md ├── c_src └── nif_ex_zstd.c ├── config └── config.exs ├── lib └── ex_zstd.ex ├── mix.exs ├── mix.lock └── test ├── ex_zstd_test.exs └── test_helper.exs /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | wgx-*.tar 24 | 25 | # Generated application binary 26 | /priv/ 27 | 28 | # ElixirLS plugin 29 | /.elixir_ls/ 30 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Mac", 5 | "includePath": [ 6 | "${workspaceFolder}/**", 7 | "/usr/local/Cellar/erlang/21.1.1/lib/erlang/erts-10.1.1/include" 8 | ], 9 | "defines": [], 10 | "macFrameworkPath": [ 11 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks" 12 | ], 13 | "compilerPath": "/usr/bin/clang", 14 | "cStandard": "c11", 15 | "cppStandard": "c++17", 16 | "intelliSenseMode": "clang-x64" 17 | } 18 | ], 19 | "version": 4 20 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MIX = mix 2 | CFLAGS = -g -O3 -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers 3 | 4 | SCRIPT = io:format("~s", [lists:concat([ \ 5 | code:root_dir(), \ 6 | "/erts-", \ 7 | erlang:system_info(version), \ 8 | "/include" \ 9 | ])]) 10 | 11 | ERLANG = $(shell erl -eval '$(SCRIPT)' -s init stop -noshell) 12 | CFLAGS += -I$(ERLANG) 13 | 14 | ifeq ($(wildcard deps/libzstd),) 15 | ZSTD_PATH = ../libzstd 16 | else 17 | ZSTD_PATH = deps/libzstd 18 | endif 19 | 20 | CFLAGS += -I$(ZSTD_PATH)/lib 21 | CFLAGS += -fPIC -flto 22 | CFLAGS += -DZSTD_STATIC_LINKING_ONLY 23 | 24 | ifeq ($(shell uname),Darwin) 25 | LDFLAGS += -dynamiclib -undefined dynamic_lookup 26 | endif 27 | 28 | .PHONY: all zstd clean 29 | 30 | all: zstd 31 | 32 | zstd: 33 | ${MIX} deps.get 34 | $(MIX) compile 35 | 36 | priv/nif_ex_zstd.so: c_src/nif_ex_zstd.c 37 | $(MAKE) -C $(ZSTD_PATH) lib-release 38 | $(CC) $(CFLAGS) -shared $(LDFLAGS) -o $@ c_src/nif_ex_zstd.c $(ZSTD_PATH)/lib/libzstd.a 39 | 40 | clean: 41 | $(MIX) clean 42 | $(MAKE) -C $(ZSTD_PATH) clean 43 | $(RM) -vrf priv/nif_ex_zstd.so* 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ExZstd 2 | 3 | Elixir binding of the Zstandard library 4 | 5 | ## Installation 6 | 7 | The package can be installed by adding `zstd` to your list of dependencies in `mix.exs`: 8 | 9 | ```elixir 10 | def deps do 11 | [ 12 | {:ex_zstd, "~> 0.1.0"} 13 | ] 14 | end 15 | ``` 16 | 17 | The docs can be found at [https://hexdocs.pm/ex_zstd](https://hexdocs.pm/ex_zstd). 18 | -------------------------------------------------------------------------------- /c_src/nif_ex_zstd.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | static ERL_NIF_TERM _zstd_cstream_new(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]); 5 | static ERL_NIF_TERM _zstd_dstream_new(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]); 6 | 7 | static ERL_NIF_TERM _zstd_cstream_init(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]); 8 | static ERL_NIF_TERM _zstd_dstream_init(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]); 9 | 10 | static ERL_NIF_TERM _zstd_cstream_reset(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]); 11 | static ERL_NIF_TERM _zstd_dstream_reset(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]); 12 | 13 | static ERL_NIF_TERM _zstd_stream_flush(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]); 14 | static ERL_NIF_TERM _zstd_stream_compress(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]); 15 | static ERL_NIF_TERM _zstd_stream_decompress(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]); 16 | 17 | static ERL_NIF_TERM _zstd_simple_compress(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]); 18 | static ERL_NIF_TERM _zstd_simple_decompress(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]); 19 | 20 | static ErlNifFunc _zstd_exports[] = { 21 | { "cstream_new" , 0, _zstd_cstream_new }, 22 | { "dstream_new" , 0, _zstd_dstream_new }, 23 | 24 | { "cstream_init" , 1, _zstd_cstream_init , ERL_DIRTY_JOB_CPU_BOUND }, 25 | { "cstream_init" , 2, _zstd_cstream_init , ERL_DIRTY_JOB_CPU_BOUND }, 26 | { "dstream_init" , 1, _zstd_dstream_init , ERL_DIRTY_JOB_CPU_BOUND }, 27 | 28 | { "cstream_reset" , 2, _zstd_cstream_reset }, 29 | { "cstream_reset" , 1, _zstd_cstream_reset }, 30 | { "dstream_reset" , 1, _zstd_dstream_reset }, 31 | 32 | { "stream_flush" , 1, _zstd_stream_flush , ERL_DIRTY_JOB_CPU_BOUND }, 33 | { "stream_compress" , 2, _zstd_stream_compress , ERL_DIRTY_JOB_CPU_BOUND }, 34 | { "stream_decompress", 2, _zstd_stream_decompress, ERL_DIRTY_JOB_CPU_BOUND }, 35 | 36 | { "simple_compress" , 1, _zstd_simple_compress , ERL_DIRTY_JOB_CPU_BOUND }, 37 | { "simple_compress" , 2, _zstd_simple_compress , ERL_DIRTY_JOB_CPU_BOUND }, 38 | { "simple_decompress", 1, _zstd_simple_decompress, ERL_DIRTY_JOB_CPU_BOUND }, 39 | }; 40 | 41 | static ERL_NIF_TERM _atom_ok; 42 | static ERL_NIF_TERM _atom_error; 43 | static ERL_NIF_TERM _atom_einval; 44 | static ERL_NIF_TERM _atom_enomem; 45 | 46 | static ErlNifResourceType *_zstd_cstream_type = NULL; 47 | static ErlNifResourceType *_zstd_dstream_type = NULL; 48 | 49 | static inline ERL_NIF_TERM _zstd_wrap_inval(ErlNifEnv *self) 50 | { 51 | return enif_make_tuple2( 52 | self, 53 | _atom_error, 54 | _atom_einval 55 | ); 56 | } 57 | 58 | static inline ERL_NIF_TERM _zstd_wrap_nomem(ErlNifEnv *self) 59 | { 60 | return enif_make_tuple2( 61 | self, 62 | _atom_error, 63 | _atom_enomem 64 | ); 65 | } 66 | 67 | static inline ERL_NIF_TERM _zstd_wrap_error(ErlNifEnv *self, size_t ret) 68 | { 69 | return enif_make_tuple2( 70 | self, 71 | _atom_error, 72 | enif_make_string( 73 | self, 74 | ZSTD_getErrorName(ret), 75 | ERL_NIF_LATIN1 76 | ) 77 | ); 78 | } 79 | 80 | static inline ERL_NIF_TERM _zstd_wrap_string(ErlNifEnv *self, const char *msg) 81 | { 82 | return enif_make_tuple2( 83 | self, 84 | _atom_error, 85 | enif_make_string(self, msg, ERL_NIF_LATIN1) 86 | ); 87 | } 88 | 89 | static inline ERL_NIF_TERM _zstd_wrap_pointer(ErlNifEnv *self, void *ptr) 90 | { 91 | ERL_NIF_TERM res = enif_make_resource(self, ptr); 92 | enif_release_resource(ptr); 93 | return res; 94 | } 95 | 96 | static void _zstd_cstream_dtor(ErlNifEnv *self, void *stream) 97 | { 98 | ZSTD_CStream **handle = stream; 99 | ZSTD_freeCStream(*handle); 100 | } 101 | 102 | static void _zstd_dstream_dtor(ErlNifEnv *self, void *stream) 103 | { 104 | ZSTD_DStream **handle = stream; 105 | ZSTD_freeDStream(*handle); 106 | } 107 | 108 | static int _zstd_init(ErlNifEnv *self) 109 | { 110 | /* cstream type */ 111 | _zstd_cstream_type = enif_open_resource_type( 112 | self, 113 | "Elixir.ExZstd", 114 | "CompressStream", 115 | _zstd_cstream_dtor, 116 | ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER, 117 | NULL 118 | ); 119 | 120 | /* cstream type */ 121 | _zstd_dstream_type = enif_open_resource_type( 122 | self, 123 | "Elixir.ExZstd", 124 | "DecompressStream", 125 | _zstd_dstream_dtor, 126 | ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER, 127 | NULL 128 | ); 129 | 130 | /* create atoms */ 131 | enif_make_existing_atom(self, "ok" , &_atom_ok , ERL_NIF_LATIN1); 132 | enif_make_existing_atom(self, "error" , &_atom_error , ERL_NIF_LATIN1); 133 | enif_make_existing_atom(self, "einval", &_atom_einval, ERL_NIF_LATIN1); 134 | enif_make_existing_atom(self, "enomem", &_atom_enomem, ERL_NIF_LATIN1); 135 | 136 | /* should all be loaded */ 137 | return !(_zstd_cstream_type && _zstd_dstream_type); 138 | } 139 | 140 | static int _zstd_on_load(ErlNifEnv *self, void **priv, ERL_NIF_TERM info) { return _zstd_init(self); } 141 | static int _zstd_on_reload(ErlNifEnv *self, void **priv, ERL_NIF_TERM info) { return _zstd_init(self); } 142 | static int _zstd_on_upgrade(ErlNifEnv *self, void **priv, void **old, ERL_NIF_TERM info) { return _zstd_init(self); } 143 | 144 | static ERL_NIF_TERM _zstd_cstream_new(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]) 145 | { 146 | /* create handle */ 147 | ZSTD_CStream **handle = enif_alloc_resource( 148 | _zstd_cstream_type, 149 | sizeof(ZSTD_CStream *) 150 | ); 151 | 152 | /* create cstream stream */ 153 | *handle = ZSTD_createCStream(); 154 | return _zstd_wrap_pointer(self, handle); 155 | } 156 | 157 | static ERL_NIF_TERM _zstd_dstream_new(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]) 158 | { 159 | /* create handle */ 160 | ZSTD_DStream **handle = enif_alloc_resource( 161 | _zstd_dstream_type, 162 | sizeof(ZSTD_DStream *) 163 | ); 164 | 165 | /* create decstream stream */ 166 | *handle = ZSTD_createDStream(); 167 | return _zstd_wrap_pointer(self, handle); 168 | } 169 | 170 | static ERL_NIF_TERM _zstd_cstream_init(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]) 171 | { 172 | int level = ZSTD_CLEVEL_DEFAULT; 173 | size_t ret; 174 | ZSTD_CStream **pzcs; 175 | 176 | /* extract the stream */ 177 | if (!(enif_get_resource(self, argv[0], _zstd_cstream_type, (void **)&pzcs))) 178 | return _zstd_wrap_inval(self); 179 | 180 | /* extract the compression level if any */ 181 | if ((argc == 2) && !(enif_get_int(self, argv[1], &level))) 182 | return _zstd_wrap_inval(self); 183 | 184 | /* initialize the stream */ 185 | if (ZSTD_isError(ret = ZSTD_initCStream(*pzcs, level))) 186 | return _zstd_wrap_error(self, ret); 187 | 188 | /* stream initialization successful */ 189 | return _atom_ok; 190 | } 191 | 192 | static ERL_NIF_TERM _zstd_dstream_init(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]) 193 | { 194 | size_t ret; 195 | ZSTD_DStream **pzds; 196 | 197 | /* extract the stream */ 198 | if (!(enif_get_resource(self, argv[0], _zstd_dstream_type, (void **)&pzds))) 199 | return _zstd_wrap_inval(self); 200 | 201 | /* initialize the stream */ 202 | if (ZSTD_isError(ret = ZSTD_initDStream(*pzds))) 203 | return _zstd_wrap_error(self, ret); 204 | 205 | /* stream initialization successful */ 206 | return _atom_ok; 207 | } 208 | 209 | static ERL_NIF_TERM _zstd_cstream_reset(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]) 210 | { 211 | size_t ret; 212 | size_t size = ZSTD_CONTENTSIZE_UNKNOWN; 213 | ZSTD_CStream **pzcs; 214 | 215 | /* extract the stream */ 216 | if (!(enif_get_resource(self, argv[0], _zstd_cstream_type, (void **)&pzcs))) 217 | return _zstd_wrap_inval(self); 218 | 219 | /* extract the pledged source size if any */ 220 | if ((argc == 2) && !(enif_get_ulong(self, argv[1], &size))) 221 | return _zstd_wrap_inval(self); 222 | 223 | /* reset the stream */ 224 | if (ZSTD_isError(ret = ZSTD_resetCStream(*pzcs, size))) 225 | return _zstd_wrap_error(self, ret); 226 | 227 | /* stream resetting successful */ 228 | return _atom_ok; 229 | } 230 | 231 | static ERL_NIF_TERM _zstd_dstream_reset(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]) 232 | { 233 | size_t ret; 234 | ZSTD_DStream **pzds; 235 | 236 | /* extract the stream */ 237 | if (!(enif_get_resource(self, argv[0], _zstd_dstream_type, (void **)&pzds))) 238 | return _zstd_wrap_inval(self); 239 | 240 | /* reset the stream */ 241 | if (ZSTD_isError(ret = ZSTD_resetDStream(*pzds))) 242 | return _zstd_wrap_error(self, ret); 243 | 244 | /* stream resetting successful */ 245 | return _atom_ok; 246 | } 247 | 248 | static ERL_NIF_TERM _zstd_stream_flush(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]) 249 | { 250 | size_t ret; 251 | ErlNifBinary bin; 252 | ZSTD_CStream **pzcs; 253 | 254 | /* extract the stream */ 255 | if (!(enif_get_resource(self, argv[0], _zstd_cstream_type, (void **)&pzcs))) 256 | return _zstd_wrap_inval(self); 257 | 258 | /* allocate binary buffer */ 259 | if (!(enif_alloc_binary(ZSTD_CStreamOutSize(), &bin))) 260 | return _zstd_wrap_nomem(self); 261 | 262 | /* output buffer */ 263 | ZSTD_outBuffer outbuf = { 264 | .pos = 0, 265 | .dst = bin.data, 266 | .size = bin.size, 267 | }; 268 | 269 | /* reset the stream */ 270 | if (ZSTD_isError(ret = ZSTD_endStream(*pzcs, &outbuf))) 271 | { 272 | enif_release_binary(&bin); 273 | return _zstd_wrap_error(self, ret); 274 | } 275 | 276 | /* transfer to binary object */ 277 | ERL_NIF_TERM binary = enif_make_binary(self, &bin); 278 | ERL_NIF_TERM result = binary; 279 | 280 | /* remove unused spaces */ 281 | if (outbuf.pos < outbuf.size) 282 | result = enif_make_sub_binary(self, binary, 0, outbuf.pos); 283 | 284 | /* construct the result tuple */ 285 | return enif_make_tuple2(self, _atom_ok, result); 286 | } 287 | 288 | static ERL_NIF_TERM _zstd_stream_compress(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]) 289 | { 290 | size_t ret; 291 | ErlNifBinary in; 292 | ErlNifBinary out; 293 | ZSTD_CStream **pzcs; 294 | 295 | /* extract the stream */ 296 | if (!(enif_get_resource(self, argv[0], _zstd_cstream_type, (void **)&pzcs)) || 297 | !(enif_inspect_iolist_as_binary(self, argv[1], &in))) 298 | return _zstd_wrap_inval(self); 299 | 300 | /* all output binary buffer */ 301 | if (!(enif_alloc_binary(ZSTD_compressBound(in.size), &out))) 302 | { 303 | enif_release_binary(&in); 304 | return _zstd_wrap_nomem(self); 305 | } 306 | 307 | /* input buffer */ 308 | ZSTD_inBuffer inbuf = { 309 | .pos = 0, 310 | .src = in.data, 311 | .size = in.size, 312 | }; 313 | 314 | /* output buffer */ 315 | ZSTD_outBuffer outbuf = { 316 | .pos = 0, 317 | .dst = out.data, 318 | .size = out.size, 319 | }; 320 | 321 | /* compress every chunk */ 322 | while (inbuf.pos < inbuf.size) 323 | { 324 | if (ZSTD_isError(ret = ZSTD_compressStream(*pzcs, &outbuf, &inbuf))) 325 | { 326 | enif_release_binary(&in); 327 | enif_release_binary(&out); 328 | return _zstd_wrap_error(self, ret); 329 | } 330 | } 331 | 332 | /* transfer to binary object */ 333 | ERL_NIF_TERM binary = enif_make_binary(self, &out); 334 | ERL_NIF_TERM result = binary; 335 | 336 | /* remove unused spaces */ 337 | if (outbuf.pos < outbuf.size) 338 | result = enif_make_sub_binary(self, binary, 0, outbuf.pos); 339 | 340 | /* construct the result tuple */ 341 | enif_release_binary(&in); 342 | return enif_make_tuple2(self, _atom_ok, result); 343 | } 344 | 345 | static ERL_NIF_TERM _zstd_stream_decompress(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]) 346 | { 347 | size_t ret; 348 | ErlNifBinary in; 349 | ErlNifBinary out; 350 | ZSTD_DStream **pzds; 351 | 352 | /* extract the stream */ 353 | if (!(enif_get_resource(self, argv[0], _zstd_dstream_type, (void **)&pzds)) || 354 | !(enif_inspect_iolist_as_binary(self, argv[1], &in))) 355 | return _zstd_wrap_inval(self); 356 | 357 | /* allocate output binary buffer */ 358 | if (!(enif_alloc_binary(ZSTD_DStreamOutSize(), &out))) 359 | { 360 | enif_release_binary(&in); 361 | return _zstd_wrap_nomem(self); 362 | } 363 | 364 | /* input buffer */ 365 | ZSTD_inBuffer inbuf = { 366 | .pos = 0, 367 | .src = in.data, 368 | .size = in.size, 369 | }; 370 | 371 | /* output buffer */ 372 | ZSTD_outBuffer outbuf = { 373 | .pos = 0, 374 | .dst = out.data, 375 | .size = out.size, 376 | }; 377 | 378 | /* decompress every chunk */ 379 | while (inbuf.pos < inbuf.size) 380 | { 381 | /* enlarge output buffer as needed */ 382 | if (outbuf.size - outbuf.pos < ZSTD_DStreamOutSize()) 383 | { 384 | /* resize the output binary */ 385 | if (!(enif_realloc_binary(&out, outbuf.size * 2))) 386 | { 387 | enif_release_binary(&in); 388 | enif_release_binary(&out); 389 | return _zstd_wrap_nomem(self); 390 | } 391 | 392 | /* update buffer pointers */ 393 | outbuf.dst = out.data + outbuf.pos; 394 | outbuf.size = out.size; 395 | } 396 | 397 | /* decompress one frame */ 398 | if (ZSTD_isError(ret = ZSTD_decompressStream(*pzds, &outbuf, &inbuf))) 399 | { 400 | enif_release_binary(&in); 401 | enif_release_binary(&out); 402 | return _zstd_wrap_error(self, ret); 403 | } 404 | } 405 | 406 | /* transfer to binary object */ 407 | ERL_NIF_TERM binary = enif_make_binary(self, &out); 408 | ERL_NIF_TERM result = binary; 409 | 410 | /* remove unused spaces */ 411 | if (outbuf.pos < outbuf.size) 412 | result = enif_make_sub_binary(self, binary, 0, outbuf.pos); 413 | 414 | /* construct the result tuple */ 415 | enif_release_binary(&in); 416 | return enif_make_tuple2(self, _atom_ok, result); 417 | } 418 | 419 | static ERL_NIF_TERM _zstd_simple_compress(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]) 420 | { 421 | int level = ZSTD_CLEVEL_DEFAULT; 422 | size_t ret; 423 | ErlNifBinary in; 424 | ErlNifBinary out; 425 | 426 | /* extract the binary data */ 427 | if (!(enif_inspect_iolist_as_binary(self, argv[0], &in))) 428 | return _zstd_wrap_inval(self); 429 | 430 | /* extract the compression level if any */ 431 | if ((argc == 2) && !(enif_get_int(self, argv[1], &level))) 432 | { 433 | enif_release_binary(&in); 434 | return _zstd_wrap_inval(self); 435 | } 436 | 437 | /* allocate output binary */ 438 | if (!(enif_alloc_binary(ZSTD_compressBound(in.size), &out))) 439 | { 440 | enif_release_binary(&in); 441 | return _zstd_wrap_nomem(self); 442 | } 443 | 444 | /* perform compression */ 445 | if (ZSTD_isError(ret = ZSTD_compress(out.data, out.size, in.data, in.size, level))) 446 | { 447 | enif_release_binary(&in); 448 | enif_release_binary(&out); 449 | return _zstd_wrap_error(self, ret); 450 | } 451 | 452 | /* transfer to binary object */ 453 | ERL_NIF_TERM binary = enif_make_binary(self, &out); 454 | ERL_NIF_TERM result = binary; 455 | 456 | /* remove unused spaces */ 457 | if (ret < out.size) 458 | result = enif_make_sub_binary(self, binary, 0, ret); 459 | 460 | /* construct the result tuple */ 461 | enif_release_binary(&in); 462 | return enif_make_tuple2(self, _atom_ok, result); 463 | } 464 | 465 | static ERL_NIF_TERM _zstd_simple_decompress(ErlNifEnv *self, int argc, const ERL_NIF_TERM argv[]) 466 | { 467 | size_t ret; 468 | ErlNifBinary in; 469 | ErlNifBinary out; 470 | 471 | /* extract the binary data */ 472 | if (!(enif_inspect_iolist_as_binary(self, argv[0], &in))) 473 | return _zstd_wrap_inval(self); 474 | 475 | /* find the output size */ 476 | if (!(ret = ZSTD_getDecompressedSize(in.data, in.size))) 477 | { 478 | enif_release_binary(&in); 479 | return _zstd_wrap_string(self, "Unable to determain decompressed size"); 480 | } 481 | 482 | /* allocate output binary */ 483 | if (!(enif_alloc_binary(ret, &out))) 484 | { 485 | enif_release_binary(&in); 486 | return _zstd_wrap_nomem(self); 487 | } 488 | 489 | /* perform decompression */ 490 | if (ZSTD_isError(ret = ZSTD_decompress(out.data, out.size, in.data, in.size))) 491 | { 492 | enif_release_binary(&in); 493 | enif_release_binary(&out); 494 | return _zstd_wrap_error(self, ret); 495 | } 496 | 497 | /* construct the result tuple */ 498 | enif_release_binary(&in); 499 | return enif_make_tuple2(self, _atom_ok, enif_make_binary(self, &out)); 500 | } 501 | 502 | ERL_NIF_INIT( 503 | Elixir.ExZstd, /* module name */ 504 | _zstd_exports, /* export table */ 505 | _zstd_on_load, /* on load */ 506 | _zstd_on_reload, /* on reload */ 507 | _zstd_on_upgrade, /* on upgrade */ 508 | NULL /* on unload */ 509 | ) 510 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure your application as: 12 | # 13 | # config :ex_zstd, key: :value 14 | # 15 | # and access this configuration in your application as: 16 | # 17 | # Application.get_env(:ex_zstd, :key) 18 | # 19 | # You can also configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env()}.exs" 31 | -------------------------------------------------------------------------------- /lib/ex_zstd.ex: -------------------------------------------------------------------------------- 1 | defmodule ExZstd do 2 | @moduledoc """ 3 | Elixir binding of the Zstandard library 4 | """ 5 | 6 | @on_load {:__init__, 0} 7 | app = Mix.Project.config()[:app] 8 | 9 | @spec __init__() :: :ok 10 | def __init__ do 11 | dir = :code.priv_dir(unquote(app)) 12 | :ok = :erlang.load_nif(:filename.join(dir, 'nif_ex_zstd'), 0) 13 | end 14 | 15 | @doc """ 16 | Create a new Compression Stream 17 | """ 18 | @spec cstream_new() :: reference() 19 | def cstream_new() 20 | def cstream_new(), do: exit(:nif_library_not_loaded) 21 | 22 | @doc """ 23 | Create a new Decompression Stream 24 | """ 25 | @spec dstream_new() :: reference() 26 | def dstream_new() 27 | def dstream_new(), do: exit(:nif_library_not_loaded) 28 | 29 | @doc """ 30 | Initialize or re-initialize a Compression Stream 31 | using default compression level 32 | """ 33 | @spec cstream_init(reference()) :: :ok | {:error, :einval | String.t()} 34 | def cstream_init(zcs) 35 | def cstream_init(_zcs), do: exit(:nif_library_not_loaded) 36 | 37 | @doc """ 38 | Initialize or re-initialize a Compression Stream, 39 | using the compression level specified by `level` 40 | """ 41 | @spec cstream_init(reference(), integer()) :: :ok | {:error, :einval | String.t()} 42 | def cstream_init(zcs, level) 43 | def cstream_init(_zcs, _level), do: exit(:nif_library_not_loaded) 44 | 45 | @doc """ 46 | Initialize or re-initialize a Decompression Stream 47 | """ 48 | @spec dstream_init(reference()) :: :ok | {:error, :einval | String.t()} 49 | def dstream_init(zds) 50 | def dstream_init(_zds), do: exit(:nif_library_not_loaded) 51 | 52 | @doc """ 53 | Reset a Compression Stream and start a new compression job, 54 | using same parameters from previous job 55 | """ 56 | @spec cstream_reset(reference()) :: :ok | {:error, :einval | String.t()} 57 | def cstream_reset(zcs) 58 | def cstream_reset(_zcs), do: exit(:nif_library_not_loaded) 59 | 60 | @doc """ 61 | Reset a Compression Stream and start a new compression job with pledged source size, 62 | using same parameters from previous job 63 | """ 64 | @spec cstream_reset(reference(), non_neg_integer()) :: :ok | {:error, :einval | String.t()} 65 | def cstream_reset(zcs, size) 66 | def cstream_reset(_zcs, _size), do: exit(:nif_library_not_loaded) 67 | 68 | @doc """ 69 | Reset a Decompression Stream and start a new compression job, 70 | using same parameters from previous job 71 | """ 72 | @spec dstream_reset(reference()) :: :ok | {:error, :einval | String.t()} 73 | def dstream_reset(zds) 74 | def dstream_reset(_zds), do: exit(:nif_library_not_loaded) 75 | 76 | @doc """ 77 | Flush the Compression Stream, then close it 78 | """ 79 | @spec stream_flush(reference()) :: {:ok, binary()} | {:error, :einval | :enomem | String.t()} 80 | def stream_flush(zcs) 81 | def stream_flush(_zcs), do: exit(:nif_library_not_loaded) 82 | 83 | @doc """ 84 | Compress a chunk of data with Compression Stream 85 | """ 86 | @spec stream_compress(reference(), binary()) :: 87 | {:ok, binary()} | {:error, :einval | :enomem | String.t()} 88 | def stream_compress(zcs, data) 89 | def stream_compress(_zcs, _data), do: exit(:nif_library_not_loaded) 90 | 91 | @doc """ 92 | Decompress a chunk of data with Decompression Stream 93 | """ 94 | @spec stream_decompress(reference(), binary()) :: 95 | {:ok, binary()} | {:error, :einval | :enomem | String.t()} 96 | def stream_decompress(zcs, data) 97 | def stream_decompress(_zcs, _data), do: exit(:nif_library_not_loaded) 98 | 99 | @doc """ 100 | Compress a chunk of data with Simple API 101 | """ 102 | @spec simple_compress(binary()) :: {:ok, binary()} | {:error, :einval | :enomem | String.t()} 103 | def simple_compress(data) 104 | def simple_compress(_data), do: exit(:nif_library_not_loaded) 105 | 106 | @doc """ 107 | Compress a chunk of data with Simple API, using specified compression level 108 | """ 109 | @spec simple_compress(binary(), integer()) :: 110 | {:ok, binary()} | {:error, :einval | :enomem | String.t()} 111 | def simple_compress(data, level) 112 | def simple_compress(_data, _level), do: exit(:nif_library_not_loaded) 113 | 114 | @doc """ 115 | Decompress a chunk of data with Simple API 116 | """ 117 | @spec simple_decompress(binary()) :: {:ok, binary()} | {:error, :einval | :enomem | String.t()} 118 | def simple_decompress(data) 119 | def simple_decompress(_data), do: exit(:nif_library_not_loaded) 120 | end 121 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ExZstd.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :ex_zstd, 7 | deps: deps(Mix.env()), 8 | elixir: "~> 1.7", 9 | version: "0.1.0", 10 | package: package(), 11 | compilers: compilers(), 12 | source_url: "https://github.com/chenzhuoyu/elixir-zstd", 13 | description: description(), 14 | build_embedded: Mix.env() == :prod, 15 | start_permanent: Mix.env() == :prod 16 | ] 17 | end 18 | 19 | defp deps(:publish) do 20 | [ 21 | {:ex_doc, ">= 0.0.0"} 22 | ] 23 | end 24 | 25 | defp deps(_) do 26 | [ 27 | {:libzstd, "~> 1.3.7", github: "facebook/zstd", app: false} 28 | ] 29 | end 30 | 31 | defp package do 32 | [ 33 | name: "ex_zstd", 34 | files: ~w(lib c_src test .formatter.exs Makefile mix.exs README.md), 35 | links: %{"GitHub" => "https://github.com/chenzhuoyu/elixir-zstd"}, 36 | licenses: ["BSD"] 37 | ] 38 | end 39 | 40 | defp compilers do 41 | [ 42 | :ex_zstd, 43 | :elixir, 44 | :app 45 | ] 46 | end 47 | 48 | defp description do 49 | "Elixir binding of the Zstandard library" 50 | end 51 | end 52 | 53 | defmodule Mix.Tasks.Compile.ExZstd do 54 | def run(_) do 55 | if match?({:win32, _}, :os.type()) do 56 | IO.warn("Windows is not supported") 57 | exit(1) 58 | else 59 | File.mkdir_p("priv") 60 | {result, _code} = System.cmd("make", ["priv/nif_ex_zstd.so"], stderr_to_stdout: true) 61 | IO.binwrite(result) 62 | Mix.Project.build_structure 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm"}, 3 | "ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, 4 | "libzstd": {:git, "https://github.com/facebook/zstd.git", "334ac69db77a7b8b187939393cf4e73fdaf0baf8", []}, 5 | "makeup": {:hex, :makeup, "0.5.5", "9e08dfc45280c5684d771ad58159f718a7b5788596099bdfb0284597d368a882", [:mix], [{:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, 6 | "makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, 7 | "nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"}, 8 | } 9 | -------------------------------------------------------------------------------- /test/ex_zstd_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExZstdTest do 2 | use ExUnit.Case 3 | doctest ExZstd 4 | 5 | test "Simple Compression" do 6 | {:ok, data} = ExZstd.simple_compress("hello, world") 7 | {:ok, orig} = ExZstd.simple_decompress(data) 8 | assert orig == "hello, world" 9 | end 10 | 11 | test "Stream Decompression" do 12 | zcs = ExZstd.cstream_new() 13 | ExZstd.cstream_init(zcs) 14 | {:ok, data} = ExZstd.stream_compress(zcs, "hello, world") 15 | {:ok, tail} = ExZstd.stream_flush(zcs) 16 | 17 | zds = ExZstd.dstream_new() 18 | ExZstd.dstream_init(zds) 19 | {:ok, orig} = ExZstd.stream_decompress(zds, [data, tail]) 20 | assert orig == "hello, world" 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------