format.cpp format.hpp bit charconv cmath iomanip limits locale sstream type_traits serif serif::utils serif::utils::serialize serif::utils::serialize::detail #include"format.hpp" #include<bit> #include<charconv> #include<cmath> #include<iomanip> #include<limits> #include<locale> #include<sstream> #include<type_traits> namespaceserif::utils::serialize{ std::stringfingerprint(std::string_viewbytes){ std::uint64_thash=14695981039346656037ULL; for(unsignedcharbyte:bytes){ hash^=byte; hash*=1099511628211ULL; } std::ostringstreamoutput; output.imbue(std::locale::classic()); output<<"fnv1a64-v1:"<<std::hex<<std::setfill('0')<<std::setw(16)<<hash; returnoutput.str(); } } namespaceserif::utils::serialize::detail{ namespace{ voidappend_word(std::string&output,std::uint64_tword){ for(intshift=0;shift<64;shift+=8){ output.push_back(static_cast<char>((word>>shift)&0xff)); } } std::uint64_tread_word(std::string_viewbytes,std::size_tindex){ std::uint64_tword=0; for(intbyte=0;byte<8;++byte){ word|=static_cast<std::uint64_t>(static_cast<unsignedchar>(bytes[index*8+byte]))<<(8*byte); } returnword; } voidwrite_real(std::ostream&output,doublevalue){ if(std::isfinite(value)){ output<<std::setprecision(std::numeric_limits<double>::max_digits10)<<value; }else{ output<<"bits:"<<std::hex<<std::setfill('0')<<std::setw(16)<<std::bit_cast<std::uint64_t>(value)<<std::dec; } } boolread_real(std::istream&input,double&value){ std::stringtoken; if(!(input>>token)){ returnfalse; } if(token.starts_with("bits:")){ std::uint64_tbits=0; constautoparsed=std::from_chars(token.data()+5,token.data()+token.size(),bits,16); if(token.size()!=21||parsed.ec!=std::errc{}||parsed.ptr!=token.data()+token.size()){ returnfalse; } value=std::bit_cast<double>(bits); returntrue; } constautoparsed=std::from_chars(token.data(),token.data()+token.size(),value); returnparsed.ec==std::errc{}&&parsed.ptr==token.data()+token.size(); } template<typenameT> Result<Value>read_array(std::string_viewbytes,constEntry&entry,Formatformat){ if((format==Format::binary&&(bytes.size()%8!=0||entry.count!=bytes.size()/8))||entry.count>bytes.size()){ returnstd::unexpected(Error{entry.path,"Arrayextentdoesnotmatchthepayloadlength."}); } std::vector<T>values(entry.count); if(format==Format::binary){ for(std::size_tindex=0;index<values.size();++index){ values[index]=std::bit_cast<T>(read_word(bytes,index)); } }else{ std::istringstreaminput{std::string{bytes}}; input.imbue(std::locale::classic()); for(T&value:values){ boolvalid; ifconstexpr(std::same_as<T,double>){ valid=read_real(input,value); }else{ std::stringtoken; input>>token; constautoparsed=std::from_chars(token.data(),token.data()+token.size(),value); valid=!token.empty()&&parsed.ec==std::errc{}&&parsed.ptr==token.data()+token.size(); } if(!valid){ returnstd::unexpected(Error{entry.path,"Invalidnumberintextpayload."}); } } input>>std::ws; if(!input.eof()){ returnstd::unexpected(Error{entry.path,"Unexpectedtrailingtextinnumericpayload."}); } } returnValue{std::move(values)}; } template<typenameT> Result<Value>read_scalar(std::string_viewbytes,constEntry&entry,Formatformat){ if(entry.count!=1){ returnstd::unexpected(Error{entry.path,"Ascalarmusthaveexactlyonevalue."}); } autovalues=read_array<T>(bytes,entry,format); if(!values){ returnstd::unexpected(values.error()); } returnValue{std::get<std::vector<T>>(*values).front()}; } } std::uint64_tvalue_count(constValue&value){ returnstd::visit([](constauto&stored)->std::uint64_t{ usingT=std::remove_cvref_t<decltype(stored)>; ifconstexpr(std::same_as<T,std::monostate>){ return0; }elseifconstexpr(requires{stored.size();}){ returnstored.size(); }else{ return1; } },value); } std::stringencode_value(constValue&value,Formatformat){ std::stringbinary; std::ostringstreamtext; text.imbue(std::locale::classic()); constautoappend_number=[&](autonumber){ usingT=decltype(number); if(format==Format::binary){ append_word(binary,std::bit_cast<std::uint64_t>(number)); }else{ ifconstexpr(std::same_as<T,double>){ write_real(text,number); }else{ text<<number; } text<<'\n'; } }; std::visit([&](constauto&stored){ usingT=std::remove_cvref_t<decltype(stored)>; ifconstexpr(std::same_as<T,bool>){ binary.push_back(stored?'\1':'\0'); text<<(stored?'1':'0'); }elseifconstexpr(std::same_as<T,std::string>){ binary=stored; text<<std::quoted(stored); }elseifconstexpr(std::is_arithmetic_v<T>){ append_number(stored); }elseifconstexpr(!std::same_as<T,std::monostate>){ for(autonumber:stored){ append_number(number); } } },value); returnformat==Format::binary?binary:text.str(); } Result<Value>decode_value(std::string_viewbytes,constEntry&entry,Formatformat){ switch(entry.kind){ case0:{ if(entry.count!=0||!bytes.empty()){ returnstd::unexpected(Error{entry.path,"Acomponentdirectorycannotcontainascalarpayload."}); } returnValue{}; } case1:{ constcharzero=format==Format::binary?'\0':'0'; constcharone=format==Format::binary?'\1':'1'; if(entry.count!=1||bytes.size()!=1||(bytes[0]!=zero&&bytes[0]!=one)){ returnstd::unexpected(Error{entry.path,"Invalidbooleanpayload."}); } returnValue{bytes[0]==one}; } case2:{returnread_scalar<std::int64_t>(bytes,entry,format);} case3:{returnread_scalar<std::uint64_t>(bytes,entry,format);} case4:{returnread_scalar<double>(bytes,entry,format);} case5:{ std::stringvalue; if(format==Format::binary){ value=bytes; }else{ std::istringstreaminput{std::string{bytes}}; input.imbue(std::locale::classic()); if(input.peek()!='"'||!(input>>std::quoted(value))){ returnstd::unexpected(Error{entry.path,"Invalidquotedtextpayload."}); } input>>std::ws; if(!input.eof()){ returnstd::unexpected(Error{entry.path,"Unexpectedtrailingtextafterstring."}); } } if(value.size()!=entry.count){ returnstd::unexpected(Error{entry.path,"Stringlengthdoesnotmatchtheledger."}); } returnValue{std::move(value)}; } case6:{returnread_array<double>(bytes,entry,format);} case7:{returnread_array<std::int64_t>(bytes,entry,format);} case8:{returnread_array<std::uint64_t>(bytes,entry,format);} default:{returnstd::unexpected(Error{entry.path,"Unknownpayloadrepresentation."});} } } boolvalid_path(std::string_viewpath){ if(path.empty()||path.size()>4096||path.front()=='/'||path.back()=='/'||path.find("//")!=std::string_view::npos){ returnfalse; } for(unsignedcharcharacter:path){ constboolalphabet=(character>='a'&&character<='z')||(character>='A'&&character<='Z'); if(!alphabet&&!(character>='0'&&character<='9')&&character!='_'&&character!='-'&&character!='.'&&character!='/'){ returnfalse; } } returntrue; } }