{"id":18913356,"url":"https://github.com/erlang-punch/berty","last_synced_at":"2026-04-28T17:34:47.963Z","repository":{"id":187915636,"uuid":"676687021","full_name":"erlang-punch/berty","owner":"erlang-punch","description":"A clean, safe and flexible BERT implementation","archived":false,"fork":false,"pushed_at":"2023-11-02T07:52:31.000Z","size":170,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-24T17:41:04.535Z","etag":null,"topics":["audit","bert","elixir","erlang","etf","externaltermformat","security","serialization","serializer"],"latest_commit_sha":null,"homepage":"https://www.erlang-punch.com","language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/erlang-punch.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","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,"zenodo":null}},"created_at":"2023-08-09T19:17:43.000Z","updated_at":"2023-08-31T10:59:45.000Z","dependencies_parsed_at":"2023-08-12T18:53:19.900Z","dependency_job_id":"2051cea4-42d5-4177-be3d-1de393b7312f","html_url":"https://github.com/erlang-punch/berty","commit_stats":null,"previous_names":["erlang-punch/berty"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/erlang-punch/berty","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erlang-punch%2Fberty","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erlang-punch%2Fberty/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erlang-punch%2Fberty/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erlang-punch%2Fberty/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/erlang-punch","download_url":"https://codeload.github.com/erlang-punch/berty/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erlang-punch%2Fberty/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32392300,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-28T14:34:11.604Z","status":"ssl_error","status_checked_at":"2026-04-28T14:32:37.009Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["audit","bert","elixir","erlang","etf","externaltermformat","security","serialization","serializer"],"created_at":"2024-11-08T10:06:50.758Z","updated_at":"2026-04-28T17:34:47.945Z","avatar_url":"https://github.com/erlang-punch.png","language":"Erlang","funding_links":[],"categories":[],"sub_categories":[],"readme":"# berty\n\n![Erlang Punch Berty License](https://img.shields.io/github/license/erlang-punch/berty)\n![Erlang Punch Berty Top Language](https://img.shields.io/github/languages/top/erlang-punch/berty)\n![Erlang Punch Berty Workflow Status (main branch)](https://img.shields.io/github/actions/workflow/status/erlang-punch/berty/test.yaml?branch=main)\n![Erlang Punch Berty Last Commit](https://img.shields.io/github/last-commit/erlang-punch/berty)\n![Erlang Punch Berty Code Size (bytes)](https://img.shields.io/github/languages/code-size/erlang-punch/berty)\n![Erlang Punch Berty Repository File Count](https://img.shields.io/github/directory-file-count/erlang-punch/berty)\n![Erlang Punch Berty Repository Size](https://img.shields.io/github/repo-size/erlang-punch/berty)\n\nA clean, safe and flexible implementation of BERT, a data-structure\nformat inspired by Erlang ETF.\n\nThis project is in active development, and should not be used in\nproduction yet.\n\n## Features\n\nPrimary features:\n\n - [x] High level implementation of ETF in pure Erlang\n - [x] Atoms protection and limitation\n - [ ] Fine grained filtering based on type\n - [ ] Callback function or MFA\n - [ ] Fallback to `binary_to_term` function on demand\n - [ ] Drop terms on demande\n - [ ] Term size limitation\n - [ ] Custom options for term\n - [ ] Property based testing\n - [ ] BERT parser subset\n - [ ] Depth type protection\n - [ ] Fully documented\n - [ ] +90% coverage\n - [ ] 100% compatible with standard ETF\n - [ ] 100% compatible with BERT\n\nSecondary features:\n\n - [ ] Global or fine grained statistics\n - [ ] Profiling and benchmarking facilities\n - [ ] Logging facilities\n - [ ] Tracing facilities\n - [ ] ETF path\n - [ ] ETF schema\n - [ ] Custom parser subset based on behaviors\n - [x] ETF as stream of data\n - [ ] Usage example with ETF, BERT and/or custom parser\n - [ ] Low level optimization (optimized module with merl)\n\n## Usage\n\nBerty was created to easily replace `binary_to_term/1` and\n`binary_to_term/2` built-in functions. In fact, the implementation is\ntransparent in many cases. The big idea is to protect your system from\noutside, in particular atom and memory exhaution.\n\n```erlang\n% create an atom from scratch\nAtom = term_to_binary(test).\n\n% An atom is automatically converted as binary\n{ok, \u003c\u003c\"test\"\u003e\u003e}\n  = berty:decode(Atom).\n\n% different methods can be used to deal with atoms.\n{ok, test}\n  = berty:decode(Atom, #{ atoms =\u003e {create, 0.2, warning} }).\n\n% Other terms are supported\nTerms = term_to_binary([{ok,1.0,\"test\",\u003c\u003c\u003e\u003e}]),\n{ok, [{ok,1.0,\"test\",\u003c\u003c\u003e\u003e}]}\n  = berty:decode(Terms).\n```\n\nMore features are present, for example, dropping terms or creating\ncustom callbacks.\n\n```erlang\nLists = term_to_binary([1024,\u003c\u003c\u003e\u003e,\"test\"]).\n\n% let drop all integers\n{ok, [\u003c\u003c\u003e\u003e, \"test\"]}\n  = berty:decode(Lists, #{ integer_ext =\u003e drop\n                         , small_integer_ext =\u003e drop\n                         }).\n\n% let create a custom callback\nCallback = fun\n  (_Term, Rest) -\u003e\n    {ok, doh, Rest}\nend.\n{ok, [doh, \u003c\u003c\u003e\u003e, \"test\"]}\n  = berty:decode(Lists, #{ integer_ext =\u003e {callback, Callback}\n                         , small_integer_ext =\u003e {callback, Callback}\n                         }).\n\n% let create another one.\nCallback2 = fun\n  (Term, Rest) when 1024 =:= Term -\u003e\n    logger:warning(\"catch term ~p\", [1024]),\n    {ok, Term, Rest};\n  (Term, Rest) -\u003e {ok, Term, Rest}\nend.\n\n{ok, [1024, \u003c\u003c\u003e\u003e, \"test\"]}\n  = berty:decode(Lists, #{ integer_ext =\u003e {callback, Callback2}\n                         , small_integer_ext =\u003e {callback, Callback2}\n                         }).\n```\n\nThose are simple examples, more features are present and will be\nadded. Here the most important functions:\n\n - `berty:decode/1`: standard BERT decoder with default options\n - `berty:decode/2`: standard BERT decoder with custom options\n - `berty:decode/3`: custom decoder with custom options\n - `berty:encode/1`: standard BERT encoder with default options\n - `berty:encode/2`: standard BERT encoder with custom options\n - `berty:encode/3`: custom encoder with custom options\n - `berty:binary_to_term/1`: wrapper around `binary_to_term/1`\n - `berty:term_to_binary/1`: wrapper around `term_to_binary/1`\n\n## Build\n\n```sh\nrebar3 compile\nrebar3 shell\n```\n\n## Test\n\n```sh\nrebar3 as test eunit\nrebar3 as test shell\n```\n\n# FAQ\n\n## Why creating another BERT implementation?\n\nMainly because of atoms management. In fact, `binary_to_term/1` and\n`term_to_binary/1` are not safe, if unknown data are coming from\nuntrusted source, it's quite easy to simply kill the node by\noverflowing the number of atoms managed by the node itself, and\nprobably also a full cluster if this data is shared.\n\n```erlang\n% first erlang shell\nfile:write_file(\"atom1\", term_to_binary([ list_to_atom(\"$test-\" ++ integer_to_list(X)) || X \u003c- lists:seq(1,1_000_000) ])).\n% second erlang shell\nfile:write_file(\"atom2\", term_to_binary([ list_to_atom(\"$test-\" ++ integer_to_list(X)) || X \u003c- lists:seq(1_000_000,2_000_000) ])).\n```\n\nNow restore those 2 files on another node.\n\n```erlang\n% third erlang shell\nf(D), {ok, D} = file:read_file(\"atom1\"), binary_to_term(D).\nf(D), {ok, D} = file:read_file(\"atom2\"), binary_to_term(D).\nno more index entries in atom_tab (max=1048576)\n\nCrash dump is being written to: erl_crash.dump...done\n```\n\nDoh. Erlang VM crashed. We can fix that in many different way, here\nfew examples:\n\n - avoid using `binary_to_term/1` and `term_to_binary/1` functions,\n   instead create our own parser based on ETF specification. When\n   terms are deserialized, atoms can be (1) converted in existing atom\n   (2) converted in binary or list (3) simply dropped or replaced with\n   something to alert the VM this part of the data is dangerous.\n\n - keep our own local atom table containing all atom deserialized. A\n   soft/hard limit can be set.\n\n## Oh?  really? Is it serious?\n\nIn fact, a simple solution already exists, using the option `safe` or\n`used` when using\n[`binary_to_term/2`](https://www.erlang.org/doc/man/erlang.html#binary_to_term-2). It\nwill protect you from creating non-existing atoms, but how many\nprojects are using that?\n\n- [`mojombo/bert.erl`](https://github.com/mojombo/bert.erl):\n  https://github.com/mojombo/bert.erl/blob/master/src/bert.erl#L25\n\n  ```erlang\n  -spec decode(binary()) -\u003e term().\n\n  decode(Bin) -\u003e\n    decode_term(binary_to_term(Bin)).\n\n  ```\n\n- [`mojombo/ernie`](https://github.com/mojombo/ernie):\n  https://github.com/mojombo/ernie/blob/master/elib/ernie_server.erl#L178\n\n  ```erlang\n  receive_term(Request, State) -\u003e\n    Sock = Request#request.sock,\n      case gen_tcp:recv(Sock, 0) of\n          {ok, BinaryTerm} -\u003e\n            logger:debug(\"Got binary term: ~p~n\", [BinaryTerm]),\n            Term = binary_to_term(BinaryTerm),\n  ```\n\n- [`sync/n2o`](https://github.com/synrc/n2o):\n  https://github.com/synrc/n2o/blob/master/src/services/n2o_bert.erl#L8\n\n  ```erlang\n  encode(#ftp{}=FTP) -\u003e term_to_binary(setelement(1,FTP,ftpack));\n  encode(Term)       -\u003e term_to_binary(Term).\n  decode(Bin)        -\u003e binary_to_term(Bin).\n  ```\n\n- [`ferd/bertconf`](https://github.com/ferd/bertconf):\n  https://github.com/ferd/bertconf/blob/master/src/bertconf_lib.erl#L10\n\n  ```erlang\n  decode(Bin) -\u003e\n      try validate(binary_to_term(Bin)) of\n        Terms -\u003e {ok, Terms}\n      catch\n        throw:Reason -\u003e {error, Reason}\n      end.\n  ```\n\n- [`a13x/aberth`](https://github.com/a13x/aberth):\n  https://github.com/a13x/aberth/blob/master/src/bert.erl#L25\n\n  ```erlang\n  -spec decode(binary()) -\u003e term().\n\n  decode(Bin) -\u003e\n    decode_term(binary_to_term(Bin)).\n  ```\n\n\n- [`yuce/bert.erl`](https://github.com/yuce/bert.erl):\n  https://github.com/yuce/bert.erl/blob/master/src/bert.erl#L24\n\n  ```erlang\n  -spec decode(binary()) -\u003e term().\n  decode(Bin) -\u003e\n      decode_term(binary_to_term(Bin)).\n  ```\n\n- And probably many more like this search on\n  [`searchcode.com`](https://searchcode.com/?lan=25\u0026q=binary_to_term)\n  or\n  [`github.com`](https://github.com/search?q=binary_to_term+language%3AErlang\u0026type=code\u0026l=Erlang)\n  suggest.\n\nIt's highly probable lot of those functions are hard to call, but it\ncould be the case. In situation where unknown data are coming,\n`erlang:binary_to_term/1` and even `erlang:binary_to_term/2` should be\navoided or carefully used.\n\n## Why am I not aware of that?\n\nFew articles[^erlef-atom-exhaustion][^paraxial-atom-dos] have been\ncreated in the past to explain these problems. On my side, if I was in\ncharge of fixing this issue, I would probably do something in two\ntimes.\n\nIn the first step, I would probably create a workaround on atom\ncreation function, with a soft/hard limit. When we reach the soft\nlimit, warnings are displayed saying we reached the soft limit, but we\ncan still create new atoms. When reaching the hard limit, atoms can't\nbe created anymore, and exceptions are raised instead of crashing the\nhost.\n\nIn a second step, I would probably create a flexible interface to\ndeal with atoms and divide the problem in half:\n\n 1. create fixed atom store containing only atoms from source code\n    (Erlang release and project), this one can't be increased.\n\n 2. create a second atom store containing dynamically created atoms\n    during runtime, this one can be increased.\n\nWhat I worry about is when dealing with mnesia. What could happen if\nsomeone create more than 2M unwanted atoms added in Mnesia or DETS?\nWhat kind of behavior the cluster will have? And how to fix that if\nit's critical.\n\nUnfortunately, I think it will totally break atom performance, but it\ncould be an interesting project to learn how Erlang BEAM works under\nthe hood.\n\n[^erlef-atom-exhaustion]: https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/atom_exhaustion.html\n[^paraxial-atom-dos]: https://paraxial.io/blog/atom-dos\n\n## Are atoms the only issue there?\n\nWell, it depends. If you are receving a (very) long string or list\ncontaining terms, it will have a direct impact on the memory, and it\nwill eventually lead to memory exhaustion:\n\n```erlang\n% size of the list should be checked\n% if not, memory exhaustion can happen\n[ $1 || _ \u003c- lists:seq(0,160_000_000) ].\n% eheap_alloc: Cannot allocate 3936326656 bytes of memory (of type \"heap\").\n% Crash dump is being written to: erl_crash.dump...\n```\n\nSame behavior can be generated using binaries:\n\n```erlang\n% big binaries can crash the BEAM\nbinary_to_term(\u003c\u003c131, 111, 4294967294:32/unsigned-integer, 0:8/integer, 255:8, 0:4294967280/unsigned-integer\u003e\u003e).\n% binary_alloc: Cannot allocate 4294967293 bytes of memory (of type \"binary\").\n% Crash dump is being written to: erl_crash.dump...\n```\n\nGenerating ETF payload with very long binaries can also have\nan impact on CPUs, the following code can generate DoS and if many process\n\n```erlang\n% big payload, high cpu usage, no crash.\n% size of the big integer must be checked\n% size: 2**18-1, binary byte size: 262_150 (~262kB)\n_ = binary_to_term(\u003c\u003c131, 111, 262_143:32/unsigned-integer, 0:8/integer, 255:2_097_144/unsigned-integer\u003e\u003e).\n\n% size: 2**19-1, binary byte size: 524_294 (~524kB)\n_ = binary_to_term(\u003c\u003c131, 111, 524_287:32/unsigned-integer, 0:8/integer, 255:4_194_296/unsigned-integer\u003e\u003e).\n\n% size: 2**20-1, binary byte size: 1_048_582 (~1MB)\n_ = binary_to_term(\u003c\u003c131, 111, 1_048_575:32/unsigned-integer, 0:8/integer, 255:8_388_600/unsigned-integer\u003e\u003e).\n```\n\nCreating a long node name can crash the VM during startup, because the\nname of the node is encoded using an `atom_ext` term, encoded on 255\nbits. If the name of the node is greater than 255, it crashes.\n\n```sh\nerl -sname $(pwgen -A0 252 1)\n# Crash dump is being written to: erl_crash.dump...done\n\nerl -name $(pwgen -A0 246 1)@localhost\n# Crash dump is being written to: erl_crash.dump...done\n```\n\nIt's highly probable other terms can have a deadly impact on a node or\na cluster.\n\n## How to fix the root cause?\n\nThe problem is from atoms, at least one\npaper[^atom-garbage-collection] talked about that. Fixing the garbage\ncollection issue could help a lot, but if it's not possible for many\nreason, using an high level implementation of ETF with some way to\ncontrol what kind of data are coming might be an \"okayish\" solution.\n\nThe \"Let it crash\" philosophy is quite nice when developing high level\napplication interacting in a safe place but this philosophy can't be\napplied in a place where uncontrolled data is coming. Some functions,\nlike `binary_to_term/1` must be avoid at all cost.\n\n[^atom-garbage-collection]: Atom garbage collection by Thomas Lindgren, https://dl.acm.org/doi/10.1145/1088361.1088369\n\n## What about ETF schema?\n\nThis answer is a draft, a sandbox to design an Erlang ETF Schema\nfeature.\n\nIt might be great to have syntax to create ETF schema, a bit like\nprotobuf[^protobuf], json schema[^json-schema], XML[^xml] (with\nXLST[^xlst]) or ASN.1[^asn.1]. In fact, when I started to find\nsomething around this feature, I also found UBF[^ubf] project from Joe\nArmstrong.\n\n```erlang\nschema1() -\u003e\n  integer().\n\nschema2() -\u003e\n  tuple([[atom(ok), integer()]\n        ,[atom(error), string(1024)]).\n\n% fun ({ok, X}) when is_integer(X) -\u003e true;\n%     ({error, X) when is_list(X) andalso length(X) =\u003c 1024 -\u003e is_string(X);\n%     (_) -\u003e false.\n\nschema3() -\u003e\n  tuple(\n```\n\nHere the final representation.\n\n```erlang\n[{tuple, [{atom, [ok]}, {integer, []}]}\n,{tuple, [{atom, [error]}, {string, [1024]}]}\n]\n% or\n[[tuple, [2]]\n,[atom, [ok,error]]\n,[integer, []]\n,[string, [1024]]\n].\n```\n\n[^protobuf]: https://protobuf.dev/overview/\n[^json-schema]: https://json-schema.org/\n[^xml]: https://en.wikipedia.org/wiki/XML\n[^xlst]: https://en.wikipedia.org/wiki/XSLT\n[^asn.1]: https://en.wikipedia.org/wiki/ASN.1\n[^ubf]: https://ubf.github.io/ubf/ubf-user-guide.en.html\n\n## What about an ETF path feature?\n\nAnother feature like xmlpath or jsonpath is also required as well, an\neasy syntax and comprehensible one needs to be created. I would like\nto include:\n\n 1. pattern matching\n\n```erlang\n% how to create an etf path?\n% first example\n% ETF = #{ key =\u003e #{ key2 =\u003e { ok, \"test\"} } }.\n\"test\" = path(ETF, \"#key#key2{ok,@}\")\n\n% second example\n% ETF = [{ok, \"test\"}, {error, badarg}, {ok, \"data\"}].\n[{ok, \"test\"},{ok, \"data\"}] = path(ETF, \"[{ok,_}]\")\n% or\n[]{ok,_}\n\n% third example\n% ETF = {ok, #{ \u003c\u003c\"data\"\u003e\u003e =\u003e [\u003c\u003c\"test\"\u003e\u003e] }}.\n[\u003c\u003c\"test\"\u003e\u003e] = path(ETF, \"{ok,@}#!data\").\n```\n\n## Nothing to add?\n\nWhen I wrote [Serialization series — Do you speak Erlang ETF or BERT?\n(part\n1)](https://medium.com/@niamtokik/serialization-series-do-you-speak-erlang-etf-or-bert-part-1-ff70096b50c0)\nin 2017, someone told me to check another project called\n[`jem.js`](https://github.com/inaka/jem.js) and read [Replacing JSON\nwhen talking to Erlang](http://inaka.net/blog/2016/08/17/why-json/)\n([archive](https://web.archive.org/web/20180301221900/http://inaka.net/blog/2016/08/17/why-json/))\nblog post. What's funny here... Is that:\n\n```erlang\nhandle_post(Req, State) -\u003e\n  {ok, Body, Req1} = cowboy_req:body(Req),\n  Decoded = erlang:binary_to_term(Body),\n  Reply = do_whatever(Decoded),\n  {erlang:term_to_binary(Reply), Req1, State}.\n```\n\nYes, \"Faster and more efficient\", but can destroy your whole platform\nin few second. Don't do that. Please. Unfortunately,\n[inaka.net](inaka.net) seems to be down, it would have been funny to\nplay with that.\n\n## Is there a \"risk analysis\" for each terms somewhere?\n\nProbably, but I did not find a lot on that. Here a short summary of\neach terms is it safe or not and with the risk(s).\n\n| Terms                 | Code |    Safe? | Risks\n|:----------------------|-----:|---------:|--------------------------|\n| `ATOM_CACHE_REF`      |   82 |       no | atom exhaustion\n| `ATOM_EXT`            |  100 |       no | atom exhaustion\n| `ATOM_UTF8_EXT`       |  118 |       no | atom exhaustion\n| `BINARY_EXT`          |  109 |    maybe | dynamic binary length (32bits)\n| `BIT_BINARY_EXT`      |   77 |    maybe | dynamic bitstring length (32bits)\n| `EXPORT_EXT`          |  113 |       no | atom exhaustion\n| `FLOAT_EXT`           |   99 |      yes | 31 bytes float fixed length\n| `FUN_EXT`             |  117 |       no | atoms exhaution\n| `INTEGER_EXT`         |   98 |      yes | 1 byte fixed length\n| `LARGE_BIG_EXT`       |  111 |    maybe | dynamic integer length (32bits)\n| `LARGE_TUPLE_EXT`     |  105 |    maybe | dynamic tuple length (32bits)\n| `LIST_EXT`            |  108 |    maybe | dynamic list length (32bits)\n| `LOCAL_EXT`           |  121 |      yes | atom exhaustion\n| `MAP_EXT`             |  116 |    maybe | dynamic pair length (32bits)\n| `NEWER_REFERENCE_EXT` |   90 |       no | memory exhaustion\n| `NEW_FLOAT_EXT`       |   70 |      yes | 8 bytes fixed float\n| `NEW_FUN_EXT`         |  112 |       no | atom exhaution\n| `NEW_PID_EXT`         |   88 |       no | atom exhaution\n| `NEW_PORT_EXT`        |   89 |       no | atom exhaution\n| `NEW_REFERENCE_EXT`   |  114 |    maybe | dynamic reference length (16bits)\n| `NIL_EXT`             |  106 |      yes | fixed length\n| `PID_EXT`             |  103 |       no | atom exhaustion\n| `PORT_EXT`            |  102 |       no | atom exhaustion\n| `REFERENCE_EXT`       |  101 |       no | atom exhaustion\n| `SMALL_ATOM_EXT`      |  115 |       no | atom exhaustion\n| `SMALL_ATOM_UTF8_EXT` |  119 |       no | atom exhaustion\n| `SMALL_BIG_EXT`       |  110 |    maybe | dynamic integer length (8bits)\n| `SMALL_INTEGER_EXT`   |   97 |      yes | fixed size\n| `SMALL_TUPLE_EXT`     |  104 |    maybe | dynamic tuple length (8bits)\n| `STRING_EXT`          |  107 |    maybe | dynamic string length (16bits)\n| `V4_PORT_EXT`         |  120 |       no | atom exhaustion\n\n# Resources\n\n - [BERT-RPC Official](https://bert-rpc.org) [(archive)](https://web.archive.org/web/20160304092040/http://bert-rpc.org/)\n - [BERT-RPC Google group](https://groups.google.com/g/bert-rpc)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferlang-punch%2Fberty","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ferlang-punch%2Fberty","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferlang-punch%2Fberty/lists"}