123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2022-2023 TriliTech <contact@trili.tech> *)(* *)(* Permission is hereby granted, free of charge, to any person obtaining a *)(* copy of this software and associated documentation files (the "Software"),*)(* to deal in the Software without restriction, including without limitation *)(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)(* and/or sell copies of the Software, and to permit persons to whom the *)(* Software is furnished to do so, subject to the following conditions: *)(* *)(* The above copyright notice and this permission notice shall be included *)(* in all copies or substantial portions of the Software. *)(* *)(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)(* DEALINGS IN THE SOFTWARE. *)(* *)(*****************************************************************************)(* Version of durable storage corresponding to
https://gitlab.com/tezos/tezos/-/blob/668fe735aa20ce0c68b9f836208e57fa15d389c1/src/lib_scoru_wasm/durable.ml
*)openTezos_lazy_containersmoduleT=Tezos_tree_encoding.WrappedmoduleRunner=Tezos_tree_encoding.Runner.Make(Tezos_tree_encoding.Wrapped)moduleE=Tezos_tree_encodingmoduleStorage=Tezos_webassembly_interpreter.Durable_storagetypet=T.tree(* The maximum size of bytes allowed to be read/written at once. *)letmax_store_io_size=2048LexceptionInvalid_keyofstringexceptionIndex_too_largeofintexceptionValue_not_foundexceptionTree_not_foundexceptionDurable_empty=Storage.Durable_emptyexceptionOut_of_boundsof(int64*int64)exceptionIO_too_largeexceptionReadonly_valueletencoding=E.wrapped_treeletof_storage~defaults=matchStorage.to_treeswithSomet->t|None->defaultletof_storage_exns=Storage.to_tree_exnsletto_storaged=Storage.of_treedtypekey=Writeableofstringlist|Readonlyofstringlist(* A key is bounded to 250 bytes, including the implicit '/durable' prefix.
Additionally, values are implicitly appended with '_'. **)letmax_key_length=250-String.length"/durable"-String.length"/@"letkey_of_string_exns=ifString.lengths>max_key_lengththenraise(Invalid_keys);letkey=matchString.split'/'swith|""::tl->tl(* Must start with '/' *)|_->raise(Invalid_keys)inletassert_valid_char=function|'.'|'a'..'z'|'A'..'Z'|'0'..'9'|'-'|'_'->()|_->raise(Invalid_keys)inletall_steps_valid=List.for_all(funx->x<>""&&(String.iterassert_valid_charx;true))inifall_steps_validkeythenmatchkeywith"readonly"::_|[]->Readonlykey|_->Writeablekeyelseraise(Invalid_keys)letkey_of_string_opts=trySome(key_of_string_exns)withInvalid_key_->None(** We append all values with '@', which is an invalid key-character w.r.t.
external use.
This ensures that an external user is prevented from accidentally writing a
value to a place which is part of another value (e.g. writing a
chunked_byte_vector to "/a/length", where "/a/length" previously existed as
part of another chunked_byte_vector encoding.)
*)letvalue_marker="@"letto_value_keyk=List.appendk[value_marker]letassert_key_writeable=function|Readonly_->raiseReadonly_value|Writeable_->()letassert_max_bytesmax_bytes=ifmax_store_io_size<max_bytesthenraiseIO_too_largeletkey_contents=functionReadonlyk|Writeablek->kletfind_valuetreekey=letopenLwt.Syntaxinletkey=key_contentskeyinlet*opt=T.find_treetree@@to_value_keykeyinmatchoptwith|None->Lwt.return_none|Somesubtree->let+value=Runner.decodeChunked_byte_vector.encodingsubtreeinSomevalueletfind_value_exntreekey=letopenLwt.Syntaxinlet+opt=find_valuetreekeyinmatchoptwithNone->raiseValue_not_found|Somevalue->value(** helper function used in the copy/move *)letfind_tree_exntreekey=letopenLwt.Syntaxinletkey=key_contentskeyinlet+opt=T.find_treetreekeyinmatchoptwithNone->raiseTree_not_found|Somesubtree->subtreeletcopy_tree_exntree?(edit_readonly=false)from_keyto_key=letopenLwt.Syntaxinifnotedit_readonlythenassert_key_writeableto_key;let*move_tree=find_tree_exntreefrom_keyinletto_key=key_contentsto_keyinT.add_treetreeto_keymove_treeletcount_subtreestreekey=T.lengthtree@@key_contentskeyletlisttreekey=letopenLwt.Syntaxinlet+subtrees=T.listtree@@key_contentskeyinList.map(fun(name,_)->ifname="@"then""elsename)subtreesletdelete?(edit_readonly=false)treekey=ifnotedit_readonlythenassert_key_writeablekey;T.removetree@@key_contentskeyletsubtree_name_attreekeyindex=letopenLwt.Syntaxinlet*subtree=find_tree_exntreekeyinlet*list=T.list~offset:index~length:1subtree[]inletnth=List.nthlist0inmatchnthwith|Some(step,_)whenCompare.String.(step=value_marker)->Lwt.return""|Some(step,_)->Lwt.returnstep|None->raise(Index_too_largeindex)letmove_tree_exntreefrom_keyto_key=letopenLwt.Syntaxinassert_key_writeablefrom_key;assert_key_writeableto_key;let*move_tree=find_tree_exntreefrom_keyinlet*tree=deletetreefrom_keyinT.add_treetree(key_contentsto_key)move_treelethashtreekey=letopenLwt.Syntaxinletkey=to_value_key(key_contentskey)inlet+opt=T.find_treetreekeyinOption.map(funsubtree->T.hashsubtree)optlethash_exntreekey=letopenLwt.Syntaxinlet+opt=hashtreekeyinmatchoptwithNone->raiseValue_not_found|Somehash->hashletset_value_exntree?(edit_readonly=false)keystr=ifnotedit_readonlythenassert_key_writeablekey;letkey=to_value_key@@key_contentskeyinletencoding=E.scopekeyChunked_byte_vector.encodinginRunner.encodeencoding(Tezos_lazy_containers.Chunked_byte_vector.of_stringstr)treeletwrite_value_exntree?(edit_readonly=false)keyoffsetbytes=ifnotedit_readonlythenassert_key_writeablekey;letopenLwt.SyntaxinletopenTezos_lazy_containersinletnum_bytes=Int64.of_int@@String.lengthbytesinassert_max_bytesnum_bytes;letkey=to_value_key@@key_contentskeyinlet*opt=T.find_treetreekeyinletencoding=E.scopekeyChunked_byte_vector.encodinginlet*value=matchoptwith|None->Lwt.return@@Chunked_byte_vector.allocate0L|Some_subtree->Runner.decodeencodingtreeinletvec_len=Chunked_byte_vector.lengthvalueinifoffset>vec_lenthenraise(Out_of_bounds(offset,vec_len));letgrow_by=Int64.(num_bytes|>addoffset|>Fun.flipsubvec_len)inifInt64.comparegrow_by0L>0thenChunked_byte_vector.growvaluegrow_by;let*()=Chunked_byte_vector.store_bytesvalueoffset@@Bytes.of_stringbytesinRunner.encodeencodingvaluetreeletread_value_exntreekeyoffsetnum_bytes=letopenLwt.SyntaxinletopenTezos_lazy_containersinassert_max_bytesnum_bytes;let*value=find_value_exntreekeyinletvec_len=Chunked_byte_vector.lengthvalueinifoffset<0L||offset>=vec_lenthenraise(Out_of_bounds(offset,vec_len));letnum_bytes=Int64.(num_bytes|>addoffset|>minvec_len|>Fun.flipsuboffset)inlet+bytes=Chunked_byte_vector.load_bytesvalueoffsetnum_bytesinBytes.to_stringbytesmoduleInternal_for_tests=structletkey_is_readonly=functionReadonly_->true|Writeable_->falseletkey_to_list=key_contentsend