123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800openCoreopenInt.Replace_polymorphic_comparemoduleArray=structincludeArray(* [init] does not promise to do so in ascending index order. I think nobody would ever
imagine changing this, and it would show up in our tests, but it's cheap to be
explicit. *)letinit_ascendinglen~f=matchlenwith|0->[||]|len->letres=create~len(f())infori=1tolen-1dounsafe_setresi(f())done;resend(* When [Word_size.word_size = W32], [int] can take at most 31 bits, so the max is 2**30.
[Iobuf.Consume.int32_be] silently truncates. This is a shame (ideally it would return
a [Int63.t]; see the comment at the top of [Int32.t], but otherwise an [Int32.t] would
certainly suffice) and makes it quite annoying to safely implement something that reads
32 bit ints on a 32 bit ocaml platform.
Looking below, the 32 bit ints are
- [message_length]: we'd refuse to read messages larger than 2**30 anyway,
- [num_fields] (in a row): guaranteed to be < 1600 by postgres
(https://www.postgresql.org/docs/11/ddl-basics.html),
- [pid]: on linux, less than 2**22 (man 5 proc),
- [secret] (from [BackendKeyData]): no guarantees.
so, for now it seems safe enough to to stumble on in 32-bit-mode even though iobuf
would silently truncate the ints. This is unsatisfying (besides not supporting reading
[secret]) because if we've made a mistake, or have a bug, we'd rather crash on the
protocol error than truncate.
We'll revisit it if someone wants it. *)moduleFrontend=structmoduleShared=structletvalidate_null_terminated_exn~field_namestr=ifString.memstr'\x00'thenraise_s[%message"String may not contain nulls"field_namestr]letfill_null_terminatediobufstr=Iobuf.Fill.stringoiobufstr;Iobuf.Fill.chariobuf'\x00'letint16_min=-32768letint16_max=32767letint32_min=matchWord_size.word_sizewith|W64->Int32.to_int_exnInt32.min_value|W32->Int.min_valueletint32_max=matchWord_size.word_sizewith|W64->Int32.to_int_exnInt32.max_value|W32->Int.max_valuelet()=matchWord_size.word_sizewith|W64->assert(String.equal(Int.to_stringint32_min)"-2147483648");assert(String.equal(Int.to_stringint32_max)"2147483647")|W32->assert(String.equal(Int.to_stringint32_min)"-1073741824");assert(String.equal(Int.to_stringint32_max)"1073741823")let[@inlinealways]fill_int16_beiobufvalue=matchint16_min<=value&&value<=int16_maxwith|true->Iobuf.Fill.int16_be_trunciobufvalue|false->failwithf"int16 out of range: %i"value()let[@inlinealways]fill_int32_beiobufvalue=matchint32_min<=value&&value<=int32_maxwith|true->Iobuf.Fill.int32_be_trunciobufvalue|false->failwithf"int32 out of range: %i"value()endmoduleStartupMessage=structletmessage_type_char=Nonetypet={user:string;database:string}letvalidate_exn{user;database}=Shared.validate_null_terminated_exn~field_name:"user"user;Shared.validate_null_terminated_exn~field_name:"database"databaseletpayload_length{user;database}=2+2+4+1+String.lengthuser+1+8+1+String.lengthdatabase+1+1letfill{user;database}iobuf=Iobuf.Fill.int16_be_trunciobuf3;(* major *)Iobuf.Fill.int16_be_trunciobuf0;(* minor *)Iobuf.Fill.stringoiobuf"user\x00";Shared.fill_null_terminatediobufuser;Iobuf.Fill.stringoiobuf"database\x00";Shared.fill_null_terminatediobufdatabase;Iobuf.Fill.chariobuf'\x00'endmodulePasswordMessage=structletmessage_type_char=Some'p'typet=|Cleartext_or_md5_hexofstring|Gss_binary_blobofstringletvalidate_exn=function|Cleartext_or_md5_hexpassword->Shared.validate_null_terminated_exn~field_name:"password"password|Gss_binary_blob_->()letpayload_length=function|Cleartext_or_md5_hexpassword->String.lengthpassword+1|Gss_binary_blobblob->String.lengthblobletfilltiobuf=matchtwith|Cleartext_or_md5_hexpassword->Shared.fill_null_terminatediobufpassword|Gss_binary_blobblob->Iobuf.Fill.stringoiobufblobendmoduleParse=structletmessage_type_char=Some'P'typet={destination:Types.Statement_name.t;query:string}letvalidate_exnt=Shared.validate_null_terminated_exnt.query~field_name:"query"letpayload_lengtht=+String.length(Types.Statement_name.to_stringt.destination)+1+String.lengtht.query+1+2letfilltiobuf=Shared.fill_null_terminatediobuf(Types.Statement_name.to_stringt.destination);Shared.fill_null_terminatediobuft.query;Iobuf.Fill.int16_be_trunciobuf0(* zero parameter types *)endmoduleBind=structletmessage_type_char=Some'B'typet={destination:Types.Portal_name.t;statement:Types.Statement_name.t;parameters:stringoptionarray}letvalidate_exn(_:t)=()letpayload_lengtht=letparameter_length=function|None->0|Somes->String.lengthsin+String.length(Types.Portal_name.to_stringt.destination)+1+String.length(Types.Statement_name.to_stringt.statement)+1+2(* # parameter format codes = 1 *)+2(* single parameter format code *)+2(* # parameters *)+4*Array.lengtht.parameters(* parameter sizes *)+Array.sum(moduleInt)t.parameters~f:parameter_length+2(* # result format codes = 1 *)+2(* single result format code *)letfilltiobuf=Shared.fill_null_terminatediobuf(Types.Portal_name.to_stringt.destination);Shared.fill_null_terminatediobuf(Types.Statement_name.to_stringt.statement);Iobuf.Fill.int16_be_trunciobuf1;(* 1 parameter format code *)Iobuf.Fill.int16_be_trunciobuf0;(* all parameters are text *)letnum_parameters=Array.lengtht.parametersinShared.fill_int16_beiobufnum_parameters;foridx=0tonum_parameters-1domatcht.parameters.(idx)with|None->Shared.fill_int32_beiobuf(-1)|Somestr->Shared.fill_int32_beiobuf(String.lengthstr);Iobuf.Fill.stringoiobufstrdone;Iobuf.Fill.int16_be_trunciobuf1;(* 1 result format code *)Iobuf.Fill.int16_be_trunciobuf0;(* all results are text *)endmoduleExecute=structletmessage_type_char=Some'E'typenum_rows=|Unlimited|Limitofinttypet={portal:Types.Portal_name.t;limit:num_rows}letvalidate_exnt=matcht.limitwith|Unlimited->()|Limitn->(ifn<=0thenfailwith"When provided, num rows limit must be positive")letpayload_lengtht=+String.length(Types.Portal_name.to_stringt.portal)+1+4letfilltiobuf=Shared.fill_null_terminatediobuf(Types.Portal_name.to_stringt.portal);letlimit=matcht.limitwith|Unlimited->0|Limitn->ninShared.fill_int32_beiobuflimitendmoduleStatement_or_portal_action=structtypet=|StatementofTypes.Statement_name.t|PortalofTypes.Portal_name.tletvalidate_exn(_t:t)=()letpayload_lengtht=letstr=matchtwith|Statements->Types.Statement_name.to_strings|Portals->Types.Portal_name.to_stringsin1+String.lengthstr+1letfilltiobuf=matchtwith|Statements->Iobuf.Fill.chariobuf'S';Shared.fill_null_terminatediobuf(Types.Statement_name.to_strings)|Portalp->Iobuf.Fill.chariobuf'P';Shared.fill_null_terminatediobuf(Types.Portal_name.to_stringp)endmoduleDescribe=structletmessage_type_char=Some'D'includeStatement_or_portal_actionendmoduleClose=structletmessage_type_char=Some'C'includeStatement_or_portal_actionendmoduleCopyFail=structletmessage_type_char=Some'f'typet={reason:string}letvalidate_exnt=Shared.validate_null_terminated_exnt.reason~field_name:"reason"letpayload_lengtht=String.lengtht.reason+1letfilltiobuf=Shared.fill_null_terminatediobuft.reason;endmoduleCopyData=structletmessage_type_char=Some'd'typet=stringletvalidate_exn(_:t)=()letpayload_lengtht=String.lengthtletfilltiobuf=Iobuf.Fill.stringoiobuftendmoduleNo_arg:sigvalflush:stringvalsync:stringvalcopy_done:stringvalterminate:stringend=structletgen~constructor=lettmp=Iobuf.create~len:5inIobuf.Poke.chartmp~pos:0constructor;(* fine to use Iobuf's int32 function, as [4] is clearly in range. *)Iobuf.Poke.int32_be_trunctmp~pos:14;Iobuf.to_stringtmpletflush=gen~constructor:'H'letsync=gen~constructor:'S'letcopy_done=gen~constructor:'c'letterminate=gen~constructor:'X'endincludeNo_argmoduleWriter=structopenAsyncmoduletypeMessage_type=sigvalmessage_type_char:charoptiontypetvalvalidate_exn:t->unitvalpayload_length:t->intvalfill:t->(read_write,Iobuf.seek)Iobuf.t->unitendtype'awith_computed_length={payload_length:int;value:'a}letwrite_message(typea)(moduleM:Message_typewithtypet=a)=letfull_length{payload_length;_}=matchM.message_type_charwith|None->payload_length+4|Some_->payload_length+5inletblit_to_bigstringwith_computed_lengthbigstring~pos=letiobuf=Iobuf.of_bigstringbigstring~pos~len:(full_lengthwith_computed_length)in(matchM.message_type_charwith|None->()|Somec->Iobuf.Fill.chariobufc);let{payload_length;value}=with_computed_lengthinShared.fill_int32_beiobuf(payload_length+4);M.fillvalueiobuf;(matchIobuf.is_emptyiobufwith|true->()|false->failwith"postgres message filler lied about length")inStaged.stage(funwritervalue->M.validate_exnvalue;letpayload_length=M.payload_lengthvalueinWriter.write_gen_wholewriter{payload_length;value}~length:full_length~blit_to_bigstring)letstartup_message=Staged.unstage(write_message(moduleStartupMessage))letpassword_message=Staged.unstage(write_message(modulePasswordMessage))letparse=Staged.unstage(write_message(moduleParse))letbind=Staged.unstage(write_message(moduleBind))letclose=Staged.unstage(write_message(moduleClose))letdescribe=Staged.unstage(write_message(moduleDescribe))letexecute=Staged.unstage(write_message(moduleExecute))letcopy_fail=Staged.unstage(write_message(moduleCopyFail))letcopy_data=Staged.unstage(write_message(moduleCopyData))letflushwriter=Writer.writewriterNo_arg.flushletsyncwriter=Writer.writewriterNo_arg.syncletcopy_donewriter=Writer.writewriterNo_arg.copy_doneletterminatewriter=Writer.writewriterNo_arg.terminateendendmoduleBackend=structtypeconstructor=|AuthenticationRequest|BackendKeyData|BindComplete|CloseComplete|CommandComplete|CopyData|CopyDone|CopyInResponse|CopyOutResponse|CopyBothResponse|DataRow|EmptyQueryResponse|ErrorResponse|FunctionCallResponse|NoData|NoticeResponse|NotificationResponse|ParameterDescription|ParameterStatus|ParseComplete|PortalSuspended|ReadyForQuery|RowDescription[@@derivingsexp,compare]typefocus_on_message_error=|Unknown_message_typeofchar|Iobuf_too_short|Nonsense_message_lengthofintletconstructor_of_char=function|'R'->OkAuthenticationRequest|'K'->OkBackendKeyData|'2'->OkBindComplete|'3'->OkCloseComplete|'C'->OkCommandComplete|'d'->OkCopyData|'c'->OkCopyDone|'G'->OkCopyInResponse|'H'->OkCopyOutResponse|'W'->OkCopyBothResponse|'D'->OkDataRow|'I'->OkEmptyQueryResponse|'E'->OkErrorResponse|'V'->OkFunctionCallResponse|'n'->OkNoData|'N'->OkNoticeResponse|'A'->OkNotificationResponse|'t'->OkParameterDescription|'S'->OkParameterStatus|'1'->OkParseComplete|'s'->OkPortalSuspended|'Z'->OkReadyForQuery|'T'->OkRowDescription|other->Error(Unknown_message_typeother)letfocus_on_messageiobuf=letiobuf_length=Iobuf.lengthiobufinifiobuf_length<5thenErrorIobuf_too_shortelse(letmessage_length=(Iobuf.Peek.int32_beiobuf~pos:1)+1inifiobuf_length<message_lengththenErrorIobuf_too_shortelseifmessage_length<5thenError(Nonsense_message_lengthmessage_length)else(letchar=Iobuf.Peek.chariobuf~pos:0inmatchconstructor_of_charcharwith|Error_aserr->err|Ok_asok->Iobuf.resizeiobuf~len:message_length;Iobuf.advanceiobuf5;ok))moduleShared=structletfind_null_exniobuf=letrecloop~iobuf~length~pos=ifChar.(=)(Iobuf.Peek.chariobuf~pos)'\x00'thenposelseifpos>length-1thenfailwith"find_null_exn could not find \\x00"elseloop~iobuf~length~pos:(pos+1)inloop~iobuf~length:(Iobuf.lengthiobuf)~pos:0letconsume_cstring_exniobuf=letlen=find_null_exniobufinletres=Iobuf.Consume.stringiobuf~len:len~str_pos:0inletzero=Iobuf.Consume.chariobufinassert(Char.(=)zero'\x00');resendmoduleError_or_Notice=structletfield_name=function|'S'->"severity"|'V'->"severity-non-localised"|'C'->"code"|'M'->"message"|'D'->"detail"|'H'->"hint"|'P'->"position"|'p'->"internal_position"|'q'->"internal_query"|'W'->"where"|'s'->"schema"|'t'->"table"|'c'->"column"|'d'->"data_type"|'n'->"constraint"|'F'->"file"|'L'->"line"|'R'->"routine"|other->(* the spec requires that we silently ignore unrecognised codes. *)sprintf"unknown-%c"otherletconsume_exniobuf=letrecloop~iobuf~fields_rev=matchIobuf.Consume.chariobufwith|'\x00'->Info.create_s[%sexp(List.revfields_rev:(string*string)list)]|other->lettok=field_nameotherinletvalue=Shared.consume_cstring_exniobufinloop~iobuf~fields_rev:((tok,value)::fields_rev)inloop~iobuf~fields_rev:[]endmoduleErrorResponse=structtypet=Error.tletconsumeiobuf=matchError_or_Notice.consume_exniobufwith|exceptionexn->error_s[%message"Failed to parse ErrorResponse"(exn:Exn.t)]|info->Ok(Error.of_infoinfo)endmoduleNoticeResponse=structtypet=Info.tletconsumeiobuf=matchError_or_Notice.consume_exniobufwith|exceptionexn->error_s[%message"Failed to parse NoticeResponse"(exn:Exn.t)]|info->OkinfoendmoduleAuthenticationRequest=structtypet=|Ok|KerberosV5|CleartextPassword|MD5Passwordof{salt:string}|SCMCredential|GSS|SSPI|GSSContinueof{data:string}[@@derivingsexp]letconsume_exniobuf=matchIobuf.Consume.int32_beiobufwith|0->Ok|2->KerberosV5|3->CleartextPassword|5->letsalt=Iobuf.Consume.stringo~len:4iobufinMD5Password{salt}|6->SCMCredential|7->GSS|9->SSPI|8->letdata=Iobuf.Consume.stringoiobufinGSSContinue{data}|other->raise_s[%message"AuthenticationRequest unrecognised type"(other:int)]letconsumeiobuf=matchconsume_exniobufwith|exceptionexn->error_s[%message"Failed to parse AuthenticationRequest"(exn:Exn.t)]|t->Result.OktendmoduleParameterStatus=structtypet={key:string;data:string}letconsume_exniobuf=letkey=Shared.consume_cstring_exniobufinletdata=Shared.consume_cstring_exniobufin{key;data}letconsumeiobuf=matchconsume_exniobufwith|exceptionexn->error_s[%message"Failed to parse ParameterStatus"(exn:Exn.t)]|t->OktendmoduleBackendKeyData=structtypet=Types.backend_keyletconsume_exniobuf=letpid=Pid.of_int(Iobuf.Consume.int32_beiobuf)inletsecret=Iobuf.Consume.int32_beiobufin{Types.pid;secret}letconsumeiobuf=matchconsume_exniobufwith|exceptionexn->error_s[%message"Failed to parse BackendKeyData"(exn:Exn.t)]|t->OktendmoduleNotificationResponse=structtypet={pid:Pid.t;channel:Types.Notification_channel.t;payload:string}letconsume_exniobuf=letpid=Pid.of_int(Iobuf.Consume.int32_beiobuf)inletchannel=Shared.consume_cstring_exniobuf|>Types.Notification_channel.of_stringinletpayload=Shared.consume_cstring_exniobufin{pid;channel;payload}letconsumeiobuf=matchconsume_exniobufwith|exceptionexn->error_s[%message"Failed to parse NotificationResponse"(exn:Exn.t)]|t->OktendmoduleReadyForQuery=structtypet=|Idle|In_transaction|In_failed_transaction[@@derivingsexp_of]letconsume_exniobuf=matchIobuf.Consume.chariobufwith|'I'->Idle|'T'->In_transaction|'E'->In_failed_transaction|other->raise_s[%message"unrecognised backend status char in ReadyForQuery"(other:char)]letconsumeiobuf=matchconsume_exniobufwith|exceptionexn->error_s[%message"Failed to parse ReadyForQuery"(exn:Exn.t)]|t->OktendmoduleParseComplete=structletconsume(_:_Iobuf.t)=()endmoduleBindComplete=structletconsume(_:_Iobuf.t)=()endmoduleNoData=structletconsume(_:_Iobuf.t)=()endmoduleEmptyQueryResponse=structletconsume(_:_Iobuf.t)=()endmoduleCopyDone=structletconsume(_:_Iobuf.t)=()endmoduleCloseComplete=structletconsume(_:_Iobuf.t)=()endmoduleRowDescription=structtypecolumn={name:string;format:[`Text]}typet=columnarrayletconsume_exniobuf=letnum_fields=Iobuf.Consume.int16_beiobufinArray.init_ascendingnum_fields~f:(fun()->letname=Shared.consume_cstring_exniobufinletskip=4(* table *)+2(* column *)+4(* type oid *)+4(* type modifier *)+2(* length-or-variable *)inIobuf.advanceiobufskip;letformat=matchIobuf.Consume.int16_beiobufwith|0->`Text|1->failwith"RowDescription format=Binary?"|i->failwithf"RowDescription: bad format %i"i()in{name;format})letconsumeiobuf=matchconsume_exniobufwith|exceptionexn->error_s[%message"Failed to parse RowDescription"(exn:Exn.t)]|cols->OkcolsendmoduleDataRow=structtypet=stringoptionarrayletconsume_exniobuf=letnum_fields=Iobuf.Consume.int16_beiobufinArray.init_ascendingnum_fields~f:(fun()->matchIobuf.Consume.int32_beiobufwith|-1->None|len->Some(Iobuf.Consume.stringoiobuf~len))letconsumeiobuf=matchconsume_exniobufwith|exceptionexn->error_s[%message"Failed to parse DataRow"(exn:Exn.t)]|t->Oktletskipiobuf=Iobuf.advanceiobuf(Iobuf.lengthiobuf)endmoduletypeCopyResponse=sigtypecolumn={name:string;format:[`Text|`Binary]}typet={overall_format:[`Text|`Binary];num_columns:int;column_formats:[`Text|`Binary]array}valconsume:([>read],Iobuf.seek)Iobuf.t->tOr_error.tendmoduleCopyResponse(A:sigvalname:stringend):CopyResponse=structtypecolumn={name:string;format:[`Text|`Binary]}typet={overall_format:[`Text|`Binary];num_columns:int;column_formats:[`Text|`Binary]array}letconsume_exniobuf=letoverall_format=matchIobuf.Consume.int8iobufwith|0->`Text|1->`Binary|i->failwithf"%s: bad overall format: %i"A.namei()inletnum_columns=Iobuf.Consume.int16_beiobufinletcolumn_formats=Array.init_ascendingnum_columns~f:(fun()->matchIobuf.Consume.int16_beiobufwith|0->`Text|1->`Binary|i->failwithf"%s: bad format %i"A.namei())in{overall_format;num_columns;column_formats}letconsumeiobuf=matchconsume_exniobufwith|cols->Okcols|exceptionexn->lets=sprintf"Failed to parse %s"A.nameinerror_s[%messages(exn:Exn.t)]endmoduleCopyInResponse=CopyResponse(structletname="CopyInResponse"end)moduleCopyOutResponse=CopyResponse(structletname="CopyOutResponse"end)moduleCopyData=struct(* After [focus_on_message] seeks over the type and length, 'CopyData'
messages are simply just the payload bytes. *)letskipiobuf=Iobuf.advanceiobuf(Iobuf.lengthiobuf)endmoduleCommandComplete=structtypet=stringletconsume_exn=Shared.consume_cstring_exnletconsumeiobuf=matchconsume_exniobufwith|exceptionexn->error_s[%message"Failed to parse CommandComplete"(exn:Exn.t)]|t->Oktendend