The purpose of this document is to provide a detailed explanation of the code related to the message-generator and it serves as a tool for developers to quickly understand the code structure and the main steps the message-generator does for test execution. The following code analysis is divided into sections that represent the different source files of the message-generator, making it easier to find references in the code.
- The code defines two enums:
Sv2Type, which contains all data types used in SV2, andActionResult, which contains the possible values representing the results of an action defined inside the test file. - The code defines the structs
UpstreamandDownstream, which represent the connection settings of the tested SV2 role. - The code defines a struct
Action, which represents an action that has to be execute and the result of which has to be checked as defined inside the test file. - The code defines a struct
Commandthat represents a shell command to be executed. - The code defines a struct
Testthat represents the parsed contents from a test configuration file. - The code defines the
mainfunction, which is the entry point of the program. - The
mainfunction reads command-line arguments, loads a test file, parses it, and creates an executor. - The executor executes the shell commands and actions defined in the test file.
Overall, the code defines a test framework for executing tests defined in a test configuration file. It performs actions and executes shell commands based on the parsed test file.
The Executor struct is responsible for executing a series of actions based on a provided test configuration.
Here's a summary of what the code does:
- It defines a struct called
Executorthat contains several fields related to the information contained inside the test file. TheExecutorstruct has an associated implementation block (impl) that defines its methods. - The
newmethod of theExecutorstruct is an asynchronous function that takes aTestobject and atest_namestring as parameters.- It creates a new instance of the
Executorstruct. - A loop iterates over the setup commands of the test. It handles different types of commands such as "kill" and "sleep", as well as executing OS commands using the
os_commandfunction. - After the setup commands are executed, the method checks the test's
as_downstreamandas_upstreamproperties to determine how to set up the communication channels. Depending on the presence or absence ofas_downstreamandas_upstream, the method sets up the communication channels using thesetup_as_downstreamandsetup_as_upstreamfunctions.
- It creates a new instance of the
- The
executemethod of theExecutorstruct is an asynchronous function that executes the actions defined in the test configuration.- A loop that iterates over the actions defined in the test. For each action, the method checks the role (Upstream or Downstream) and retrieves the corresponding sender and receiver channels.
- The method sends messages from the action's
messagesfield to the appropriate channel and receives a response. - The method then checks the received response against the expected results defined in the action's
resultfield. - If the received response does not match the expected results, the method sets the
successflag to false. - Finally, the method prints various debug information about the executed actions and results.
Overall, the code implements an Executor struct that orchestrates the execution of actions based on a provided test configuration, communicating with different roles (Upstream and Downstream) and checking the received responses against expected results.
Two asynchronous functions are defined: setup_as_upstream and setup_as_downstream, which facilitate the setup of network connections.
- The
setup_as_upstreamfunction takes the following parameters:
socket: The socket address to bind the TCP listener.keys: An optional tuple containing an encoded Ed25519 public key and an encoded Ed25519 secret key.execution_commands: A vector ofCommandstructs representing execution commands.childs: A mutable vector ofOption<tokio::process::Child>.
The function begins by binding a TCP listener to the provided socket address using TcpListener::bind. It then iterates over each Command in the execution_commands vector and spawns an OS command using the os_command function. The resulting child processes are stored in the childs vector.
Next, the function accepts an incoming TCP connection using listner.accept().await. It retrieves the socket stream and ignores the corresponding address.
Based on the presence of keys, the function follows two different paths:
- If
keysisSome, it extracts the encoded Ed25519 public key and secret key from the tuple. It creates aResponderusingResponder::from_authority_kpwith the public and secret keys, along with a duration. It then creates aConnectionwith the stream andHandshakeRole::Responderusing theConnection::newfunction. - If
keysisNone, it creates aPlainConnectionwith the stream using thePlainConnection::newfunction.
Finally, the function returns a tuple containing the receiver and sender of EitherFrame<Message>.
- The
setup_as_downstreamfunction is similar tosetup_as_upstream, but with a few differences:
- It takes
socketas the socket address to connect to. - It takes
keyas an optional encoded Ed25519 public key. - It directly connects to the specified socket address using
TcpStream::connect. - Based on the presence of
key, it either creates anInitiatoror aPlainConnectionusing theConnection::neworPlainConnection::newfunctions, respectively.
The function then returns a tuple containing the receiver and sender of EitherFrame<Message>.
In summary, these functions provide convenient ways to set up network connections, either as an upstream or a downstream role, with or without encryption using Ed25519 keys, and with optional execution of additional commands.
Here the code defines a parser for the test configuration. The test configuration is provided as a JSON string and is parsed into a structured representation. The parsing is done in several steps, each represented by an enum variant of the Parser type.
Here is a summary of each step:
-
Step 1: In this step, the test messages in the configuration are parsed and stored in a
HashMapwith message IDs as keys andAnyMessageobjects as values. TheAnyMessagetype is a generic representation of a message that can be parsed later. -
Step 2: This step takes the parsed messages from Step1, creates
Sv2Frames(frames of SV2 protocol) identified by message IDs and stores them in aHashMap. -
Step 3: In this step the actions are parsed into a vector of
Action. -
Step 4: In this step, the setup, execution and cleanup commands, as well as roles, are parsed. The setup, execution, and cleanup commands are extracted from the test configuration and stored as
Commandobjects in separate vectors. The roles (client, server, proxy) are parsed intoUpstreamorDownstream.
Then the parsed data is prepared for execution. A new Test object, containing all the necessary information for executing the test, is created. This includes the actions, role-specific information (such as upstream and downstream details), and the setup, execution, and cleanup commands.
Here we find the definition of several data structures and functions related to SV2 message parsing and serialization.
-
The
TestMessageParserstructure represents a parsed message. It contains optional fields for different types of messages: common messages, job negotiation messages, mining messages, and template distribution messages. -
The
into_mapmethod ofTestMessageParserconverts the parsed messages into a HashMap, where the ID is the key and the corresponding message is the value. It iterates over each type of message, extracts the ID and the message, and inserts them into the HashMap. -
The
from_strmethod ofTestMessageParserparses a JSON string into aTestMessageParserstructure using theserde_jsoncrate. -
There are several
enumdefinitions (CommonMessages,TemplateDistribution,JobNegotiation, andMining) that represent different types of messages with associated data. These enums implement conversion methods to convert the defined message types into the corresponding types from theroles_logic_sv2::parsersmodule.
In summary, this code provides functionality for parsing and serializing different types of messages and allows retrieving specific messages by ID from a parsed message structure.
A struct called Frames is defined. It contains a HashMap called frames, which maps strings to Sv2Frame<AnyMessage<'a>, Slice> objects.
The method from_step_1 creates an instance of Frames from input data. It takes two parameters: a string test and a HashMap called messages.
-
It parses the
teststring as JSON into aMap<String, Value>using theserde_json::from_strfunction. -
It retrieves the value associated with the key "frame_builders" from the JSON map and assumes it is an array. It then iterates over each element in the array.
-
For each frame, it extracts the message ID as a string and splits it using "::" as the delimiter.
-
If the resulting
idvector has a length of 1, it means the message is contained in the present file. It looks up the message in themessageshashmap using the ID and clones it. Theidis stored in a variable for later use. -
If the
idvector has a length of 2, it means the message appears in a different file. It constructs the path by prepending "../../../../" to the first element of theidvector. Then it calls themessage_from_pathfunction with the path and the second element of theidvector, and assigns the result to themessagevariable. Theidis also stored for later use. -
If the
idvector has a length other than 1 or 2, it raises an error.
-
-
It then retrieves the value associated with the key "type" from the frame and assumes it is a string. Based on the value of
type_, it performs different actions.-
If
type_is "automatic", it attempts to convert themessageinto anSv2Frame<AnyMessage<'a>, Slice>using thetry_intomethod, and inserts it into theresulthashmap using theidas the key. -
If
type_is "manual", it extracts the message type, extension type, and channel_msg values from the frame. It converts the message type from a hexadecimal string to au8value, removes underscores from the extension type string and parses it as au16value, and retrieves the channel_msg as a boolean value. It then creates anSv2Frameusing theSv2Frame::from_messagemethod, passing in themessage, message type, extension type, and channel_msg. It inserts the resulting frame into theresulthashmap using theidas the key. -
If
type_has any other value, it raises an error.
-
Finally, it creates an instance of Frames with the result hashmap and returns it.
In summary, this code parses JSON input, retrieves frame data, and constructs Sv2Frame objects based on the frame type and associated messages.
A struct called ActionParser is defined. It doesn't have any fields or state as it serves as a container for the from_step_3 method.
The method from_step_3 parses JSON input and constructs a vector of Action objects. It takes two parameters: a string test and a HashMap called frames.
-
It parses the
teststring as JSON into aMap<String, Value>using theserde_json::from_strfunction. -
It retrieves the value associated with the key "actions" from the JSON map and assumes it is an array. It then iterates over each element in the array.
-
For each action, it retrieves the value associated with the key "role" from the action and assumes it is a string. Based on the value of
role, it assigns the correspondingRoleenum variant (Role::DownstreamorRole::Upstream) to therolevariable. If the value is neither "client" nor "server", it raises an error. -
It initializes an empty vector called
action_framesand retrieves the value associated with the key "message_ids" from the action. It assumes it is an array and iterates over each element in the array. For each message ID, it looks up the corresponding frame in theframeshashmap using the ID as the key. If the frame is found, it clones it and wraps it in aStandardEitherFrame::Sv2. The resulting frame is then added to theaction_framesvector. -
It retrieves the value associated with the key "actiondoc" from the action. If the value is
Some, it converts it to a string and assigns it to theactiondocvariable. If the value isNone, it assignsNoneto theactiondocvariable. -
It initializes an empty vector called
action_resultsand retrieves the value associated with the key "results" from the action. It assumes it is an array and iterates over each element in the array. For each result, it retrieves the value associated with the key "type" and assumes it is a string. Based on the value oftype, it performs different actions:-
If
typeis "match_message_type", it retrieves the value associated with the key "value" from the result. It assumes it is a hexadecimal string and converts it to au8value. It constructs anActionResult::MatchMessageTypevariant with the message type and adds it to theaction_resultsvector. -
If
typeis "match_message_field", it retrieves the value associated with the key "value" from the result. It assumes it is a JSON value and attempts to deserialize it into a tuple(String, String, Vec<(String, Sv2Type)>). If the deserialization is successful, it constructs anActionResult::MatchMessageFieldvariant with the deserialized value and adds it to theaction_resultsvector. -
If
typeis "match_message_len", it retrieves the value associated with the key "value" from the result. It assumes it is an unsigned 64-bit integer and converts it to a usize value. It constructs anActionResult::MatchMessageLenvariant with the message length and adds it to theaction_resultsvector. -
If
typeis "match_extension_type", it retrieves the value associated with the key "extension_type" from the result. It assumes it is a string, removes underscores from it, and parses it as au16value. It constructs anActionResult::MatchExtensionTypevariant with the extension type and adds it to theaction_resultsvector. -
If
typeis "close_connection", it adds anActionResult::CloseConnectionvariant to theaction_resultsvector. -
If
typeis "none", it adds anActionResult::Nonevariant to theaction_resultsvector. -
If
typehas any other value, it raises an error.
-
Finally, it constructs an Action object using the action_frames, action_results, role, and actiondoc variables, and adds it to the result vector. After iterating over all actions, it returns the result vector containing all constructed Action objects.
In summary, this code parses JSON input, retrieves action data, and constructs Action objects with associated messages, results, roles, and action documents.