123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242open!Coreopen!AsyncmoduleMan_in_the_middle_debugger=Man_in_the_middle_debugger.Make(structtypemessage=Msgpack.tletparser_=Msgpack.Internal.Parser.msgletto_string=Msgpack.string_of_t_exn?bufsize:Noneend)letcreate_debug_printers?(pp=Msgpack.pp?pp_ext:None)?(color=false)~peer1~peer2log=letformatter=Format.formatter_of_out_channelloginletmaybe_color=matchcolorwith|false->funs_color->s|true->funscolor->Console.Ansi.string_with_attr[color;`Bright]sinletpeer1=maybe_colorpeer1`Blueinletpeer2=maybe_colorpeer2`Yellowinletcreate_debug_printer~labelmessage=letopenFormatinpp_open_hovboxformatter0;pp_print_stringformatterlabel;pp_print_charformatter':';pp_print_spaceformatter();ppformattermessage;pp_close_boxformatter();pp_print_newlineformatter()inletpeer_1_to_2=create_debug_printer~label:[%string"%{peer1} -> %{peer2}"]inletpeer_2_to_1=create_debug_printer~label:[%string"%{peer2} -> %{peer1}"]in`Peer_1_to_2peer_1_to_2,`Peer_2_to_1peer_2_to_1;;letconnect_peers_and_listen?pp?color~peer1~peer2~log()=let`Peer_1_to_2peer_1_to_2,`Peer_2_to_1peer_2_to_1=create_debug_printers?pp?color~peer1:peer1.Man_in_the_middle_debugger.Peer.name~peer2:peer2.Man_in_the_middle_debugger.Peer.nameloginlet%map`Peer1peer1_stopped,`Peer2peer2_stopped=Man_in_the_middle_debugger.connect_peers_and_listen~peer1~peer2~f:(function|`Peer_1_to_2->peer_1_to_2|`Peer_2_to_1->peer_2_to_1)inletexit_statusangstrom_exit_status~from~to_=[%message[%string"%{from} -> %{to_}"](angstrom_exit_status:(unit,string)Result.t)]in[%message"Connection closed."~_:(exit_statuspeer1_stopped~from:peer1.name~to_:peer2.name:Sexp.t)~_:(exit_statuspeer2_stopped~from:peer2.name~to_:peer1.name:Sexp.t)]|>Sexp.to_string_hum|>Out_channel.output_stringlog;Out_channel.output_charlog'\n';Out_channel.flushlog;;letconnect_unix_peers_and_listen?pp?color~client_name~client_socket~server_name~server_socket()=letterminated=Ivar.create()inlet%bindserver=Tcp.Server.create~on_handler_error:`Raiseclient_socket(* Act as a server to the client. *)(fun(_:Socket.Address.Unix.t)client_readerclient_writer->let%bind(_:_Socket.t),server_reader,server_writer=Tcp.connectserver_socket(* Act as a client to the server. *)inlet%bind()=client_socket|>Tcp.Where_to_listen.address|>Socket.Address.Unix.to_string|>Unix.unlinkinlet%bind()=connect_peers_and_listen?pp?color~peer1:{name=client_name;reader=client_reader;writer=client_writer}~peer2:{name=server_name;reader=server_reader;writer=server_writer}~log:stderr()inIvar.fill_exnterminated();return())inlet%bind()=Ivar.readterminatedinTcp.Server.closeserver;;letconnect_stdio_peers_and_listen?pp?color~log~parent_name~child_name~prog~args()=letlog=Out_channel.createloginlet%bindchild=Process.create_exn~prog~args()inletdone_transferring_stderr=letchild_reader=Process.stderrchildinletparent_writer=forceWriter.stderrinletparent_pipe=Writer.pipeparent_writerinlet%bind()=Reader.transferchild_readerparent_pipeinlet%bind()=Reader.closechild_readerinlet%bind(`Ok|`Reader_closed)=Pipe.upstream_flushedparent_pipeinWriter.closeparent_writerinlet%bind()=connect_peers_and_listen?pp?color~peer1:{name=parent_name;reader=forceReader.stdin;writer=forceWriter.stdout}~peer2:{name=child_name;reader=Process.stdoutchild;writer=Process.stdinchild}~log()inlet%bind()=done_transferring_stderrinlet%bindexit_message=Process.waitchild>>|Core_unix.Exit_or_signal.to_string_huminOut_channel.output_stringlog[%string"%{child_name} %{exit_message}"];Out_channel.closelog;return();;letunix_domain_sockets_command?pp()=Command.async~behave_nicely_in_pipeline:true~summary:"Capture Msgpack RPC traffic over unix domain sockets"~readme:(fun()->{|
Each endpoint uses a different socket and the debugger ferries messages between them. The
server program should be listening on [server-socket] and the client program should try
connecting to [client-socket]. The debugger acts as a server to the client and a client
to the server.
First start the server program, then start this debugger specifying the [server-socket]
where that server program is listening and a new [client-socket] where the debugger will
start listening. Finally, start the client program and have it connect to [client-socket]. |})(let%map_open.Command()=return()andclient_name=flag"client-name"(requiredstring)~doc:"STRING Name to use for client in log"andclient_socket=flag"client-socket"(requiredFilename_unix.arg_type)~doc:"SOCKET Where the client will connect">>|Tcp.Where_to_listen.of_fileandserver_name=flag"server-name"(requiredstring)~doc:"STRING Name to use for server in log"andserver_socket=flag"server-socket"(requiredFilename_unix.arg_type)~doc:"SOCKET Where the server is listening">>|Tcp.Where_to_connect.of_fileandcolor=letalways_never=Command.Arg_type.of_alist_exn~list_values_in_help:false["always",true;"never",false]inflag"color"(optionalalways_never)~doc:"(always|never) Color names in the log"infun()->letcolor=matchcolorwith|Somebool->bool|None->Core_unix.(isattystderr)inconnect_unix_peers_and_listen?pp~color~client_name~client_socket~server_name~server_socket());;letstdio_command?pp()=Command.async~behave_nicely_in_pipeline:true~summary:"Capture Msgpack RPC traffic over stdio"~readme:(fun()->{|
Run an embedded program that expects to communicate Msgpack RPC over stdio and write the
communication to a file. Stderr from the child is passed through. |})(let%map_open.Command()=return()andlog=flag"log"(requiredFilename_unix.arg_type)~doc:"FILENAME Where to log the communication"andparent_name=flag"parent-name"(requiredstring)~doc:"STRING Name to use for parent in log"andchild_name=flag"child-name"(requiredstring)~doc:"STRING Name to use for child in log"andprogram=flag"--"escape~doc:"PROG Child program to run"andcolor=flag"color"(optional_with_defaultfalsebool)~doc:"BOOL Color names in the log (default: false)"infun()->matchprogramwith|None|Some[]->failwith"Must provide an embedded program to run"|Some(prog::args)->connect_stdio_peers_and_listen?pp~color~log~parent_name~child_name~prog~args());;letcommand?pp()=Command.group~summary:"Capture Msgpack RPC traffic"["stdio",stdio_command?pp();"unix",unix_domain_sockets_command?pp();"convert",Conversions.command];;