{"id":13605981,"url":"https://github.com/aklomp/base64","last_synced_at":"2025-10-22T02:48:59.203Z","repository":{"id":38428439,"uuid":"13057937","full_name":"aklomp/base64","owner":"aklomp","description":"Fast Base64 stream encoder/decoder in C99, with SIMD acceleration","archived":false,"fork":false,"pushed_at":"2024-09-11T20:47:33.000Z","size":450,"stargazers_count":865,"open_issues_count":28,"forks_count":162,"subscribers_count":37,"default_branch":"master","last_synced_at":"2024-09-12T06:58:36.023Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aklomp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2013-09-24T07:46:42.000Z","updated_at":"2024-09-11T20:47:37.000Z","dependencies_parsed_at":"2024-01-13T22:11:17.431Z","dependency_job_id":"e73e7ea1-ae2e-4d8f-83b5-a4c759467ca7","html_url":"https://github.com/aklomp/base64","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aklomp%2Fbase64","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aklomp%2Fbase64/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aklomp%2Fbase64/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aklomp%2Fbase64/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aklomp","download_url":"https://codeload.github.com/aklomp/base64/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223498130,"owners_count":17155268,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T19:01:04.878Z","updated_at":"2025-10-22T02:48:54.171Z","avatar_url":"https://github.com/aklomp.png","language":"C","funding_links":[],"categories":["C"],"sub_categories":[],"readme":"# Fast Base64 stream encoder/decoder\n\n[![Build Status](https://github.com/aklomp/base64/actions/workflows/test.yml/badge.svg)](https://github.com/aklomp/base64/actions/workflows/test.yml)\n\nThis is an implementation of a base64 stream encoding/decoding library in C99\nwith SIMD (AVX2, AVX512, NEON, AArch64/NEON, SSSE3, SSE4.1, SSE4.2, AVX) and\n[OpenMP](http://www.openmp.org) acceleration. It also contains wrapper functions\nto encode/decode simple length-delimited strings. This library aims to be:\n\n- FAST;\n- easy to use;\n- elegant.\n\nOn x86, the library does runtime feature detection. The first time it's called,\nthe library will determine the appropriate encoding/decoding routines for the\nmachine. It then remembers them for the lifetime of the program. If your\nprocessor supports AVX2, SSSE3, SSE4.1, SSE4.2 or AVX instructions, the library\nwill pick an optimized codec that lets it encode/decode 12 or 24 bytes at a\ntime, which gives a speedup of four or more times compared to the \"plain\"\nbytewise codec.\n\nAVX512 support is only for encoding at present, utilizing the AVX512 VL and VBMI\ninstructions. Decoding part reused AVX2 implementations. For CPUs later than\nCannonlake (manufactured in 2018) supports these instructions.\n\nNEON support is hardcoded to on or off at compile time, because portable\nruntime feature detection is unavailable on ARM.\n\nEven if your processor does not support SIMD instructions, this is a very fast\nlibrary. The fallback routine can process 32 or 64 bits of input in one round,\ndepending on your processor's word width, which still makes it significantly\nfaster than naive bytewise implementations. On some 64-bit machines, the 64-bit\nroutines even outperform the SSSE3 ones.\n\nTo the author's knowledge, at the time of original release, this was the only\nBase64 library to offer SIMD acceleration. The author wrote\n[an article](http://www.alfredklomp.com/programming/sse-base64) explaining one\npossible SIMD approach to encoding/decoding Base64. The article can help figure\nout what the code is doing, and why.\n\nNotable features:\n\n- Really fast on x86 and ARM systems by using SIMD vector processing;\n- Can use [OpenMP](http://www.openmp.org) for even more parallel speedups;\n- Really fast on other 32 or 64-bit platforms through optimized routines;\n- Reads/writes blocks of streaming data;\n- Does not dynamically allocate memory;\n- Valid C99 that compiles with pedantic options on;\n- Re-entrant and threadsafe;\n- Unit tested;\n- Uses Duff's Device.\n\n## Acknowledgements\n\nThe original AVX2, NEON and Aarch64/NEON codecs were generously contributed by\n[Inkymail](https://github.com/inkymail/base64), who, in their fork, also\nimplemented some additional features. Their work is slowly being backported\ninto this project.\n\nThe SSSE3 and AVX2 codecs were substantially improved by using some very clever\noptimizations described by Wojciech Muła in a\n[series](http://0x80.pl/notesen/2016-01-12-sse-base64-encoding.html) of\n[articles](http://0x80.pl/notesen/2016-01-17-sse-base64-decoding.html).\nHis own code is [here](https://github.com/WojciechMula/toys/tree/master/base64).\n\nThe AVX512 encoder is based on code from Wojciech Muła's\n[base64simd](https://github.com/WojciechMula/base64simd) library.\n\nThe OpenMP implementation was added by Ferry Toth (@htot) from [Exalon Delft](http://www.exalondelft.nl).\n\n## Building\n\nThe `lib` directory contains the code for the actual library.\nTyping `make` in the toplevel directory will build `lib/libbase64.o` and `bin/base64`.\nThe first is a single, self-contained object file that you can link into your own project.\nThe second is a standalone test binary that works similarly to the `base64` system utility.\n\nThe matching header file needed to use this library is in `include/libbase64.h`.\n\nTo compile just the \"plain\" library without SIMD codecs, type:\n\n```sh\nmake lib/libbase64.o\n```\n\nOptional SIMD codecs can be included by specifying the `AVX2_CFLAGS`, `AVX512_CFLAGS`,\n`NEON32_CFLAGS`, `NEON64_CFLAGS`, `SSSE3_CFLAGS`, `SSE41_CFLAGS`, `SSE42_CFLAGS` and/or `AVX_CFLAGS` environment variables.\nA typical build invocation on x86 looks like this:\n\n```sh\nAVX2_CFLAGS=-mavx2 SSSE3_CFLAGS=-mssse3 SSE41_CFLAGS=-msse4.1 SSE42_CFLAGS=-msse4.2 AVX_CFLAGS=-mavx make lib/libbase64.o\n```\n\n### AVX2\n\nTo build and include the AVX2 codec, set the `AVX2_CFLAGS` environment variable to a value that will turn on AVX2 support in your compiler, typically `-mavx2`.\nExample:\n\n```sh\nAVX2_CFLAGS=-mavx2 make\n```\n\n### AVX512\n\nTo build and include the AVX512 codec, set the `AVX512_CFLAGS` environment variable to a value that will turn on AVX512 support in your compiler, typically `-mavx512vl -mavx512vbmi`.\nExample:\n\n```sh\nAVX512_CFLAGS=\"-mavx512vl -mavx512vbmi\" make\n```\n\nThe codec will only be used if runtime feature detection shows that the target machine supports AVX2.\n\n### SSSE3\n\nTo build and include the SSSE3 codec, set the `SSSE3_CFLAGS` environment variable to a value that will turn on SSSE3 support in your compiler, typically `-mssse3`.\nExample:\n\n```sh\nSSSE3_CFLAGS=-mssse3 make\n```\n\nThe codec will only be used if runtime feature detection shows that the target machine supports SSSE3.\n\n### NEON\n\nThis library includes two NEON codecs: one for regular 32-bit ARM and one for the 64-bit AArch64 with NEON, which has double the amount of SIMD registers and can do full 64-byte table lookups.\nThese codecs encode in 48-byte chunks and decode in massive 64-byte chunks, so they had to be augmented with an uint32/64 codec to stay fast on smaller inputs!\n\nUse LLVM/Clang for compiling the NEON codecs.\nThe code generation of at least GCC 4.6 (the version shipped with Raspbian and used for testing) contains a bug when compiling `vstq4_u8()`, and the generated assembly code is of low quality.\nNEON intrinsics are a known weak area of GCC.\nClang does a better job.\n\nNEON support can unfortunately not be portably detected at runtime from userland (the `mrc` instruction is privileged), so the default value for using the NEON codec is determined at compile-time.\nBut you can do your own runtime detection.\nYou can include the NEON codec and make it the default, then do a runtime check if the CPU has NEON support, and if not, force a downgrade to non-NEON with `BASE64_FORCE_PLAIN`.\n\nThese are your options:\n\n1. Don't include NEON support;\n2. build NEON support and make it the default, but build all other code without NEON flags so that you can override the default at runtime with `BASE64_FORCE_PLAIN`;\n3. build everything with NEON support and make it the default;\n4. build everything with NEON support, but don't make it the default (which makes no sense).\n\nFor option 1, simply don't specify any NEON-specific compiler flags at all, like so:\n\n```sh\nCC=clang CFLAGS=\"-march=armv6\" make\n```\n\nFor option 2, keep your `CFLAGS` plain, but set the `NEON32_CFLAGS` environment variable to a value that will build NEON support.\nThe line below, for instance, will build all the code at ARMv6 level, except for the NEON codec, which is built at ARMv7.\nIt will also make the NEON codec the default.\nFor ARMv6 platforms, override that default at runtime with the `BASE64_FORCE_PLAIN` flag.\nNo ARMv7/NEON code will then be touched.\n\n```sh\nCC=clang CFLAGS=\"-march=armv6\" NEON32_CFLAGS=\"-march=armv7 -mfpu=neon\" make\n```\n\nFor option 3, put everything in your `CFLAGS` and use a stub, but non-empty, `NEON32_CFLAGS`.\nThis example works for the Raspberry Pi 2B V1.1, which has NEON support:\n\n```sh\nCC=clang CFLAGS=\"-march=armv7 -mtune=cortex-a7\" NEON32_CFLAGS=\"-mfpu=neon\" make\n```\n\nTo build and include the NEON64 codec, use `CFLAGS` as usual to define the platform and set `NEON64_CFLAGS` to a nonempty stub.\n(The AArch64 target has mandatory NEON64 support.)\nExample:\n\n```sh\nCC=clang CFLAGS=\"--target=aarch64-linux-gnu -march=armv8-a\" NEON64_CFLAGS=\" \" make\n```\n\n### OpenMP\n\nTo enable OpenMP on GCC you need to build with `-fopenmp`. This can be by setting the the `OPENMP` environment variable to `1`.\n\nExample:\n\n```sh\nOPENMP=1 make\n```\n\nThis will let the compiler define `_OPENMP`, which in turn will include the OpenMP optimized `lib_openmp.c` into `lib.c`.\n\nBy default the number of parallel threads will be equal to the number of cores of the processor.\nOn a quad core with hyperthreading eight cores will be detected, but hyperthreading will not increase the performance.\n\nTo get verbose information about OpenMP start the program with `OMP_DISPLAY_ENV=VERBOSE`, for instance\n\n```sh\nOMP_DISPLAY_ENV=VERBOSE test/benchmark\n```\n\nTo put a limit on the number of threads, start the program with `OMP_THREAD_LIMIT=n`, for instance\n\n```sh\nOMP_THREAD_LIMIT=2 test/benchmark\n```\n\nAn example of running a benchmark with OpenMP, SSSE3 and AVX2 enabled:\n\n```sh\nmake clean \u0026\u0026 OPENMP=1 SSSE3_CFLAGS=-mssse3 AVX2_CFLAGS=-mavx2 make \u0026\u0026 OPENMP=1 make -C test\n```\n\n## API reference\n\nStrings are represented as a pointer and a length; they are not\nzero-terminated. This was a conscious design decision. In the decoding step,\nrelying on zero-termination would make no sense since the output could contain\nlegitimate zero bytes. In the encoding step, returning the length saves the\noverhead of calling `strlen()` on the output. If you insist on the trailing\nzero, you can easily add it yourself at the given offset.\n\n### Flags\n\nSome API calls take a `flags` argument.\nThat argument can be used to force the use of a specific codec, even if that codec is a no-op in the current build.\nMainly there for testing purposes, this is also useful on ARM where the only way to do runtime NEON detection is to ask the OS if it's available.\nThe following constants can be used:\n\n- `BASE64_FORCE_AVX2`\n- `BASE64_FORCE_AVX512`\n- `BASE64_FORCE_NEON32`\n- `BASE64_FORCE_NEON64`\n- `BASE64_FORCE_PLAIN`\n- `BASE64_FORCE_SSSE3`\n- `BASE64_FORCE_SSE41`\n- `BASE64_FORCE_SSE42`\n- `BASE64_FORCE_AVX`\n\nSet `flags` to `0` for the default behavior, which is runtime feature detection on x86, a compile-time fixed codec on ARM, and the plain codec on other platforms.\n\n### Encoding\n\n#### base64_encode\n\n```c\nvoid base64_encode\n    ( const char  *src\n    , size_t       srclen\n    , char        *out\n    , size_t      *outlen\n    , int          flags\n    ) ;\n```\n\nWrapper function to encode a plain string of given length.\nOutput is written to `out` without trailing zero.\nOutput length in bytes is written to `outlen`.\nThe buffer in `out` has been allocated by the caller and is at least 4/3 the size of the input.\n\n#### base64_stream_encode_init\n\n```c\nvoid base64_stream_encode_init\n    ( struct base64_state  *state\n    , int                   flags\n    ) ;\n```\n\nCall this before calling `base64_stream_encode()` to init the state.\n\n#### base64_stream_encode\n\n```c\nvoid base64_stream_encode\n    ( struct base64_state  *state\n    , const char           *src\n    , size_t                srclen\n    , char                 *out\n    , size_t               *outlen\n    ) ;\n```\n\nEncodes the block of data of given length at `src`, into the buffer at `out`.\nCaller is responsible for allocating a large enough out-buffer; it must be at least 4/3 the size of the in-buffer, but take some margin.\nPlaces the number of new bytes written into `outlen` (which is set to zero when the function starts).\nDoes not zero-terminate or finalize the output.\n\n#### base64_stream_encode_final\n\n```c\nvoid base64_stream_encode_final\n    ( struct base64_state  *state\n    , char                 *out\n    , size_t               *outlen\n    ) ;\n```\n\nFinalizes the output begun by previous calls to `base64_stream_encode()`.\nAdds the required end-of-stream markers if appropriate.\n`outlen` is modified and will contain the number of new bytes written at `out` (which will quite often be zero).\n\n### Decoding\n\n#### base64_decode\n\n```c\nint base64_decode\n    ( const char  *src\n    , size_t       srclen\n    , char        *out\n    , size_t      *outlen\n    , int          flags\n    ) ;\n```\n\nWrapper function to decode a plain string of given length.\nOutput is written to `out` without trailing zero. Output length in bytes is written to `outlen`.\nThe buffer in `out` has been allocated by the caller and is at least 3/4 the size of the input.\nReturns `1` for success, and `0` when a decode error has occured due to invalid input.\nReturns `-1` if the chosen codec is not included in the current build.\n\n#### base64_stream_decode_init\n\n```c\nvoid base64_stream_decode_init\n    ( struct base64_state  *state\n    , int                   flags\n    ) ;\n```\n\nCall this before calling `base64_stream_decode()` to init the state.\n\n#### base64_stream_decode\n\n```c\nint base64_stream_decode\n    ( struct base64_state  *state\n    , const char           *src\n    , size_t                srclen\n    , char                 *out\n    , size_t               *outlen\n    ) ;\n```\n\nDecodes the block of data of given length at `src`, into the buffer at `out`.\nCaller is responsible for allocating a large enough out-buffer; it must be at least 3/4 the size of the in-buffer, but take some margin.\nPlaces the number of new bytes written into `outlen` (which is set to zero when the function starts).\nDoes not zero-terminate the output.\nReturns 1 if all is well, and 0 if a decoding error was found, such as an invalid character.\nReturns -1 if the chosen codec is not included in the current build.\nUsed by the test harness to check whether a codec is available for testing.\n\n## Examples\n\nA simple example of encoding a static string to base64 and printing the output\nto stdout:\n\n```c\n#include \u003cstdio.h\u003e\t/* fwrite */\n#include \"libbase64.h\"\n\nint main ()\n{\n\tchar src[] = \"hello world\";\n\tchar out[20];\n\tsize_t srclen = sizeof(src) - 1;\n\tsize_t outlen;\n\n\tbase64_encode(src, srclen, out, \u0026outlen, 0);\n\n\tfwrite(out, outlen, 1, stdout);\n\n\treturn 0;\n}\n```\n\nA simple example (no error checking, etc) of stream encoding standard input to\nstandard output:\n\n```c\n#include \u003cstdio.h\u003e\n#include \"libbase64.h\"\n\nint main ()\n{\n\tsize_t nread, nout;\n\tchar buf[12000], out[16000];\n\tstruct base64_state state;\n\n\t// Initialize stream encoder:\n\tbase64_stream_encode_init(\u0026state, 0);\n\n\t// Read contents of stdin into buffer:\n\twhile ((nread = fread(buf, 1, sizeof(buf), stdin)) \u003e 0) {\n\n\t\t// Encode buffer:\n\t\tbase64_stream_encode(\u0026state, buf, nread, out, \u0026nout);\n\n\t\t// If there's output, print it to stdout:\n\t\tif (nout) {\n\t\t\tfwrite(out, nout, 1, stdout);\n\t\t}\n\n\t\t// If an error occurred, exit the loop:\n\t\tif (feof(stdin)) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Finalize encoding:\n\tbase64_stream_encode_final(\u0026state, out, \u0026nout);\n\n\t// If the finalizing resulted in extra output bytes, print them:\n\tif (nout) {\n\t\tfwrite(out, nout, 1, stdout);\n\t}\n\n\treturn 0;\n}\n```\n\nAlso see `bin/base64.c` for a simple re-implementation of the `base64` utility.\nA file or standard input is fed through the encoder/decoder, and the output is\nwritten to standard output.\n\n## Tests\n\nSee `tests/` for a small test suite. Testing is automated with\n[GitHub Actions](https://github.com/aklomp/base64/actions), which builds and\ntests the code across various architectures.\n\n## Benchmarks\n\nBenchmarks can be run with the built-in benchmark program as follows:\n\n```sh\nmake -C test benchmark \u003cbuildflags\u003e \u0026\u0026 test/benchmark\n```\n\nIt will run an encoding and decoding benchmark for all of the compiled-in codecs.\n\nThe tables below contain some results on random machines. All numbers measured with a 10MB buffer in MB/sec, rounded to the nearest integer.\n\n\\*: Update needed\n\nx86 processors\n\n| Processor                                 | Plain enc | Plain dec | SSSE3 enc | SSSE3 dec | AVX enc | AVX dec | AVX2 enc | AVX2 dec |\n|-------------------------------------------|----------:|----------:|----------:|----------:|--------:|--------:|---------:|---------:|\n| i7-4771 @ 3.5 GHz                         | 833\\*     | 1111\\*    | 3333\\*    | 4444\\*    | TBD     | TBD     | 4999\\*   | 6666\\*   |\n| i7-4770 @ 3.4 GHz DDR1600                 | 1790\\*    | 3038\\*    | 4899\\*    | 4043\\*    | 4796\\*  | 5709\\*  | 4681\\*   | 6386\\*   |\n| i7-4770 @ 3.4 GHz DDR1600 OPENMP 1 thread | 1784\\*    | 3041\\*    | 4945\\*    | 4035\\*    | 4776\\*  | 5719\\*  | 4661\\*   | 6294\\*   |\n| i7-4770 @ 3.4 GHz DDR1600 OPENMP 2 thread | 3401\\*    | 5729\\*    | 5489\\*    | 7444\\*    | 5003\\*  | 8624\\*  | 5105\\*   | 8558\\*   |\n| i7-4770 @ 3.4 GHz DDR1600 OPENMP 4 thread | 4884\\*    | 7099\\*    | 4917\\*    | 7057\\*    | 4799\\*  | 7143\\*  | 4902\\*   | 7219\\*   |\n| i7-4770 @ 3.4 GHz DDR1600 OPENMP 8 thread | 5212\\*    | 8849\\*    | 5284\\*    | 9099\\*    | 5289\\*  | 9220\\*  | 4849\\*   | 9200\\*   |\n| i7-4870HQ @ 2.5 GHz                       | 1471\\*    | 3066\\*    | 6721\\*    | 6962\\*    | 7015\\*  | 8267\\*  | 8328\\*   | 11576\\*  |\n| i5-4590S @ 3.0 GHz                        | 3356      | 3197      | 4363      | 6104      | 4243\\*  | 6233    | 4160\\*   | 6344     |\n| Xeon X5570 @ 2.93 GHz                     | 2161      | 1508      | 3160      | 3915      | -       | -       | -        | -        |\n| Pentium4 @ 3.4 GHz                        | 896       | 740       | -         | -         | -       | -       | -        | -        |\n| Atom N270                                 | 243       | 266       | 508       | 387       | -       | -       | -        | -        |\n| AMD E-450                                 | 645       | 564       | 625       | 634       | -       | -       | -        | -        |\n| Intel Edison @ 500 MHz                    | 79\\*      | 92\\*      | 152\\*     | 172\\*     | -       | -       | -        | -        |\n| Intel Edison @ 500 MHz OPENMP 2 thread    | 158\\*     | 184\\*     | 300\\*     | 343\\*     | -       | -       | -        | -        |\n| Intel Edison @ 500 MHz (x86-64)           | 162       | 119       | 209       | 164       | -       | -       | -        | -        |\n| Intel Edison @ 500 MHz (x86-64) 2 thread  | 319       | 237       | 412       | 329       | -       | -       | -        | -        |\n\nARM processors\n\n| Processor                                 | Plain enc | Plain dec | NEON32 enc | NEON32 dec | NEON64 enc | NEON64 dec |\n|-------------------------------------------|----------:|----------:|-----------:|-----------:|-----------:|-----------:|\n| Raspberry PI B+ V1.2                      | 46\\*      | 40\\*      | -          | -          | -          | -          |\n| Raspberry PI 2 B V1.1                     | 85        | 141       | 300        | 225        | -          | -          |\n| Apple iPhone SE armv7                     | 1056\\*    | 895\\*     | 2943\\*     | 2618\\*     | -          | -          |\n| Apple iPhone SE arm64                     | 1061\\*    | 1239\\*    | -          | -          | 4098\\*     | 3983\\*     |\n\nPowerPC processors\n\n| Processor                                 | Plain enc | Plain dec |\n|-------------------------------------------|----------:|----------:|\n| PowerPC E6500 @ 1.8GHz                    | 270\\*     | 265\\*     |\n\n\nBenchmarks on i7-4770 @ 3.4 GHz DDR1600 with varrying buffer sizes:\n![Benchmarks](base64-benchmarks.png)\n\nNote: optimal buffer size to take advantage of the cache is in the range of 100 kB to 1 MB, leading to 12x faster AVX encoding/decoding compared to Plain, or a throughput of 24/27GB/sec.\nAlso note the performance degradation when the buffer size is less than 10 kB due to thread creation overhead.\nTo prevent this from happening `lib_openmp.c` defines `OMP_THRESHOLD 20000`, requiring at least a 20000 byte buffer to enable multithreading.\n\n## License\n\nThis repository is licensed under the\n[BSD 2-clause License](http://opensource.org/licenses/BSD-2-Clause). See the\nLICENSE file.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faklomp%2Fbase64","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faklomp%2Fbase64","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faklomp%2Fbase64/lists"}