%% #0. BASIC INFORMATION
%% ----------------------------------------------------------------------
%% %CCaseFile: sbgStSupport.erl
%% Author: etxkols
%% Description: IS hardware functions.
%%
%% ----------------------------------------------------------------------
%% @doc This the the API interface for module <code>sbgStSupport</code>.
%%
%% <font size="4">Follow this guide, feature team can run eNST easily.
%% <ol>
%% <li>prepare your sipp scenario file, and call <code>sbgEstSupport:prepare_sipp_cmd/3</code> to generate sipp commands.<br></br>
%% <strong>Note:</strong> Some import parameters have default value, and there are configurable.</li>
%% <li>call <code>sbgEstSupport:prepare_systeminfo_filenames/1</code> to prepare the log files according which blades you want to monitor.</li>
%% <li>call <code>sbgEstSupport:start_collect_system_infos/1</code> to start collecting logs.</li>
%% <li>call <code>sbgEstSupport:run_scenario</code> to run your sipp traffic.<br></br>
%% <strong>Note:</strong> the traffic run time is configurable and you also can configure monitoring the SIPp running status.</li>
%% <li>call <code>sbgEstSupport:generate_log_statistics/4</code> to analyze the log files and draw the picture.</li>
%% </ol>
%% You can refter the demo case in suite <code>sbg_eST_template.erl</code>, here is the <a href="https://a...content-available-to-author-only...n.se/jpT/elqstux/15-11-02/ct_run.ct_5@sekilxc0007.2015-11-02_12.04.49/auto.suite.sbg_eST_template.sbg_est_001.logs/run.2015-11-02_12.04.54/suite.log.html">log</a>
%% </font>
- module ( sbgEstSupport) .
- date ( '2015-11-02' ) .
- author ( 'elqstux' ) .
- vsn ( '/main/R20A/R21A/R22A/R99A/2, checkout by elqstux in elqstux_ki_ppb' ) .
%% ----------------------------------------------------------------------
%% %CCaseCopyrightBegin%
%% Copyright (c) Ericsson AB 2009-2013 All rights reserved.
%%
%% The program may be used and/or copied only with the written
%% permission from Ericsson Telecom AB, or in accordance with
%% the terms and conditions stipulated in the agreement/contract
%% under which the program has been supplied.
%%
%% All rights reserved
%%
%% ----------------------------------------------------------------------
%%
%% #1. EXPORT LISTS
%% ----------------------------------------------------------------------
%% #1.1 EXPORTED INTERFACE FUNCTIONS
%% ----------------------------------------------------------------------
%% ----------------------------------------------------------------------
%% #2. REVISION LOG
%% ----------------------------------------------------------------------
%% Rev Date Name What
%% ----------------------------------------------------------------------
%% R21A/1 130731 elilige Created
%% R21A/2 131016 elilige Add draw_graph
%% ----------------------------------------------------------------------
%% ----------------------------------------------------------
%% #2. EXPORT LISTS
%% ----------------------------------------------------------
%% #2.1 EXPORTED INTERFACE FUNCTIONS
%% ----------------------------------------------------------
- export ( [ prepare_sipp_cmd/ 3 ,
prepare_systeminfo_filenames/ 1 ,
start_collect_system_infos/ 1 ,
stop_collect_system_infos/ 1 ,
generate_log_statistics/ 4 ,
run_scenario/ 6 ] ) .
- define ( DEBUG ( Format , Args ) , ct :
pal ( Format , Args ) ) .
- define ( INFO_MSG ( Format , Args ) , ct :
pal ( Format , Args ) ) .
- define ( WARNING_MSG ( Format , Args ) , ct :
pal ( Format , Args ) ) .
- define ( ERROR_MSG ( Format , Args ) , ct :
pal ( Format , Args ) ) .
- define ( CRITICAL_MSG ( Format , Args ) , ct :
pal ( Format , Args ) ) .
- define ( STAT_FAIL , 10 ) .
- define ( CALL_LOSS_RATE , 0.0005 ) .
- define ( SIPP_PARAS , [
{ service, [ " -s " , "" ] } ,
{ scenarioFile, [ " -sf " , "" ] } ,
{ infFile, [ " -inf " , "" ] } ,
{ localIp, [ " -i " , "" ] } ,
{ localPort, [ " -p " , "" ] } ,
{ mediaIp, [ " -mi " , "" ] } ,
{ mediaPort, [ " -mp " , "" ] } ,
{ autoAnswer, [ " -aa" ] } ,
{ background, [ " -bg" ] } ,
{ callRate, [ " -r " , "" ] } ,
{ maxCalls, [ " -m " , "" ] } ,
{ callLength, [ " -d " , "" ] } ,
{ parallelCalls, [ " -l " , "" ] } ,
{ statFile, [ " -stf " , "" ] } ,
{ errorFile, [ " -error_file " , "" ] } ,
{ statFrequence, [ " -fd " , "" ] } ,
{ openStat, [ " -trace_stat" ] } ,
{ traceScreen, [ " -trace_screen" ] } ,
{ traceErr, [ " -trace_err" ] } ,
{ transport, [ " -t " , "" ] }
] ) .
- define ( SIPP_MANDATORY_PARAMETERS , [ scenarioFile, localIp, localPort, mediaIp, statFile] ) .
- define ( SIPP_DEFAULT_PARAMETERS , [ autoAnswer, background, openStat, traceScreen, traceErr] ) .
- define ( SIPP_OPTIONAL_PARAMETERS , [ service, callRate, infFile, mediaPort, maxCalls, callLength,
parallelCalls, statFrequence, transport, errorFile] ) .
- define ( SIPP_BIN , "/vobs/mgwblade/PPB/SBG_HSD10196_1/test/auto/suite/test/est_script/sipp" ) .
- define ( XML_DIR , "/vobs/mgwblade/PPB/SBG_HSD10196_1/test/auto/suite/test/sipp_xml/" ) .
- define ( EST_LOG_DIR , "/home/" ++ os :
get env ( "USER" ) ++ "/est_log_tmp/" ) . - define ( EST_DATA_BIN , "/vobs/mgwblade/PPB/SBG_HSD10196_1/test/auto/suite/test/est_script/data_analysis/" ) .
%%-define(SGCchNode,jpTstates:find_role(jpTnames:ty2na(sgc),ch)).
- define ( SGCchNode , jpTstates:find_role ( sbgFtSupport:get _bs_name ( sgc, 1 ) , ch) ) .
- define ( CH ( M , F , A ) , jpT
rpc :
call ( ?
SGCchNode , M , F , A ) ) .
- define ( SGComNode , jpTstates:find_role ( sbgFtSupport:get _bs_name ( sgc, 1 ) , om) ) .
- define ( OM ( M , F , A ) , jpT
rpc :
call ( ?
SGComNode , M , F , A ) ) .
- define ( SBGchNode , jpTstates:find_role ( sbgFtSupport:get _bs_name ( ommp, 1 ) , ch) ) .
- define ( SBGCH ( M , F , A ) , jpT
rpc :
call ( ?
SBGchNode , M , F , A ) ) .
- define ( SBGomNode , jpTstates:find_role ( sbgFtSupport:get _bs_name ( ommp, 1 ) , om) ) .
- define ( SBGOM ( M , F , A ) , jpT
rpc :
call ( ?
SBGomNode , M , F , A ) ) .
- define ( REG_MATCH , { regKey, '_' , '_' , '_' , '_' , '_' , '_' , '_' , '_' } ) .
- define ( REG_MATCH2 , { regKey, '_' , '_' , '_' } ) .
- define ( DC_MATCH ( APP ) , { APP , list_to_atom ( atom_to_list ( APP ) ++ "DcMain" ) , '_' } ) .
- define ( DEFAULT_MONITOR_INTERVAL , { 0 , 5 , 0 } ) .
%% @spec prepare_sipp_cmd(SIPp_Config, Role, IpVsn) -> Res :: tuple()
%% @doc Shows <a href="http://s...content-available-to-author-only...e.net/">SIPp</a> online help documentation of what config parameters there are.<br></br>
%% Prepare sipp cmd for starting sipp process, default value is used if cannot get from user input.<br></br>
%% <pre>
%% <strong>Node:</strong>
%% SIPp_Config proplists()
%% Role access | core4access
%% IpVsm ipv4 | ipv6
%% Res tuple, just like {ok, {UacScenarioFile, UacStatFile, UacSippCmd}=UacParam}
%% </pre>
%% <dl>
%% <dt>======================================================================== </dt>
%% <dt> The key for parameter(SIPp_Config) </dt>
%% <dt>======================================================================== </dt>
%% <dt><pre><strong>Parameter</strong> <strong>Description</strong> </pre></dt>
%% <dt><pre>localIp IP address of source, mandatory but has default value</pre></dt>
%% <dt><pre>mediaIp Media address of source, mandatory but has default value</pre></dt>
%% <dt><pre>localPort Source port, mandatory but has default value</pre></dt>
%% <dt><pre>remoteIp IP address of destination, mandatory but has default value</pre></dt>
%% <dt><pre>remotePort Destination port, mandatory but has default value</pre></dt>
%% <dt><pre>scenarioFile Path of scenario file, mandatory</pre></dt>
%% <dt><pre>statFile Path of statistics file, mandatory but has default value</pre></dt>
%% <dt><pre>infFile Path of inject parameter file, optional</pre></dt>
%% <dt><pre>callrate Number of calls/s, optional</pre></dt>
%% <dt><pre>maxcalls Maximum number of calls setup, optional</pre></dt>
%% <dt><pre>calllength Length in seconds of calls, optional</pre></dt>
%% <dt><pre>parallelCalls Total number of calls in parallel, optional</pre></dt>
%% <dt><pre>statFrequence The statistics dump log report frequency, optional</pre></dt>
%% <dt><pre>transport The transport mode, optional</pre></dt>
%% <dt>======================================================================== </dt>
%% </dl>
%% === Example usage ===
%% ```jpTsipp:prepare_sipp_cmd([{localIp, "10.10.121.7"},
%% {mediaIp, "10.10.121.7"},
%% {localPort, "8004"},
%% {remoteIp, "10.21.0.4"},
%% {remotePort, "10004"},
%% {scenarioFile, "./sipp/st/uac_register.xml"},
%% {infFile, "./sipp/st/database.csv"},
%% {statFile, "./sipp/st/sipp_regtister_uac.csv"},
%% {callRate, "5"}], access, ipv4).'''
prepare_sipp_cmd ( SIPp_Config , Role , IpVsn ) ->
SippParameters = prepare_sipp_param ( SIPp_Config , Role , IpVsn ) ,
RemoteAddr = proplists :
get _
value ( remoteIp
, SippParameters ) , RemotePort = proplists :
get _
value ( remotePort
, SippParameters ) , RemoteUri = build_remote_uri ( RemoteAddr , RemotePort ) ,
StatFile = proplists :
get _
value ( statFile
, SippParameters ) , ScenarioFile = proplists :
get _
value ( scenarioFile
, SippParameters ) ,
MandatoryBool = check_sipp_param ( SippParameters ,
?SIPP_MANDATORY_PARAMETERS , mandatory) ,
OptionalBool = check_sipp_param ( SippParameters ,
?SIPP_OPTIONAL_PARAMETERS , optional) ,
if
( MandatoryBool =:= true) and ( OptionalBool =:= true) ->
NewSippParameters = set_default_sipp_param ( SippParameters ,
?SIPP_DEFAULT_PARAMETERS ) ,
SippParaString = build_sipp_parameters_string ( NewSippParameters ,
"" ) ,
if
RemoteUri =:= "" ->
?ERROR_MSG ( "Error:~nRemote Address Information is invalid: ~p, ~p~n" ,
[ RemoteAddr , RemotePort ] ) ,
{ error, RemoteAddr } ;
true ->
SippCmd = ?SIPP_BIN ++ " " ++ RemoteUri ++ " " ++ SippParaString ,
?DEBUG ( "Generate Sipp command:~n~p~nGet StatFile:~n~p~n" ,
[ SippCmd , StatFile ] ) ,
{ ok, { ScenarioFile , StatFile , SippCmd } }
end ;
true ->
?WARNING_MSG ( "Warning:~nThe mandatory parameters check result: ~p~n the optional check result: ~p.~n" ,
[ MandatoryBool , OptionalBool ] ) ,
{ error, { MandatoryBool , OptionalBool } }
end .
%% -----------------------------------------------------------------------
%% prepare_sipp_param(SIPp_Config, Role, IpVsn)
%% @spec prepare_sipp_param(SIPp_Config::list(), Role::atom(), IpVsn::atom()) -> ResList::list()
%% @doc Update sipp parameters, combine path with file
%% SIPp_Config::list() is a list of parameters to sipp. More info about parameters
%% in jpTsipp:prepare_sipp_cmd/1
%% <pre>
%% <b>Input:</b>
%% SIPp_Config List Argument list of tuples in form {TypeTag,Param}
%% Role Atom access|core4access|foreign|core4foreign
%% IpVsn Atom ipv4|ipv6
%%
%% <b>Output:</b>
%% ResList List Argument list of tuples
%%
%% <i>Example:</i>
%% jpTsipp:prepare_sipp_param([{localIp, "10.10.121.7"},
%% {mediaIp, "10.10.121.7"},
%% {localPort, "8004"},
%% {remoteIp, "10.21.0.4"},
%% {remotePort, "10004"}
%% {scenarioFile, "./sipp/st/uac_register.xml"},
%% {infFile, "./sipp/st/database.csv"},
%% {statFile, "./sipp/st/sipp_regtister_uac.csv"},
%% {errorFile, "./sipp/st/sipp-error.log"},
%% {callRate, "5"}], access, ipv4).
%% </pre>
%% @end
%% -----------------------------------------------------------------------
prepare_sipp_param ( SIPp_Config , Role , IpVsn ) ->
PreLocalIp = proplists :
get _
value ( localIp
, SIPp_Config ) , PreLocalPort = proplists :
get _
value ( localPort
, SIPp_Config ) , PreRemoteIp = proplists :
get _
value ( remoteIp
, SIPp_Config ) , PreRemotePort = proplists :
get _
value ( remotePort
, SIPp_Config ) ,
LocalIp = handle_local_ip ( PreLocalIp , Role , IpVsn ) ,
LocalPort = handle_local_port ( PreLocalPort , Role ) ,
RemoteIp = handle_remote_ip ( PreRemoteIp , Role , IpVsn ) ,
RemotePort = handle_remote_port ( PreRemotePort , Role ) ,
PreMediaIp = proplists :
get _
value ( mediaIp
, SIPp_Config ) , MediaIp = choose ( PreMediaIp =:= undefined, LocalIp , PreMediaIp ) ,
ErrorFile = case proplists :
get _
value ( errorFile
, SIPp_Config ) of undefined ->
?EST_LOG_DIR ++ to_string ( Role ) ++ "_" ++ write_time ( now ) ++ "_" ++ "sipp-error.log" ;
TmpErrorFile ->
filename :
dirname ( TmpErrorFile ) ++ "/" ++ write_time ( now ) ++ "_" ++ filename :
basename ( TmpErrorFile ) end ,
StatFile = case proplists :
get _
value ( statFile
, SIPp_Config ) of undefined ->
?EST_LOG_DIR ++ to_string ( Role ) ++ "_" ++ write_time ( now ) ++ "_" ++ "sipp-statistics.csv" ;
TmpStatFile ->
filename :
dirname ( TmpStatFile ) ++ "/" ++ write_time ( now ) ++ "_" ++ filename :
basename ( TmpStatFile ) end ,
update_sipp_param ( [ { localIp, LocalIp } , { localPort, LocalPort } , { remoteIp, RemoteIp } ,
{ remotePort, RemotePort } , { mediaIp, MediaIp } , { statFile, StatFile } ,
{ errorFile, ErrorFile } ] , SIPp_Config ) .
update_sipp_param ( [ ] , [ ] ) ->
[ ] ;
update_sipp_param ( [ ] , SIPp_Config ) ->
SIPp_Config ;
update_sipp_param ( [ { Attr , Value } |Rest] = _Param_List , SIPp_Config ) ->
case { Attr , Value } of
{ _ , undefined} ->
update_sipp_param ( Rest , SIPp_Config ) ;
{ _ , _ } ->
NewSIPp_Config = proplists :
delete ( Attr , SIPp_Config ) ++ [ { Attr , Value } ] , update_sipp_param ( Rest , NewSIPp_Config )
end .
%% -----------------------------------------------------------------------
%% check_sipp_param(SippParameters, _ParameterList, Type)
%% @spec check_sipp_param(SippParameters::list(), _ParameterList::list(), Type::atom()) -> Res::atom()
%% @doc Check whether parameters are lost and duplicated in parameter list
%% SippParameters::list() is a list of parameters to sipp. More info about parameters
%% in jpTsipp:prepare_sipp_cmd/1
%% <pre>
%% <b>Input:</b>
%% SippParameters List Argument list of tuples in form {TypeTag,Param}
%% _ParameterList List Parameter list
%% Type Atom mandatory|optional
%%
%% <b>Output:</b>
%% Res Atom true|false
%%
%% <i>Example:</i>
%% jpTsipp:check_sipp_param([{localIp, "10.10.121.7"},
%% {mediaIp, "10.10.121.7"},
%% {localPort, "8004"},
%% {remoteIp, "10.21.0.4"},
%% {remotePort, "10004"},
%% {scenarioFile, "./sipp/st/uac_register.xml"},
%% {infFile, "./sipp/st/database.csv"},
%% {statFile, "./sipp/st/sipp_regtister_uac.csv"},
%% {callRate, "5"}],
%% [scenarioFile, localIp, localPort, mediaIp, statFile],
%% mandatory).
%% </pre>
%% @end
%% -----------------------------------------------------------------------
check_sipp_param ( SippParameters , [ Parameter |Rest] = _ParameterList , mandatory) ->
case get _parame_quantity ( Parameter , SippParameters ) of
1 ->
Cont = proplists :
get _
value ( Parameter , SippParameters ) , case is_string ( Cont ) of
true ->
check_sipp_param ( SippParameters , Rest , mandatory) ;
false ->
?WARNING_MSG ( "Wrong Parameter ~p Content: ~p~n" , [ Parameter , Cont ] ) ,
false
end ;
0 ->
?WARNING_MSG ( "the mandatory parameter(~p) is not included.~n " , [ Parameter ] ) ,
false;
_ ->
?WARNING_MSG ( "the mandatory parameter(~p) is more than one.~n" , [ Parameter ] ) ,
false
end ;
check_sipp_param ( SippParameters , [ Parameter |Rest] = _ParameterList , optional) ->
case get _parame_quantity ( Parameter , SippParameters ) of
1 ->
case is_string ( proplists :
get _
value ( Parameter , SippParameters ) ) of true ->
check_sipp_param ( SippParameters , Rest , optional) ;
false ->
false
end ;
0 ->
check_sipp_param ( SippParameters , Rest , optional) ;
_ ->
?WARNING_MSG ( "the optional parameter(~p) is more than one.~n" , [ Parameter ] ) ,
false
end ;
check_sipp_param ( _SippParameters , [ ] , _ ) ->
true .
%% -----------------------------------------------------------------------
%% set_default_sipp_param(SippParameters, _DefaultParameterList)
%% @spec set_default_sipp_param(SippParameters::list(), _DefaultParameterList::list()) -> ResList::list()
%% @doc Add default sipp parameter in SippParameters
%% SippParameters::list() is a list of parameters to sipp. More info about parameters
%% in jpTsipp:prepare_sipp_cmd/1
%% <pre>
%% <b>Input:</b>
%% SippParameters List Argument list of tuples in form {TypeTag,Param}
%% _ParameterList List Parameter list
%%
%% <b>Output:</b>
%% ResList List Argument list of tuples in form {TypeTag, Param}
%%
%% <i>Example:</i>
%% jpTsipp:set_default_sipp_param([{localIp, "10.10.121.7"},
%% {mediaIp, "10.10.121.7"},
%% {localPort, "8004"},
%% {remoteIp, "10.21.0.4"},
%% {remotePort, "10004"},
%% {scenarioFile, "./sipp/st/uac_register.xml"},
%% {infFile, "./sipp/st/database.csv"},
%% {statFile, "./sipp/st/sipp_regtister_uac.csv"},
%% {callRate, "5"},
%% {maxCalls, "20000"}],
%% [autoAnswer, background, openStat]).
%% </pre>
%% @end
%% -----------------------------------------------------------------------
set_default_sipp_param ( SippParameters , [ Parameter |Rest] = _DefaultParameterList ) ->
case get _parame_quantity ( Parameter , SippParameters ) of
0 ->
set_default_sipp_param ( [ { Parameter , true} |SippParameters] , Rest ) ;
_ ->
NewSippParameters = proplists :
delete ( Parameter , SippParameters ) , set_default_sipp_param ( [ { Parameter , true} |NewSippParameters] , Rest )
end ;
set_default_sipp_param ( SippParameters , [ ] ) ->
SippParameters .
%% -----------------------------------------------------------------------
%% get_parame_quantity(Key, Parameters)
%% @spec get_parame_quantity(Key::atom(), Parameters::list()) -> Res::atom()
%% @doc Calculate the number of Key in Parameters list
%% Parameters::list() is a list of parameters to sipp. More info about parameters
%% in jpTsipp:prepare_sipp_cmd/1
%% <pre>
%% <b>Input:</b>
%% Key Atom key in Parameters list
%% Parameters List Argument list of tuples in form {TypeTag,Param}
%%
%% <b>Output:</b>
%% Res Atom Number of keys
%%
%% <i>Example:</i>
%% jpTsipp:get_parame_quantity(localIp, [{localIp, "10.10.121.7"},
%% {mediaIp, "10.10.121.7"},
%% {localPort, "8004"},
%% {callRate, "5"}]).
%% </pre>
%% @end
%% -----------------------------------------------------------------------
get _parame_quantity ( Key , Parameters ) ->
length ( proplists :
get _
all_values ( Key , Parameters ) ) .
%% -----------------------------------------------------------------------
%% build_sipp_parameters_string(_CmdList, CmdStr)
%% @spec build_sipp_parameters_string(_CmdList::list(), CmdStr::string()) -> Res::string()
%% @doc Generate parameters part of sipp cmd
%% _CmdList::list() is a list of parameters to sipp
%% <pre>
%% <b>Input:</b>
%% _CmdList List Parameters list
%% CmdStr String connector
%%
%% <b>Output:</b>
%% Res String Parameters part of sipp cmd
%%
%% </pre>
%% @end
%% -----------------------------------------------------------------------
build_sipp_parameters_string ( [ { Attr , Cont } |Rest] = _CmdList , CmdStr ) ->
case proplists :
get _
value ( Attr , ?
SIPP_PARAS ) of [ Parameter ] ->
if
Cont =:= true ->
build_sipp_parameters_string ( Rest , CmdStr ++ Parameter ) ;
Cont =:= false ->
build_sipp_parameters_string ( Rest , CmdStr )
end ;
[ Parameter , "" ] ->
build_sipp_parameters_string ( Rest , CmdStr ++ Parameter ++ to_string ( Cont ) ) ;
undefined ->
?WARNING_MSG ( "Warning:~nbuild_sipp_parameters_string recv unexpected input: ~p, skip the parameter and go ahead.~n" , [ Attr ] ) ,
build_sipp_parameters_string ( Rest , CmdStr )
end ;
build_sipp_parameters_string ( [ ] , CmdStr ) ->
CmdStr .
%% -----------------------------------------------------------------------
%% build_remote_uri(RemoteIp, RemoterPort)
%% @spec build_remote_uri(RemoteIp::string(), RemoterPort::string()) -> Res::string()
%% @doc Generate remoter uri
%% <pre>
%% <b>Input:</b>
%% RemoterIp String
%% RemoterPort String
%%
%% <b>Output:</b>
%% Res String Remtoe URI part of sipp cmd
%%
%% </pre>
%% @end
%% -----------------------------------------------------------------------
build_remote_uri ( RemoteIp , RemotePort ) ->
if
( RemoteIp =:= undefined) or ( RemotePort =:= undefined) ->
?ERROR_MSG ( "Error:~nbuild_remote_uri unknow remote parameters: remote ip=~p; remote port=~p~n" , [ RemoteIp , RemotePort ] ) ,
"" ;
true ->
to_string ( RemoteIp ) ++ ":" ++ to_string ( RemotePort )
end .
run_scenario ( UacParam , UasParam , CallDuration , RunTime ) ->
run_scenario ( UacParam , UasParam , CallDuration , RunTime , ?DEFAULT_MONITOR_INTERVAL , false) .
%% -----------------------------------------------------------------------
%% run_scenario(_UacParam, _UasParam, CallDuration, RunTime, MonitorInterval, StatMonitorFlag)
%% @spec run_scenario(_UacParam::tuple(), _UasParam::tuple(), CallDuration::integer(),
%% RunTime::tuple(), MonitorInterval::tuple(),
%% StatMonitorFlag::atom()) -> Res::atom()
%% @doc Run the SIPp scenario.
%% <pre>
%% <b>Input:</b>
%% _UacParam Tuple {UacSnrFile, UacStatFile, UacCmd}
%% UacSnrFile String The scenario file for UAC
%% UacStatFile String The Statistic file for UAC
%% UacCmd String The SIPp command string for UAC
%%
%% _UasParam Tuple {UasSnrFile, UasStatFile, UasCmd}
%% UasSnrFile String The scenario file for UAS
%% UasStatFile String The Statistic file for UAS
%% UasCmd String The SIPp command string for UAS
%%
%% CallDuration Integer Duration time for each call
%% RunTime Tuple Total runtime for SIPp
%% MonitorInterval Tuple The monitor interval for SIPp
%% StatMonitorFlag Atom true|false whether to monitor pass rate during call ongoing
%%
%% <b>Output:</b>
%% Res Atom ok|error
%% </pre>
%% @end
%% ----------------------------------------------------------------------
run_scenario ( { UacSnrFile , UacStatFile , UacCmd } = _UacParam ,
{ UasSnrFile , UasStatFile , UasCmd } = _UasParam ,
CallDuration , RunTime , MonitorInterval , StatMonitorFlag ) ->
%% Start SIPp
case start_sipp ( UasCmd ) of
{ ok, UasPid } ->
ct :
pal ( "### UAS started, UasPid=~p ###~n" , [ UasPid ] ) , time r:sleep ( 1000 ) , % Sometimes UAS needs more time to open TCP port etc.
case start_sipp ( UacCmd ) of
{ ok, UacPid } ->
ct :
pal ( "### UAC started, UacPid=~p ###~n" , [ UacPid ] ) , do_handle_scenario ( { UasPid , UasSnrFile , UasStatFile } ,
{ UacPid , UacSnrFile , UacStatFile } ,
CallDuration , RunTime , MonitorInterval ,
StatMonitorFlag ) ;
_ ->
ct :
pal ( "### UAC start failed, kill the started UAS process and quit the function!" ) , os :
cmd ( "kill -9" ++ to_string ( UasPid ) ) , time r:sleep ( 1000 ) ,
UasScreenFile = filename :
dirname ( UasSnrFile ) ++ "/" ++ filename :
basename ( UasSnrFile , ".xml" ) ++ "_" ++ to_string ( UasPid ) ++ "_screen.log" ,
ct :
pal ( "UasScreenFile is:~p~n" , [ UasScreenFile ] ) , os :
cmd ( "mv " ++ UasScreenFile ++ " " ++ ?
EST_LOG_DIR ) , error
end ;
_ ->
ct :
pal ( "### UAS start failed, quit the function!" ) , error
end .
%% -----------------------------------------------------------------------
%% do_handle_scenario(_UasParam, _UacParam, CallDuration,
%% RunTime, MonitorInterval, StatMonitorFlag)
%% @spec do_handle_scenario(_UasParam :: tuple(),
%% _UacParam :: tuple(),
%% CallDuration :: integer(),
%% RunTime :: tuple(),
%% MonitorInterval :: tuple(),
%% StatMonitorFlag :: atom()) -> Res::atom()
%%
%% @doc monitor SIPp running status and returns the result of scenario executing
%% <pre>
%% <b>Input:</b>
%% _UasParam Tuple {UasPid, UasSnrFile, UasStatFile}
%% UasPid String The SIPp process id for UAS
%% UaSSnrFile String The scenario file for UAS
%% UaSStatFile String The Statistic file for UAS
%%
%% _UacParam Tuple {UacPid, UacSnrFile, UacStatFile}
%% UacPid String The SIPp process id for UAC
%% UacSnrFile String The scenario file for UAC
%% UacStatFile String The Statistic file for UAC
%% UasPid Pid The SIPp process id for UAS
%% UacPid Pid The SIPp process id for UAC
%% CallDuration Integer Call duration for each call
%% RunTime Tuple Total runtime for SIPp
%% MonitorInterval Tuple The monitor interval for SIPp
%% StatMonitorFlag Atom true|false whether to monitor pass rate during call ongoing
%%
%% <b>Output:</b>
%% Res Atom ok|error
%%
%% </pre>
%% @end
%% ----------------------------------------------------------------------
do_handle_scenario ( { UasPid , UasSnrFile , UasStatFile } = _UasParam ,
{ UacPid , UacSnrFile , UacStatFile } = _UacParam ,
CallDuration , RunTime , MonitorInterval ,
StatMonitorFlag ) ->
%% Start Interval Check Timer
{ { RunHour , RunMinute , RunSecond } ,
{ MonitorHour , MonitorMinute , MonitorSecond } } = { RunTime , MonitorInterval } ,
%% check the status of sipp process
{ ok, MonitorSippStatusRef } =
time r:apply _interval ( time r:hms ( MonitorHour , MonitorMinute , MonitorSecond ) ,
?MODULE ,
check_sipp_process_status,
[ self ( ) , [ UasPid , UacPid ] ] ) ,
MonitorRefs =
case StatMonitorFlag of
true ->
%% check the call status of sipp traffic
{ ok, MonitorCallStatusRef }
= time r:apply _interval ( time r:hms ( MonitorHour , MonitorMinute , MonitorSecond ) ,
?MODULE ,
check_sipp_call_status,
[ self ( ) , [ UasStatFile , UacStatFile ] ] ) ,
[ MonitorSippStatusRef , MonitorCallStatusRef ] ;
_ ->
[ MonitorSippStatusRef ]
end ,
{ ok, TRefClose } =
time r:apply _after ( time r:hms ( RunHour , RunMinute , RunSecond ) ,
?MODULE ,
close_monitor,
[ self ( ) , MonitorRefs ] ) ,
TRefList = MonitorRefs ++ [ TRefClose ] ,
%% Interval Check
IntervalCheckResult = interval_loop_check ( [ { UacStatFile , 0 } , { UasStatFile , 0 } ] ) ,
case IntervalCheckResult of
ok ->
ct :
pal ( "Interval check ok~n" ) , ok;
{ failed, FailedResult } ->
?WARNING_MSG ( "Warning: interval_loop_check recv failed(~p)~nto cancel all timers~n" , [ FailedResult ] ) ,
lists :
foreach ( fun ( P ) -> time r:
cancel ( P ) end , TRefList ) end ,
stop_uac_uas_sipp_processes ( [ UacPid , UasPid ] , CallDuration ) ,
%% Final Check
{ ok, UacStatData } = ?MODULE :get _stat_tail ( UacStatFile ) ,
UacStatResult = check_sipp_stat ( final, UacStatData ) ,
case UacStatResult of
true ->
?DEBUG ( "Final UAC call failed rate is less than 0.05%.~n" , [ ] ) ;
false ->
?WARNING_MSG ( "Warning: final UAC call failed rate is more than 0.05%.~n" , [ ] )
end ,
{ ok, UasStatData } = ?MODULE :get _stat_tail ( UasStatFile ) ,
UasStatResult = check_sipp_stat ( final, UasStatData ) ,
case UasStatResult of
true ->
?DEBUG ( "Final UAS call failed rate is less than 0.05%.~n" , [ ] ) ;
false ->
?WARNING_MSG ( "Warning: final UAS call failed rate is more than 0.05%.~n" , [ ] )
end ,
UasScreenFile = filename :
dirname ( UasSnrFile ) ++ "/" ++ filename :
basename ( UasSnrFile , ".xml" ) ++ "_" ++ to_string ( list_to_integer ( UasPid ) - 1 ) ++ "_screen.log" ,
UacScreenFile = filename :
dirname ( UacSnrFile ) ++ "/" ++ filename :
basename ( UacSnrFile , ".xml" ) ++ "_" ++ to_string ( list_to_integer ( UacPid ) - 1 ) ++ "_screen.log" ,
ct :
pal ( "UasScreenFile is:~p~n" , [ UasScreenFile ] ) , ct :
pal ( "UacScreenFile is:~p~n" , [ UacScreenFile ] ) ,
os :
cmd ( "mv " ++ UasScreenFile ++ " " ++ ?
EST_LOG_DIR ) , os :
cmd ( "mv " ++ UacScreenFile ++ " " ++ ?
EST_LOG_DIR ) ,
if
IntervalCheckResult =:= ok, UacStatResult =:= true, UasStatResult =:= true -> ok ;
true -> error
end .
prepare_env ( ) ->
%% Kill any lingering SIPp processes to avoid that the scenario fails
os :
cmd ( "mkdir " ++ ?
EST_LOG_DIR ) , { ok, _ } = ?CH ( b2bDbg, start_error_trace, [ ] ) ,
ok = ?CH ( oabDbg, log, [ start] ) ,
true = ?CH ( regDbg, err_trace, [ ] ) ,
true = ?CH ( sipDbg, err_trace, [ ] ) .
fallback_env ( ) ->
ok = ?CH ( dbg, stop_clear, [ ] ) .
prepare_systeminfo_filename ( CurrentTime , sgc1, ch) ->
Filename_suffix = write_time ( CurrentTime ) ++ ".log" ,
Vmstat_Filename = string :
concat ( "/blade/homedir/sgc1-ch-vmstat-" , Filename_suffix ) , SysInfo_Filename = ?EST_LOG_DIR ++ "sgc1-ch-system-info-" ++ Filename_suffix ,
PlcRes_Filename = ?EST_LOG_DIR ++ "sgc1-ch-plc-res-" ++ Filename_suffix ,
{ SysInfo_Filename , Vmstat_Filename , PlcRes_Filename } ;
prepare_systeminfo_filename ( CurrentTime , sgc1, om) ->
Filename_suffix = write_time ( CurrentTime ) ++ ".log" ,
Vmstat_Filename = string :
concat ( "/blade/homedir/sgc1-om-vmstat-" , Filename_suffix ) , SysInfo_Filename = ?EST_LOG_DIR ++ "sgc1-om-system-info-" ++ Filename_suffix ,
PlcRes_Filename = ?EST_LOG_DIR ++ "sgc1-om-plc-res-" ++ Filename_suffix ,
{ SysInfo_Filename , Vmstat_Filename , PlcRes_Filename } ;
prepare_systeminfo_filename ( CurrentTime , ommp, ch) ->
Filename_suffix = write_time ( CurrentTime ) ++ ".log" ,
Vmstat_Filename = string :
concat ( "/blade/homedir/ommp-ch-vmstat-" , Filename_suffix ) , SysInfo_Filename = ?EST_LOG_DIR ++ "ommp-ch-system-info-" ++ Filename_suffix ,
PlcRes_Filename = ?EST_LOG_DIR ++ "ommp-ch-plc-res-" ++ Filename_suffix ,
{ SysInfo_Filename , Vmstat_Filename , PlcRes_Filename } ;
prepare_systeminfo_filename ( CurrentTime , ommp, om) ->
Filename_suffix = write_time ( CurrentTime ) ++ ".log" ,
Vmstat_Filename = string :
concat ( "/blade/homedir/ommp-om-vmstat-" , Filename_suffix ) , SysInfo_Filename = ?EST_LOG_DIR ++ "ommp-om-system-info-" ++ Filename_suffix ,
PlcRes_Filename = ?EST_LOG_DIR ++ "ommp-om-plc-res-" ++ Filename_suffix ,
{ SysInfo_Filename , Vmstat_Filename , PlcRes_Filename } .
collect_system_info ( SysInfo_Filename , Vmstat_Filename , sgc1, ch) ->
Mem_result = ?CH ( ets, tab2list, [ plcInfo] ) ,
ProcessNum = length ( ?CH ( erlang, process es, [ ] ) ) ,
Result = Mem_result ++ [ { process _Num, ProcessNum } ] ,
{ ok
, File_handler } = file :
open ( SysInfo_Filename , [ append
] ) , io :
format ( File_handler , "~p~n" , [ Result ] ) , ok
= file :
close ( File_handler ) ,
VmCmd = "vmstat 5 5 >>" ++ Vmstat_Filename ,
?CH ( os, cmd, [ VmCmd ] ) ;
collect_system_info ( SysInfo_Filename , Vmstat_Filename , sgc1, om) ->
Mem_result = ?OM ( ets, tab2list, [ plcInfo] ) ,
ProcessNum = length ( ?OM ( erlang, process es, [ ] ) ) ,
Result = Mem_result ++ [ { process _Num, ProcessNum } ] ,
{ ok
, File_handler } = file :
open ( SysInfo_Filename , [ append
] ) , io :
format ( File_handler , "~p~n" , [ Result ] ) , ok
= file :
close ( File_handler ) ,
VmCmd = "vmstat 5 5 >>" ++ Vmstat_Filename ,
?OM ( os, cmd, [ VmCmd ] ) ;
collect_system_info ( SysInfo_Filename , Vmstat_Filename , ommp, ch) ->
Mem_result = ?SBGCH ( ets, tab2list, [ plcInfo] ) ,
ProcessNum = length ( ?SBGCH ( erlang, process es, [ ] ) ) ,
Result = Mem_result ++ [ { process _Num, ProcessNum } ] ,
{ ok
, File_handler } = file :
open ( SysInfo_Filename , [ append
] ) , io :
format ( File_handler , "~p~n" , [ Result ] ) , ok
= file :
close ( File_handler ) ,
VmCmd = "vmstat 5 5 >>" ++ Vmstat_Filename ,
?SBGCH ( os, cmd, [ VmCmd ] ) ;
collect_system_info ( SysInfo_Filename , Vmstat_Filename , ommp, om) ->
Mem_result = ?SBGOM ( ets, tab2list, [ plcInfo] ) ,
ProcessNum = length ( ?SBGOM ( erlang, process es, [ ] ) ) ,
Result = Mem_result ++ [ { process _Num, ProcessNum } ] ,
{ ok
, File_handler } = file :
open ( SysInfo_Filename , [ append
] ) , io :
format ( File_handler , "~p~n" , [ Result ] ) , ok
= file :
close ( File_handler ) ,
VmCmd = "vmstat 5 5 >>" ++ Vmstat_Filename ,
?SBGOM ( os, cmd, [ VmCmd ] ) .
collect_plc_res ( PlcRes_Filename ) ->
PlcRes_result = ?CH ( plcScheduler, plc_res, [ 1 , 1000 ] ) ,
{ ok
, File_handler } = file :
open ( PlcRes_Filename , [ append
] ) , io :
format ( File_handler , "~p~n" , [ PlcRes_result ] ) , ok
= file :
close ( File_handler ) .
transfer_vmstat_file ( Filename , Type , Role ) ->
BladeNum = get _blade_no ( Type , Role ) ,
File = "/private" ++ re :
replace ( Filename , "blade" , BladeNum , [ { return
, list } ] ) ,
SisRootPswd = sbgFtSupport:get _sis_root_pw ( ) ,
ok = scp_from ( "root" , SisRootPswd , "sis1" , File , LocalFile ) ,
LocalFile .
get _blade_no ( sgc1, ch) ->
[ _ , BladeNum ] = re :
split ( to_string ( ?
SGCchNode ) , "[\@ ]" , [ { return
, list } ] ) , BladeNum ;
get _blade_no ( sgc1, om) ->
[ _ , BladeNum ] = re :
split ( to_string ( ?
SGComNode ) , "[\@ ]" , [ { return
, list } ] ) , BladeNum ;
get _blade_no ( ommp, ch) ->
[ _ , BladeNum ] = re :
split ( to_string ( ?
SBGchNode ) , "[\@ ]" , [ { return
, list } ] ) , BladeNum ;
get _blade_no ( ommp, om) ->
[ _ , BladeNum ] = re :
split ( to_string ( ?
SBGomNode ) , "[\@ ]" , [ { return
, list } ] ) , BladeNum .
scp_from ( User , Passwd , RemoteHost , RemoteFile , LocalFile ) ->
H = case is_atom ( RemoteHost ) of
true -> atom_to_list ( RemoteHost ) ;
false -> RemoteHost
end ,
RemoteIp = jpTutil:get _ip_from_hostname ( H ) ,
Options = [ { user, User } ,
{ password, Passwd } ,
{ user_interaction, false} ,
{ silently_accept_hosts, true} ] ,
case ssh_sftp :
start_channel ( RemoteIp , Options ) of { ok, Pid , ConnRef } ->
case ssh_sftp :
read_file ( Pid , RemoteFile ) of { ok, Data } ->
case file :
write_file ( LocalFile , Data ) of ok ->
{ error, Error } ->
ct :
pal ( "scp_from remote host ~p Error ~p~n" , [ RemoteHost , Error ] ) , { nok, Error }
end ;
ErrorReadingFile ->
{ nok, { error_reading_source_file, ErrorReadingFile } }
end ;
{ error, Error } ->
{ nok, Error } ;
ErrorFromConnect ->
{ nok, ErrorFromConnect }
end .
get _all_counter ( ) ->
RegCount = count ( reg) ,
B2bCount = count ( b2b) ,
HiwCount = count ( hiw) ,
OabCount = count ( oab) ,
[ RegCount , B2bCount , HiwCount , OabCount ] .
count ( reg) ->
Count1 = ?CH ( sysProc, select_count_names, [ [ { { ?REG_MATCH , '_' } , [ ] , [ true] } ] ] ) ,
Count2 = ?CH ( sysProc, select_count_names, [ [ { { ?REG_MATCH2 , '_' } , [ ] , [ true] } ] ] ) ,
Count = reg_count ( Count1 , Count2 ) ,
ResultCount = count ( Count ) ,
io :
format ( "\t Number of REG processes in the system are ~p~n" , [ ResultCount ] ) , ResultCount ;
count ( b2b) ->
Count = ?CH ( sysProc, select_count_names, [ [ { { ?DC_MATCH ( b2b) , '_' } , [ ] , [ true] } ] ] ) ,
ResultCount = count ( Count ) ,
io :
format ( "\t Number of B2B processes in the system are ~p~n" , [ ResultCount ] ) , ResultCount ;
count ( hiw) ->
Count = ?CH ( sysProc, select_count_names, [ [ { { ?DC_MATCH ( hiw) , '_' } , [ ] , [ true] } ] ] ) ,
ResultCount = count ( Count ) ,
io :
format ( "\t Number of HIW processes in the system are ~p~n" , [ ResultCount ] ) , ResultCount ;
count ( oab) ->
Count = ?CH ( sysProc, select_count_names, [ [ { { ?DC_MATCH ( oab) , '_' } , [ ] , [ true] } ] ] ) ,
ResultCount = count ( Count ) ,
io :
format ( "\t Number of OAB processes in the system are ~p~n" , [ ResultCount ] ) , ResultCount ;
count ( Int ) when is_integer ( Int ) ->
Int ;
count ( Other ) ->
[ Other ] .
reg_count ( C1 , C2 ) when is_integer ( C1 ) , is_integer ( C2 ) -> C1 + C2 ;
reg_count ( C1 , _C2 ) when is_integer ( C1 ) -> C1 ;
reg_count ( _C1 , C2 ) when is_integer ( C2 ) -> C2 ;
reg_count ( _C1 , _C2 ) -> 0 .
draw_graph ( InputFile , sysinfo) ->
OutputFile = ?
EST_LOG_DIR ++ filename :
rootname ( filename :
basename ( InputFile ) ) ++ ".svg" , Cmd = "java -jar " ++ ?EST_DATA_BIN ++ "sysinfo.jar " ++ "\< " ++ " " ++
InputFile ++ " \| java -jar " ++ ?EST_DATA_BIN ++ "xml2svg.jar " ++
"\> " ++ " " ++ OutputFile ,
ct :
pal ( "The graph draw sysinfo command:~n~p~n" , [ Cmd ] ) , OutputFile ;
draw_graph ( InputFile , vmstat) ->
OutputFile = ?
EST_LOG_DIR ++ filename :
rootname ( filename :
basename ( InputFile ) ) ++ ".svg" , Cmd = "java -jar " ++ ?EST_DATA_BIN ++ "vmstat.jar " ++ "\< " ++ " " ++
InputFile ++ " \| java -jar " ++ ?EST_DATA_BIN ++ "xml2svg.jar " ++
"\> " ++ " " ++ OutputFile ,
ct :
pal ( "The graph draw vmstat command:~n~p~n" , [ Cmd ] ) , OutputFile ;
draw_graph ( InputFile , passrate) ->
OutputFile = ?
EST_LOG_DIR ++ filename :
rootname ( filename :
basename ( InputFile ) ) ++ ".svg" , Cmd = "java -jar " ++ ?EST_DATA_BIN ++ "passrate.jar " ++ "\< " ++ " " ++
InputFile ++ " \| java -jar " ++ ?EST_DATA_BIN ++ "xml2svg.jar " ++
"\> " ++ " " ++ OutputFile ,
ct :
pal ( "The graph draw passrate command:~n~p~n" , [ Cmd ] ) , OutputFile .
generate_html ( SysInfoSvg , VmstatSvg , UacPassrateSvg , UasPassrateSvg ) ->
Cmd = ?EST_DATA_BIN ++ "html_generate.sh" ++ " " ++ SysInfoSvg ++ " " ++
VmstatSvg ++ " " ++ UacPassrateSvg ++ " " ++ UasPassrateSvg ++ " " ++ ?EST_LOG_DIR ,
generate_graph_on_log_html ( SysInfoSvg , VmstatSvg , UacPassrateSvg , UasPassrateSvg ) ->
{ ok
, Cwd } = file :
get _
cwd ( ) , Cmd = "cp " ++ SysInfoSvg ++ " " ++ VmstatSvg ++ " " ++ UacPassrateSvg ++
" " ++ UasPassrateSvg ++ " " ++ Cwd ++ "/" ,
FileInfo = [ { "Erlang VM Information" , filename :
basename ( SysInfoSvg ) } , { "Vmstat Information" , filename :
basename ( VmstatSvg ) } , { "UacPass Rate" , filename :
basename ( UacPassrateSvg ) } , { "UasPass Rate" , filename :
basename ( UasPassrateSvg ) } ] , Res = gen_html_format ( FileInfo ) ,
gen_html_format ( FileInfo ) ->
F = fun ( { Desp , FileName } ) ->
"<h1>" ++ Desp ++ "</h1>" ++ "<embed type=\" image/svg+xml\" src=\" ../../" ++ FileName ++ "\" />"
end ,
List = lists :
map ( F , FileInfo ) , lists :
flatten ( [ "<div>" |
List ] , "</div>" ) .
%% #---------------------------------------------------------
%% #3.2 CODE FOR INTERNAL FUNCTIONS
%% #---------------------------------------------------------
%% -----------------------------------------------------------------------
%% start_sipp(SIPpCommand)
%% @spec start_sipp(SIPpCommand) -> Res::tuple()
%% @doc Start SIPP command and returns the process id of SIPp
%% <pre>
%% <b>Input:</b>
%% SIPpCommand String
%%
%% <b>Output:</b>
%% Res Tuple {ok|error, PID::string()}
%%
%% </pre>
%% @end
%% -----------------------------------------------------------------------
start_sipp ( SIPpCommand ) ->
SIPpShellPrintout = os :
cmd ( SIPpCommand ) , ct :
pal ( "startsipp result: ##### ~p~n" , [ SIPpShellPrintout ] ) , get _pid_str ( SIPpShellPrintout ) .
%% -----------------------------------------------------------------------
%% interval_loop_check(Counters)
%% @spec interval_loop_check(Counters::integer()) -> Res::atom()
%% @doc Check the sipp process status and sipp call status, return failed if
%% sipp process killed or call fail rate is more than expect in a certain time
%% <pre>
%% <b>Input:</b>
%% Counters Integer The number of times for check
%%
%% <b>Output:</b>
%% Res Tuple
%%
%% </pre>
%% @end
%% -----------------------------------------------------------------------
interval_loop_check ( Counters ) ->
receive
{ ua, SippPid , SippStatus } ->
case SippStatus of
true ->
ct :
pal ( "Sipp(PID=~p) is running~n" , [ SippPid ] ) , interval_loop_check ( Counters ) ;
false ->
?WARNING_MSG ( "the SIPp process(~p) doesn't work" , [ SippPid ] ) ,
{ failed, { sippStatus, SippPid } }
end ;
{ uaStat, StatFile , StatData } ->
CounterValue = proplists :
get _
value ( StatFile , Counters ) , RestCounters = proplists :
delete ( StatFile , Counters ) , case check_sipp_stat ( interval, StatData ) of
true ->
ct :
pal ( "Statistics file(~p) interval loop check ok~n" , [ StatFile ] ) , interval_loop_check ( [ { StatFile , 0 } |RestCounters] ) ;
false ->
?WARNING_MSG ( "the SIPp(~p) has some failed calls in the past ~p interval times~n" ,
[ StatFile , CounterValue ] ) ,
if CounterValue < ?STAT_FAIL ->
interval_loop_check ( [ { StatFile , CounterValue + 1 } |RestCounters] ) ;
true ->
{ failed, { sippStat, StatFile } }
end
end ;
stop ->
ok
end .
%% -----------------------------------------------------------------------
%% close_monitor(Pid, TRefs)
%% @spec close_monitor(Pid, TRefs)->Res
%% @doc Close the monitor for SIPp
%% <pre>
%% <b>Input:</b>
%% TRefs Time Reference of monitor
%%
%% </pre>
%% @end
%% -----------------------------------------------------------------------
close_monitor ( Pid , TRefs ) ->
[ time r:cancel ( TRef ) || TRef<- TRefs ] ,
ct :
pal ( "close_monitor: TRefs: ~p~n" , [ TRefs ] ) , Pid ! stop.
choose ( true, __True , _ ) -> __True ;
choose ( false, _ , __False ) -> __False .
handle_local_ip ( undefined, Role , IpVsn ) ->
local_ip_string ( IpVsn , Role ) ;
handle_local_ip ( LocalIp , _ , _ ) ->
LocalIp .
local_ip_string ( IpVsn , Role ) ->
case ?CH ( sysEnv, is_ssit, [ ] ) of
true ->
ip_string_ssit ( IpVsn ) ;
_ ->
local_ip_string_stp ( IpVsn , Role )
end .
local_ip_string_stp ( ipv4, access) ->
"10.10.121.7" ;
local_ip_string_stp ( ipv6, access) ->
"3001:10:121::7" ;
local_ip_string_stp ( ipv4, core4access) ->
"10.10.240.7" ;
local_ip_string_stp ( ipv6, core4access) ->
"3001:10:240::7" ;
local_ip_string_stp ( ipv4, foreign) ->
"10.10.141.7" ;
local_ip_string_stp ( ipv6, foreign) ->
"3001:10:141::7" ;
local_ip_string_stp ( ipv4, core4foreign) ->
"10.10.240.7" ;
local_ip_string_stp ( ipv6, core4foreign) ->
"3001:10:240::7" ;
local_ip_string_stp ( _IpVsn , Role ) ->
local_ip_string ( ipv4, Role ) .
ip_string_ssit ( IpVsn ) ->
IPtuple = case IpVsn of
ipv6 ->
get _local_ip_tuple ( inet6) ;
_ ->
get _local_ip_tuple ( inet)
end ,
inet_parse:ntoa ( IPtuple ) .
get _local_ip_tuple ( inet6) ->
%% The IPv6 linc local address that exists on the linux machine
%% can not be used by erlang. The address must be a global address or
%% loopback. Only loopback address is available in simulated environment.
{ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 } ;
get _local_ip_tuple ( InetFamily ) ->
{ ok
, Host } = inet :
get hostname ( ) , { ok
, IP } = inet :
get addr ( Host , InetFamily ) , IP .
handle_local_port ( undefined, Role ) ->
local_port_string ( Role ) ;
handle_local_port ( LocalPort , _Role ) ->
LocalPort .
local_port_string ( access) ->
"8004" ;
local_port_string ( core4access) ->
"8003" ;
local_port_string ( foreign) ->
"8002" ;
local_port_string ( core4foreign) ->
"8003" .
handle_remote_ip ( undefined, Role , IpVsn ) ->
remote_ip_string ( IpVsn , Role ) ;
handle_remote_ip ( RemoteIp , _ , _ ) ->
RemoteIp .
remote_ip_string ( IpVsn , Role ) ->
case ?CH ( sysEnv, is_ssit, [ ] ) of
true ->
ip_string_ssit ( IpVsn ) ;
_ ->
remote_ip_string_stp ( IpVsn , Role )
end .
remote_ip_string_stp ( ipv4, access) ->
"10.21.0.4" ;
remote_ip_string_stp ( ipv6, access) ->
"3001:21::4" ;
remote_ip_string_stp ( ipv4, core4access) ->
"10.10.230.4" ;
remote_ip_string_stp ( ipv6, core4access) ->
"3001:10:230::4" ;
remote_ip_string_stp ( ipv4, foreign) ->
"10.41.0.4" ;
remote_ip_string_stp ( ipv6, foreign) ->
"3001:41::4" ;
remote_ip_string_stp ( ipv4, core4foreign) ->
"10.10.230.5" ;
remote_ip_string_stp ( ipv6, core4foreign) ->
"3001:10:230::5" ;
remote_ip_string_stp ( _IpVsn , Role ) ->
remote_ip_string_stp ( ipv4, Role ) .
handle_remote_port ( undefined, Role ) ->
remote_port_string ( Role ) ;
handle_remote_port ( RemotePort , _ ) ->
RemotePort .
remote_port_string ( access) ->
"10004" ;
remote_port_string ( core4access) ->
"10003" ;
remote_port_string ( foreign) ->
"10002" ;
remote_port_string ( core4foreign) ->
"10005" .
stop_sipp_process ( Pid ) ->
stop_sipp_process ( Pid , graceful) .
stop_sipp_process ( Pid , graceful) ->
Cmd = "kill -USR1 " ++ to_string ( Pid ) ,
?DEBUG ( "the command is ~p~n" , [ Cmd ] ) ,
?DEBUG ( "the command result is ~p~n" , [ Res ] ) ;
stop_sipp_process ( Pid , enforced) ->
os :
cmd ( "kill -9 " ++ to_string ( Pid ) ) .
stop_uac_uas_sipp_processes ( [ Pid |T] = _Pids , CallDuration ) ->
stop_sipp_process ( Pid ) ,
time r:sleep ( CallDuration + 180000 ) ,
case check_pid_alive ( Pid ) of
false ->
ok ;
_ ->
?WARNING_MSG ( "Failed to stop the PID(~p) gracefully.~n" , [ Pid ] ) ,
?DEBUG ( "the PID(~p) status ~p~n" , [ Pid , check_pid_alive ( Pid ) ] ) ,
stop_sipp_process ( Pid , enforced)
end ,
time r:sleep ( 120000 ) ,
stop_uac_uas_sipp_processes ( T , 0 ) ;
stop_uac_uas_sipp_processes ( [ ] , _CallDuration ) ->
ok .
close_sipp ( ) ->
Res = os :
cmd ( "pgrep sipp" ) , case length ( Res ) of
0 -> ok ;
_ -> os :
cmd ( "pkill sipp" ) end .
check_sipp_process_status ( Pid , [ SippPid |T] = _SippPids ) ->
Pid ! { ua, SippPid , check_pid_alive ( SippPid ) } ,
check_sipp_process_status ( Pid , T ) ;
check_sipp_process_status ( _Pid , [ ] ) ->
ok .
check_sipp_call_status ( Pid , [ StatFile |T] = _StatFiles ) ->
{ ok, StatData } = get _stat_tail ( StatFile ) ,
Pid ! { uaStat, StatFile , StatData } ,
check_sipp_call_status ( Pid , T ) ;
check_sipp_call_status ( _Pid , [ ] ) ->
ok .
check_pid_alive ( Pid ) ->
IsString = is_string ( Pid ) ,
if
IsString ->
is_integer ( Pid ) ->
filelib :
is_dir ( "/proc/" ++ to_string ( Pid ) ) ; true ->
?WARNING_MSG ( "Wrong Pid Content:~p~n" , [ Pid ] ) ,
false
end .
check_sipp_stat ( interval, StatData ) ->
FailedCall_P = lists :
nth ( 17 , StatData ) , if
FailedCall_P > 0 ->
false ;
FailedCall_P == 0 ->
true
end ;
check_sipp_stat ( final, StatData ) ->
TotalCreatedCall = lists :
nth ( 13 , StatData ) , FailedCall_C = lists :
nth ( 18 , StatData ) ,
ct :
pal ( "Total number of calls is:~p~n" , [ TotalCreatedCall ] ) , ct :
pal ( "Number of failed calls is:~p~n" , [ FailedCall_C ] ) ,
if
TotalCreatedCall > 0 ->
CallLossRate = FailedCall_C / TotalCreatedCall ;
true ->
CallLossRate = 0
end ,
PrintInfo = [ { "Total calls" , to_list ( TotalCreatedCall ) } ,
{ "Total failed calls" , to_list ( FailedCall_C ) } ,
{ "Error rate" , to_list ( CallLossRate * 100 ) ++ "%" } ] ,
print ( PrintInfo ) ,
SIPpResult = if
CallLossRate > ?CALL_LOSS_RATE ->
false ;
true ->
true
end ,
SIPpResult .
get _stat_tail ( StatFile ) ->
case filelib :
is_regular ( StatFile ) of true ->
TailStat = os :
cmd ( "tail -1 " ++ StatFile ) , StrippedTailStat = string :
strip ( TailStat , right
, $\n
) , RowList = convert_datatype ( split_statistics ( StrippedTailStat , ";" ) ) ,
{ ok, RowList } ;
false ->
?WARNING_MSG ( "the StatFile ~p doesn't exist~n" , [ StatFile ] ) ,
{ error, StatFile }
end .
get _stat_head ( StatFile ) ->
case filelib :
is_regular ( StatFile ) of true ->
HeadStat = os :
cmd ( "head -1 " ++ StatFile ) , StrippedHeadStat = string :
strip ( HeadStat , right
, $\n
) , RowList = split_statistics ( StrippedHeadStat , ";" ) ,
{ ok, RowList } ;
false ->
?WARNING_MSG ( "the StatFile ~p doesn't exist~n" , [ StatFile ] ) ,
{ error, StatFile }
end .
get _pid_str ( Str ) ->
{ ok
, Mp } = re :
compile ( "PID=\\ \[ ([0-9]+)\\ \] " ) , case re :
run ( Str , Mp , [ { capture
, all_but_first
, list } ] ) of { match, [ PID ] } -> { ok, PID } ;
_ -> { error, Str }
end .
convert_datatype ( List ) ->
%% the first 5 items are date
%% the next 13 items are counters
lists :
sublist ( List , 5 ) ++ [ to_number ( X ) || X
<- lists :
sublist ( List , 6 , 13 ) ] .
write_time ( now ) ->
write_time ( { { Year , Month , Day } , { Hour , Minute , Second } } = _Time ) ->
lists :
flatten ( io_lib :
format ( "~w-~.2.0w-~.2.0w_~.2.0w:~.2.0w:~.2.0w" , [ Year , Month , Day , Hour , Minute , Second ] ) ) .
%%18 is for SIPp stat File
split_statistics ( String , StatDelimiter ) ->
split_statistics ( String , StatDelimiter , 18 ) .
split_statistics ( String , StatDelimiter , Length ) ->
RowList = re :
split ( String , StatDelimiter , [ { return
, list } ] ) , lists :
sublist ( RowList , Length ) .
print ( PrintInfo ) ->
Subject = "Early ST test report" ,
L1 = length ( Subject ) ,
L = integer_to_list ( trunc ( ( 69 - L1 ) / 2 + L1 ) ) ,
io :
format ( "|~69." ++ L ++ ". s|" , [ Subject ] ) , io :
format ( "+~69.35.-s+" , [ "+" ] ) , lists :
foreach ( fun ( { Desp , Num } ) -> io :
format ( "|~34.33. s|~34.20. s|" , [ Desp , Num ] ) , io :
format ( "+~69.35.-s+" , [ "+" ] ) end , PrintInfo ) ,
io :
format ( "+~69.0.-~+~n" ) .
to_list ( Integer ) when is_integer ( Integer ) ->
erlang :
integer_to_list ( Integer ) ; to_list ( Atom ) when is_atom ( Atom ) ->
to_list ( Float ) when is_float ( Float ) ->
Result = round ( Float * N ) / N ,
to_list ( 0 ) ->
"0" ;
to_list ( 0.0 ) ->
"0" .
is_string ( Input ) ->
io_lib :
printable_unicode_list ( Input ) .
to_string ( Input ) when is_integer ( Input ) ->
erlang :
integer_to_list ( Input ) ; to_string ( Input ) when is_float ( Input ) ->
to_string ( Input ) when is_atom ( Input ) ->
to_string ( Input ) ->
case is_string ( Input ) of
true -> Input ;
end . %% Is String already
to_number ( Str ) ->
{ error, _ } ->
case string :
to_integer ( Str ) of { error, _ } ->
?WARNING_MSG ( "the String(~p) is not integer and float.~n" , [ Str ] ) ,
Str ;
{ Int , _ } ->
Int
end ;
{ Float , _ } ->
Float
end .
- define ( FILE_NAMES , [
{ sgc1_ch, [ "/blade/homedir/sgc1-ch-vmstat-" , ?EST_LOG_DIR ++ "sgc1-ch-system-info-" , ?EST_LOG_DIR ++ "sgc1-ch-plc-res-" ] } ,
{ sgc1_om, [ "/blade/homedir/sgc1-om-vmstat-" , ?EST_LOG_DIR ++ "sgc1-om-system-info-" , ?EST_LOG_DIR ++ "sgc1-om-plc-res-" ] } ,
{ ommp_ch, [ "/blade/homedir/ommp-ch-vmstat-" , ?EST_LOG_DIR ++ "ommp-ch-system-info-" , ?EST_LOG_DIR ++ "ommp-ch-plc-res-" ] } ,
{ ommp_om, [ "/blade/homedir/ommp-om-vmstat-" , ?EST_LOG_DIR ++ "ommp-om-system-info-" , ?EST_LOG_DIR ++ "ommp-om-plc-res-" ] }
] ) .
%% -----------------------------------------------------------------------
%% @spec prepare_systeminfo_filenames(Input :: lists()) -> lists()
%% @doc Prepare the log files according which blades you want to monitor.
%% <pre>
%% <strong>Example</strong>:
%% prepare_systeminfo_filenames([sgc1_ch])
%% prepare_systeminfo_filenames([sgc1_ch, sgc1_om, ommp_ch, ommp_om])
%% </pre>
%% @end
%% -----------------------------------------------------------------------
prepare_systeminfo_filenames ( Input ) when is_list ( Input ) ->
Filename_suffix = write_time ( CurrentTime ) ++ ".log" ,
prepare_systeminfo_filenames_help ( Input , Filename_suffix , [ ] ) ;
prepare_systeminfo_filenames ( _ ) ->
ct :
pal ( "prepare_systeminfo_filenames: wrong input, just like [sgc1_ch, sgc1_om, ommp_ch, ommp_om]~n" ) , [ ] .
prepare_systeminfo_filenames_help ( [ ] , _ , Res ) ->
prepare_systeminfo_filenames_help ( [ H | T ] , Filename_suffix , Res ) ->
[ VmStat , SysInfo , Plc ] ->
TempRes = { H , { SysInfo ++ Filename_suffix , VmStat ++ Filename_suffix , Plc ++ Filename_suffix } } ,
print_systeminfo_file ( TempRes ) ,
prepare_systeminfo_filenames_help ( T , Filename_suffix , [ TempRes | Res ] ) ;
undefined ->
ct :
pal ( "There have wrong input parmeters, please select from (sgc1_ch, sgc1_om, ommp_ch, ommp_om)~n" ) , prepare_systeminfo_filenames_help ( T , Filename_suffix , Res )
end .
print_systeminfo_file ( { Blade , { SysInfo_Filename , Vmstat_Filename , PlcRes_Filename } } ) ->
ct :
pal ( "Blade: ~p~nSysInfo_Filename:~p~nVmstat_Filename:~p~nPlcRes_Filename:~p~n" , [ Blade , SysInfo_Filename , Vmstat_Filename , PlcRes_Filename ] ) .
%% -----------------------------------------------------------------------
%% @spec start_collect_system_infos(Input :: lists()) -> lists()
%% @doc Start collecting the logs.<br></br>
%% <strong>Note</strong>: The input parameter of this function is the result of <code>sbgEstSupport:prepare_systeminfo_filenames/1</code>.
%% @end
%% -----------------------------------------------------------------------
start_collect_system_infos ( FileNames ) ->
start_collect_system_infos ( FileNames , [ ] ) .
start_collect_system_infos ( [ ] , Res ) ->
start_collect_system_infos ( [ { sgc1_ch, { Sys , VmStat , Plc } } | T ] , Res ) ->
{ ok, SysInfoTimerRef } = time r:apply _interval ( time r:hms ( 0 , 2 , 0 ) ,
?MODULE , collect_system_info, [ sgc1_ch, Sys , VmStat ] ) ,
{ ok, PlcResTimerRef } = time r:apply _interval ( time r:hms ( 0 , 5 , 0 ) ,
sbgEstSupport, collect_plc_res, [ Plc ] ) ,
start_collect_system_infos ( T ,
[ { sgc1_ch, SysInfoTimerRef , PlcResTimerRef } | Res ] ) ;
start_collect_system_infos ( [ { sgc1_om, { Sys , VmStat , Plc } } | T ] , Res ) ->
{ ok, SysInfoTimerRef } = time r:apply _interval ( time r:hms ( 0 , 2 , 0 ) ,
?MODULE , collect_system_info, [ sgc1_om, Sys , VmStat ] ) ,
{ ok, PlcResTimerRef } = time r:apply _interval ( time r:hms ( 0 , 5 , 0 ) ,
sbgEstSupport, collect_plc_res, [ Plc ] ) ,
start_collect_system_infos ( T , [ { sgc1_om, SysInfoTimerRef , PlcResTimerRef } | Res ] ) ;
start_collect_system_infos ( [ { ommp_ch, { Sys , VmStat , Plc } } | T ] , Res ) ->
{ ok, SysInfoTimerRef } = time r:apply _interval ( time r:hms ( 0 , 2 , 0 ) ,
?MODULE , collect_system_info, [ ommp_ch, Sys , VmStat ] ) ,
{ ok, PlcResTimerRef } = time r:apply _interval ( time r:hms ( 0 , 5 , 0 ) ,
sbgEstSupport, collect_plc_res, [ Plc ] ) ,
start_collect_system_infos ( T , [ { ommp_ch, SysInfoTimerRef , PlcResTimerRef } | Res ] ) ;
start_collect_system_infos ( [ { ommp_om, { Sys , VmStat , Plc } } | T ] , Res ) ->
{ ok, SysInfoTimerRef } = time r:apply _interval ( time r:hms ( 0 , 2 , 0 ) ,
?MODULE , collect_system_info, [ ommp_om, Sys , VmStat ] ) ,
{ ok, PlcResTimerRef } = time r:apply _interval ( time r:hms ( 0 , 5 , 0 ) ,
sbgEstSupport, collect_plc_res, [ Plc ] ) ,
start_collect_system_infos ( T , [ { ommp_om, SysInfoTimerRef , PlcResTimerRef } | Res ] ) .
- define ( BLADES , [
{ sgc1_ch, ?SGCchNode } ,
{ sgc1_om, ?SGComNode } ,
{ ommp_ch, ?SBGchNode } ,
{ ommp_om, ?SBGomNode }
] ) .
collect_system_info ( BladeType , SysInfo_Filename , Vmstat_Filename ) ->
case proplists :
get _
value ( BladeType , ?
BLADES ) of undefined ->
ct :
pal ( "The BladeType is wrong, pleaea refer: ~p~n" , [ ?
BLADES ] ) ; Node ->
io :
format ( "data is collecting, node:~p~n" , [ Node ] ) , Mem_result = jpT
rpc :
call ( Node , ets
, tab2list
, [ plcInfo
] ) , ProcessNum = length ( jpT
rpc :
call ( Node , erlang
, process es
, [ ] ) ) , Result = lists :
sort ( Mem_result ++ [ { process _Num
, ProcessNum } ] ) ,
{ ok
, File_handler } = file :
open ( SysInfo_Filename , [ append
] ) , io :
format ( File_handler , "~p~n" , [ Result ] ) , ok
= file :
close ( File_handler ) ,
VmCmd = "vmstat 5 5 >>" ++ Vmstat_Filename ,
jpT
rpc :
call ( Node , os
, cmd
, [ VmCmd ] ) end .
%% -----------------------------------------------------------------------
%% @spec stop_collect_system_infos(Input :: lists()) -> lists()
%% @doc Stop collecting the logs.<br></br>
%% <strong>Note</strong>: The input parameter of this function is the result of <code>sbgEstSupport:start_collect_system_infos/1</code>.
%% @end
%% -----------------------------------------------------------------------
stop_collect_system_infos ( TimerRefs ) ->
Fun = fun ( { BladeType , SysTimerRef , PlcTimerRef } ) ->
ct :
pal ( "stop_collect_system_infos: ~p ~p ~p~n" , [ BladeType , SysTimerRef , PlcTimerRef ] ) ,
time r:cancel ( SysTimerRef ) ,
time r:cancel ( PlcTimerRef )
end ,
lists :
foreach ( Fun , TimerRefs ) .
%% -----------------------------------------------------------------------
%% @spec generate_log_statistics(FileInfos :: lists(), UacStatFile :: string(), UasStatFile :: string(), BladeType :: atom()) -> ok
%% @doc Analyse the log files and draw graph.<br></br>
%% <pre>
%% <strong>Note</strong>:
%% FileInfos is the result of sbgEstSupport:prepare_systeminfo_filenames/1.
%% UacStatFile can get from the result of sbgEstSupport:prepare_sipp_cmd/3.
%% UasStatFile can get from the result of sbgEstSupport:prepare_sipp_cmd/3.
%% BladeType sgc1_ch | sgc1_om | ommp_ch | ommp_om
%% </pre>
%% @end
%% -----------------------------------------------------------------------
generate_log_statistics ( FileInfos , UacStatFile , UasStatFile , BladeType )
when is_atom ( BladeType ) andalso is_list ( FileInfos ) ->
case proplists :
get _
value ( BladeType , FileInfos ) of { SysInfo , VmState , _PlcInfo } ->
ct :
pal ( "generate_log_statistics for blade: ~p~n" , [ BladeType ] ) , %% --- transfer the collected vmstat file to LMWP ---
Local_Vmstat = sbgEstSupport:transfer_vmstat_file ( VmState , sgc1, ch) ,
time r:sleep ( 60000 ) ,
%% ------ script to draw graph need to be updated-----
SysInfo_Svg = sbgEstSupport:draw_graph ( SysInfo , sysinfo) ,
Vmstat_Svg = sbgEstSupport:draw_graph ( Local_Vmstat , vmstat) ,
Uac_Svg = sbgEstSupport:draw_graph ( UacStatFile , passrate) ,
Uas_Svg = sbgEstSupport:draw_graph ( UasStatFile , passrate) ,
time r:sleep ( 60000 ) ,
%% --- generate graph on jpt log page
sbgEstSupport:generate_graph_on_log_html ( SysInfo_Svg , Vmstat_Svg ,
Uac_Svg , Uas_Svg ) ;
undefined ->
ct :
pal ( "generate_log_statistics: The fourth parameter should be atom: sgc1_ch, sgc1_om, ommp_ch, ommp_om.~n" ) end ;
generate_log_statistics ( _ , _ , _ , _ ) ->
ct :
pal ( "generate_log_statistics: The input parameters are wrong, the fourth should be atom: sgc1_ch, sgc1_om, ommp_ch, ommp_om.~n" ) .
JSUgIzAuICAgIEJBU0lDIElORk9STUFUSU9OCiUlIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KJSUgJUNDYXNlRmlsZTogIHNiZ1N0U3VwcG9ydC5lcmwgCiUlIEF1dGhvcjogICAgICBldHhrb2xzCiUlIERlc2NyaXB0aW9uOiBJUyBoYXJkd2FyZSBmdW5jdGlvbnMuCiUlCiUlIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiUlIEBkb2MgVGhpcyB0aGUgdGhlIEFQSSBpbnRlcmZhY2UgZm9yIG1vZHVsZSA8Y29kZT5zYmdTdFN1cHBvcnQ8L2NvZGU+LgolJQolJSA8Zm9udCBzaXplPSI0Ij5Gb2xsb3cgdGhpcyBndWlkZSwgZmVhdHVyZSB0ZWFtIGNhbiBydW4gZU5TVCBlYXNpbHkuCiUlIDxvbD4KJSUgPGxpPnByZXBhcmUgeW91ciBzaXBwIHNjZW5hcmlvIGZpbGUsIGFuZCBjYWxsIDxjb2RlPnNiZ0VzdFN1cHBvcnQ6cHJlcGFyZV9zaXBwX2NtZC8zPC9jb2RlPiB0byBnZW5lcmF0ZSBzaXBwIGNvbW1hbmRzLjxicj48L2JyPgolJSA8c3Ryb25nPk5vdGU6PC9zdHJvbmc+ICBTb21lIGltcG9ydCBwYXJhbWV0ZXJzIGhhdmUgZGVmYXVsdCB2YWx1ZSwgYW5kIHRoZXJlIGFyZSBjb25maWd1cmFibGUuPC9saT4KJSUgPGxpPmNhbGwgPGNvZGU+c2JnRXN0U3VwcG9ydDpwcmVwYXJlX3N5c3RlbWluZm9fZmlsZW5hbWVzLzE8L2NvZGU+IHRvIHByZXBhcmUgdGhlIGxvZyBmaWxlcyBhY2NvcmRpbmcgd2hpY2ggYmxhZGVzIHlvdSB3YW50IHRvIG1vbml0b3IuPC9saT4KJSUgPGxpPmNhbGwgPGNvZGU+c2JnRXN0U3VwcG9ydDpzdGFydF9jb2xsZWN0X3N5c3RlbV9pbmZvcy8xPC9jb2RlPiB0byBzdGFydCBjb2xsZWN0aW5nIGxvZ3MuPC9saT4KJSUgPGxpPmNhbGwgPGNvZGU+c2JnRXN0U3VwcG9ydDpydW5fc2NlbmFyaW88L2NvZGU+IHRvIHJ1biB5b3VyIHNpcHAgdHJhZmZpYy48YnI+PC9icj4KJSUgPHN0cm9uZz5Ob3RlOjwvc3Ryb25nPiAgdGhlIHRyYWZmaWMgcnVuIHRpbWUgaXMgY29uZmlndXJhYmxlIGFuZCB5b3UgYWxzbyBjYW4gY29uZmlndXJlIG1vbml0b3JpbmcgdGhlIFNJUHAgcnVubmluZyBzdGF0dXMuPC9saT4KJSUgPGxpPmNhbGwgPGNvZGU+c2JnRXN0U3VwcG9ydDpnZW5lcmF0ZV9sb2dfc3RhdGlzdGljcy80PC9jb2RlPiB0byBhbmFseXplIHRoZSBsb2cgZmlsZXMgYW5kIGRyYXcgdGhlIHBpY3R1cmUuPC9saT4KJSUgPC9vbD4KJSUgWW91IGNhbiByZWZ0ZXIgdGhlIGRlbW8gY2FzZSBpbiBzdWl0ZSA8Y29kZT5zYmdfZVNUX3RlbXBsYXRlLmVybDwvY29kZT4sIGhlcmUgaXMgdGhlIDxhIGhyZWY9Imh0dHBzOi8vYS4uLmNvbnRlbnQtYXZhaWxhYmxlLXRvLWF1dGhvci1vbmx5Li4ubi5zZS9qcFQvZWxxc3R1eC8xNS0xMS0wMi9jdF9ydW4uY3RfNUBzZWtpbHhjMDAwNy4yMDE1LTExLTAyXzEyLjA0LjQ5L2F1dG8uc3VpdGUuc2JnX2VTVF90ZW1wbGF0ZS5zYmdfZXN0XzAwMS5sb2dzL3J1bi4yMDE1LTExLTAyXzEyLjA0LjU0L3N1aXRlLmxvZy5odG1sIj5sb2c8L2E+CiUlIDwvZm9udD4KLW1vZHVsZShzYmdFc3RTdXBwb3J0KS4KLWRhdGUoJzIwMTUtMTEtMDInKS4KLWF1dGhvcignZWxxc3R1eCcpLgotdnNuKCcvbWFpbi9SMjBBL1IyMUEvUjIyQS9SOTlBLzIsIGNoZWNrb3V0IGJ5IGVscXN0dXggaW4gZWxxc3R1eF9raV9wcGInKS4KCiUlIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KJSUgJUNDYXNlQ29weXJpZ2h0QmVnaW4lCiUlIENvcHlyaWdodCAoYykgRXJpY3Nzb24gQUIgMjAwOS0yMDEzIEFsbCByaWdodHMgcmVzZXJ2ZWQuCiUlCiUlIFRoZSBwcm9ncmFtIG1heSBiZSB1c2VkIGFuZC9vciBjb3BpZWQgb25seSB3aXRoIHRoZSB3cml0dGVuCiUlIHBlcm1pc3Npb24gZnJvbSBFcmljc3NvbiBUZWxlY29tIEFCLCBvciBpbiBhY2NvcmRhbmNlIHdpdGgKJSUgdGhlIHRlcm1zIGFuZCBjb25kaXRpb25zIHN0aXB1bGF0ZWQgaW4gdGhlIGFncmVlbWVudC9jb250cmFjdAolJSB1bmRlciB3aGljaCB0aGUgcHJvZ3JhbSBoYXMgYmVlbiBzdXBwbGllZC4KJSUKJSUgQWxsIHJpZ2h0cyByZXNlcnZlZAolJQolJSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiUlCiUlICMxLiAgICBFWFBPUlQgTElTVFMKJSUgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQolJSAjMS4xICAgRVhQT1JURUQgSU5URVJGQUNFIEZVTkNUSU9OUwolJSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgolJSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiUlICMyLiAgICAgICAgIFJFVklTSU9OIExPRwolJSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiUlIFJldiAgICAgICAgIERhdGUgICAgICAgICAgICBOYW1lICAgICAgICAgICAgV2hhdAolJSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiUlIFIyMUEvMSAgICAgIDEzMDczMSAgICAgICAgICBlbGlsaWdlICAgICAgICAgIENyZWF0ZWQKJSUgUjIxQS8yICAgICAgMTMxMDE2ICAgICAgICAgIGVsaWxpZ2UgICAgICAgICAgQWRkIGRyYXdfZ3JhcGgKJSUgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQolJSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiUlICMyLiAgICBFWFBPUlQgTElTVFMKJSUgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQolJSAjMi4xICAgRVhQT1JURUQgSU5URVJGQUNFIEZVTkNUSU9OUwolJSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCi1leHBvcnQoW3ByZXBhcmVfc2lwcF9jbWQvMywKICAgICAgICAgcHJlcGFyZV9zeXN0ZW1pbmZvX2ZpbGVuYW1lcy8xLAogICAgICAgICBzdGFydF9jb2xsZWN0X3N5c3RlbV9pbmZvcy8xLApzdG9wX2NvbGxlY3Rfc3lzdGVtX2luZm9zLzEsCmdlbmVyYXRlX2xvZ19zdGF0aXN0aWNzLzQsCiAgICAgICAgIHJ1bl9zY2VuYXJpby82XSkuCgotZGVmaW5lKERFQlVHKEZvcm1hdCwgQXJncyksIGN0OnBhbChGb3JtYXQsIEFyZ3MpKS4KCi1kZWZpbmUoSU5GT19NU0coRm9ybWF0LCBBcmdzKSwgY3Q6cGFsKEZvcm1hdCwgQXJncykpLgogICAgICAgICAgICAgICAgICAKLWRlZmluZShXQVJOSU5HX01TRyhGb3JtYXQsIEFyZ3MpLCBjdDpwYWwoRm9ybWF0LCBBcmdzKSkuCiAgICAgICAgICAgICAgICAgIAotZGVmaW5lKEVSUk9SX01TRyhGb3JtYXQsIEFyZ3MpLCBjdDpwYWwoRm9ybWF0LCBBcmdzKSkuCgotZGVmaW5lKENSSVRJQ0FMX01TRyhGb3JtYXQsIEFyZ3MpLCBjdDpwYWwoRm9ybWF0LCBBcmdzKSkuCgotZGVmaW5lKFNUQVRfRkFJTCwgMTApLgotZGVmaW5lKENBTExfTE9TU19SQVRFLCAwLjAwMDUpLgoKLWRlZmluZShTSVBQX1BBUkFTLCBbCiAgICAgICAgICAgICAgICAgICAgIHtzZXJ2aWNlLCBbIiAtcyAiLCAiIl19LAogICAgICAgICAgICAgICAgICAgICB7c2NlbmFyaW9GaWxlLCBbIiAtc2YgIiwgIiJdfSwKICAgICAgICAgICAgICAgICAgICAge2luZkZpbGUsIFsiIC1pbmYgIiwgIiJdfSwKICAgICAgICAgICAgICAgICAgICAge2xvY2FsSXAsIFsiIC1pICIsICIiXX0sCiAgICAgICAgICAgICAgICAgICAgIHtsb2NhbFBvcnQsIFsiIC1wICIsICIiXX0sCiAgICAgICAgICAgICAgICAgICAgIHttZWRpYUlwLCBbIiAtbWkgIiwgIiJdfSwKICAgICAgICAgICAgICAgICAgICAge21lZGlhUG9ydCwgWyIgLW1wICIsICIiXX0sCiAgICAgICAgICAgICAgICAgICAgIHthdXRvQW5zd2VyLCBbIiAtYWEiXX0sCiAgICAgICAgICAgICAgICAgICAgIHtiYWNrZ3JvdW5kLCBbIiAtYmciXX0sCiAgICAgICAgICAgICAgICAgICAgIHtjYWxsUmF0ZSwgWyIgLXIgIiwgIiJdfSwKICAgICAgICAgICAgICAgICAgICAge21heENhbGxzLCBbIiAtbSAiLCAiIl19LAogICAgICAgICAgICAgICAgICAgICB7Y2FsbExlbmd0aCwgWyIgLWQgIiwgIiJdfSwKICAgICAgICAgICAgICAgICAgICAge3BhcmFsbGVsQ2FsbHMsIFsiIC1sICIsICIiXX0sCiAgICAgICAgICAgICAgICAgICAgIHtzdGF0RmlsZSwgWyIgLXN0ZiAiLCAiIl19LAogICAgICAgICAgICAgICAgICAgICB7ZXJyb3JGaWxlLCBbIiAtZXJyb3JfZmlsZSAiLCAiIl19LAogICAgICAgICAgICAgICAgICAgICB7c3RhdEZyZXF1ZW5jZSwgWyIgLWZkICIsICIiXX0sCiAgICAgICAgICAgICAgICAgICAgIHtvcGVuU3RhdCwgWyIgLXRyYWNlX3N0YXQiXX0sCiAgICAgICAgICAgICAgICAgICAgIHt0cmFjZVNjcmVlbiwgWyIgLXRyYWNlX3NjcmVlbiJdfSwKICAgICAgICAgICAgICAgICAgICAge3RyYWNlRXJyLCBbIiAtdHJhY2VfZXJyIl19LAogICAgICAgICAgICAgICAgICAgICB7dHJhbnNwb3J0LCBbIiAtdCAiLCAiIl19CiAgICAgICAgICAgICAgICAgICAgXSkuCi1kZWZpbmUoU0lQUF9NQU5EQVRPUllfUEFSQU1FVEVSUywgW3NjZW5hcmlvRmlsZSwgbG9jYWxJcCwgbG9jYWxQb3J0LCBtZWRpYUlwLCBzdGF0RmlsZV0pLgotZGVmaW5lKFNJUFBfREVGQVVMVF9QQVJBTUVURVJTLCBbYXV0b0Fuc3dlciwgYmFja2dyb3VuZCwgb3BlblN0YXQsIHRyYWNlU2NyZWVuLCB0cmFjZUVycl0pLgotZGVmaW5lKFNJUFBfT1BUSU9OQUxfUEFSQU1FVEVSUywgW3NlcnZpY2UsIGNhbGxSYXRlLCBpbmZGaWxlLCBtZWRpYVBvcnQsIG1heENhbGxzLCBjYWxsTGVuZ3RoLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFsbGVsQ2FsbHMsIHN0YXRGcmVxdWVuY2UsIHRyYW5zcG9ydCwgZXJyb3JGaWxlXSkuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKLWRlZmluZShTSVBQX0JJTiwgIi92b2JzL21nd2JsYWRlL1BQQi9TQkdfSFNEMTAxOTZfMS90ZXN0L2F1dG8vc3VpdGUvdGVzdC9lc3Rfc2NyaXB0L3NpcHAiKS4KLWRlZmluZShYTUxfRElSLCAiL3ZvYnMvbWd3YmxhZGUvUFBCL1NCR19IU0QxMDE5Nl8xL3Rlc3QvYXV0by9zdWl0ZS90ZXN0L3NpcHBfeG1sLyIpLgotZGVmaW5lKEVTVF9MT0dfRElSLCAiL2hvbWUvIisrb3M6Z2V0ZW52KCJVU0VSIikrKyIvZXN0X2xvZ190bXAvIikuCi1kZWZpbmUoRVNUX0RBVEFfQklOLCAiL3ZvYnMvbWd3YmxhZGUvUFBCL1NCR19IU0QxMDE5Nl8xL3Rlc3QvYXV0by9zdWl0ZS90ZXN0L2VzdF9zY3JpcHQvZGF0YV9hbmFseXNpcy8iKS4KCiUlLWRlZmluZShTR0NjaE5vZGUsanBUc3RhdGVzOmZpbmRfcm9sZShqcFRuYW1lczp0eTJuYShzZ2MpLGNoKSkuCi1kZWZpbmUoU0dDY2hOb2RlLGpwVHN0YXRlczpmaW5kX3JvbGUoc2JnRnRTdXBwb3J0OmdldF9ic19uYW1lKHNnYywxKSxjaCkpLgotZGVmaW5lKENIKE0sRixBKSwganBUcnBjOmNhbGwoP1NHQ2NoTm9kZSwgTSwgRiwgQSkpLgoKLWRlZmluZShTR0NvbU5vZGUsanBUc3RhdGVzOmZpbmRfcm9sZShzYmdGdFN1cHBvcnQ6Z2V0X2JzX25hbWUoc2djLDEpLG9tKSkuCi1kZWZpbmUoT00oTSxGLEEpLCBqcFRycGM6Y2FsbCg/U0dDb21Ob2RlLCBNLCBGLCBBKSkuCgotZGVmaW5lKFNCR2NoTm9kZSxqcFRzdGF0ZXM6ZmluZF9yb2xlKHNiZ0Z0U3VwcG9ydDpnZXRfYnNfbmFtZShvbW1wLDEpLGNoKSkuCi1kZWZpbmUoU0JHQ0goTSxGLEEpLCBqcFRycGM6Y2FsbCg/U0JHY2hOb2RlLCBNLCBGLCBBKSkuCgotZGVmaW5lKFNCR29tTm9kZSxqcFRzdGF0ZXM6ZmluZF9yb2xlKHNiZ0Z0U3VwcG9ydDpnZXRfYnNfbmFtZShvbW1wLDEpLG9tKSkuCi1kZWZpbmUoU0JHT00oTSxGLEEpLCBqcFRycGM6Y2FsbCg/U0JHb21Ob2RlLCBNLCBGLCBBKSkuCgotZGVmaW5lKFJFR19NQVRDSCwge3JlZ0tleSwgJ18nLCAnXycsICdfJywgJ18nLCAnXycsICdfJywgJ18nLCAnXyd9KS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKLWRlZmluZShSRUdfTUFUQ0gyLCB7cmVnS2V5LCAnXycsICdfJywgJ18nfSkuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCi1kZWZpbmUoRENfTUFUQ0goQVBQKSwge0FQUCwgbGlzdF90b19hdG9tKGF0b21fdG9fbGlzdChBUFApKysiRGNNYWluIiksJ18nfSkuCgotZGVmaW5lKERFRkFVTFRfTU9OSVRPUl9JTlRFUlZBTCwgezAsIDUsIDB9KS4KCgolJSBAc3BlYyBwcmVwYXJlX3NpcHBfY21kKFNJUHBfQ29uZmlnLCBSb2xlLCBJcFZzbikgLT4gUmVzIDo6IHR1cGxlKCkKJSUgQGRvYyAgU2hvd3MgPGEgaHJlZj0iaHR0cDovL3MuLi5jb250ZW50LWF2YWlsYWJsZS10by1hdXRob3Itb25seS4uLmUubmV0LyI+U0lQcDwvYT4gb25saW5lIGhlbHAgZG9jdW1lbnRhdGlvbiBvZiB3aGF0IGNvbmZpZyBwYXJhbWV0ZXJzIHRoZXJlIGFyZS48YnI+PC9icj4KJSUgUHJlcGFyZSBzaXBwIGNtZCBmb3Igc3RhcnRpbmcgc2lwcCBwcm9jZXNzLCBkZWZhdWx0IHZhbHVlIGlzIHVzZWQgaWYgY2Fubm90IGdldCBmcm9tIHVzZXIgaW5wdXQuPGJyPjwvYnI+CiUlIDxwcmU+CiUlIDxzdHJvbmc+Tm9kZTo8L3N0cm9uZz4KJSUgU0lQcF9Db25maWcgICAgIHByb3BsaXN0cygpCiUlIFJvbGUgICAgICAgICAgICBhY2Nlc3MgfCBjb3JlNGFjY2VzcwolJSBJcFZzbSAgICAgICAgICAgaXB2NCB8IGlwdjYKJSUgUmVzICAgICAgICAgICAgIHR1cGxlLCBqdXN0IGxpa2Uge29rLCB7VWFjU2NlbmFyaW9GaWxlLCBVYWNTdGF0RmlsZSwgVWFjU2lwcENtZH09VWFjUGFyYW19CiUlIDwvcHJlPgolJSA8ZGw+CiUlIDxkdD49PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0gPC9kdD4KJSUgPGR0PiAgICAgICBUaGUga2V5IGZvciBwYXJhbWV0ZXIoU0lQcF9Db25maWcpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2R0PgolJSA8ZHQ+PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09IDwvZHQ+CiUlIDxkdD48cHJlPjxzdHJvbmc+UGFyYW1ldGVyPC9zdHJvbmc+ICAgICAgICAgICAgICAgICAgICAgICAgIDxzdHJvbmc+RGVzY3JpcHRpb248L3N0cm9uZz4gICAgICAgIDwvcHJlPjwvZHQ+CiUlIDxkdD48cHJlPmxvY2FsSXAgICAgICAgICAgICAgICAgICAgICAgICAgICBJUCBhZGRyZXNzIG9mIHNvdXJjZSwgbWFuZGF0b3J5IGJ1dCBoYXMgZGVmYXVsdCB2YWx1ZTwvcHJlPjwvZHQ+CiUlIDxkdD48cHJlPm1lZGlhSXAgICAgICAgICAgICAgICAgICAgICAgICAgICBNZWRpYSBhZGRyZXNzIG9mIHNvdXJjZSwgbWFuZGF0b3J5IGJ1dCBoYXMgZGVmYXVsdCB2YWx1ZTwvcHJlPjwvZHQ+CiUlIDxkdD48cHJlPmxvY2FsUG9ydCAgICAgICAgICAgICAgICAgICAgICAgICBTb3VyY2UgcG9ydCwgbWFuZGF0b3J5IGJ1dCBoYXMgZGVmYXVsdCB2YWx1ZTwvcHJlPjwvZHQ+CiUlIDxkdD48cHJlPnJlbW90ZUlwICAgICAgICAgICAgICAgICAgICAgICAgICBJUCBhZGRyZXNzIG9mIGRlc3RpbmF0aW9uLCBtYW5kYXRvcnkgYnV0IGhhcyBkZWZhdWx0IHZhbHVlPC9wcmU+PC9kdD4KJSUgPGR0PjxwcmU+cmVtb3RlUG9ydCAgICAgICAgICAgICAgICAgICAgICAgIERlc3RpbmF0aW9uIHBvcnQsIG1hbmRhdG9yeSBidXQgaGFzIGRlZmF1bHQgdmFsdWU8L3ByZT48L2R0PgolJSA8ZHQ+PHByZT5zY2VuYXJpb0ZpbGUgICAgICAgICAgICAgICAgICAgICAgUGF0aCBvZiBzY2VuYXJpbyBmaWxlLCBtYW5kYXRvcnk8L3ByZT48L2R0PgolJSA8ZHQ+PHByZT5zdGF0RmlsZSAgICAgICAgICAgICAgICAgICAgICAgICAgUGF0aCBvZiBzdGF0aXN0aWNzIGZpbGUsIG1hbmRhdG9yeSBidXQgaGFzIGRlZmF1bHQgdmFsdWU8L3ByZT48L2R0PgolJSA8ZHQ+PHByZT5pbmZGaWxlICAgICAgICAgICAgICAgICAgICAgICAgICAgUGF0aCBvZiBpbmplY3QgcGFyYW1ldGVyIGZpbGUsIG9wdGlvbmFsPC9wcmU+PC9kdD4KJSUgPGR0PjxwcmU+Y2FsbHJhdGUgICAgICAgICAgICAgICAgICAgICAgICAgIE51bWJlciBvZiBjYWxscy9zLCBvcHRpb25hbDwvcHJlPjwvZHQ+CiUlIDxkdD48cHJlPm1heGNhbGxzICAgICAgICAgICAgICAgICAgICAgICAgICBNYXhpbXVtIG51bWJlciBvZiBjYWxscyBzZXR1cCwgb3B0aW9uYWw8L3ByZT48L2R0PgolJSA8ZHQ+PHByZT5jYWxsbGVuZ3RoICAgICAgICAgICAgICAgICAgICAgICAgTGVuZ3RoIGluIHNlY29uZHMgb2YgY2FsbHMsIG9wdGlvbmFsPC9wcmU+PC9kdD4KJSUgPGR0PjxwcmU+cGFyYWxsZWxDYWxscyAgICAgICAgICAgICAgICAgICAgIFRvdGFsIG51bWJlciBvZiBjYWxscyBpbiBwYXJhbGxlbCwgb3B0aW9uYWw8L3ByZT48L2R0PgolJSA8ZHQ+PHByZT5zdGF0RnJlcXVlbmNlICAgICAgICAgICAgICAgICAgICAgVGhlIHN0YXRpc3RpY3MgZHVtcCBsb2cgcmVwb3J0IGZyZXF1ZW5jeSwgb3B0aW9uYWw8L3ByZT48L2R0PgolJSA8ZHQ+PHByZT50cmFuc3BvcnQgICAgICAgICAgICAgICAgICAgICAgICAgVGhlIHRyYW5zcG9ydCBtb2RlLCBvcHRpb25hbDwvcHJlPjwvZHQ+CiUlIDxkdD49PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0gPC9kdD4KJSUgPC9kbD4KJSUgPT09IEV4YW1wbGUgdXNhZ2UgPT09CiUlIGBgYGpwVHNpcHA6cHJlcGFyZV9zaXBwX2NtZChbe2xvY2FsSXAsICIxMC4xMC4xMjEuNyJ9LAolJSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHttZWRpYUlwLCAiMTAuMTAuMTIxLjcifSwKJSUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7bG9jYWxQb3J0LCAiODAwNCJ9LAolJSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHtyZW1vdGVJcCwgIjEwLjIxLjAuNCJ9LAolJSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHtyZW1vdGVQb3J0LCAiMTAwMDQifSwKJSUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7c2NlbmFyaW9GaWxlLCAiLi9zaXBwL3N0L3VhY19yZWdpc3Rlci54bWwifSwKJSUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7aW5mRmlsZSwgIi4vc2lwcC9zdC9kYXRhYmFzZS5jc3YifSwKJSUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7c3RhdEZpbGUsICIuL3NpcHAvc3Qvc2lwcF9yZWd0aXN0ZXJfdWFjLmNzdiJ9LAolJSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHtjYWxsUmF0ZSwgIjUifV0sIGFjY2VzcywgaXB2NCkuJycnCgpwcmVwYXJlX3NpcHBfY21kKFNJUHBfQ29uZmlnLCBSb2xlLCBJcFZzbikgLT4KICAgIAogICAgU2lwcFBhcmFtZXRlcnMgPSBwcmVwYXJlX3NpcHBfcGFyYW0oU0lQcF9Db25maWcsIFJvbGUsIElwVnNuKSwKICAgIAogICAgUmVtb3RlQWRkciA9IHByb3BsaXN0czpnZXRfdmFsdWUocmVtb3RlSXAsIFNpcHBQYXJhbWV0ZXJzKSwKICAgIFJlbW90ZVBvcnQgPSBwcm9wbGlzdHM6Z2V0X3ZhbHVlKHJlbW90ZVBvcnQsIFNpcHBQYXJhbWV0ZXJzKSwKICAgIFJlbW90ZVVyaSAgPSBidWlsZF9yZW1vdGVfdXJpKFJlbW90ZUFkZHIsIFJlbW90ZVBvcnQpLCAKICAgIFN0YXRGaWxlICAgPSBwcm9wbGlzdHM6Z2V0X3ZhbHVlKHN0YXRGaWxlLCBTaXBwUGFyYW1ldGVycyksCiAgICBTY2VuYXJpb0ZpbGUgPSBwcm9wbGlzdHM6Z2V0X3ZhbHVlKHNjZW5hcmlvRmlsZSwgU2lwcFBhcmFtZXRlcnMpLAogICAgCiAgICBNYW5kYXRvcnlCb29sPSBjaGVja19zaXBwX3BhcmFtKFNpcHBQYXJhbWV0ZXJzLAoJCQkJICAgID9TSVBQX01BTkRBVE9SWV9QQVJBTUVURVJTLCBtYW5kYXRvcnkpLAogICAgT3B0aW9uYWxCb29sID0gY2hlY2tfc2lwcF9wYXJhbShTaXBwUGFyYW1ldGVycywKCQkJCSAgICA/U0lQUF9PUFRJT05BTF9QQVJBTUVURVJTLCBvcHRpb25hbCksCiAgICAKICAgIGlmIAogICAgICAgIChNYW5kYXRvcnlCb29sID06PSB0cnVlKSBhbmQgKE9wdGlvbmFsQm9vbCA9Oj0gdHJ1ZSkgLT4KICAgICAgICAgICAgTmV3U2lwcFBhcmFtZXRlcnMgPSBzZXRfZGVmYXVsdF9zaXBwX3BhcmFtKFNpcHBQYXJhbWV0ZXJzLAoJCQkJCQkgICAgICAgP1NJUFBfREVGQVVMVF9QQVJBTUVURVJTKSwKICAgICAgICAgICAgU2lwcFBhcmFTdHJpbmcgPSBidWlsZF9zaXBwX3BhcmFtZXRlcnNfc3RyaW5nKE5ld1NpcHBQYXJhbWV0ZXJzLAoJCQkJCQkJICAiIiksCiAgICAgICAgICAgIGlmCiAgICAgICAgICAgICAgICBSZW1vdGVVcmkgPTo9ICIiIC0+IAogICAgICAgICAgICAgICAgICAgID9FUlJPUl9NU0coIkVycm9yOn5uUmVtb3RlIEFkZHJlc3MgSW5mb3JtYXRpb24gaXMgaW52YWxpZDogfnAsIH5wfm4iLAoJCQkgICAgICAgW1JlbW90ZUFkZHIsIFJlbW90ZVBvcnRdKSwKICAgICAgICAgICAgICAgICAgICB7ZXJyb3IsIFJlbW90ZUFkZHJ9OwogICAgICAgICAgICAgICAgdHJ1ZSAtPgogICAgICAgICAgICAgICAgICAgIFNpcHBDbWQgPSA/U0lQUF9CSU4gKysgIiAiICsrIFJlbW90ZVVyaSArKyAiICIgKysgU2lwcFBhcmFTdHJpbmcsCiAgICAgICAgICAgICAgICAgICAgP0RFQlVHKCJHZW5lcmF0ZSBTaXBwIGNvbW1hbmQ6fm5+cH5uR2V0IFN0YXRGaWxlOn5ufnB+biIsCgkJCSAgIFtTaXBwQ21kLCBTdGF0RmlsZV0pLAogICAgICAgICAgICAgICAgICAgIHtvaywge1NjZW5hcmlvRmlsZSwgU3RhdEZpbGUsIFNpcHBDbWR9fQogICAgICAgICAgICBlbmQ7CiAgICAgICAgdHJ1ZSAtPgogICAgICAgICAgICA/V0FSTklOR19NU0coIldhcm5pbmc6fm5UaGUgbWFuZGF0b3J5IHBhcmFtZXRlcnMgY2hlY2sgcmVzdWx0OiB+cH5uIHRoZSBvcHRpb25hbCBjaGVjayByZXN1bHQ6IH5wLn5uIiwKCQkJIFtNYW5kYXRvcnlCb29sLCBPcHRpb25hbEJvb2xdKSwKICAgICAgICAgICAge2Vycm9yLCB7TWFuZGF0b3J5Qm9vbCwgT3B0aW9uYWxCb29sfX0KICAgIGVuZC4KCiUlIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiUlIHByZXBhcmVfc2lwcF9wYXJhbShTSVBwX0NvbmZpZywgUm9sZSwgSXBWc24pCiUlIEBzcGVjIHByZXBhcmVfc2lwcF9wYXJhbShTSVBwX0NvbmZpZzo6bGlzdCgpLCBSb2xlOjphdG9tKCksIElwVnNuOjphdG9tKCkpIC0+IFJlc0xpc3Q6Omxpc3QoKQolJSBAZG9jIFVwZGF0ZSBzaXBwIHBhcmFtZXRlcnMsIGNvbWJpbmUgcGF0aCB3aXRoIGZpbGUKJSUgU0lQcF9Db25maWc6Omxpc3QoKSBpcyBhIGxpc3Qgb2YgcGFyYW1ldGVycyB0byBzaXBwLiBNb3JlIGluZm8gYWJvdXQgcGFyYW1ldGVycwolJSBpbiBqcFRzaXBwOnByZXBhcmVfc2lwcF9jbWQvMQolJSA8cHJlPgolJSA8Yj5JbnB1dDo8L2I+CiUlIFNJUHBfQ29uZmlnICAgICAgTGlzdCAgICBBcmd1bWVudCBsaXN0IG9mIHR1cGxlcyBpbiBmb3JtIHtUeXBlVGFnLFBhcmFtfSAKJSUgUm9sZSAgICAgQXRvbSAgICAgYWNjZXNzfGNvcmU0YWNjZXNzfGZvcmVpZ258Y29yZTRmb3JlaWduICAKJSUgSXBWc24gICAgICBBdG9tICAgICBpcHY0fGlwdjYgICAgCiUlCiUlIDxiPk91dHB1dDo8L2I+CiUlIFJlc0xpc3QgICBMaXN0ICAgICBBcmd1bWVudCBsaXN0IG9mIHR1cGxlcwolJQolJSA8aT5FeGFtcGxlOjwvaT4KJSUganBUc2lwcDpwcmVwYXJlX3NpcHBfcGFyYW0oW3tsb2NhbElwLCAiMTAuMTAuMTIxLjcifSwKJSUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7bWVkaWFJcCwgIjEwLjEwLjEyMS43In0sCiUlICAgICAgICAgICAgICAgICAgICAgICAgICAgICAge2xvY2FsUG9ydCwgIjgwMDQifSwKJSUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7cmVtb3RlSXAsICIxMC4yMS4wLjQifSwKJSUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7cmVtb3RlUG9ydCwgIjEwMDA0In0KJSUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7c2NlbmFyaW9GaWxlLCAiLi9zaXBwL3N0L3VhY19yZWdpc3Rlci54bWwifSwKJSUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7aW5mRmlsZSwgIi4vc2lwcC9zdC9kYXRhYmFzZS5jc3YifSwKJSUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7c3RhdEZpbGUsICIuL3NpcHAvc3Qvc2lwcF9yZWd0aXN0ZXJfdWFjLmNzdiJ9LAolJSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHtlcnJvckZpbGUsICIuL3NpcHAvc3Qvc2lwcC1lcnJvci5sb2cifSwKJSUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7Y2FsbFJhdGUsICI1In1dLCBhY2Nlc3MsIGlwdjQpLgolJSA8L3ByZT4KJSUgQGVuZAolJSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpwcmVwYXJlX3NpcHBfcGFyYW0oU0lQcF9Db25maWcsIFJvbGUsIElwVnNuKS0+CiAgICBQcmVMb2NhbElwID0gcHJvcGxpc3RzOmdldF92YWx1ZShsb2NhbElwLCBTSVBwX0NvbmZpZyksCiAgICBQcmVMb2NhbFBvcnQgPSBwcm9wbGlzdHM6Z2V0X3ZhbHVlKGxvY2FsUG9ydCwgU0lQcF9Db25maWcpLAogICAgUHJlUmVtb3RlSXAgPSBwcm9wbGlzdHM6Z2V0X3ZhbHVlKHJlbW90ZUlwLCBTSVBwX0NvbmZpZyksCiAgICBQcmVSZW1vdGVQb3J0ID0gcHJvcGxpc3RzOmdldF92YWx1ZShyZW1vdGVQb3J0LCBTSVBwX0NvbmZpZyksCiAgICAKICAgIExvY2FsSXAgPSBoYW5kbGVfbG9jYWxfaXAoUHJlTG9jYWxJcCwgUm9sZSwgSXBWc24pLAogICAgTG9jYWxQb3J0ID0gaGFuZGxlX2xvY2FsX3BvcnQoUHJlTG9jYWxQb3J0LCBSb2xlKSwKICAgIFJlbW90ZUlwID0gaGFuZGxlX3JlbW90ZV9pcChQcmVSZW1vdGVJcCwgUm9sZSwgSXBWc24pLAogICAgUmVtb3RlUG9ydCA9IGhhbmRsZV9yZW1vdGVfcG9ydChQcmVSZW1vdGVQb3J0LCBSb2xlKSwKCiAgICBQcmVNZWRpYUlwID0gcHJvcGxpc3RzOmdldF92YWx1ZShtZWRpYUlwLCBTSVBwX0NvbmZpZyksCiAgICBNZWRpYUlwID0gY2hvb3NlKFByZU1lZGlhSXA9Oj11bmRlZmluZWQsIExvY2FsSXAsIFByZU1lZGlhSXApLAogICAgCiAgICBFcnJvckZpbGUgPSBjYXNlIHByb3BsaXN0czpnZXRfdmFsdWUoZXJyb3JGaWxlLCBTSVBwX0NvbmZpZykgb2YKICAgICAgICAgICAgICAgICAgICB1bmRlZmluZWQgLT4KICAgICAgICAgICAgICAgICAgICAgICAgP0VTVF9MT0dfRElSICsrIHRvX3N0cmluZyhSb2xlKSArKyAiXyIgKysgd3JpdGVfdGltZShub3cpICsrICJfIiArKyAic2lwcC1lcnJvci5sb2ciOwogICAgICAgICAgICAgICAgICAgIFRtcEVycm9yRmlsZSAtPgogICAgICAgICAgICAgICAgICAgICAgICBmaWxlbmFtZTpkaXJuYW1lKFRtcEVycm9yRmlsZSkgKysgIi8iICsrIHdyaXRlX3RpbWUobm93KSArKyAiXyIgKysgZmlsZW5hbWU6YmFzZW5hbWUoVG1wRXJyb3JGaWxlKQogICAgICAgICAgICAgICAgZW5kLAogICAgCiAgICBTdGF0RmlsZSA9IGNhc2UgcHJvcGxpc3RzOmdldF92YWx1ZShzdGF0RmlsZSwgU0lQcF9Db25maWcpIG9mCiAgICAgICAgICAgICAgICAgICB1bmRlZmluZWQgLT4KICAgICAgICAgICAgICAgICAgICAgICA/RVNUX0xPR19ESVIgKysgdG9fc3RyaW5nKFJvbGUpICsrICJfIiArKyB3cml0ZV90aW1lKG5vdykgKysgIl8iICsrICJzaXBwLXN0YXRpc3RpY3MuY3N2IjsKICAgICAgICAgICAgICAgICAgIFRtcFN0YXRGaWxlIC0+CiAgICAgICAgICAgICAgICAgICAgICAgZmlsZW5hbWU6ZGlybmFtZShUbXBTdGF0RmlsZSkgKysgIi8iICsrIHdyaXRlX3RpbWUobm93KSArKyAiXyIgKysgZmlsZW5hbWU6YmFzZW5hbWUoVG1wU3RhdEZpbGUpCiAgICAgICAgICAgICAgIGVuZCwKICAgCiAgICB1cGRhdGVfc2lwcF9wYXJhbShbe2xvY2FsSXAsIExvY2FsSXB9LCB7bG9jYWxQb3J0LCBMb2NhbFBvcnR9LCB7cmVtb3RlSXAsIFJlbW90ZUlwfSwgCiAgICAgICAgICAgICAgICAgICAgICAge3JlbW90ZVBvcnQsIFJlbW90ZVBvcnR9LCB7bWVkaWFJcCwgTWVkaWFJcH0sIHtzdGF0RmlsZSwgU3RhdEZpbGV9LAogICAgICAgICAgICAgICAgICAgICAgIHtlcnJvckZpbGUsIEVycm9yRmlsZX1dLCBTSVBwX0NvbmZpZykuCgoKdXBkYXRlX3NpcHBfcGFyYW0oW10sIFtdKSAtPgogICAgW107CnVwZGF0ZV9zaXBwX3BhcmFtKFtdLCBTSVBwX0NvbmZpZykgLT4KICAgIFNJUHBfQ29uZmlnOwp1cGRhdGVfc2lwcF9wYXJhbShbe0F0dHIsIFZhbHVlfXxSZXN0XT1fUGFyYW1fTGlzdCwgU0lQcF9Db25maWcpIC0+CiAgICBjYXNlIHtBdHRyLCBWYWx1ZX0gb2YKICAgICAgICB7XywgdW5kZWZpbmVkfSAtPgogICAgICAgICAgICB1cGRhdGVfc2lwcF9wYXJhbShSZXN0LCBTSVBwX0NvbmZpZyk7CiAgICAgICAge18sIF99IC0+CiAgICAgICAgICAgIE5ld1NJUHBfQ29uZmlnID0gcHJvcGxpc3RzOmRlbGV0ZShBdHRyLCBTSVBwX0NvbmZpZykgKysgW3tBdHRyLCBWYWx1ZX1dLAogICAgICAgICAgICB1cGRhdGVfc2lwcF9wYXJhbShSZXN0LCBOZXdTSVBwX0NvbmZpZykKICAgIGVuZC4gICAgICAgIAoKJSUgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KJSUgY2hlY2tfc2lwcF9wYXJhbShTaXBwUGFyYW1ldGVycywgX1BhcmFtZXRlckxpc3QsIFR5cGUpCiUlIEBzcGVjIGNoZWNrX3NpcHBfcGFyYW0oU2lwcFBhcmFtZXRlcnM6Omxpc3QoKSwgX1BhcmFtZXRlckxpc3Q6Omxpc3QoKSwgVHlwZTo6YXRvbSgpKSAtPiBSZXM6OmF0b20oKQolJSBAZG9jIENoZWNrIHdoZXRoZXIgcGFyYW1ldGVycyBhcmUgbG9zdCBhbmQgZHVwbGljYXRlZCBpbiBwYXJhbWV0ZXIgbGlzdAolJSBTaXBwUGFyYW1ldGVyczo6bGlzdCgpIGlzIGEgbGlzdCBvZiBwYXJhbWV0ZXJzIHRvIHNpcHAuIE1vcmUgaW5mbyBhYm91dCBwYXJhbWV0ZXJzCiUlIGluIGpwVHNpcHA6cHJlcGFyZV9zaXBwX2NtZC8xCiUlIDxwcmU+CiUlIDxiPklucHV0OjwvYj4KJSUgU2lwcFBhcmFtZXRlcnMgICAgICBMaXN0ICAgIEFyZ3VtZW50IGxpc3Qgb2YgdHVwbGVzIGluIGZvcm0ge1R5cGVUYWcsUGFyYW19CiUlIF9QYXJhbWV0ZXJMaXN0ICAgICAgTGlzdCAgICBQYXJhbWV0ZXIgbGlzdAolJSBUeXBlICAgICAgICAgICAgICAgIEF0b20gICAgbWFuZGF0b3J5fG9wdGlvbmFsICAgICAgIAolJQolJSA8Yj5PdXRwdXQ6PC9iPgolJSBSZXMgICBBdG9tICAgICB0cnVlfGZhbHNlCiUlCiUlIDxpPkV4YW1wbGU6PC9pPgolJSBqcFRzaXBwOmNoZWNrX3NpcHBfcGFyYW0oW3tsb2NhbElwLCAiMTAuMTAuMTIxLjcifSwKJSUgICAgICAgICAgICAgICAgICAgICAgICAgICB7bWVkaWFJcCwgIjEwLjEwLjEyMS43In0sCiUlICAgICAgICAgICAgICAgICAgICAgICAgICAge2xvY2FsUG9ydCwgIjgwMDQifSwKJSUgICAgICAgICAgICAgICAgICAgICAgICAgICB7cmVtb3RlSXAsICIxMC4yMS4wLjQifSwKJSUgICAgICAgICAgICAgICAgICAgICAgICAgICB7cmVtb3RlUG9ydCwgIjEwMDA0In0sCiUlICAgICAgICAgICAgICAgICAgICAgICAgICAge3NjZW5hcmlvRmlsZSwgIi4vc2lwcC9zdC91YWNfcmVnaXN0ZXIueG1sIn0sCiUlICAgICAgICAgICAgICAgICAgICAgICAgICAge2luZkZpbGUsICIuL3NpcHAvc3QvZGF0YWJhc2UuY3N2In0sCiUlICAgICAgICAgICAgICAgICAgICAgICAgICAge3N0YXRGaWxlLCAiLi9zaXBwL3N0L3NpcHBfcmVndGlzdGVyX3VhYy5jc3YifSwKJSUgICAgICAgICAgICAgICAgICAgICAgICAgICB7Y2FsbFJhdGUsICI1In1dLCAKJSUgICAgICAgICAgICAgICAgICAgICAgICAgIFtzY2VuYXJpb0ZpbGUsIGxvY2FsSXAsIGxvY2FsUG9ydCwgbWVkaWFJcCwgc3RhdEZpbGVdLAolJSAgICAgICAgICAgICAgICAgICAgICAgICAgbWFuZGF0b3J5KS4KJSUgPC9wcmU+CiUlIEBlbmQKJSUgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KY2hlY2tfc2lwcF9wYXJhbShTaXBwUGFyYW1ldGVycywgW1BhcmFtZXRlcnxSZXN0XT1fUGFyYW1ldGVyTGlzdCwgbWFuZGF0b3J5KSAtPgogICAgY2FzZSBnZXRfcGFyYW1lX3F1YW50aXR5KFBhcmFtZXRlciwgU2lwcFBhcmFtZXRlcnMpIG9mCiAgICAgICAgMSAtPgogICAgICAgICAgICBDb250PXByb3BsaXN0czpnZXRfdmFsdWUoUGFyYW1ldGVyLCBTaXBwUGFyYW1ldGVycyksCiAgICAgICAgICAgIGNhc2UgaXNfc3RyaW5nKENvbnQpIG9mCiAgICAgICAgICAgICAgICB0cnVlIC0+IAogICAgICAgICAgICAgICAgICAgIGNoZWNrX3NpcHBfcGFyYW0oU2lwcFBhcmFtZXRlcnMsIFJlc3QsIG1hbmRhdG9yeSk7CiAgICAgICAgICAgICAgICBmYWxzZSAtPgogICAgICAgICAgICAgICAgICAgID9XQVJOSU5HX01TRygiV3JvbmcgUGFyYW1ldGVyIH5wIENvbnRlbnQ6IH5wfm4iLFtQYXJhbWV0ZXIsIENvbnRdKSwKICAgICAgICAgICAgICAgICAgICBmYWxzZQogICAgICAgICAgICBlbmQ7CiAgICAgICAgMCAtPgogICAgICAgICAgICA/V0FSTklOR19NU0coInRoZSBtYW5kYXRvcnkgcGFyYW1ldGVyKH5wKSBpcyBub3QgaW5jbHVkZWQufm4gIixbUGFyYW1ldGVyXSksCiAgICAgICAgICAgIGZhbHNlOwogICAgICAgIF8gLT4gCiAgICAgICAgICAgID9XQVJOSU5HX01TRygidGhlIG1hbmRhdG9yeSBwYXJhbWV0ZXIofnApIGlzIG1vcmUgdGhhbiBvbmUufm4iLCBbUGFyYW1ldGVyXSksCiAgICAgICAgICAgIGZhbHNlCiAgICBlbmQ7CmNoZWNrX3NpcHBfcGFyYW0oU2lwcFBhcmFtZXRlcnMsIFtQYXJhbWV0ZXJ8UmVzdF09X1BhcmFtZXRlckxpc3QsIG9wdGlvbmFsKSAtPgogICAgY2FzZSBnZXRfcGFyYW1lX3F1YW50aXR5KFBhcmFtZXRlciwgU2lwcFBhcmFtZXRlcnMpIG9mCiAgICAgICAgMSAtPiAKICAgICAgICAgICAgY2FzZSBpc19zdHJpbmcocHJvcGxpc3RzOmdldF92YWx1ZShQYXJhbWV0ZXIsIFNpcHBQYXJhbWV0ZXJzKSkgb2YKICAgICAgICAgICAgICAgIHRydWUgLT4gCiAgICAgICAgICAgICAgICAgICAgY2hlY2tfc2lwcF9wYXJhbShTaXBwUGFyYW1ldGVycywgUmVzdCwgb3B0aW9uYWwpOwogICAgICAgICAgICAgICAgZmFsc2UgLT4KICAgICAgICAgICAgICAgICAgICBmYWxzZQogICAgICAgICAgICBlbmQ7CiAgICAgICAgMCAtPiAKICAgICAgICAgICAgY2hlY2tfc2lwcF9wYXJhbShTaXBwUGFyYW1ldGVycywgUmVzdCwgb3B0aW9uYWwpOwogICAgICAgIF8gLT4KICAgICAgICAgICAgP1dBUk5JTkdfTVNHKCJ0aGUgb3B0aW9uYWwgcGFyYW1ldGVyKH5wKSBpcyBtb3JlIHRoYW4gb25lLn5uIiwgW1BhcmFtZXRlcl0pLAogICAgICAgICAgICBmYWxzZSAKICAgIGVuZDsKY2hlY2tfc2lwcF9wYXJhbShfU2lwcFBhcmFtZXRlcnMsIFtdLCBfKSAtPiAKICAgIHRydWUuCgolJSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQolJSBzZXRfZGVmYXVsdF9zaXBwX3BhcmFtKFNpcHBQYXJhbWV0ZXJzLCBfRGVmYXVsdFBhcmFtZXRlckxpc3QpCiUlIEBzcGVjIHNldF9kZWZhdWx0X3NpcHBfcGFyYW0oU2lwcFBhcmFtZXRlcnM6Omxpc3QoKSwgX0RlZmF1bHRQYXJhbWV0ZXJMaXN0OjpsaXN0KCkpIC0+IFJlc0xpc3Q6Omxpc3QoKQolJSBAZG9jIEFkZCBkZWZhdWx0IHNpcHAgcGFyYW1ldGVyIGluIFNpcHBQYXJhbWV0ZXJzCiUlIFNpcHBQYXJhbWV0ZXJzOjpsaXN0KCkgaXMgYSBsaXN0IG9mIHBhcmFtZXRlcnMgdG8gc2lwcC4gTW9yZSBpbmZvIGFib3V0IHBhcmFtZXRlcnMKJSUgaW4ganBUc2lwcDpwcmVwYXJlX3NpcHBfY21kLzEKJSUgPHByZT4KJSUgPGI+SW5wdXQ6PC9iPgolJSBTaXBwUGFyYW1ldGVycyAgICAgIExpc3QgICAgQXJndW1lbnQgbGlzdCBvZiB0dXBsZXMgaW4gZm9ybSB7VHlwZVRhZyxQYXJhbX0KJSUgX1BhcmFtZXRlckxpc3QgICAgICBMaXN0ICAgIFBhcmFtZXRlciBsaXN0ICAgICAKJSUKJSUgPGI+T3V0cHV0OjwvYj4KJSUgUmVzTGlzdCAgTGlzdCAgICAgQXJndW1lbnQgbGlzdCBvZiB0dXBsZXMgaW4gZm9ybSB7VHlwZVRhZywgUGFyYW19CiUlCiUlIDxpPkV4YW1wbGU6PC9pPgolJSBqcFRzaXBwOnNldF9kZWZhdWx0X3NpcHBfcGFyYW0oW3tsb2NhbElwLCAiMTAuMTAuMTIxLjcifSwKJSUgICAgICAgICAgICAgICAgICAgICAgICAgICB7bWVkaWFJcCwgIjEwLjEwLjEyMS43In0sCiUlICAgICAgICAgICAgICAgICAgICAgICAgICAge2xvY2FsUG9ydCwgIjgwMDQifSwKJSUgICAgICAgICAgICAgICAgICAgICAgICAgICB7cmVtb3RlSXAsICIxMC4yMS4wLjQifSwKJSUgICAgICAgICAgICAgICAgICAgICAgICAgICB7cmVtb3RlUG9ydCwgIjEwMDA0In0sCiUlICAgICAgICAgICAgICAgICAgICAgICAgICAge3NjZW5hcmlvRmlsZSwgIi4vc2lwcC9zdC91YWNfcmVnaXN0ZXIueG1sIn0sCiUlICAgICAgICAgICAgICAgICAgICAgICAgICAge2luZkZpbGUsICIuL3NpcHAvc3QvZGF0YWJhc2UuY3N2In0sCiUlICAgICAgICAgICAgICAgICAgICAgICAgICAge3N0YXRGaWxlLCAiLi9zaXBwL3N0L3NpcHBfcmVndGlzdGVyX3VhYy5jc3YifSwKJSUgICAgICAgICAgICAgICAgICAgICAgICAgICB7Y2FsbFJhdGUsICI1In0sCiUlICAgICAgICAgICAgICAgICAgICAgICAgICAge21heENhbGxzLCAiMjAwMDAifV0sIAolJSAgICAgICAgICAgICAgICAgICAgICAgICAgW2F1dG9BbnN3ZXIsIGJhY2tncm91bmQsIG9wZW5TdGF0XSkuCiUlIDwvcHJlPgolJSBAZW5kCiUlIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCnNldF9kZWZhdWx0X3NpcHBfcGFyYW0oU2lwcFBhcmFtZXRlcnMsIFtQYXJhbWV0ZXJ8UmVzdF0gPSBfRGVmYXVsdFBhcmFtZXRlckxpc3QpIC0+CiAgICBjYXNlIGdldF9wYXJhbWVfcXVhbnRpdHkoUGFyYW1ldGVyLCBTaXBwUGFyYW1ldGVycykgb2YgCiAgICAgICAgMCAtPiAKICAgICAgICAgICAgc2V0X2RlZmF1bHRfc2lwcF9wYXJhbShbe1BhcmFtZXRlciwgdHJ1ZX18U2lwcFBhcmFtZXRlcnNdLCBSZXN0KTsKICAgICAgICBfIC0+IAogICAgICAgICAgICBOZXdTaXBwUGFyYW1ldGVycyA9IHByb3BsaXN0czpkZWxldGUoUGFyYW1ldGVyLCBTaXBwUGFyYW1ldGVycyksCiAgICAgICAgICAgIHNldF9kZWZhdWx0X3NpcHBfcGFyYW0oW3tQYXJhbWV0ZXIsIHRydWV9fE5ld1NpcHBQYXJhbWV0ZXJzXSwgUmVzdCkKICAgIGVuZDsKc2V0X2RlZmF1bHRfc2lwcF9wYXJhbShTaXBwUGFyYW1ldGVycywgW10pIC0+ICAgCiAgICBTaXBwUGFyYW1ldGVycy4KCgolJSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQolJSBnZXRfcGFyYW1lX3F1YW50aXR5KEtleSwgUGFyYW1ldGVycykKJSUgQHNwZWMgZ2V0X3BhcmFtZV9xdWFudGl0eShLZXk6OmF0b20oKSwgUGFyYW1ldGVyczo6bGlzdCgpKSAtPiBSZXM6OmF0b20oKQolJSBAZG9jIENhbGN1bGF0ZSB0aGUgbnVtYmVyIG9mIEtleSBpbiBQYXJhbWV0ZXJzIGxpc3QKJSUgUGFyYW1ldGVyczo6bGlzdCgpIGlzIGEgbGlzdCBvZiBwYXJhbWV0ZXJzIHRvIHNpcHAuIE1vcmUgaW5mbyBhYm91dCBwYXJhbWV0ZXJzCiUlIGluIGpwVHNpcHA6cHJlcGFyZV9zaXBwX2NtZC8xCiUlIDxwcmU+CiUlIDxiPklucHV0OjwvYj4KJSUgS2V5ICAgICAgICAgICAgIEF0b20gICAga2V5IGluIFBhcmFtZXRlcnMgbGlzdAolJSBQYXJhbWV0ZXJzICAgICAgTGlzdCAgICBBcmd1bWVudCBsaXN0IG9mIHR1cGxlcyBpbiBmb3JtIHtUeXBlVGFnLFBhcmFtfSAgCiUlCiUlIDxiPk91dHB1dDo8L2I+CiUlIFJlcyAgIEF0b20gICAgICBOdW1iZXIgb2Yga2V5cwolJQolJSA8aT5FeGFtcGxlOjwvaT4KJSUganBUc2lwcDpnZXRfcGFyYW1lX3F1YW50aXR5KGxvY2FsSXAsIFt7bG9jYWxJcCwgIjEwLjEwLjEyMS43In0sCiUlICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAge21lZGlhSXAsICIxMC4xMC4xMjEuNyJ9LAolJSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHtsb2NhbFBvcnQsICI4MDA0In0sCiUlICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAge2NhbGxSYXRlLCAiNSJ9XSkuCiUlIDwvcHJlPgolJSBAZW5kCiUlIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCmdldF9wYXJhbWVfcXVhbnRpdHkoS2V5LCBQYXJhbWV0ZXJzKSAtPgogICAgbGVuZ3RoKHByb3BsaXN0czpnZXRfYWxsX3ZhbHVlcyhLZXksIFBhcmFtZXRlcnMpKS4KCiUlIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiUlIGJ1aWxkX3NpcHBfcGFyYW1ldGVyc19zdHJpbmcoX0NtZExpc3QsIENtZFN0cikKJSUgQHNwZWMgYnVpbGRfc2lwcF9wYXJhbWV0ZXJzX3N0cmluZyhfQ21kTGlzdDo6bGlzdCgpLCBDbWRTdHI6OnN0cmluZygpKSAtPiBSZXM6OnN0cmluZygpCiUlIEBkb2MgR2VuZXJhdGUgcGFyYW1ldGVycyBwYXJ0IG9mIHNpcHAgY21kCiUlIF9DbWRMaXN0OjpsaXN0KCkgaXMgYSBsaXN0IG9mIHBhcmFtZXRlcnMgdG8gc2lwcAolJSA8cHJlPgolJSA8Yj5JbnB1dDo8L2I+CiUlIF9DbWRMaXN0ICAgICAgICBMaXN0ICAgIFBhcmFtZXRlcnMgbGlzdAolJSBDbWRTdHIgICAgICAgICAgU3RyaW5nICBjb25uZWN0b3IgIAolJQolJSA8Yj5PdXRwdXQ6PC9iPgolJSBSZXMgICBTdHJpbmcgICAgICBQYXJhbWV0ZXJzIHBhcnQgb2Ygc2lwcCBjbWQKJSUKJSUgPC9wcmU+CiUlIEBlbmQKJSUgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KYnVpbGRfc2lwcF9wYXJhbWV0ZXJzX3N0cmluZyhbe0F0dHIsIENvbnR9fFJlc3RdPV9DbWRMaXN0LCBDbWRTdHIpIC0+IAogICAgY2FzZSBwcm9wbGlzdHM6Z2V0X3ZhbHVlKEF0dHIsID9TSVBQX1BBUkFTKSBvZgogICAgICAgIFtQYXJhbWV0ZXJdIC0+IAogICAgICAgICAgICBpZiAKICAgICAgICAgICAgICAgIENvbnQgPTo9IHRydWUgLT4KICAgICAgICAgICAgICAgICAgICBidWlsZF9zaXBwX3BhcmFtZXRlcnNfc3RyaW5nKFJlc3QsIENtZFN0ciArKyBQYXJhbWV0ZXIpOwogICAgICAgICAgICAgICAgQ29udCA9Oj0gZmFsc2UgLT4KICAgICAgICAgICAgICAgICAgICBidWlsZF9zaXBwX3BhcmFtZXRlcnNfc3RyaW5nKFJlc3QsIENtZFN0cikKICAgICAgICAgICAgZW5kOwogICAgICAgIFtQYXJhbWV0ZXIsICIiXSAtPiAKICAgICAgICAgICAgYnVpbGRfc2lwcF9wYXJhbWV0ZXJzX3N0cmluZyhSZXN0LCBDbWRTdHIgKysgUGFyYW1ldGVyICsrIHRvX3N0cmluZyhDb250KSk7CiAgICAgICAgdW5kZWZpbmVkIC0+IAogICAgICAgICAgICA/V0FSTklOR19NU0coIldhcm5pbmc6fm5idWlsZF9zaXBwX3BhcmFtZXRlcnNfc3RyaW5nIHJlY3YgdW5leHBlY3RlZCBpbnB1dDogfnAsIHNraXAgdGhlIHBhcmFtZXRlciBhbmQgZ28gYWhlYWQufm4iLFtBdHRyXSksCiAgICAgICAgICAgIGJ1aWxkX3NpcHBfcGFyYW1ldGVyc19zdHJpbmcoUmVzdCwgQ21kU3RyKQogICAgZW5kOwpidWlsZF9zaXBwX3BhcmFtZXRlcnNfc3RyaW5nKFtdLCBDbWRTdHIpIC0+CiAgICBDbWRTdHIuCgolJSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQolJSBidWlsZF9yZW1vdGVfdXJpKFJlbW90ZUlwLCBSZW1vdGVyUG9ydCkKJSUgQHNwZWMgYnVpbGRfcmVtb3RlX3VyaShSZW1vdGVJcDo6c3RyaW5nKCksIFJlbW90ZXJQb3J0OjpzdHJpbmcoKSkgLT4gUmVzOjpzdHJpbmcoKQolJSBAZG9jIEdlbmVyYXRlIHJlbW90ZXIgdXJpCiUlIDxwcmU+CiUlIDxiPklucHV0OjwvYj4KJSUgUmVtb3RlcklwICAgICAgICBTdHJpbmcKJSUgUmVtb3RlclBvcnQgICAgICBTdHJpbmcgCiUlCiUlIDxiPk91dHB1dDo8L2I+CiUlIFJlcyAgIFN0cmluZyAgICAgIFJlbXRvZSBVUkkgcGFydCBvZiBzaXBwIGNtZAolJQolJSA8L3ByZT4KJSUgQGVuZAolJSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpidWlsZF9yZW1vdGVfdXJpKFJlbW90ZUlwLCBSZW1vdGVQb3J0KSAtPgogICAgaWYgCiAgICAgICAgKFJlbW90ZUlwID06PSB1bmRlZmluZWQpIG9yIChSZW1vdGVQb3J0ID06PXVuZGVmaW5lZCkgLT4KICAgICAgICAgICAgP0VSUk9SX01TRygiRXJyb3I6fm5idWlsZF9yZW1vdGVfdXJpIHVua25vdyByZW1vdGUgcGFyYW1ldGVyczogcmVtb3RlIGlwPX5wOyByZW1vdGUgcG9ydD1+cH5uIixbUmVtb3RlSXAsIFJlbW90ZVBvcnRdKSwKICAgICAgICAgICAgIiI7CiAgICAgICAgdHJ1ZSAtPiAKICAgICAgICAgICAgdG9fc3RyaW5nKFJlbW90ZUlwKSArKyAiOiIgKysgdG9fc3RyaW5nKFJlbW90ZVBvcnQpCiAgICBlbmQuCgpydW5fc2NlbmFyaW8oVWFjUGFyYW0sIFVhc1BhcmFtLCBDYWxsRHVyYXRpb24sIFJ1blRpbWUpIC0+CiAgICBydW5fc2NlbmFyaW8oVWFjUGFyYW0sIFVhc1BhcmFtLCBDYWxsRHVyYXRpb24sIFJ1blRpbWUsID9ERUZBVUxUX01PTklUT1JfSU5URVJWQUwsIGZhbHNlKS4KJSUgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KJSUgcnVuX3NjZW5hcmlvKF9VYWNQYXJhbSwgX1Vhc1BhcmFtLCAgQ2FsbER1cmF0aW9uLCBSdW5UaW1lLCBNb25pdG9ySW50ZXJ2YWwsIFN0YXRNb25pdG9yRmxhZykKJSUgQHNwZWMgcnVuX3NjZW5hcmlvKF9VYWNQYXJhbTo6dHVwbGUoKSwgX1Vhc1BhcmFtOjp0dXBsZSgpLCBDYWxsRHVyYXRpb246OmludGVnZXIoKSwgCiUlICAgICAgICAgICAgICAgICAgICBSdW5UaW1lOjp0dXBsZSgpLCBNb25pdG9ySW50ZXJ2YWw6OnR1cGxlKCksCiUlICAgICAgICAgICAgICAgICAgICBTdGF0TW9uaXRvckZsYWc6OmF0b20oKSkgLT4gUmVzOjphdG9tKCkKJSUgQGRvYyBSdW4gdGhlIFNJUHAgc2NlbmFyaW8uCiUlIDxwcmU+CiUlIDxiPklucHV0OjwvYj4KJSUgICAgICAgIF9VYWNQYXJhbSAgICAgICBUdXBsZSAgICAge1VhY1NuckZpbGUsIFVhY1N0YXRGaWxlLCBVYWNDbWR9CiUlICAgICAgICBVYWNTbnJGaWxlICAgICAgU3RyaW5nICAgIFRoZSBzY2VuYXJpbyBmaWxlIGZvciBVQUMKJSUgICAgICAgIFVhY1N0YXRGaWxlICAgICBTdHJpbmcgICAgVGhlIFN0YXRpc3RpYyBmaWxlIGZvciBVQUMKJSUgICAgICAgIFVhY0NtZCAgICAgICAgICBTdHJpbmcgICAgVGhlIFNJUHAgY29tbWFuZCBzdHJpbmcgZm9yIFVBQwolJQolJSAgICAgICAgX1Vhc1BhcmFtICAgICAgIFR1cGxlICAgICB7VWFzU25yRmlsZSwgVWFzU3RhdEZpbGUsIFVhc0NtZH0KJSUgICAgICAgIFVhc1NuckZpbGUgICAgICBTdHJpbmcgICAgVGhlIHNjZW5hcmlvIGZpbGUgZm9yIFVBUwolJSAgICAgICAgVWFzU3RhdEZpbGUgICAgIFN0cmluZyAgICBUaGUgU3RhdGlzdGljIGZpbGUgZm9yIFVBUwolJSAgICAgICAgVWFzQ21kICAgICAgICAgIFN0cmluZyAgICBUaGUgU0lQcCBjb21tYW5kIHN0cmluZyBmb3IgVUFTCiUlCiUlICAgICAgICBDYWxsRHVyYXRpb24gICAgSW50ZWdlciAgIER1cmF0aW9uIHRpbWUgZm9yIGVhY2ggY2FsbAolJSAgICAgICAgUnVuVGltZSAgICAgICAgIFR1cGxlICAgICBUb3RhbCBydW50aW1lIGZvciBTSVBwCiUlICAgICAgICBNb25pdG9ySW50ZXJ2YWwgVHVwbGUgICAgIFRoZSBtb25pdG9yIGludGVydmFsIGZvciBTSVBwCiUlICAgICAgICBTdGF0TW9uaXRvckZsYWcgQXRvbSAgICAgIHRydWV8ZmFsc2UgICB3aGV0aGVyIHRvIG1vbml0b3IgcGFzcyByYXRlIGR1cmluZyBjYWxsIG9uZ29pbmcKJSUKJSUgPGI+T3V0cHV0OjwvYj4KJSUgICAgICAgIFJlcyAgICAgICAgICAgICBBdG9tICAgICAgb2t8ZXJyb3IKJSUgPC9wcmU+CiUlIEBlbmQKJSUgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpydW5fc2NlbmFyaW8oe1VhY1NuckZpbGUsIFVhY1N0YXRGaWxlLCBVYWNDbWR9ID0gX1VhY1BhcmFtLCAKCSAgICAge1Vhc1NuckZpbGUsIFVhc1N0YXRGaWxlLCBVYXNDbWR9ID0gX1Vhc1BhcmFtLCAKCSAgICAgQ2FsbER1cmF0aW9uLCBSdW5UaW1lLCBNb25pdG9ySW50ZXJ2YWwsIFN0YXRNb25pdG9yRmxhZykgLT4KCiAgICAlJSAgU3RhcnQgU0lQcAogICAgY2FzZSBzdGFydF9zaXBwKFVhc0NtZCkgb2YKICAgICAgICB7b2ssIFVhc1BpZH0gLT4KICAgICAgICAgICAgY3Q6cGFsKCIjIyMgIFVBUyBzdGFydGVkLCBVYXNQaWQ9fnAgICMjI35uIiwgW1Vhc1BpZF0pLAogICAgICAgICAgICB0aW1lcjpzbGVlcCgxMDAwKSwgJSBTb21ldGltZXMgVUFTIG5lZWRzIG1vcmUgdGltZSB0byBvcGVuIFRDUCBwb3J0IGV0Yy4KCiAgICAgICAgICAgIGNhc2Ugc3RhcnRfc2lwcChVYWNDbWQpIG9mCiAgICAgICAgICAgICAgICB7b2ssIFVhY1BpZH0gLT4gCgkJICAgIGN0OnBhbCgiIyMjICBVQUMgc3RhcnRlZCwgVWFjUGlkPX5wICAjIyN+biIsIFtVYWNQaWRdKSwKCQkgICAgZG9faGFuZGxlX3NjZW5hcmlvKHtVYXNQaWQsIFVhc1NuckZpbGUsIFVhc1N0YXRGaWxlfSwgCgkJCQkgICAgICAge1VhY1BpZCwgVWFjU25yRmlsZSwgVWFjU3RhdEZpbGV9LAoJCQkJICAgICAgIENhbGxEdXJhdGlvbiwgUnVuVGltZSwgTW9uaXRvckludGVydmFsLCAKCQkJCSAgICAgICBTdGF0TW9uaXRvckZsYWcpOwoKICAgICAgICAgICAgICAgIF8gLT4gCgkJICAgIGN0OnBhbCgiIyMjIFVBQyBzdGFydCBmYWlsZWQsIGtpbGwgdGhlIHN0YXJ0ZWQgVUFTIHByb2Nlc3MgYW5kIHF1aXQgdGhlIGZ1bmN0aW9uISIpLAoJCSAgICBvczpjbWQoImtpbGwgLTkiICsrIHRvX3N0cmluZyhVYXNQaWQpKSwKCQkgICAgdGltZXI6c2xlZXAoMTAwMCksCgkJICAgIFVhc1NjcmVlbkZpbGUgPSBmaWxlbmFtZTpkaXJuYW1lKFVhc1NuckZpbGUpICsrCgkJCSIvIiArKyBmaWxlbmFtZTpiYXNlbmFtZShVYXNTbnJGaWxlLCAiLnhtbCIpICsrCgkJCSJfIiArKyB0b19zdHJpbmcoVWFzUGlkKSArKyAiX3NjcmVlbi5sb2ciLAoJCSAgICBjdDpwYWwoIlVhc1NjcmVlbkZpbGUgaXM6fnB+biIsIFtVYXNTY3JlZW5GaWxlXSksCgkJICAgIG9zOmNtZCgibXYgIisrVWFzU2NyZWVuRmlsZSsrIiAiKys/RVNUX0xPR19ESVIpLAoJCSAgICBlcnJvcgogICAgICAgICAgICBlbmQ7CiAgICAgICAgXyAtPgoJICAgIGN0OnBhbCgiIyMjIFVBUyBzdGFydCBmYWlsZWQsIHF1aXQgdGhlIGZ1bmN0aW9uISIpLAoJICAgIGVycm9yCiAgICBlbmQuCiAgICAKJSUgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KJSUgZG9faGFuZGxlX3NjZW5hcmlvKF9VYXNQYXJhbSwgX1VhY1BhcmFtLCBDYWxsRHVyYXRpb24sCiUlICAgICAgICAgICAgICAgICAgICBSdW5UaW1lLCBNb25pdG9ySW50ZXJ2YWwsIFN0YXRNb25pdG9yRmxhZykKJSUgQHNwZWMgZG9faGFuZGxlX3NjZW5hcmlvKF9VYXNQYXJhbSAgICAgICA6OiB0dXBsZSgpLCAKJSUgICAgICAgICAgICAgICAgICAgICAgICAgIF9VYWNQYXJhbSAgICAgICA6OiB0dXBsZSgpLCAKJSUgICAgICAgICAgICAgICAgICAgICAgICAgIENhbGxEdXJhdGlvbiAgICA6OiBpbnRlZ2VyKCksIAolJSAgICAgICAgICAgICAgICAgICAgICAgICAgUnVuVGltZSAgICAgICAgIDo6IHR1cGxlKCksIAolJSAgICAgICAgICAgICAgICAgICAgICAgICAgTW9uaXRvckludGVydmFsIDo6IHR1cGxlKCksIAolJSAgICAgICAgICAgICAgICAgICAgICAgICAgU3RhdE1vbml0b3JGbGFnIDo6IGF0b20oKSkgLT4gUmVzOjphdG9tKCkKJSUgICAgICAgICAgICAgICAgICAgICAgICAgIAolJSBAZG9jIG1vbml0b3IgU0lQcCBydW5uaW5nIHN0YXR1cyBhbmQgcmV0dXJucyB0aGUgcmVzdWx0IG9mIHNjZW5hcmlvIGV4ZWN1dGluZwolJSA8cHJlPgolJSA8Yj5JbnB1dDo8L2I+CiUlICAgICAgICBfVWFzUGFyYW0gICAgICAgVHVwbGUgICAgIHtVYXNQaWQsICBVYXNTbnJGaWxlLCBVYXNTdGF0RmlsZX0KJSUgICAgICAgIFVhc1BpZCAgICAgICAgICBTdHJpbmcgICAgVGhlIFNJUHAgcHJvY2VzcyBpZCBmb3IgVUFTCiUlICAgICAgICBVYVNTbnJGaWxlICAgICAgU3RyaW5nICAgIFRoZSBzY2VuYXJpbyBmaWxlIGZvciBVQVMKJSUgICAgICAgIFVhU1N0YXRGaWxlICAgICBTdHJpbmcgICAgVGhlIFN0YXRpc3RpYyBmaWxlIGZvciBVQVMKJSUKJSUgICAgICAgIF9VYWNQYXJhbSAgICAgICBUdXBsZSAgICAge1VhY1BpZCwgVWFjU25yRmlsZSwgVWFjU3RhdEZpbGV9CiUlICAgICAgICBVYWNQaWQgICAgICAgICAgU3RyaW5nICAgIFRoZSBTSVBwIHByb2Nlc3MgaWQgZm9yIFVBQwolJSAgICAgICAgVWFjU25yRmlsZSAgICAgIFN0cmluZyAgICBUaGUgc2NlbmFyaW8gZmlsZSBmb3IgVUFDCiUlICAgICAgICBVYWNTdGF0RmlsZSAgICAgU3RyaW5nICAgIFRoZSBTdGF0aXN0aWMgZmlsZSBmb3IgVUFDCiUlIFVhc1BpZCAgICAgICAgICBQaWQgICAgICAgVGhlIFNJUHAgcHJvY2VzcyBpZCBmb3IgVUFTCiUlIFVhY1BpZCAgICAgICAgICBQaWQgICAgICAgVGhlIFNJUHAgcHJvY2VzcyBpZCBmb3IgVUFDCiUlIENhbGxEdXJhdGlvbiAgICBJbnRlZ2VyICAgQ2FsbCBkdXJhdGlvbiBmb3IgZWFjaCBjYWxsCiUlIFJ1blRpbWUgICAgICAgICBUdXBsZSAgICAgVG90YWwgcnVudGltZSBmb3IgU0lQcAolJSBNb25pdG9ySW50ZXJ2YWwgVHVwbGUgICAgIFRoZSBtb25pdG9yIGludGVydmFsIGZvciBTSVBwCiUlIFN0YXRNb25pdG9yRmxhZyBBdG9tICAgICAgdHJ1ZXxmYWxzZSAgIHdoZXRoZXIgdG8gbW9uaXRvciBwYXNzIHJhdGUgZHVyaW5nIGNhbGwgb25nb2luZwolJQolJSA8Yj5PdXRwdXQ6PC9iPgolJSBSZXMgICBBdG9tICAgICAgb2t8ZXJyb3IKJSUKJSUgPC9wcmU+CiUlIEBlbmQKJSUgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpkb19oYW5kbGVfc2NlbmFyaW8oe1Vhc1BpZCwgVWFzU25yRmlsZSwgVWFzU3RhdEZpbGV9ID0gX1Vhc1BhcmFtLCAKCQkgICB7VWFjUGlkLCBVYWNTbnJGaWxlLCBVYWNTdGF0RmlsZX0gPSBfVWFjUGFyYW0sCgkJICAgQ2FsbER1cmF0aW9uLCBSdW5UaW1lLCBNb25pdG9ySW50ZXJ2YWwsCgkJICAgU3RhdE1vbml0b3JGbGFnKSAtPgogICAgJSUgIFN0YXJ0IEludGVydmFsIENoZWNrIFRpbWVyCiAgICB7e1J1bkhvdXIsIFJ1bk1pbnV0ZSwgUnVuU2Vjb25kfSwgCiAgICAge01vbml0b3JIb3VyLCBNb25pdG9yTWludXRlLCBNb25pdG9yU2Vjb25kfX0gPSB7UnVuVGltZSwgTW9uaXRvckludGVydmFsfSwKCiAgICAlJSBjaGVjayB0aGUgc3RhdHVzIG9mIHNpcHAgcHJvY2VzcwogICAge29rLCBNb25pdG9yU2lwcFN0YXR1c1JlZn0gPQoJdGltZXI6YXBwbHlfaW50ZXJ2YWwodGltZXI6aG1zKE1vbml0b3JIb3VyLCBNb25pdG9yTWludXRlLCBNb25pdG9yU2Vjb25kKSwgCgkJCSAgICAgP01PRFVMRSwgCgkJCSAgICAgY2hlY2tfc2lwcF9wcm9jZXNzX3N0YXR1cywKCQkJICAgICBbc2VsZigpLCBbVWFzUGlkLCBVYWNQaWRdXSksCgogICAgTW9uaXRvclJlZnMgPQogICAgICAgIGNhc2UgU3RhdE1vbml0b3JGbGFnIG9mCgkgICAgdHJ1ZSAtPgogICAgICAgICAgICAgICAgJSUgY2hlY2sgdGhlIGNhbGwgc3RhdHVzIG9mIHNpcHAgdHJhZmZpYwoJCXtvaywgTW9uaXRvckNhbGxTdGF0dXNSZWZ9CgkJICAgID0gdGltZXI6YXBwbHlfaW50ZXJ2YWwodGltZXI6aG1zKE1vbml0b3JIb3VyLCBNb25pdG9yTWludXRlLCBNb25pdG9yU2Vjb25kKSwgCgkJCQkJICAgP01PRFVMRSwgCgkJCQkJICAgY2hlY2tfc2lwcF9jYWxsX3N0YXR1cywKCQkJCQkgICBbc2VsZigpLCBbVWFzU3RhdEZpbGUsIFVhY1N0YXRGaWxlXV0pLAoJCVtNb25pdG9yU2lwcFN0YXR1c1JlZiwgTW9uaXRvckNhbGxTdGF0dXNSZWZdOwoJICAgIF8gLT4KCQlbTW9uaXRvclNpcHBTdGF0dXNSZWZdCgllbmQsCgogICAge29rLCBUUmVmQ2xvc2V9ID0gCgl0aW1lcjphcHBseV9hZnRlcih0aW1lcjpobXMoUnVuSG91ciwgUnVuTWludXRlLCBSdW5TZWNvbmQpLCAKCQkJICA/TU9EVUxFLCAKCQkJICBjbG9zZV9tb25pdG9yLCAKCQkJICBbc2VsZigpLCBNb25pdG9yUmVmc10pLAoKICAgIFRSZWZMaXN0ID0gTW9uaXRvclJlZnMgKysgW1RSZWZDbG9zZV0sCgogICAgJSUgIEludGVydmFsIENoZWNrCiAgICBJbnRlcnZhbENoZWNrUmVzdWx0ID0gaW50ZXJ2YWxfbG9vcF9jaGVjayhbe1VhY1N0YXRGaWxlLCAwfSwge1Vhc1N0YXRGaWxlLCAwfV0pLAogICAgY2FzZSBJbnRlcnZhbENoZWNrUmVzdWx0IG9mCiAgICAgICAgb2sgLT4KICAgICAgICAgICAgY3Q6cGFsKCJJbnRlcnZhbCBjaGVjayBva35uIiksCiAgICAgICAgICAgIG9rOwogICAgICAgIHtmYWlsZWQsIEZhaWxlZFJlc3VsdH0gLT4KICAgICAgICAgICAgP1dBUk5JTkdfTVNHKCJXYXJuaW5nOiBpbnRlcnZhbF9sb29wX2NoZWNrIHJlY3YgZmFpbGVkKH5wKX5udG8gY2FuY2VsIGFsbCB0aW1lcnN+biIsIFtGYWlsZWRSZXN1bHRdKSwKICAgICAgICAgICAgbGlzdHM6Zm9yZWFjaChmdW4oUCkgLT4gdGltZXI6Y2FuY2VsKFApIGVuZCwgVFJlZkxpc3QpCiAgICBlbmQsCgogICAgc3RvcF91YWNfdWFzX3NpcHBfcHJvY2Vzc2VzKFtVYWNQaWQsIFVhc1BpZF0sIENhbGxEdXJhdGlvbiksCgogICAgJSUgIEZpbmFsIENoZWNrCiAgICB7b2ssIFVhY1N0YXREYXRhfSA9ID9NT0RVTEU6Z2V0X3N0YXRfdGFpbChVYWNTdGF0RmlsZSksCiAgICBVYWNTdGF0UmVzdWx0ID0gY2hlY2tfc2lwcF9zdGF0KGZpbmFsLCBVYWNTdGF0RGF0YSksCiAgICBjYXNlIFVhY1N0YXRSZXN1bHQgb2YKICAgICAgICB0cnVlIC0+CiAgICAgICAgICAgID9ERUJVRygiRmluYWwgVUFDIGNhbGwgZmFpbGVkIHJhdGUgaXMgbGVzcyB0aGFuIDAuMDUlLn5uIiwgW10pOwogICAgICAgIGZhbHNlIC0+CiAgICAgICAgICAgID9XQVJOSU5HX01TRygiV2FybmluZzogZmluYWwgVUFDIGNhbGwgZmFpbGVkIHJhdGUgaXMgbW9yZSB0aGFuIDAuMDUlLn5uIiwgW10pCiAgICBlbmQsCgogICAge29rLCBVYXNTdGF0RGF0YX0gPSA/TU9EVUxFOmdldF9zdGF0X3RhaWwoVWFzU3RhdEZpbGUpLAogICAgVWFzU3RhdFJlc3VsdCA9IGNoZWNrX3NpcHBfc3RhdChmaW5hbCwgVWFzU3RhdERhdGEpLAogICAgY2FzZSBVYXNTdGF0UmVzdWx0IG9mCiAgICAgICAgdHJ1ZSAtPgogICAgICAgICAgICA/REVCVUcoIkZpbmFsIFVBUyBjYWxsIGZhaWxlZCByYXRlIGlzIGxlc3MgdGhhbiAwLjA1JS5+biIsIFtdKTsKICAgICAgICBmYWxzZSAtPgogICAgICAgICAgICA/V0FSTklOR19NU0coIldhcm5pbmc6IGZpbmFsIFVBUyBjYWxsIGZhaWxlZCByYXRlIGlzIG1vcmUgdGhhbiAwLjA1JS5+biIsIFtdKQogICAgZW5kLAoKICAgIFVhc1NjcmVlbkZpbGUgPSBmaWxlbmFtZTpkaXJuYW1lKFVhc1NuckZpbGUpICsrICIvIiArKyBmaWxlbmFtZTpiYXNlbmFtZShVYXNTbnJGaWxlLCAiLnhtbCIpICsrCgkiXyIgKysgdG9fc3RyaW5nKGxpc3RfdG9faW50ZWdlcihVYXNQaWQpLTEpICsrICJfc2NyZWVuLmxvZyIsIAoKICAgIFVhY1NjcmVlbkZpbGUgPSBmaWxlbmFtZTpkaXJuYW1lKFVhY1NuckZpbGUpICsrICIvIiArKyBmaWxlbmFtZTpiYXNlbmFtZShVYWNTbnJGaWxlLCAiLnhtbCIpICsrCgkiXyIgKysgdG9fc3RyaW5nKGxpc3RfdG9faW50ZWdlcihVYWNQaWQpLTEpICsrICJfc2NyZWVuLmxvZyIsCgogICAgY3Q6cGFsKCJVYXNTY3JlZW5GaWxlIGlzOn5wfm4iLCBbVWFzU2NyZWVuRmlsZV0pLAogICAgY3Q6cGFsKCJVYWNTY3JlZW5GaWxlIGlzOn5wfm4iLCBbVWFjU2NyZWVuRmlsZV0pLAoKICAgIG9zOmNtZCgibXYgIisrVWFzU2NyZWVuRmlsZSsrIiAiKys/RVNUX0xPR19ESVIpLAogICAgb3M6Y21kKCJtdiAiKytVYWNTY3JlZW5GaWxlKysiICIrKz9FU1RfTE9HX0RJUiksCgogICAgaWYgCiAgICAgICAgSW50ZXJ2YWxDaGVja1Jlc3VsdCA9Oj0gb2ssIFVhY1N0YXRSZXN1bHQgPTo9dHJ1ZSwgVWFzU3RhdFJlc3VsdCA9Oj0gdHJ1ZSAtPiBvazsKICAgICAgICB0cnVlIC0+IGVycm9yCiAgICBlbmQuICAgIAoKcHJlcGFyZV9lbnYoKSAtPgogICAgJSUgS2lsbCBhbnkgbGluZ2VyaW5nIFNJUHAgcHJvY2Vzc2VzIHRvIGF2b2lkIHRoYXQgdGhlIHNjZW5hcmlvIGZhaWxzCiAgICBvczpjbWQoInBraWxsIC05IHNpcHAiKSwKICAgIG9zOmNtZCgibWtkaXIgIisrID9FU1RfTE9HX0RJUiksCiAgICB7b2ssIF99ID0gP0NIKGIyYkRiZywgc3RhcnRfZXJyb3JfdHJhY2UsIFtdKSwKICAgIG9rID0gP0NIKG9hYkRiZywgbG9nLCBbc3RhcnRdKSwKICAgIHRydWUgPSA/Q0gocmVnRGJnLCBlcnJfdHJhY2UsIFtdKSwKICAgIHRydWUgPSA/Q0goc2lwRGJnLCBlcnJfdHJhY2UsIFtdKS4KCmZhbGxiYWNrX2VudigpIC0+CiAgICBvayA9ID9DSChkYmcsIHN0b3BfY2xlYXIsIFtdKS4KCgpwcmVwYXJlX3N5c3RlbWluZm9fZmlsZW5hbWUoQ3VycmVudFRpbWUsIHNnYzEsIGNoKSAtPgogICAgRmlsZW5hbWVfc3VmZml4ID0gd3JpdGVfdGltZShDdXJyZW50VGltZSkgKysgIi5sb2ciLAogICAgCiAgICBWbXN0YXRfRmlsZW5hbWUgPSBzdHJpbmc6Y29uY2F0KCIvYmxhZGUvaG9tZWRpci9zZ2MxLWNoLXZtc3RhdC0iLCBGaWxlbmFtZV9zdWZmaXgpLAogICAgU3lzSW5mb19GaWxlbmFtZSA9ID9FU1RfTE9HX0RJUiArKyAic2djMS1jaC1zeXN0ZW0taW5mby0iICsrIEZpbGVuYW1lX3N1ZmZpeCwKICAgIAogICAgUGxjUmVzX0ZpbGVuYW1lID0gP0VTVF9MT0dfRElSICsrICJzZ2MxLWNoLXBsYy1yZXMtIiArKyBGaWxlbmFtZV9zdWZmaXgsCiAgICAKICAgIHtTeXNJbmZvX0ZpbGVuYW1lLCBWbXN0YXRfRmlsZW5hbWUsIFBsY1Jlc19GaWxlbmFtZX07CgpwcmVwYXJlX3N5c3RlbWluZm9fZmlsZW5hbWUoQ3VycmVudFRpbWUsIHNnYzEsIG9tKSAtPgogICAgRmlsZW5hbWVfc3VmZml4ID0gd3JpdGVfdGltZShDdXJyZW50VGltZSkgKysgIi5sb2ciLAogICAgCiAgICBWbXN0YXRfRmlsZW5hbWUgPSBzdHJpbmc6Y29uY2F0KCIvYmxhZGUvaG9tZWRpci9zZ2MxLW9tLXZtc3RhdC0iLCBGaWxlbmFtZV9zdWZmaXgpLAogICAgU3lzSW5mb19GaWxlbmFtZSA9ID9FU1RfTE9HX0RJUiArKyAic2djMS1vbS1zeXN0ZW0taW5mby0iICsrIEZpbGVuYW1lX3N1ZmZpeCwKICAgIAogICAgUGxjUmVzX0ZpbGVuYW1lID0gP0VTVF9MT0dfRElSICsrICJzZ2MxLW9tLXBsYy1yZXMtIiArKyBGaWxlbmFtZV9zdWZmaXgsCiAgICAKICAgIHtTeXNJbmZvX0ZpbGVuYW1lLCBWbXN0YXRfRmlsZW5hbWUsIFBsY1Jlc19GaWxlbmFtZX07CgpwcmVwYXJlX3N5c3RlbWluZm9fZmlsZW5hbWUoQ3VycmVudFRpbWUsIG9tbXAsIGNoKSAtPgogICAgRmlsZW5hbWVfc3VmZml4ID0gd3JpdGVfdGltZShDdXJyZW50VGltZSkgKysgIi5sb2ciLAogICAgCiAgICBWbXN0YXRfRmlsZW5hbWUgPSBzdHJpbmc6Y29uY2F0KCIvYmxhZGUvaG9tZWRpci9vbW1wLWNoLXZtc3RhdC0iLCBGaWxlbmFtZV9zdWZmaXgpLAogICAgU3lzSW5mb19GaWxlbmFtZSA9ID9FU1RfTE9HX0RJUiArKyAib21tcC1jaC1zeXN0ZW0taW5mby0iICsrIEZpbGVuYW1lX3N1ZmZpeCwKICAgIAogICAgUGxjUmVzX0ZpbGVuYW1lID0gP0VTVF9MT0dfRElSICsrICJvbW1wLWNoLXBsYy1yZXMtIiArKyBGaWxlbmFtZV9zdWZmaXgsCiAgICAKICAgIHtTeXNJbmZvX0ZpbGVuYW1lLCBWbXN0YXRfRmlsZW5hbWUsIFBsY1Jlc19GaWxlbmFtZX07CgpwcmVwYXJlX3N5c3RlbWluZm9fZmlsZW5hbWUoQ3VycmVudFRpbWUsIG9tbXAsIG9tKSAtPgogICAgRmlsZW5hbWVfc3VmZml4ID0gd3JpdGVfdGltZShDdXJyZW50VGltZSkgKysgIi5sb2ciLAogICAgCiAgICBWbXN0YXRfRmlsZW5hbWUgPSBzdHJpbmc6Y29uY2F0KCIvYmxhZGUvaG9tZWRpci9vbW1wLW9tLXZtc3RhdC0iLCBGaWxlbmFtZV9zdWZmaXgpLAogICAgU3lzSW5mb19GaWxlbmFtZSA9ID9FU1RfTE9HX0RJUiArKyAib21tcC1vbS1zeXN0ZW0taW5mby0iICsrIEZpbGVuYW1lX3N1ZmZpeCwKICAgIAogICAgUGxjUmVzX0ZpbGVuYW1lID0gP0VTVF9MT0dfRElSICsrICJvbW1wLW9tLXBsYy1yZXMtIiArKyBGaWxlbmFtZV9zdWZmaXgsCiAgICAKICAgIHtTeXNJbmZvX0ZpbGVuYW1lLCBWbXN0YXRfRmlsZW5hbWUsIFBsY1Jlc19GaWxlbmFtZX0uCgpjb2xsZWN0X3N5c3RlbV9pbmZvKFN5c0luZm9fRmlsZW5hbWUsIFZtc3RhdF9GaWxlbmFtZSxzZ2MxLGNoKSAtPgogICAgTWVtX3Jlc3VsdCA9ID9DSChldHMsIHRhYjJsaXN0LCBbcGxjSW5mb10pLAogICAgUHJvY2Vzc051bSA9IGxlbmd0aCg/Q0goZXJsYW5nLCBwcm9jZXNzZXMsIFtdKSksCiAgICBSZXN1bHQgPSBNZW1fcmVzdWx0ICsrIFt7cHJvY2Vzc19OdW0sIFByb2Nlc3NOdW19XSwKICAgIAogICAge29rLCBGaWxlX2hhbmRsZXJ9ID0gZmlsZTpvcGVuKFN5c0luZm9fRmlsZW5hbWUsIFthcHBlbmRdKSwKICAgIGlvOmZvcm1hdChGaWxlX2hhbmRsZXIsICJ+cH5uIiwgW1Jlc3VsdF0pLAogICAgb2sgPSBmaWxlOmNsb3NlKEZpbGVfaGFuZGxlciksCiAgICAKICAgIFZtQ21kID0gInZtc3RhdCA1IDUgPj4iICsrIFZtc3RhdF9GaWxlbmFtZSwKICAgID9DSChvcywgY21kLCBbVm1DbWRdKTsKCmNvbGxlY3Rfc3lzdGVtX2luZm8oU3lzSW5mb19GaWxlbmFtZSwgVm1zdGF0X0ZpbGVuYW1lLHNnYzEsb20pIC0+CiAgICBNZW1fcmVzdWx0ID0gP09NKGV0cywgdGFiMmxpc3QsIFtwbGNJbmZvXSksCiAgICBQcm9jZXNzTnVtID0gbGVuZ3RoKD9PTShlcmxhbmcsIHByb2Nlc3NlcywgW10pKSwKICAgIFJlc3VsdCA9IE1lbV9yZXN1bHQgKysgW3twcm9jZXNzX051bSwgUHJvY2Vzc051bX1dLAogICAgCiAgICB7b2ssIEZpbGVfaGFuZGxlcn0gPSBmaWxlOm9wZW4oU3lzSW5mb19GaWxlbmFtZSwgW2FwcGVuZF0pLAogICAgaW86Zm9ybWF0KEZpbGVfaGFuZGxlciwgIn5wfm4iLCBbUmVzdWx0XSksCiAgICBvayA9IGZpbGU6Y2xvc2UoRmlsZV9oYW5kbGVyKSwKICAgIAogICAgVm1DbWQgPSAidm1zdGF0IDUgNSA+PiIgKysgVm1zdGF0X0ZpbGVuYW1lLAogICAgP09NKG9zLCBjbWQsIFtWbUNtZF0pOwoKY29sbGVjdF9zeXN0ZW1faW5mbyhTeXNJbmZvX0ZpbGVuYW1lLCBWbXN0YXRfRmlsZW5hbWUsb21tcCxjaCkgLT4KICAgIE1lbV9yZXN1bHQgPSA/U0JHQ0goZXRzLCB0YWIybGlzdCwgW3BsY0luZm9dKSwKICAgIFByb2Nlc3NOdW0gPSBsZW5ndGgoP1NCR0NIKGVybGFuZywgcHJvY2Vzc2VzLCBbXSkpLAogICAgUmVzdWx0ID0gTWVtX3Jlc3VsdCArKyBbe3Byb2Nlc3NfTnVtLCBQcm9jZXNzTnVtfV0sCiAgICAKICAgIHtvaywgRmlsZV9oYW5kbGVyfSA9IGZpbGU6b3BlbihTeXNJbmZvX0ZpbGVuYW1lLCBbYXBwZW5kXSksCiAgICBpbzpmb3JtYXQoRmlsZV9oYW5kbGVyLCAifnB+biIsIFtSZXN1bHRdKSwKICAgIG9rID0gZmlsZTpjbG9zZShGaWxlX2hhbmRsZXIpLAogICAgCiAgICBWbUNtZCA9ICJ2bXN0YXQgNSA1ID4+IiArKyBWbXN0YXRfRmlsZW5hbWUsCiAgICA/U0JHQ0gob3MsIGNtZCwgW1ZtQ21kXSk7Cgpjb2xsZWN0X3N5c3RlbV9pbmZvKFN5c0luZm9fRmlsZW5hbWUsIFZtc3RhdF9GaWxlbmFtZSxvbW1wLG9tKSAtPgogICAgTWVtX3Jlc3VsdCA9ID9TQkdPTShldHMsIHRhYjJsaXN0LCBbcGxjSW5mb10pLAogICAgUHJvY2Vzc051bSA9IGxlbmd0aCg/U0JHT00oZXJsYW5nLCBwcm9jZXNzZXMsIFtdKSksCiAgICBSZXN1bHQgPSBNZW1fcmVzdWx0ICsrIFt7cHJvY2Vzc19OdW0sIFByb2Nlc3NOdW19XSwKICAgIAogICAge29rLCBGaWxlX2hhbmRsZXJ9ID0gZmlsZTpvcGVuKFN5c0luZm9fRmlsZW5hbWUsIFthcHBlbmRdKSwKICAgIGlvOmZvcm1hdChGaWxlX2hhbmRsZXIsICJ+cH5uIiwgW1Jlc3VsdF0pLAogICAgb2sgPSBmaWxlOmNsb3NlKEZpbGVfaGFuZGxlciksCiAgICAKICAgIFZtQ21kID0gInZtc3RhdCA1IDUgPj4iICsrIFZtc3RhdF9GaWxlbmFtZSwKICAgID9TQkdPTShvcywgY21kLCBbVm1DbWRdKS4KCmNvbGxlY3RfcGxjX3JlcyhQbGNSZXNfRmlsZW5hbWUpIC0+CiAgICBQbGNSZXNfcmVzdWx0ID0gP0NIKHBsY1NjaGVkdWxlciwgcGxjX3JlcywgWzEsIDEwMDBdKSwKICAgIHtvaywgRmlsZV9oYW5kbGVyfSA9IGZpbGU6b3BlbihQbGNSZXNfRmlsZW5hbWUsIFthcHBlbmRdKSwKICAgIGlvOmZvcm1hdChGaWxlX2hhbmRsZXIsICJ+cH5uIiwgW1BsY1Jlc19yZXN1bHRdKSwKICAgIG9rID0gZmlsZTpjbG9zZShGaWxlX2hhbmRsZXIpLgoKdHJhbnNmZXJfdm1zdGF0X2ZpbGUoRmlsZW5hbWUsVHlwZSwgUm9sZSkgLT4KICAgIEJsYWRlTnVtID0gZ2V0X2JsYWRlX25vKFR5cGUsIFJvbGUpLAogICAgRmlsZSA9ICIvcHJpdmF0ZSIgKysgcmU6cmVwbGFjZShGaWxlbmFtZSwgImJsYWRlIiwgQmxhZGVOdW0sIFt7cmV0dXJuLCBsaXN0fV0pLAoKICAgIExvY2FsRmlsZSA9IGZpbGVuYW1lOmpvaW4oWz9FU1RfTE9HX0RJUiwgIGZpbGVuYW1lOmJhc2VuYW1lKEZpbGUpXSksCiAgICBTaXNSb290UHN3ZCA9IHNiZ0Z0U3VwcG9ydDpnZXRfc2lzX3Jvb3RfcHcoKSwKICAgIG9rID0gc2NwX2Zyb20oInJvb3QiLCBTaXNSb290UHN3ZCwgInNpczEiLCBGaWxlLCBMb2NhbEZpbGUpLAogICAgTG9jYWxGaWxlLgoKZ2V0X2JsYWRlX25vKHNnYzEsIGNoKSAtPgogICAgW18sIEJsYWRlTnVtXSA9IHJlOnNwbGl0KHRvX3N0cmluZyg/U0dDY2hOb2RlKSwgIltcQF0iLCBbe3JldHVybiwgbGlzdH1dKSwKICAgIEJsYWRlTnVtOwpnZXRfYmxhZGVfbm8oc2djMSwgb20pIC0+CiAgICBbXywgQmxhZGVOdW1dID0gcmU6c3BsaXQodG9fc3RyaW5nKD9TR0NvbU5vZGUpLCAiW1xAXSIsIFt7cmV0dXJuLCBsaXN0fV0pLAogICAgQmxhZGVOdW07CmdldF9ibGFkZV9ubyhvbW1wLCBjaCkgLT4KICAgIFtfLCBCbGFkZU51bV0gPSByZTpzcGxpdCh0b19zdHJpbmcoP1NCR2NoTm9kZSksICJbXEBdIiwgW3tyZXR1cm4sIGxpc3R9XSksCiAgICBCbGFkZU51bTsKZ2V0X2JsYWRlX25vKG9tbXAsIG9tKSAtPgogICAgW18sIEJsYWRlTnVtXSA9IHJlOnNwbGl0KHRvX3N0cmluZyg/U0JHb21Ob2RlKSwgIltcQF0iLCBbe3JldHVybiwgbGlzdH1dKSwKICAgIEJsYWRlTnVtLgoKc2NwX2Zyb20oVXNlciwgUGFzc3dkLCBSZW1vdGVIb3N0LCBSZW1vdGVGaWxlLCBMb2NhbEZpbGUpIC0+CiAgICBIID0gY2FzZSBpc19hdG9tKFJlbW90ZUhvc3QpIG9mCiAgICAgICAgdHJ1ZSAtPiBhdG9tX3RvX2xpc3QoUmVtb3RlSG9zdCk7CiAgICAgICAgZmFsc2UgLT4gUmVtb3RlSG9zdAogICAgZW5kLAoKICAgIFJlbW90ZUlwID0ganBUdXRpbDpnZXRfaXBfZnJvbV9ob3N0bmFtZShIKSwKICAgIE9wdGlvbnMgPSAgICAgW3t1c2VyLFVzZXJ9LAogICAgICAgICAgICAgICAgICAge3Bhc3N3b3JkLFBhc3N3ZH0sCiAgICAgICAgICAgICAgICAgICB7dXNlcl9pbnRlcmFjdGlvbixmYWxzZX0sCiAgICAgICAgICAgICAgICAgICB7c2lsZW50bHlfYWNjZXB0X2hvc3RzLHRydWV9XSwKICAgIAogICAgYXBwbGljYXRpb246c3RhcnQoY3J5cHRvKSwKICAgIGFwcGxpY2F0aW9uOnN0YXJ0KHNzaCksICAgIAogICAgCiAgICBjYXNlIHNzaF9zZnRwOnN0YXJ0X2NoYW5uZWwoUmVtb3RlSXAsIE9wdGlvbnMpIG9mCiAgICAgICAge29rLCBQaWQsIENvbm5SZWZ9IC0+CiAgICAgICAgICAgIGNhc2Ugc3NoX3NmdHA6cmVhZF9maWxlKFBpZCwgUmVtb3RlRmlsZSkgb2YKICAgICAgICAgICAgICAgIHtvaywgRGF0YX0gLT4KICAgICAgICAgICAgICAgICAgICBjYXNlIGZpbGU6d3JpdGVfZmlsZShMb2NhbEZpbGUsIERhdGEpIG9mCiAgICAgICAgICAgICAgICAgICAgICAgIG9rIC0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzc2g6Y2xvc2UoQ29ublJlZik7CiAgICAgICAgICAgICAgICAgICAgICAgIHtlcnJvciwgRXJyb3J9IC0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjdDpwYWwoInNjcF9mcm9tIHJlbW90ZSBob3N0IH5wIEVycm9yIH5wfm4iLCBbUmVtb3RlSG9zdCwgRXJyb3JdKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNzaDpjbG9zZShDb25uUmVmKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHtub2ssIEVycm9yfQogICAgICAgICAgICAgICAgICAgIGVuZDsKICAgICAgICAgICAgICAgIEVycm9yUmVhZGluZ0ZpbGUgLT4KICAgICAgICAgICAgICAgICAgICBzc2g6Y2xvc2UoQ29ublJlZiksCiAgICAgICAgICAgICAgICAgICAge25vaywge2Vycm9yX3JlYWRpbmdfc291cmNlX2ZpbGUsIEVycm9yUmVhZGluZ0ZpbGV9fSAgCiAgICAgICAgICAgIGVuZDsKICAgICAgICB7ZXJyb3IsIEVycm9yfSAtPgogICAgICAgICAgICB7bm9rLCBFcnJvcn07CiAgICAgICAgRXJyb3JGcm9tQ29ubmVjdCAtPgogICAgICAgICAgICB7bm9rLCBFcnJvckZyb21Db25uZWN0fQogICAgZW5kLiAgCgpnZXRfYWxsX2NvdW50ZXIoKSAtPgogICAgUmVnQ291bnQgPSBjb3VudChyZWcpLAogICAgQjJiQ291bnQgPSBjb3VudChiMmIpLAogICAgSGl3Q291bnQgPSBjb3VudChoaXcpLAogICAgT2FiQ291bnQgPSBjb3VudChvYWIpLAogICAgW1JlZ0NvdW50LCBCMmJDb3VudCwgSGl3Q291bnQsIE9hYkNvdW50XS4KCmNvdW50KHJlZykgLT4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgQ291bnQxID0gP0NIKHN5c1Byb2MsIHNlbGVjdF9jb3VudF9uYW1lcywgW1t7ez9SRUdfTUFUQ0gsICdfJ30sW10sW3RydWVdfV1dKSwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgQ291bnQyID0gP0NIKHN5c1Byb2MsIHNlbGVjdF9jb3VudF9uYW1lcywgW1t7ez9SRUdfTUFUQ0gyLCAnXyd9LFtdLFt0cnVlXX1dXSksICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgQ291bnQgID0gcmVnX2NvdW50KENvdW50MSwgQ291bnQyKSwKICAgIFJlc3VsdENvdW50ID0gY291bnQoQ291bnQpLAogICAgaW86Zm9ybWF0KCJcdCBOdW1iZXIgb2YgUkVHIHByb2Nlc3NlcyBpbiB0aGUgc3lzdGVtIGFyZSB+cH5uIiwgW1Jlc3VsdENvdW50XSksCiAgICBSZXN1bHRDb3VudDsKY291bnQoYjJiKSAtPiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICBDb3VudCA9ID9DSChzeXNQcm9jLCBzZWxlY3RfY291bnRfbmFtZXMsIFtbe3s/RENfTUFUQ0goYjJiKSwgJ18nfSxbXSxbdHJ1ZV19XV0pLCAKICAgIFJlc3VsdENvdW50ID0gY291bnQoQ291bnQpLAogICAgaW86Zm9ybWF0KCJcdCBOdW1iZXIgb2YgQjJCIHByb2Nlc3NlcyBpbiB0aGUgc3lzdGVtIGFyZSB+cH5uIiwgW1Jlc3VsdENvdW50XSksCiAgICBSZXN1bHRDb3VudDsKY291bnQoaGl3KSAtPiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICBDb3VudCA9ID9DSChzeXNQcm9jLCBzZWxlY3RfY291bnRfbmFtZXMsIFtbe3s/RENfTUFUQ0goaGl3KSwnXyd9LFtdLFt0cnVlXX1dXSksCiAgICBSZXN1bHRDb3VudCA9IGNvdW50KENvdW50KSwKICAgIGlvOmZvcm1hdCgiXHQgTnVtYmVyIG9mIEhJVyBwcm9jZXNzZXMgaW4gdGhlIHN5c3RlbSBhcmUgfnB+biIsIFtSZXN1bHRDb3VudF0pLAogICAgUmVzdWx0Q291bnQ7CmNvdW50KG9hYikgLT4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgQ291bnQgPSA/Q0goc3lzUHJvYywgc2VsZWN0X2NvdW50X25hbWVzLCBbW3t7P0RDX01BVENIKG9hYiksJ18nfSxbXSxbdHJ1ZV19XV0pLAogICAgUmVzdWx0Q291bnQgPSBjb3VudChDb3VudCksCiAgICBpbzpmb3JtYXQoIlx0IE51bWJlciBvZiBPQUIgcHJvY2Vzc2VzIGluIHRoZSBzeXN0ZW0gYXJlIH5wfm4iLCBbUmVzdWx0Q291bnRdKSwKICAgIFJlc3VsdENvdW50Owpjb3VudChJbnQpIHdoZW4gaXNfaW50ZWdlcihJbnQpLT4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgIEludDsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCmNvdW50KE90aGVyKSAtPiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgW090aGVyXS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIApyZWdfY291bnQoIEMxLCAgQzIpIHdoZW4gaXNfaW50ZWdlcihDMSksIGlzX2ludGVnZXIoQzIpIC0+IEMxK0MyOyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKcmVnX2NvdW50KCBDMSwgX0MyKSB3aGVuIGlzX2ludGVnZXIoQzEpICAgICAgICAgICAgICAgICAtPiBDMTsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCnJlZ19jb3VudChfQzEsICBDMikgd2hlbiBpc19pbnRlZ2VyKEMyKSAgICAgICAgICAgICAgICAgLT4gQzI7ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIApyZWdfY291bnQoX0MxLCBfQzIpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC0+IDAuIAoKZHJhd19ncmFwaChJbnB1dEZpbGUsIHN5c2luZm8pIC0+CiAgICBPdXRwdXRGaWxlID0gP0VTVF9MT0dfRElSICsrIGZpbGVuYW1lOnJvb3RuYW1lKGZpbGVuYW1lOmJhc2VuYW1lKElucHV0RmlsZSkpICsrICIuc3ZnIiwKICAgIENtZCA9ICJqYXZhIC1qYXIgIiArKyA/RVNUX0RBVEFfQklOICsrICJzeXNpbmZvLmphciAiICsrICJcPCIgKysgIiAiICsrIAogICAgICAgICAgICAgIElucHV0RmlsZSArKyAiIFx8IGphdmEgLWphciAiICsrID9FU1RfREFUQV9CSU4gKysgInhtbDJzdmcuamFyICIgKysKICAgICAgICAgICAgICAiXD4iICsrICIgIiArKyBPdXRwdXRGaWxlLAogICAgY3Q6cGFsKCJUaGUgZ3JhcGggZHJhdyBzeXNpbmZvIGNvbW1hbmQ6fm5+cH5uIiwgW0NtZF0pLAogICAgb3M6Y21kKENtZCksCiAgICBPdXRwdXRGaWxlOwoKZHJhd19ncmFwaChJbnB1dEZpbGUsIHZtc3RhdCkgLT4KICAgIE91dHB1dEZpbGUgPSA/RVNUX0xPR19ESVIgKysgZmlsZW5hbWU6cm9vdG5hbWUoZmlsZW5hbWU6YmFzZW5hbWUoSW5wdXRGaWxlKSkgKysgIi5zdmciLAogICAgQ21kID0gImphdmEgLWphciAiICsrID9FU1RfREFUQV9CSU4gKysgInZtc3RhdC5qYXIgIiArKyAiXDwiICsrICIgIiArKwogICAgICAgICAgICAgIElucHV0RmlsZSArKyAiIFx8IGphdmEgLWphciAiICsrID9FU1RfREFUQV9CSU4gKysgInhtbDJzdmcuamFyICIgKysKICAgICAgICAgICAgICAiXD4iICsrICIgIiArKyBPdXRwdXRGaWxlLAogICAgY3Q6cGFsKCJUaGUgZ3JhcGggZHJhdyB2bXN0YXQgY29tbWFuZDp+bn5wfm4iLCBbQ21kXSksCiAgICBvczpjbWQoQ21kKSwKICAgIE91dHB1dEZpbGU7CgpkcmF3X2dyYXBoKElucHV0RmlsZSwgcGFzc3JhdGUpIC0+CiAgICBPdXRwdXRGaWxlID0gP0VTVF9MT0dfRElSICsrIGZpbGVuYW1lOnJvb3RuYW1lKGZpbGVuYW1lOmJhc2VuYW1lKElucHV0RmlsZSkpICsrICIuc3ZnIiwKICAgIENtZCA9ICJqYXZhIC1qYXIgIiArKyA/RVNUX0RBVEFfQklOICsrICJwYXNzcmF0ZS5qYXIgIiArKyAiXDwiICsrICIgIiArKyAKICAgICAgICAgICAgICBJbnB1dEZpbGUgKysgIiBcfCBqYXZhIC1qYXIgIiArKyA/RVNUX0RBVEFfQklOICsrICJ4bWwyc3ZnLmphciAiICsrCiAgICAgICAgICAgICAgIlw+IiArKyAiICIgKysgT3V0cHV0RmlsZSwKICAgIGN0OnBhbCgiVGhlIGdyYXBoIGRyYXcgcGFzc3JhdGUgY29tbWFuZDp+bn5wfm4iLCBbQ21kXSksCiAgICBvczpjbWQoQ21kKSwKICAgIE91dHB1dEZpbGUuCgpnZW5lcmF0ZV9odG1sKFN5c0luZm9TdmcsIFZtc3RhdFN2ZywgVWFjUGFzc3JhdGVTdmcsIFVhc1Bhc3NyYXRlU3ZnKSAtPgogICAgQ21kID0gP0VTVF9EQVRBX0JJTiArKyAiaHRtbF9nZW5lcmF0ZS5zaCIgKysgIiAiICsrIFN5c0luZm9TdmcgKysgIiAiICsrCiAgICAgICAgICAgICAgVm1zdGF0U3ZnICsrICIgIiArKyBVYWNQYXNzcmF0ZVN2ZyArKyAiICIgKysgVWFzUGFzc3JhdGVTdmcgKysgIiAiICsrID9FU1RfTE9HX0RJUiwKICAgIG9zOmNtZChDbWQpLgoKZ2VuZXJhdGVfZ3JhcGhfb25fbG9nX2h0bWwoU3lzSW5mb1N2ZywgVm1zdGF0U3ZnLCBVYWNQYXNzcmF0ZVN2ZywgVWFzUGFzc3JhdGVTdmcpIC0+CiAgICB7b2ssIEN3ZH0gPSBmaWxlOmdldF9jd2QoKSwKICAgIENtZCA9ICJjcCAiICsrIFN5c0luZm9TdmcgKysgIiAiICsrIFZtc3RhdFN2ZyArKyAiICIgKysgVWFjUGFzc3JhdGVTdmcgKysKICAgICAgICAgICAgICAiICIgKysgVWFzUGFzc3JhdGVTdmcgKysgIiAiICsrIEN3ZCArKyAiLyIsCiAgICBvczpjbWQoQ21kKSwKICAgIEZpbGVJbmZvID0gW3siRXJsYW5nIFZNIEluZm9ybWF0aW9uIiwgZmlsZW5hbWU6YmFzZW5hbWUoU3lzSW5mb1N2Zyl9LAogICAgICAgICAgICAgICAgeyJWbXN0YXQgSW5mb3JtYXRpb24iLCBmaWxlbmFtZTpiYXNlbmFtZShWbXN0YXRTdmcpfSwKICAgICAgICAgICAgICAgIHsiVWFjUGFzcyBSYXRlIiwgZmlsZW5hbWU6YmFzZW5hbWUoVWFjUGFzc3JhdGVTdmcpfSwKICAgICAgICAgICAgICAgIHsiVWFzUGFzcyBSYXRlIiwgZmlsZW5hbWU6YmFzZW5hbWUoVWFzUGFzc3JhdGVTdmcpfV0sCiAgICBSZXMgPSBnZW5faHRtbF9mb3JtYXQoRmlsZUluZm8pLAogICAgaW86Zm9ybWF0KFJlcykuCgpnZW5faHRtbF9mb3JtYXQoRmlsZUluZm8pIC0+CiAgICBGID0gZnVuKHtEZXNwLCBGaWxlTmFtZX0pIC0+CiAgICAgICAgICAgICI8aDE+IiArKyBEZXNwICsrICI8L2gxPiIgKysgIjxlbWJlZCB0eXBlPVwiaW1hZ2Uvc3ZnK3htbFwiIHNyYz1cIi4uLy4uLyIgKysgRmlsZU5hbWUgKysgIlwiLz4iCiAgICAgICAgZW5kLAogICAgTGlzdCA9IGxpc3RzOm1hcChGLCBGaWxlSW5mbyksCiAgICBsaXN0czpmbGF0dGVuKFsiPGRpdj4iIHwgTGlzdF0sICI8L2Rpdj4iKS4KCgolJSAjLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiUlICMzLjIgICBDT0RFIEZPUiBJTlRFUk5BTCBGVU5DVElPTlMKJSUgIy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKJSUgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KJSUgc3RhcnRfc2lwcChTSVBwQ29tbWFuZCkKJSUgQHNwZWMgc3RhcnRfc2lwcChTSVBwQ29tbWFuZCkgLT4gUmVzOjp0dXBsZSgpCiUlIEBkb2MgU3RhcnQgU0lQUCBjb21tYW5kIGFuZCByZXR1cm5zIHRoZSBwcm9jZXNzIGlkIG9mIFNJUHAKJSUgPHByZT4KJSUgPGI+SW5wdXQ6PC9iPgolJSBTSVBwQ29tbWFuZCAgICAgICAgU3RyaW5nCiUlCiUlIDxiPk91dHB1dDo8L2I+CiUlIFJlcyAgIFR1cGxlICAgICAge29rfGVycm9yLCBQSUQ6OnN0cmluZygpfQolJQolJSA8L3ByZT4KJSUgQGVuZAolJSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpzdGFydF9zaXBwKFNJUHBDb21tYW5kKSAtPgogICAgU0lQcFNoZWxsUHJpbnRvdXQgPSBvczpjbWQoU0lQcENvbW1hbmQpLAogICAgY3Q6cGFsKCJzdGFydHNpcHAgcmVzdWx0OiAjIyMjIyB+cH5uIiwgW1NJUHBTaGVsbFByaW50b3V0XSksCiAgICBnZXRfcGlkX3N0cihTSVBwU2hlbGxQcmludG91dCkuCgoKJSUgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KJSUgaW50ZXJ2YWxfbG9vcF9jaGVjayhDb3VudGVycykKJSUgQHNwZWMgaW50ZXJ2YWxfbG9vcF9jaGVjayhDb3VudGVyczo6aW50ZWdlcigpKSAtPiBSZXM6OmF0b20oKQolJSBAZG9jIENoZWNrIHRoZSBzaXBwIHByb2Nlc3Mgc3RhdHVzIGFuZCBzaXBwIGNhbGwgc3RhdHVzLCByZXR1cm4gZmFpbGVkIGlmCiUlIHNpcHAgcHJvY2VzcyBraWxsZWQgb3IgY2FsbCBmYWlsIHJhdGUgaXMgbW9yZSB0aGFuIGV4cGVjdCBpbiBhIGNlcnRhaW4gdGltZQolJSA8cHJlPgolJSA8Yj5JbnB1dDo8L2I+CiUlIENvdW50ZXJzICAgICBJbnRlZ2VyICAgVGhlIG51bWJlciBvZiB0aW1lcyBmb3IgY2hlY2sKJSUKJSUgPGI+T3V0cHV0OjwvYj4KJSUgUmVzICAgVHVwbGUKJSUKJSUgPC9wcmU+CiUlIEBlbmQKJSUgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KaW50ZXJ2YWxfbG9vcF9jaGVjayhDb3VudGVycyktPgogICAgcmVjZWl2ZQogICAgICAgIHt1YSwgU2lwcFBpZCwgU2lwcFN0YXR1c30gLT4gCiAgICAgICAgICAgIGNhc2UgU2lwcFN0YXR1cyBvZiAKICAgICAgICAgICAgICAgIHRydWUgLT4KICAgICAgICAgICAgICAgICAgICBjdDpwYWwoIlNpcHAoUElEPX5wKSBpcyBydW5uaW5nfm4iLCBbU2lwcFBpZF0pLAogICAgICAgICAgICAgICAgICAgIGludGVydmFsX2xvb3BfY2hlY2soQ291bnRlcnMpOwogICAgICAgICAgICAgICAgZmFsc2UgLT4gCiAgICAgICAgICAgICAgICAgICAgP1dBUk5JTkdfTVNHKCJ0aGUgU0lQcCBwcm9jZXNzKH5wKSBkb2Vzbid0IHdvcmsiLCBbU2lwcFBpZF0pLAogICAgICAgICAgICAgICAgICAgIHtmYWlsZWQsIHtzaXBwU3RhdHVzLCBTaXBwUGlkfX0KICAgICAgICAgICAgZW5kOwogICAgICAgIHt1YVN0YXQsIFN0YXRGaWxlLCBTdGF0RGF0YX0gLT4KICAgICAgICAgICAgQ291bnRlclZhbHVlID0gcHJvcGxpc3RzOmdldF92YWx1ZShTdGF0RmlsZSwgQ291bnRlcnMpLAogICAgICAgICAgICBSZXN0Q291bnRlcnMgPSBwcm9wbGlzdHM6ZGVsZXRlKFN0YXRGaWxlLCBDb3VudGVycyksCiAgICAgICAgICAgIGNhc2UgY2hlY2tfc2lwcF9zdGF0KGludGVydmFsLCBTdGF0RGF0YSkgb2YKICAgICAgICAgICAgICAgIHRydWUgLT4gCiAgICAgICAgICAgICAgICAgICAgY3Q6cGFsKCJTdGF0aXN0aWNzIGZpbGUofnApIGludGVydmFsIGxvb3AgY2hlY2sgb2t+biIsIFtTdGF0RmlsZV0pLAogICAgICAgICAgICAgICAgICAgIGludGVydmFsX2xvb3BfY2hlY2soW3tTdGF0RmlsZSwgMH18UmVzdENvdW50ZXJzXSk7CiAgICAgICAgICAgICAgICBmYWxzZSAtPiAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgP1dBUk5JTkdfTVNHKCJ0aGUgU0lQcCh+cCkgaGFzIHNvbWUgZmFpbGVkIGNhbGxzIGluIHRoZSBwYXN0IH5wIGludGVydmFsIHRpbWVzfm4iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBbU3RhdEZpbGUsIENvdW50ZXJWYWx1ZV0pLAogICAgICAgICAgICAgICAgICAgIGlmIENvdW50ZXJWYWx1ZSA8ID9TVEFUX0ZBSUwgLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgaW50ZXJ2YWxfbG9vcF9jaGVjayhbe1N0YXRGaWxlLCBDb3VudGVyVmFsdWUgKyAxfXxSZXN0Q291bnRlcnNdKTsKICAgICAgICAgICAgICAgICAgICAgICB0cnVlIC0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgIHtmYWlsZWQsIHtzaXBwU3RhdCwgU3RhdEZpbGV9fQogICAgICAgICAgICAgICAgICAgIGVuZAogICAgICAgICAgICBlbmQ7CiAgICAgICAgc3RvcCAtPgogICAgICAgICAgICBvawogICAgZW5kLgoKJSUgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KJSUgY2xvc2VfbW9uaXRvcihQaWQsIFRSZWZzKQolJSBAc3BlYyBjbG9zZV9tb25pdG9yKFBpZCwgVFJlZnMpLT5SZXMKJSUgQGRvYyBDbG9zZSB0aGUgbW9uaXRvciBmb3IgU0lQcAolJSA8cHJlPgolJSA8Yj5JbnB1dDo8L2I+CiUlIFRSZWZzICAgICAgICBUaW1lIFJlZmVyZW5jZSBvZiBtb25pdG9yCiUlCiUlIDwvcHJlPgolJSBAZW5kCiUlIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCmNsb3NlX21vbml0b3IoUGlkLCBUUmVmcykgLT4KICAgIFt0aW1lcjpjYW5jZWwoVFJlZil8fFRSZWY8LVRSZWZzXSwKICAgIGN0OnBhbCgiY2xvc2VfbW9uaXRvcjogVFJlZnM6IH5wfm4iLCBbVFJlZnNdKSwKICAgIFBpZCAhIHN0b3AuCgpjaG9vc2UodHJ1ZSwgX19UcnVlLCBfKSAtPiBfX1RydWU7CmNob29zZShmYWxzZSwgXywgX19GYWxzZSkgLT4gX19GYWxzZS4KCgpoYW5kbGVfbG9jYWxfaXAodW5kZWZpbmVkLCBSb2xlLCBJcFZzbikgLT4KICAgIGxvY2FsX2lwX3N0cmluZyhJcFZzbiwgUm9sZSk7CmhhbmRsZV9sb2NhbF9pcChMb2NhbElwLCBfLCBfKSAtPgogICAgTG9jYWxJcC4KCgpsb2NhbF9pcF9zdHJpbmcoSXBWc24sIFJvbGUpIC0+CiAgICBjYXNlID9DSChzeXNFbnYsIGlzX3NzaXQsIFtdKSBvZgogICAgICAgIHRydWUgLT4KICAgICAgICAgICAgaXBfc3RyaW5nX3NzaXQoSXBWc24pOwogICAgICAgIF8gLT4KICAgICAgICAgICAgbG9jYWxfaXBfc3RyaW5nX3N0cChJcFZzbiwgUm9sZSkKICAgIGVuZC4KCmxvY2FsX2lwX3N0cmluZ19zdHAoaXB2NCwgYWNjZXNzKSAtPgogICAgIjEwLjEwLjEyMS43IjsKbG9jYWxfaXBfc3RyaW5nX3N0cChpcHY2LCBhY2Nlc3MpIC0+CiAgICAiMzAwMToxMDoxMjE6OjciOwpsb2NhbF9pcF9zdHJpbmdfc3RwKGlwdjQsIGNvcmU0YWNjZXNzKSAtPgogICAgIjEwLjEwLjI0MC43IjsKbG9jYWxfaXBfc3RyaW5nX3N0cChpcHY2LCBjb3JlNGFjY2VzcykgLT4KICAgICIzMDAxOjEwOjI0MDo6NyI7CmxvY2FsX2lwX3N0cmluZ19zdHAoaXB2NCwgZm9yZWlnbikgLT4KICAgICIxMC4xMC4xNDEuNyI7CmxvY2FsX2lwX3N0cmluZ19zdHAoaXB2NiwgZm9yZWlnbikgLT4KICAgICIzMDAxOjEwOjE0MTo6NyI7CmxvY2FsX2lwX3N0cmluZ19zdHAoaXB2NCwgY29yZTRmb3JlaWduKSAtPgogICAgIjEwLjEwLjI0MC43IjsKbG9jYWxfaXBfc3RyaW5nX3N0cChpcHY2LCBjb3JlNGZvcmVpZ24pIC0+CiAgICAiMzAwMToxMDoyNDA6OjciOwpsb2NhbF9pcF9zdHJpbmdfc3RwKF9JcFZzbiwgUm9sZSkgLT4KICAgIGxvY2FsX2lwX3N0cmluZyhpcHY0LCBSb2xlKS4KCmlwX3N0cmluZ19zc2l0KElwVnNuKSAtPgogICAgSVB0dXBsZSA9IGNhc2UgSXBWc24gb2YKICAgICAgICAgICAgICAgICAgaXB2NiAtPgogICAgICAgICAgICAgICAgICAgICAgZ2V0X2xvY2FsX2lwX3R1cGxlKGluZXQ2KTsKICAgICAgICAgICAgICAgICAgXyAtPgogICAgICAgICAgICAgICAgICAgICAgZ2V0X2xvY2FsX2lwX3R1cGxlKGluZXQpCiAgICAgICAgICAgICAgZW5kLAogICAgaW5ldF9wYXJzZTpudG9hKElQdHVwbGUpLgoKZ2V0X2xvY2FsX2lwX3R1cGxlKGluZXQ2KSAtPgogICAgJSUgVGhlIElQdjYgbGluYyBsb2NhbCBhZGRyZXNzIHRoYXQgZXhpc3RzIG9uIHRoZSBsaW51eCBtYWNoaW5lCiAgICAlJSBjYW4gbm90IGJlIHVzZWQgYnkgZXJsYW5nLiBUaGUgYWRkcmVzcyBtdXN0IGJlIGEgZ2xvYmFsIGFkZHJlc3Mgb3IKICAgICUlIGxvb3BiYWNrLiBPbmx5IGxvb3BiYWNrIGFkZHJlc3MgaXMgYXZhaWxhYmxlIGluIHNpbXVsYXRlZCBlbnZpcm9ubWVudC4KICAgIHswLDAsMCwwLDAsMCwwLDF9OwpnZXRfbG9jYWxfaXBfdHVwbGUoSW5ldEZhbWlseSkgLT4KICAgIHtvaywgSG9zdH0gID0gaW5ldDpnZXRob3N0bmFtZSgpLAogICAge29rLCBJUH0gICAgPSBpbmV0OmdldGFkZHIoSG9zdCwgSW5ldEZhbWlseSksCiAgICBJUC4gCgpoYW5kbGVfbG9jYWxfcG9ydCh1bmRlZmluZWQsIFJvbGUpIC0+CiAgICBsb2NhbF9wb3J0X3N0cmluZyhSb2xlKTsKaGFuZGxlX2xvY2FsX3BvcnQoTG9jYWxQb3J0LCBfUm9sZSkgLT4KICAgIExvY2FsUG9ydC4KCmxvY2FsX3BvcnRfc3RyaW5nKGFjY2VzcykgLT4KICAgICI4MDA0IjsKbG9jYWxfcG9ydF9zdHJpbmcoY29yZTRhY2Nlc3MpIC0+CiAgICAiODAwMyI7CmxvY2FsX3BvcnRfc3RyaW5nKGZvcmVpZ24pIC0+CiAgICAiODAwMiI7CmxvY2FsX3BvcnRfc3RyaW5nKGNvcmU0Zm9yZWlnbikgLT4KICAgICI4MDAzIi4KCgpoYW5kbGVfcmVtb3RlX2lwKHVuZGVmaW5lZCwgUm9sZSwgSXBWc24pIC0+CiAgICByZW1vdGVfaXBfc3RyaW5nKElwVnNuLCBSb2xlKTsKaGFuZGxlX3JlbW90ZV9pcChSZW1vdGVJcCwgXywgXykgLT4KICAgIFJlbW90ZUlwLgoKcmVtb3RlX2lwX3N0cmluZyhJcFZzbiwgUm9sZSkgLT4KICAgIGNhc2UgP0NIKHN5c0VudiwgaXNfc3NpdCwgW10pIG9mCiAgICAgICAgdHJ1ZSAtPgogICAgICAgICAgICBpcF9zdHJpbmdfc3NpdChJcFZzbik7CiAgICAgICAgXyAtPgogICAgICAgICAgICByZW1vdGVfaXBfc3RyaW5nX3N0cChJcFZzbiwgUm9sZSkKICAgIGVuZC4KCnJlbW90ZV9pcF9zdHJpbmdfc3RwKGlwdjQsIGFjY2VzcykgLT4KICAgICIxMC4yMS4wLjQiOwpyZW1vdGVfaXBfc3RyaW5nX3N0cChpcHY2LCBhY2Nlc3MpIC0+CiAgICAiMzAwMToyMTo6NCI7CnJlbW90ZV9pcF9zdHJpbmdfc3RwKGlwdjQsIGNvcmU0YWNjZXNzKSAtPgogICAgIjEwLjEwLjIzMC40IjsKcmVtb3RlX2lwX3N0cmluZ19zdHAoaXB2NiwgY29yZTRhY2Nlc3MpIC0+CiAgICAiMzAwMToxMDoyMzA6OjQiOwpyZW1vdGVfaXBfc3RyaW5nX3N0cChpcHY0LCBmb3JlaWduKSAtPgogICAgIjEwLjQxLjAuNCI7CnJlbW90ZV9pcF9zdHJpbmdfc3RwKGlwdjYsIGZvcmVpZ24pIC0+CiAgICAiMzAwMTo0MTo6NCI7CnJlbW90ZV9pcF9zdHJpbmdfc3RwKGlwdjQsIGNvcmU0Zm9yZWlnbikgLT4KICAgICIxMC4xMC4yMzAuNSI7CnJlbW90ZV9pcF9zdHJpbmdfc3RwKGlwdjYsIGNvcmU0Zm9yZWlnbikgLT4KICAgICIzMDAxOjEwOjIzMDo6NSI7CnJlbW90ZV9pcF9zdHJpbmdfc3RwKF9JcFZzbiwgUm9sZSkgLT4KICAgIHJlbW90ZV9pcF9zdHJpbmdfc3RwKGlwdjQsIFJvbGUpLgoKCmhhbmRsZV9yZW1vdGVfcG9ydCh1bmRlZmluZWQsIFJvbGUpIC0+CiAgICByZW1vdGVfcG9ydF9zdHJpbmcoUm9sZSk7CmhhbmRsZV9yZW1vdGVfcG9ydChSZW1vdGVQb3J0LCBfKSAtPgogICAgUmVtb3RlUG9ydC4KCnJlbW90ZV9wb3J0X3N0cmluZyhhY2Nlc3MpIC0+CiAgICAiMTAwMDQiOwpyZW1vdGVfcG9ydF9zdHJpbmcoY29yZTRhY2Nlc3MpIC0+CiAgICAiMTAwMDMiOwpyZW1vdGVfcG9ydF9zdHJpbmcoZm9yZWlnbikgLT4KICAgICIxMDAwMiI7CnJlbW90ZV9wb3J0X3N0cmluZyhjb3JlNGZvcmVpZ24pIC0+CiAgICAiMTAwMDUiLgoKc3RvcF9zaXBwX3Byb2Nlc3MoUGlkKSAtPgogICAgc3RvcF9zaXBwX3Byb2Nlc3MoUGlkLCBncmFjZWZ1bCkuCgpzdG9wX3NpcHBfcHJvY2VzcyhQaWQsIGdyYWNlZnVsKSAtPgogICAgQ21kID0gImtpbGwgLVVTUjEgIiArKyB0b19zdHJpbmcoUGlkKSwKICAgID9ERUJVRygidGhlIGNvbW1hbmQgaXMgfnB+biIsW0NtZF0pLAogICAgUmVzID0gb3M6Y21kKENtZCksCiAgICA/REVCVUcoInRoZSBjb21tYW5kIHJlc3VsdCBpcyB+cH5uIixbUmVzXSk7CnN0b3Bfc2lwcF9wcm9jZXNzKFBpZCwgZW5mb3JjZWQpIC0+CiAgICBvczpjbWQoImtpbGwgLTkgIiArKyB0b19zdHJpbmcoUGlkKSkuCgpzdG9wX3VhY191YXNfc2lwcF9wcm9jZXNzZXMoW1BpZHxUXSA9IF9QaWRzLCBDYWxsRHVyYXRpb24pIC0+CiAgICBzdG9wX3NpcHBfcHJvY2VzcyhQaWQpLAogICAgdGltZXI6c2xlZXAoQ2FsbER1cmF0aW9uKzE4MDAwMCksCiAgICBjYXNlIGNoZWNrX3BpZF9hbGl2ZShQaWQpIG9mCiAgICAgICAgZmFsc2UgLT4gCiAgICAgICAgICAgIG9rOwogICAgICAgIF8gLT4KICAgICAgICAgICAgP1dBUk5JTkdfTVNHKCJGYWlsZWQgdG8gc3RvcCB0aGUgUElEKH5wKSBncmFjZWZ1bGx5Ln5uIixbUGlkXSksCiAgICAgICAgICAgID9ERUJVRygidGhlIFBJRCh+cCkgc3RhdHVzIH5wfm4iLCBbUGlkLCBjaGVja19waWRfYWxpdmUoUGlkKV0pLAogICAgICAgICAgICBzdG9wX3NpcHBfcHJvY2VzcyhQaWQsIGVuZm9yY2VkKQogICAgZW5kLAogICAgdGltZXI6c2xlZXAoMTIwMDAwKSwKICAgIHN0b3BfdWFjX3Vhc19zaXBwX3Byb2Nlc3NlcyhULCAwKTsKCnN0b3BfdWFjX3Vhc19zaXBwX3Byb2Nlc3NlcyhbXSwgX0NhbGxEdXJhdGlvbikgLT4KICBvay4KCmNsb3NlX3NpcHAoKS0+CiAgICBSZXMgPSBvczpjbWQoInBncmVwIHNpcHAiKSwKICAgIGNhc2UgbGVuZ3RoKFJlcykgb2YgCiAgICAgICAgMCAtPiBvazsKICAgICAgICBfIC0+IG9zOmNtZCgicGtpbGwgc2lwcCIpCiAgICBlbmQuCgpjaGVja19zaXBwX3Byb2Nlc3Nfc3RhdHVzKFBpZCwgW1NpcHBQaWR8VF09X1NpcHBQaWRzKSAtPgogICAgUGlkICEge3VhLCBTaXBwUGlkLCBjaGVja19waWRfYWxpdmUoU2lwcFBpZCl9LAogICAgY2hlY2tfc2lwcF9wcm9jZXNzX3N0YXR1cyhQaWQsIFQpOwpjaGVja19zaXBwX3Byb2Nlc3Nfc3RhdHVzKF9QaWQsIFtdKSAtPgogICAgb2suCgpjaGVja19zaXBwX2NhbGxfc3RhdHVzKFBpZCwgW1N0YXRGaWxlfFRdPV9TdGF0RmlsZXMpLT4KICAgIHtvaywgU3RhdERhdGF9ID0gZ2V0X3N0YXRfdGFpbChTdGF0RmlsZSksCiAgICBQaWQgISB7dWFTdGF0LCBTdGF0RmlsZSwgU3RhdERhdGF9LAogICAgY2hlY2tfc2lwcF9jYWxsX3N0YXR1cyhQaWQsIFQpOwpjaGVja19zaXBwX2NhbGxfc3RhdHVzKF9QaWQsIFtdKSAtPgogICAgb2suCgpjaGVja19waWRfYWxpdmUoUGlkKSAtPgogICAgSXNTdHJpbmc9aXNfc3RyaW5nKFBpZCksCiAgICBpZiAKICAgICAgICBJc1N0cmluZyAtPiAKICAgICAgICAgICAgZmlsZWxpYjppc19kaXIoIi9wcm9jLyIgKysgUGlkKTsKICAgICAgICBpc19pbnRlZ2VyKFBpZCkgLT4gCiAgICAgICAgICAgIGZpbGVsaWI6aXNfZGlyKCIvcHJvYy8iICsrIHRvX3N0cmluZyhQaWQpKTsKICAgICAgICB0cnVlIC0+CiAgICAgICAgICAgID9XQVJOSU5HX01TRygiV3JvbmcgUGlkIENvbnRlbnQ6fnB+biIsW1BpZF0pLAogICAgICAgICAgICBmYWxzZQogICAgZW5kLgoKY2hlY2tfc2lwcF9zdGF0KGludGVydmFsLCBTdGF0RGF0YSktPgogICAgRmFpbGVkQ2FsbF9QID0gbGlzdHM6bnRoKDE3LCBTdGF0RGF0YSksCiAgICBpZgogICAgICAgIEZhaWxlZENhbGxfUCA+IDAgLT4KICAgICAgICAgICAgZmFsc2U7CiAgICAgICAgRmFpbGVkQ2FsbF9QID09IDAgLT4KICAgICAgICAgICAgdHJ1ZQogICAgZW5kOwpjaGVja19zaXBwX3N0YXQoZmluYWwsIFN0YXREYXRhKS0+CiAgICBUb3RhbENyZWF0ZWRDYWxsPWxpc3RzOm50aCgxMywgU3RhdERhdGEpLAogICAgRmFpbGVkQ2FsbF9DID0gbGlzdHM6bnRoKDE4LCBTdGF0RGF0YSksCiAgICAKICAgIGN0OnBhbCgiVG90YWwgbnVtYmVyIG9mIGNhbGxzIGlzOn5wfm4iLCBbVG90YWxDcmVhdGVkQ2FsbF0pLAogICAgY3Q6cGFsKCJOdW1iZXIgb2YgZmFpbGVkIGNhbGxzIGlzOn5wfm4iLCBbRmFpbGVkQ2FsbF9DXSksCiAgICAKICAgIGlmIAogICAgICAgIFRvdGFsQ3JlYXRlZENhbGwgPiAwIC0+CiAgICAgICAgICAgIENhbGxMb3NzUmF0ZSA9IEZhaWxlZENhbGxfQyAvIFRvdGFsQ3JlYXRlZENhbGw7CiAgICAgICAgdHJ1ZSAtPgogICAgICAgICAgICBDYWxsTG9zc1JhdGUgPSAwCiAgICBlbmQsCiAgICAKICAgIFByaW50SW5mbyA9IFt7IlRvdGFsIGNhbGxzIiwgdG9fbGlzdChUb3RhbENyZWF0ZWRDYWxsKX0sCiAgICAgICAgICAgICAgICAgeyJUb3RhbCBmYWlsZWQgY2FsbHMiLCB0b19saXN0KEZhaWxlZENhbGxfQyl9LAogICAgICAgICAgICAgICAgIHsiRXJyb3IgcmF0ZSIsIHRvX2xpc3QoQ2FsbExvc3NSYXRlKjEwMCkgKysgIiUifV0sCiAgICBwcmludChQcmludEluZm8pLCAKICAgIAogICAgU0lQcFJlc3VsdCA9IGlmIAogICAgICAgICAgICAgICAgICAgICBDYWxsTG9zc1JhdGUgPiA/Q0FMTF9MT1NTX1JBVEUgLT4KICAgICAgICAgICAgICAgICAgICAgICAgIGZhbHNlOwogICAgICAgICAgICAgICAgICAgICB0cnVlIC0+CiAgICAgICAgICAgICAgICAgICAgICAgICB0cnVlCiAgICAgICAgICAgICAgICAgZW5kLAoKICAgIFNJUHBSZXN1bHQuCgpnZXRfc3RhdF90YWlsKFN0YXRGaWxlKSAtPgogICAgY2FzZSBmaWxlbGliOmlzX3JlZ3VsYXIoU3RhdEZpbGUpIG9mCiAgICAgICAgdHJ1ZSAtPgogICAgICAgICAgICBUYWlsU3RhdCA9IG9zOmNtZCgidGFpbCAtMSAiICsrIFN0YXRGaWxlKSwKICAgICAgICAgICAgU3RyaXBwZWRUYWlsU3RhdD1zdHJpbmc6c3RyaXAoVGFpbFN0YXQsIHJpZ2h0LCAkXG4pLAogICAgICAgICAgICBSb3dMaXN0PWNvbnZlcnRfZGF0YXR5cGUoc3BsaXRfc3RhdGlzdGljcyhTdHJpcHBlZFRhaWxTdGF0LCAiOyIpKSwKICAgICAgICAgICAge29rLCBSb3dMaXN0fTsKICAgICAgICBmYWxzZSAtPgogICAgICAgICAgICA/V0FSTklOR19NU0coInRoZSBTdGF0RmlsZSB+cCBkb2Vzbid0IGV4aXN0fm4iLCBbU3RhdEZpbGVdKSwKICAgICAgICAgICAge2Vycm9yLCBTdGF0RmlsZX0KICAgIGVuZC4KCmdldF9zdGF0X2hlYWQoU3RhdEZpbGUpIC0+CiAgICBjYXNlIGZpbGVsaWI6aXNfcmVndWxhcihTdGF0RmlsZSkgb2YKICAgICAgICB0cnVlIC0+CiAgICAgICAgICAgIEhlYWRTdGF0ID0gb3M6Y21kKCJoZWFkIC0xICIgKysgU3RhdEZpbGUpLAogICAgICAgICAgICBTdHJpcHBlZEhlYWRTdGF0PXN0cmluZzpzdHJpcChIZWFkU3RhdCwgcmlnaHQsICRcbiksCiAgICAgICAgICAgIFJvd0xpc3Q9c3BsaXRfc3RhdGlzdGljcyhTdHJpcHBlZEhlYWRTdGF0LCAiOyIpLAogICAgICAgICAgICB7b2ssIFJvd0xpc3R9OwogICAgICAgIGZhbHNlIC0+CiAgICAgICAgICAgID9XQVJOSU5HX01TRygidGhlIFN0YXRGaWxlIH5wIGRvZXNuJ3QgZXhpc3R+biIsIFtTdGF0RmlsZV0pLAogICAgICAgICAgICB7ZXJyb3IsIFN0YXRGaWxlfQogICAgZW5kLgoKZ2V0X3BpZF9zdHIoU3RyKS0+CiAgICB7b2ssIE1wfSA9IHJlOmNvbXBpbGUoIlBJRD1cXFxbKFswLTldKylcXFxdIiksCiAgICBjYXNlIHJlOnJ1bihTdHIsIE1wLCBbe2NhcHR1cmUsIGFsbF9idXRfZmlyc3QsIGxpc3R9XSkgb2YKICAgICAgICB7bWF0Y2gsIFtQSURdfSAtPiB7b2ssIFBJRH07CiAgICAgICAgXyAtPiB7ZXJyb3IsIFN0cn0KICAgIGVuZC4KCmNvbnZlcnRfZGF0YXR5cGUoTGlzdCkgLT4KICAgICUlIHRoZSBmaXJzdCA1IGl0ZW1zIGFyZSBkYXRlCiAgICAlJSB0aGUgbmV4dCAxMyBpdGVtcyBhcmUgY291bnRlcnMgCiAgICBsaXN0czpzdWJsaXN0KExpc3QsIDUpKytbdG9fbnVtYmVyKFgpfHxYPC1saXN0czpzdWJsaXN0KExpc3QsIDYsIDEzKV0uCgoKd3JpdGVfdGltZShub3cpLT4KICAgIHdyaXRlX3RpbWUoY2FsZW5kYXI6bG9jYWxfdGltZSgpKTsKCndyaXRlX3RpbWUoe3tZZWFyLCBNb250aCwgRGF5fSwge0hvdXIsIE1pbnV0ZSwgU2Vjb25kfX09X1RpbWUpLT4KICAgIGxpc3RzOmZsYXR0ZW4oaW9fbGliOmZvcm1hdCgifnctfi4yLjB3LX4uMi4wd19+LjIuMHc6fi4yLjB3On4uMi4wdyIsIFtZZWFyLCBNb250aCwgRGF5LCBIb3VyLCBNaW51dGUsIFNlY29uZF0pKS4KCiUlMTggaXMgZm9yIFNJUHAgc3RhdCBGaWxlIApzcGxpdF9zdGF0aXN0aWNzKFN0cmluZywgU3RhdERlbGltaXRlcikgLT4KICAgIHNwbGl0X3N0YXRpc3RpY3MoU3RyaW5nLCBTdGF0RGVsaW1pdGVyLCAxOCkuCgpzcGxpdF9zdGF0aXN0aWNzKFN0cmluZywgU3RhdERlbGltaXRlciwgTGVuZ3RoKSAtPgogICAgUm93TGlzdCA9IHJlOnNwbGl0KFN0cmluZywgU3RhdERlbGltaXRlciwgW3tyZXR1cm4sIGxpc3R9XSksCiAgICBsaXN0czpzdWJsaXN0KFJvd0xpc3QsIExlbmd0aCkuCgpwcmludChQcmludEluZm8pIC0+CiAgICBlcmxhbmc6eWllbGQoKSwKICAgIFN1YmplY3QgPSAiRWFybHkgU1QgdGVzdCByZXBvcnQiLAogICAgTDEgPSBsZW5ndGgoU3ViamVjdCksCiAgICBMID0gaW50ZWdlcl90b19saXN0KHRydW5jKCg2OSAtIEwxKS8yICsgTDEpKSwKICAgIGlvOmZvcm1hdCgiK342OS4wLi1+KyIpLAogICAgaW86Zm9ybWF0KCJ8fjY5LiIrKyBMICsrIi4gc3wiLCBbU3ViamVjdF0pLAogICAgaW86Zm9ybWF0KCIrfjY5LjM1Li1zKyIsIFsiKyJdKSwKICAgIGxpc3RzOmZvcmVhY2goZnVuKHtEZXNwLCBOdW19KSAtPgogICAgICAgICAgICAgICAgICAgICAgIGlvOmZvcm1hdCgifH4zNC4zMy4gc3x+MzQuMjAuIHN8IiwgW0Rlc3AsIE51bV0pLAogICAgICAgICAgICAgICAgICAgICAgIGlvOmZvcm1hdCgiK342OS4zNS4tcysiLCBbIisiXSkKICAgICAgICAgICAgICAgICAgZW5kLCBQcmludEluZm8pLAoKICAgIGlvOmZvcm1hdCgiK342OS4wLi1+K35uIikuCgp0b19saXN0KEludGVnZXIpIHdoZW4gaXNfaW50ZWdlcihJbnRlZ2VyKS0+CiAgICBlcmxhbmc6aW50ZWdlcl90b19saXN0KEludGVnZXIpOwp0b19saXN0KEF0b20pIHdoZW4gaXNfYXRvbShBdG9tKSAtPgogICAgZXJsYW5nOmF0b21fdG9fbGlzdChBdG9tKTsKdG9fbGlzdChGbG9hdCkgd2hlbiBpc19mbG9hdChGbG9hdCkgLT4KICAgIE4gPSBtYXRoOnBvdygxMCwgMyksCiAgICBSZXN1bHQgPSByb3VuZChGbG9hdCpOKS9OLAogICAgbGlzdHM6ZmxhdHRlbihpb19saWI6cHJpbnQoUmVzdWx0KSk7CnRvX2xpc3QoMCkgLT4KICAgICIwIjsKdG9fbGlzdCgwLjApIC0+CiAgICAiMCIuCgppc19zdHJpbmcoSW5wdXQpLT4KICAgIGlvX2xpYjpwcmludGFibGVfdW5pY29kZV9saXN0KElucHV0KS4KCnRvX3N0cmluZyhJbnB1dCkgd2hlbiBpc19pbnRlZ2VyKElucHV0KSAtPgogICAgZXJsYW5nOmludGVnZXJfdG9fbGlzdChJbnB1dCk7CnRvX3N0cmluZyhJbnB1dCkgd2hlbiBpc19mbG9hdChJbnB1dCkgLT4KICAgIGVybGFuZzpmbG9hdF90b19saXN0KElucHV0KTsKdG9fc3RyaW5nKElucHV0KSB3aGVuIGlzX2F0b20oSW5wdXQpIC0+CiAgICBlcmxhbmc6YXRvbV90b19saXN0KElucHV0KTsKdG9fc3RyaW5nKElucHV0KSAtPgogICAgY2FzZSBpc19zdHJpbmcoSW5wdXQpIG9mCiAgICAgICAgdHJ1ZSAtPiBJbnB1dDsKICAgICAgICBmYWxzZSAtPiBsaXN0czpmbGF0dGVuKGlvX2xpYjpwcmludChJbnB1dCkpIAogICAgZW5kLiAlJSBJcyBTdHJpbmcgYWxyZWFkeQoKdG9fbnVtYmVyKFN0cikgLT4KICAgIGNhc2Ugc3RyaW5nOnRvX2Zsb2F0KFN0cikgb2YKICAgICAgICB7ZXJyb3IsIF99IC0+IAoJICAgIGNhc2Ugc3RyaW5nOnRvX2ludGVnZXIoU3RyKSBvZgoJCXtlcnJvciwgX30gLT4gCgkJICAgID9XQVJOSU5HX01TRygidGhlIFN0cmluZyh+cCkgaXMgbm90IGludGVnZXIgYW5kIGZsb2F0Ln5uIiwgW1N0cl0pLAoJCSAgICBTdHI7CgkJe0ludCwgX30gLT4gCgkJICAgIEludAoJICAgIGVuZDsKICAgICAgICB7RmxvYXQsIF99IC0+IAogICAgICAgICAgICBGbG9hdAogICAgZW5kLgoKCgoKCgoKCgoKCgoKCgoKCgoKCgotZGVmaW5lKEZJTEVfTkFNRVMsIFsKCQkgICAgIHtzZ2MxX2NoLCBbIi9ibGFkZS9ob21lZGlyL3NnYzEtY2gtdm1zdGF0LSIsID9FU1RfTE9HX0RJUiArKyAic2djMS1jaC1zeXN0ZW0taW5mby0iLCA/RVNUX0xPR19ESVIgKysgInNnYzEtY2gtcGxjLXJlcy0iXX0sCgkJICAgICB7c2djMV9vbSwgWyIvYmxhZGUvaG9tZWRpci9zZ2MxLW9tLXZtc3RhdC0iLCA/RVNUX0xPR19ESVIgKysgInNnYzEtb20tc3lzdGVtLWluZm8tIiwgP0VTVF9MT0dfRElSICsrICJzZ2MxLW9tLXBsYy1yZXMtIl19LAoJCSAgICAge29tbXBfY2gsIFsiL2JsYWRlL2hvbWVkaXIvb21tcC1jaC12bXN0YXQtIiwgP0VTVF9MT0dfRElSICsrICJvbW1wLWNoLXN5c3RlbS1pbmZvLSIsID9FU1RfTE9HX0RJUiArKyAib21tcC1jaC1wbGMtcmVzLSJdfSwKCQkgICAgIHtvbW1wX29tLCBbIi9ibGFkZS9ob21lZGlyL29tbXAtb20tdm1zdGF0LSIsID9FU1RfTE9HX0RJUiArKyAib21tcC1vbS1zeXN0ZW0taW5mby0iLCA/RVNUX0xPR19ESVIgKysgIm9tbXAtb20tcGxjLXJlcy0iXX0KCQkgICAgXSkuCiUlIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiUlIEBzcGVjIHByZXBhcmVfc3lzdGVtaW5mb19maWxlbmFtZXMoSW5wdXQgOjogbGlzdHMoKSkgLT4gbGlzdHMoKQolJSBAZG9jIFByZXBhcmUgdGhlIGxvZyBmaWxlcyBhY2NvcmRpbmcgd2hpY2ggYmxhZGVzIHlvdSB3YW50IHRvIG1vbml0b3IuCiUlIDxwcmU+CiUlICAgPHN0cm9uZz5FeGFtcGxlPC9zdHJvbmc+OgolJSAgICAgIHByZXBhcmVfc3lzdGVtaW5mb19maWxlbmFtZXMoW3NnYzFfY2hdKQolJSAgICAgIHByZXBhcmVfc3lzdGVtaW5mb19maWxlbmFtZXMoW3NnYzFfY2gsIHNnYzFfb20sIG9tbXBfY2gsIG9tbXBfb21dKQolJSA8L3ByZT4KJSUgQGVuZAolJSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpwcmVwYXJlX3N5c3RlbWluZm9fZmlsZW5hbWVzKElucHV0KSB3aGVuIGlzX2xpc3QoSW5wdXQpIC0+CiAgICBDdXJyZW50VGltZSA9IGNhbGVuZGFyOmxvY2FsX3RpbWUoKSwKICAgIEZpbGVuYW1lX3N1ZmZpeCA9IHdyaXRlX3RpbWUoQ3VycmVudFRpbWUpICsrICIubG9nIiwKICAgIHByZXBhcmVfc3lzdGVtaW5mb19maWxlbmFtZXNfaGVscChJbnB1dCwgRmlsZW5hbWVfc3VmZml4LCBbXSk7CnByZXBhcmVfc3lzdGVtaW5mb19maWxlbmFtZXMoXykgLT4KICAgIGN0OnBhbCgicHJlcGFyZV9zeXN0ZW1pbmZvX2ZpbGVuYW1lczogd3JvbmcgaW5wdXQsIGp1c3QgbGlrZSBbc2djMV9jaCwgc2djMV9vbSwgb21tcF9jaCwgb21tcF9vbV1+biIpLAogICAgW10uCgpwcmVwYXJlX3N5c3RlbWluZm9fZmlsZW5hbWVzX2hlbHAoW10sIF8sIFJlcykgLT4KICAgIGxpc3RzOnJldmVyc2UoUmVzKTsKcHJlcGFyZV9zeXN0ZW1pbmZvX2ZpbGVuYW1lc19oZWxwKFtIIHwgVF0sIEZpbGVuYW1lX3N1ZmZpeCwgUmVzKSAtPgogICAgY2FzZSBwcm9wbGlzdHM6Z2V0X3ZhbHVlKEgsID9GSUxFX05BTUVTKSBvZgogICAgICAgIFtWbVN0YXQsIFN5c0luZm8sIFBsY10gLT4KCSAgICBUZW1wUmVzID0gIHtILCB7U3lzSW5mbyArKyBGaWxlbmFtZV9zdWZmaXgsIFZtU3RhdCArKyBGaWxlbmFtZV9zdWZmaXgsIFBsYyArKyBGaWxlbmFtZV9zdWZmaXh9fSwKICAgICAgICAgICAgcHJpbnRfc3lzdGVtaW5mb19maWxlKFRlbXBSZXMpLAogICAgICAgICAgICBwcmVwYXJlX3N5c3RlbWluZm9fZmlsZW5hbWVzX2hlbHAoVCwgRmlsZW5hbWVfc3VmZml4LCBbVGVtcFJlcyB8IFJlc10pOwogICAgICAgIHVuZGVmaW5lZCAtPgogICAgICAgICAgICBjdDpwYWwoIlRoZXJlIGhhdmUgd3JvbmcgaW5wdXQgcGFybWV0ZXJzLCBwbGVhc2Ugc2VsZWN0IGZyb20gKHNnYzFfY2gsIHNnYzFfb20sIG9tbXBfY2gsIG9tbXBfb20pfm4iKSwKICAgICAgICAgICAgcHJlcGFyZV9zeXN0ZW1pbmZvX2ZpbGVuYW1lc19oZWxwKFQsIEZpbGVuYW1lX3N1ZmZpeCwgUmVzKQogICAgZW5kLgoKcHJpbnRfc3lzdGVtaW5mb19maWxlKHtCbGFkZSwge1N5c0luZm9fRmlsZW5hbWUsIFZtc3RhdF9GaWxlbmFtZSwgUGxjUmVzX0ZpbGVuYW1lfX0pIC0+CiAgICBjdDpwYWwoIkJsYWRlOiB+cH5uU3lzSW5mb19GaWxlbmFtZTp+cH5uVm1zdGF0X0ZpbGVuYW1lOn5wfm5QbGNSZXNfRmlsZW5hbWU6fnB+biIsCgkgICBbQmxhZGUsIFN5c0luZm9fRmlsZW5hbWUsIFZtc3RhdF9GaWxlbmFtZSwgUGxjUmVzX0ZpbGVuYW1lXSkuCgoKCiUlIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiUlIEBzcGVjIHN0YXJ0X2NvbGxlY3Rfc3lzdGVtX2luZm9zKElucHV0IDo6IGxpc3RzKCkpIC0+IGxpc3RzKCkKJSUgQGRvYyBTdGFydCBjb2xsZWN0aW5nIHRoZSBsb2dzLjxicj48L2JyPgolJSA8c3Ryb25nPk5vdGU8L3N0cm9uZz46IFRoZSBpbnB1dCBwYXJhbWV0ZXIgb2YgdGhpcyBmdW5jdGlvbiBpcyB0aGUgcmVzdWx0IG9mIDxjb2RlPnNiZ0VzdFN1cHBvcnQ6cHJlcGFyZV9zeXN0ZW1pbmZvX2ZpbGVuYW1lcy8xPC9jb2RlPi4KJSUgQGVuZAolJSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpzdGFydF9jb2xsZWN0X3N5c3RlbV9pbmZvcyhGaWxlTmFtZXMpIC0+CiAgICBzdGFydF9jb2xsZWN0X3N5c3RlbV9pbmZvcyhGaWxlTmFtZXMsIFtdKS4KCnN0YXJ0X2NvbGxlY3Rfc3lzdGVtX2luZm9zKFtdLCBSZXMpIC0+CiAgICBsaXN0czpyZXZlcnNlKFJlcyk7CnN0YXJ0X2NvbGxlY3Rfc3lzdGVtX2luZm9zKFt7c2djMV9jaCwge1N5cywgVm1TdGF0LCBQbGN9fSB8IFRdLCBSZXMpIC0+CiAgICB7b2ssIFN5c0luZm9UaW1lclJlZn0gPSB0aW1lcjphcHBseV9pbnRlcnZhbCh0aW1lcjpobXMoMCwgMiwgMCksCgkJCQkJCSA/TU9EVUxFLCBjb2xsZWN0X3N5c3RlbV9pbmZvLCBbc2djMV9jaCwgU3lzLCBWbVN0YXRdKSwKICAgIHtvaywgUGxjUmVzVGltZXJSZWZ9ICA9IHRpbWVyOmFwcGx5X2ludGVydmFsKHRpbWVyOmhtcygwLCA1LCAwKSwKCQkJCQkJIHNiZ0VzdFN1cHBvcnQsIGNvbGxlY3RfcGxjX3JlcywgW1BsY10pLAogICAgc3RhcnRfY29sbGVjdF9zeXN0ZW1faW5mb3MoVCwKCQkJICAgICAgIFt7c2djMV9jaCwgU3lzSW5mb1RpbWVyUmVmLCBQbGNSZXNUaW1lclJlZn0gfCBSZXNdKTsKCnN0YXJ0X2NvbGxlY3Rfc3lzdGVtX2luZm9zKFt7c2djMV9vbSwge1N5cywgVm1TdGF0LCBQbGN9fSB8IFRdLCBSZXMpIC0+CiAgICB7b2ssIFN5c0luZm9UaW1lclJlZn0gPSB0aW1lcjphcHBseV9pbnRlcnZhbCh0aW1lcjpobXMoMCwgMiwgMCksCgkJCQkJCSA/TU9EVUxFLCBjb2xsZWN0X3N5c3RlbV9pbmZvLCBbc2djMV9vbSwgU3lzLCBWbVN0YXRdKSwKICAgIHtvaywgUGxjUmVzVGltZXJSZWZ9ID0gdGltZXI6YXBwbHlfaW50ZXJ2YWwodGltZXI6aG1zKDAsIDUsIDApLAoJCQkJCQlzYmdFc3RTdXBwb3J0LCBjb2xsZWN0X3BsY19yZXMsIFtQbGNdKSwKICAgIHN0YXJ0X2NvbGxlY3Rfc3lzdGVtX2luZm9zKFQsIFt7c2djMV9vbSwgU3lzSW5mb1RpbWVyUmVmLCBQbGNSZXNUaW1lclJlZn0gfCBSZXNdKTsKCnN0YXJ0X2NvbGxlY3Rfc3lzdGVtX2luZm9zKFt7b21tcF9jaCwge1N5cywgVm1TdGF0LCBQbGN9fSB8IFRdLCBSZXMpIC0+CiAgICB7b2ssIFN5c0luZm9UaW1lclJlZn0gPSB0aW1lcjphcHBseV9pbnRlcnZhbCh0aW1lcjpobXMoMCwgMiwgMCksCgkJCQkJCSA/TU9EVUxFLCBjb2xsZWN0X3N5c3RlbV9pbmZvLCBbb21tcF9jaCwgU3lzLCBWbVN0YXRdKSwKICAgIHtvaywgUGxjUmVzVGltZXJSZWZ9ID0gdGltZXI6YXBwbHlfaW50ZXJ2YWwodGltZXI6aG1zKDAsIDUsIDApLAoJCQkJCQlzYmdFc3RTdXBwb3J0LCBjb2xsZWN0X3BsY19yZXMsIFtQbGNdKSwKICAgIHN0YXJ0X2NvbGxlY3Rfc3lzdGVtX2luZm9zKFQsIFt7b21tcF9jaCwgU3lzSW5mb1RpbWVyUmVmLCBQbGNSZXNUaW1lclJlZn0gfCBSZXNdKTsKCnN0YXJ0X2NvbGxlY3Rfc3lzdGVtX2luZm9zKFt7b21tcF9vbSwge1N5cywgVm1TdGF0LCBQbGN9fSB8IFRdLCBSZXMpIC0+CiAgICB7b2ssIFN5c0luZm9UaW1lclJlZn0gPSB0aW1lcjphcHBseV9pbnRlcnZhbCh0aW1lcjpobXMoMCwgMiwgMCksCgkJCQkJCSA/TU9EVUxFLCBjb2xsZWN0X3N5c3RlbV9pbmZvLCBbb21tcF9vbSwgU3lzLCBWbVN0YXRdKSwKICAgIHtvaywgUGxjUmVzVGltZXJSZWZ9ID0gdGltZXI6YXBwbHlfaW50ZXJ2YWwodGltZXI6aG1zKDAsIDUsIDApLAoJCQkJCQlzYmdFc3RTdXBwb3J0LCBjb2xsZWN0X3BsY19yZXMsIFtQbGNdKSwKICAgIHN0YXJ0X2NvbGxlY3Rfc3lzdGVtX2luZm9zKFQsIFt7b21tcF9vbSwgU3lzSW5mb1RpbWVyUmVmLCBQbGNSZXNUaW1lclJlZn0gfCBSZXNdKS4KCgotZGVmaW5lKEJMQURFUywgWwoJCSB7c2djMV9jaCwgP1NHQ2NoTm9kZX0sCgkJIHtzZ2MxX29tLCA/U0dDb21Ob2RlfSwKCQkge29tbXBfY2gsID9TQkdjaE5vZGV9LAoJCSB7b21tcF9vbSwgP1NCR29tTm9kZX0KCQldKS4KY29sbGVjdF9zeXN0ZW1faW5mbyhCbGFkZVR5cGUsIFN5c0luZm9fRmlsZW5hbWUsIFZtc3RhdF9GaWxlbmFtZSkgLT4KICAgIGNhc2UgcHJvcGxpc3RzOmdldF92YWx1ZShCbGFkZVR5cGUsID9CTEFERVMpIG9mCiAgICAgICAgdW5kZWZpbmVkIC0+CiAgICAgICAgICAgIGN0OnBhbCgiVGhlIEJsYWRlVHlwZSBpcyB3cm9uZywgcGxlYWVhIHJlZmVyOiB+cH5uIiwgWz9CTEFERVNdKTsKICAgICAgICBOb2RlIC0+CgkgICAgaW86Zm9ybWF0KCJkYXRhIGlzIGNvbGxlY3RpbmcsIG5vZGU6fnB+biIsIFtOb2RlXSksCiAgICAgICAgICAgIE1lbV9yZXN1bHQgPSBqcFRycGM6Y2FsbChOb2RlLCBldHMsIHRhYjJsaXN0LCBbcGxjSW5mb10pLAogICAgICAgICAgICBQcm9jZXNzTnVtID0gbGVuZ3RoKGpwVHJwYzpjYWxsKE5vZGUsIGVybGFuZywgcHJvY2Vzc2VzLCBbXSkpLAogICAgICAgICAgICBSZXN1bHQgPSBsaXN0czpzb3J0KE1lbV9yZXN1bHQgKysgW3twcm9jZXNzX051bSwgUHJvY2Vzc051bX1dKSwKCiAgICAgICAgICAgIHtvaywgRmlsZV9oYW5kbGVyfSA9IGZpbGU6b3BlbihTeXNJbmZvX0ZpbGVuYW1lLCBbYXBwZW5kXSksCiAgICAgICAgICAgIGlvOmZvcm1hdChGaWxlX2hhbmRsZXIsICJ+cH5uIiwgW1Jlc3VsdF0pLAogICAgICAgICAgICBvayA9IGZpbGU6Y2xvc2UoRmlsZV9oYW5kbGVyKSwKCiAgICAgICAgICAgIFZtQ21kID0gInZtc3RhdCA1IDUgPj4iICsrIFZtc3RhdF9GaWxlbmFtZSwKICAgICAgICAgICAganBUcnBjOmNhbGwoTm9kZSwgb3MsIGNtZCwgW1ZtQ21kXSkKICAgIGVuZC4KCgoKJSUgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KJSUgQHNwZWMgc3RvcF9jb2xsZWN0X3N5c3RlbV9pbmZvcyhJbnB1dCA6OiBsaXN0cygpKSAtPiBsaXN0cygpCiUlIEBkb2MgU3RvcCBjb2xsZWN0aW5nIHRoZSBsb2dzLjxicj48L2JyPgolJSA8c3Ryb25nPk5vdGU8L3N0cm9uZz46IFRoZSBpbnB1dCBwYXJhbWV0ZXIgb2YgdGhpcyBmdW5jdGlvbiBpcyB0aGUgcmVzdWx0IG9mIDxjb2RlPnNiZ0VzdFN1cHBvcnQ6c3RhcnRfY29sbGVjdF9zeXN0ZW1faW5mb3MvMTwvY29kZT4uCiUlIEBlbmQKJSUgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0Kc3RvcF9jb2xsZWN0X3N5c3RlbV9pbmZvcyhUaW1lclJlZnMpIC0+CiAgICBGdW4gPSBmdW4oe0JsYWRlVHlwZSwgU3lzVGltZXJSZWYsIFBsY1RpbWVyUmVmfSkgLT4KCQkgIGN0OnBhbCgic3RvcF9jb2xsZWN0X3N5c3RlbV9pbmZvczogfnAgfnAgfnB+biIsCgkJCSBbQmxhZGVUeXBlLCBTeXNUaW1lclJlZiwgUGxjVGltZXJSZWZdKSwKCQkgIHRpbWVyOmNhbmNlbChTeXNUaW1lclJlZiksCgkJICB0aW1lcjpjYW5jZWwoUGxjVGltZXJSZWYpCgkgIGVuZCwKICAgIGxpc3RzOmZvcmVhY2goRnVuLCBUaW1lclJlZnMpLgoKCiUlIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiUlIEBzcGVjIGdlbmVyYXRlX2xvZ19zdGF0aXN0aWNzKEZpbGVJbmZvcyA6OiBsaXN0cygpLCBVYWNTdGF0RmlsZSA6OiBzdHJpbmcoKSwgVWFzU3RhdEZpbGUgOjogc3RyaW5nKCksIEJsYWRlVHlwZSA6OiBhdG9tKCkpIC0+IG9rCiUlIEBkb2MgQW5hbHlzZSB0aGUgbG9nIGZpbGVzIGFuZCBkcmF3IGdyYXBoLjxicj48L2JyPgolJSA8cHJlPgolJSA8c3Ryb25nPk5vdGU8L3N0cm9uZz46CiUlICAgRmlsZUluZm9zICAgIGlzIHRoZSByZXN1bHQgb2Ygc2JnRXN0U3VwcG9ydDpwcmVwYXJlX3N5c3RlbWluZm9fZmlsZW5hbWVzLzEuCiUlICAgVWFjU3RhdEZpbGUgIGNhbiBnZXQgZnJvbSB0aGUgcmVzdWx0IG9mIHNiZ0VzdFN1cHBvcnQ6cHJlcGFyZV9zaXBwX2NtZC8zLgolJSAgIFVhc1N0YXRGaWxlICBjYW4gZ2V0IGZyb20gdGhlIHJlc3VsdCBvZiBzYmdFc3RTdXBwb3J0OnByZXBhcmVfc2lwcF9jbWQvMy4KJSUgICBCbGFkZVR5cGUgICAgc2djMV9jaCB8IHNnYzFfb20gfCBvbW1wX2NoIHwgb21tcF9vbQolJSA8L3ByZT4KJSUgQGVuZAolJSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpnZW5lcmF0ZV9sb2dfc3RhdGlzdGljcyhGaWxlSW5mb3MsIFVhY1N0YXRGaWxlLCBVYXNTdGF0RmlsZSwgQmxhZGVUeXBlKQogICAgICAgIHdoZW4gaXNfYXRvbShCbGFkZVR5cGUpIGFuZGFsc28gaXNfbGlzdChGaWxlSW5mb3MpIC0+CiAgICBjYXNlIHByb3BsaXN0czpnZXRfdmFsdWUoQmxhZGVUeXBlLCBGaWxlSW5mb3MpIG9mCiAgICAgICAge1N5c0luZm8sIFZtU3RhdGUsIF9QbGNJbmZvfSAtPgogICAgICAgICAgICBjdDpwYWwoImdlbmVyYXRlX2xvZ19zdGF0aXN0aWNzIGZvciBibGFkZTogfnB+biIsIFtCbGFkZVR5cGVdKSwKICAgICAgICAgICAgJSUgLS0tIHRyYW5zZmVyIHRoZSBjb2xsZWN0ZWQgdm1zdGF0IGZpbGUgdG8gTE1XUCAtLS0KICAgICAgICAgICAgTG9jYWxfVm1zdGF0ID0gc2JnRXN0U3VwcG9ydDp0cmFuc2Zlcl92bXN0YXRfZmlsZShWbVN0YXRlLCBzZ2MxLCBjaCksCgogICAgICAgICAgICB0aW1lcjpzbGVlcCg2MDAwMCksCgogICAgICAgICAgICAlJSAtLS0tLS0gc2NyaXB0IHRvIGRyYXcgZ3JhcGggbmVlZCB0byBiZSB1cGRhdGVkLS0tLS0KICAgICAgICAgICAgU3lzSW5mb19TdmcgPSBzYmdFc3RTdXBwb3J0OmRyYXdfZ3JhcGgoU3lzSW5mbywgc3lzaW5mbyksCiAgICAgICAgICAgIFZtc3RhdF9TdmcgPSBzYmdFc3RTdXBwb3J0OmRyYXdfZ3JhcGgoTG9jYWxfVm1zdGF0LCB2bXN0YXQpLAogICAgICAgICAgICBVYWNfU3ZnID0gc2JnRXN0U3VwcG9ydDpkcmF3X2dyYXBoKFVhY1N0YXRGaWxlLCBwYXNzcmF0ZSksCiAgICAgICAgICAgIFVhc19TdmcgPSBzYmdFc3RTdXBwb3J0OmRyYXdfZ3JhcGgoVWFzU3RhdEZpbGUsIHBhc3NyYXRlKSwKICAgICAgICAgICAgdGltZXI6c2xlZXAoNjAwMDApLAoKICAgICAgICAgICAgJSUgLS0tIGdlbmVyYXRlIGdyYXBoIG9uIGpwdCBsb2cgcGFnZQogICAgICAgICAgICBzYmdFc3RTdXBwb3J0OmdlbmVyYXRlX2dyYXBoX29uX2xvZ19odG1sKFN5c0luZm9fU3ZnLCBWbXN0YXRfU3ZnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFVhY19TdmcsIFVhc19TdmcpOwogICAgICAgIHVuZGVmaW5lZCAtPgogICAgICAgICAgICBjdDpwYWwoImdlbmVyYXRlX2xvZ19zdGF0aXN0aWNzOiBUaGUgZm91cnRoIHBhcmFtZXRlciBzaG91bGQgYmUgYXRvbTogc2djMV9jaCwgc2djMV9vbSwgb21tcF9jaCwgb21tcF9vbS5+biIpCiAgICBlbmQ7CmdlbmVyYXRlX2xvZ19zdGF0aXN0aWNzKF8sIF8sIF8sIF8pIC0+CiAgICBjdDpwYWwoImdlbmVyYXRlX2xvZ19zdGF0aXN0aWNzOiBUaGUgaW5wdXQgcGFyYW1ldGVycyBhcmUgd3JvbmcsIHRoZSBmb3VydGggc2hvdWxkIGJlIGF0b206IHNnYzFfY2gsIHNnYzFfb20sIG9tbXBfY2gsIG9tbXBfb20ufm4iKS4K