Major upgrade and changes

the MRCI protocol has been upgraded to support branch ids
so running multiple instances of the same command can run
at the same time at the host side. this client however
will still continue to run host commands in FIFO but this
is something that can be added in the near future.

another change to the protocol is the version negotiation
is now entirely one-sided so just the client can now
decide of the host version is acceptable or not.

the NEW_CMD frames from MRCI host will now include all of
the command documentation so listing of all available
commands and documentation is now entirely client
controlled because the host no longer have built in
documentation commands.

this client's Session object now operate in it's own
thread. for now, this does nothing but this is a prelude
for future performance updates that will including
multi-threading.
This commit is contained in:
Maurice ONeal 2019-11-23 14:37:41 -05:00
parent ccbaf2c531
commit 51c2e63a24
22 changed files with 1048 additions and 529 deletions

View File

@ -42,6 +42,7 @@ LIBS += -lcrypto -lssl
SOURCES += src/main.cpp \
src/cmd_line.cpp \
src/cmd_objs/host_doc.cpp \
src/common.cpp \
src/main_ui.cpp \
src/session.cpp \
@ -56,6 +57,7 @@ SOURCES += src/main.cpp \
HEADERS += src/session.h \
src/cmd_line.h \
src/cmd_objs/host_doc.h \
src/common.h \
src/gen_file.h \
src/main_ui.h \

View File

@ -1,184 +0,0 @@
### Async Commands ###
An async command is a virtual command that the host can use to send data to the client at any time while connected to the host. As the name implies, the occurance of a client receiving data from an async command is not always the result of running a regular command in the current session. This can occur for example when information in your account is changed by another client connected to the host; your client would not know about this change until an async command is sent notify it of the change. These commands cannot be called directly by a client or even a module command object.
Async commands are not only used send data to the client but also used internally within the host to help session objects operating in different processes to communicate with each other. Some async commands in fact are considered internal only because the client should never see any data come from them at anytime.
These are considered "virtual" commands because there is no defined command objects attached to them. Instead, async commands are best identified by command id values 1-255. Here is a list of currently "defined" async commands:
```
#define ASYNC_RDY 1
#define ASYNC_SYS_MSG 2
#define ASYNC_EXE_CRASH 3
#define ASYNC_EXIT 4 // internal only
#define ASYNC_CAST 5 // internal only
#define ASYNC_MAXSES 6 // internal only
#define ASYNC_LOGOUT 7 // internal only
#define ASYNC_USER_DELETED 8
#define ASYNC_GROUP_RENAMED 9 // internal only
#define ASYNC_DISP_RENAMED 10 // internal only
#define ASYNC_GRP_TRANS 11 // internal only
#define ASYNC_USER_GROUP_CHANGED 12 // internal only
#define ASYNC_CMD_RANKS_CHANGED 13 // internal only
#define ASYNC_RESTART 14 // internal only
#define ASYNC_ENABLE_MOD 15 // internal only
#define ASYNC_DISABLE_MOD 16 // internal only
#define ASYNC_GROUP_UPDATED 17 // internal only
#define ASYNC_END_SESSION 18 // internal only
#define ASYNC_USER_LOGIN 19 // internal only
#define ASYNC_RESTORE_AUTH 20 // internal only
#define ASYNC_TO_PEER 21
#define ASYNC_LIMITED_CAST 22
#define ASYNC_RW_MY_INFO 23 // internal only
#define ASYNC_P2P 24
#define ASYNC_CLOSE_P2P 25 // internal only
#define ASYNC_NEW_CH_MEMBER 26
#define ASYNC_DEL_CH 27
#define ASYNC_RENAME_CH 28
#define ASYNC_CH_ACT_FLAG 29
#define ASYNC_NEW_SUB_CH 30
#define ASYNC_RM_SUB_CH 31
#define ASYNC_RENAME_SUB_CH 32
#define ASYNC_INVITED_TO_CH 33
#define ASYNC_RM_CH_MEMBER 34
#define ASYNC_INVITE_ACCEPTED 35
#define ASYNC_MEM_LEVEL_CHANGED 36
#define ASYNC_SUB_CH_LEVEL_CHG 37
#define ASYNC_ADD_RDONLY 38
#define ASYNC_RM_RDONLY 39
#define ASYNC_ADD_CMD 40
#define ASYNC_RM_CMD 41
#define ASYNC_USER_RENAMED 42
#define ASYNC_PUBLIC_AUTH 43 // internal only
```
### Async Data ###
```ASYNC_RDY (1)```
This command signals to the client that your current session is now ready to start running commands. This is usually sent after successfully setting up the tcp connection or after successfully recovering from a session crash. It can carry ```TEXT``` data that can be displayed directly to the user if needed.
```ASYNC_SYS_MSG (2)```
This command carry ```TEXT``` or ```ERR``` data that are system messages that can be directly displayed to the user of needed. It is also used to carry ```HOST_CERT``` data during the tcp connection setup and ```MY_INFO``` when local user account information has changed.
```ASYNC_EXE_CRASH (3)```
This is used to send ```ERR``` messages to the client if your session crashes or fails to setup a command executor at the host side for any reason.
```ASYNC_EXIT (4)```
This is an internal async command that doesn't carry any data. This will cause the host to stop listing for clients, close all sessions and then close the main process.
```ASYNC_CAST (5)```
This is an internal only command that carries a 54byte open sub-channels list and an embedded mrci frame that can then be sent to clients that have any of the matching open sub-channels. It drops that sub-channel list before arriving at the client so it will apppear like a regular mrci frame of any data type.
```ASYNC_MAXSES (6)```
Internal only async command that is used by internal commands to send a ```BYTES``` frame to the main process to update the maximum amount the concurrent sessions for the host. The data itself is actually a 32bit unsigned int.
```ASYNC_LOGOUT (7)```
This internal only async command doesn't carry any data. This is just used to notify the main process that the user has logged out and should not attempt to restore authentication in case of a session crash. This doesn't actually do the logout.
```ASYNC_USER_DELETED (8)```
This async command carry ```TEXT``` data that is the user name of the user account that was deleted from the host database. All sessions that get this command must read and match this to the user name that is currently logged in. If the user name matches, the session must logout and forward this to the client since the user account no longer exists.
```ASYNC_GROUP_RENAMED (9)```
Internal only async command that carry ```TEXT``` command line arguments to notify all sessions that the a host group name has changed. Example: ```-src "old_group_name" -dst "new_group_name"```. All sessions that have matching current group names to ```old_group_name``` must update that group name to ```new_group_name``` and also send a ```ASYNC_SYS_MSG``` to the client containing an updated ```MY_INFO```.
```ASYNC_DISP_RENAMED (10)```
Internal only async command that carry ```TEXT``` command line arguments to notify all sessions that a user has changed the display name. Example: ```-name "new_display_name" -user "user_name"```. All sessions that have matching current user names to ```user_name``` must update the display name to ```new_display_name``` and also send a ```ASYNC_SYS_MSG``` to the client containing an updated ```MY_INFO```.
```ASYNC_GRP_TRANS (11)```
Internal only async command that carry the same ```TEXT``` command line arguments as ```ASYNC_GROUP_RENAMED``` to notify all sessions that the all users currently in the group given in ```-src``` must be updated to the group given in ```-dst```. This triggers a ```ASYNC_SYS_MSG``` to send an updated ```MY_INFO``` and the session will load all the commands that the user now have access to and remove any commands that lost access due to the group change.
```ASYNC_USER_GROUP_CHANGED (12)```
This is an internal only async command that carry ```TEXT``` command line arguments to notify all sessions that a user's group was changed to another group. example: ```-user "user_name" -group "new_group"```. All sessions the have the matching user name need to update it's group to ```new_group```, send a ```ASYNC_SYS_MSG``` with an updated ```MY_INFO``` and load all the commands that the user now have access to and remove any commands that lost access due to the group change.
```ASYNC_CMD_RANKS_CHANGED (13)```
This internal only async commmand doesn't carry any data. Instead, it notifies all sessions that the assigned command ranks have changed. This will cause all sessions to re-check the currently loaded commands and determine if it needs to remove any commands that the user no longer have access to and add any commands that the user may have gained access to.
```ASYNC_RESTART (14)```
This internal only async commmand doesn't carry any data. This will cause the host to stop listing for clients, close all sessions, reload the host settings and start listening for clients again.
```ASYNC_ENABLE_MOD (15)```
This internal only async commmand carry ```TEXT``` of a module name. All sessions that receive this will then load the requested module.
```ASYNC_DISABLE_MOD (16)```
This internal only async commmand carry ```TEXT``` of a module name. All sessions that receive this will delete all commands associated with this module and then unload it.
```ASYNC_GROUP_UPDATED (17)```
This is an internal only command that carry ```TEXT``` command line arguments to notify all sessions that a group's host rank has changed. Example: ```-name "group_name" -rank 2```. All sessions that have matching group names to ```group_name``` will need to update the host rank to ```2```. When the session's host rank is adjusted this way, the session will need to re-check the currently loaded commands and determine if it needs to remove any commands that the user no longer have access to and add any commands that the user may have gained access to.
```ASYNC_END_SESSION (18)```
This internal only async commmand doesn't carry any data. It will signal the session to close at the host side.
```ASYNC_USER_LOGIN (19)```
This is an internal only command that carry ```PRIV_IPC``` data containing the 256bit Keccak hash user id of the user that has successfully logged in. This is used by the command executor to notify the main process of the current user associated with the current session. The main process will use this information to restore the user authorization in case of a session crash.
```ASYNC_RESTORE_AUTH (20)```
This is an internal only command that carry ```PRIV_IPC``` data containing the 256bit Keccak hash user id of a user that has successfully logged in with the current session. It is used by the main process to tell the command executor in the slave process to authorize the user without a password. This is only used when attempting to restore the session from a crash and a user was logged in at the time.
```ASYNC_TO_PEER (21)```
This is an async command that carry an embedded mrci frame directly to/from peer sessions without any restrictions. It is prepended with the 224bit sha3 hash of the target session id; however, it drops that session id before arriving at the client so it will apppear as a regular mrci frame of any data type. Users should not be given direct access to this for security reasons.
```ASYNC_LIMITED_CAST (22)```
This operate exactly like ```ASYNC_CAST``` except only sessions with active update sub-channels will respond to it.
```ASYNC_RW_MY_INFO (23)```
This internal only async command carry ```TEXT``` data of the user name to tell all sessions that have the matching user name to send an update ```MY_INFO``` to the client. This is useful for when a host admin force updates user information of other lesser privileged users to make their clients aware of the changes.
```ASYNC_P2P (24)```
This async command carry an embedded mrci frame directly to/from peer sessions following the p2p negotiation process as described in [Type_IDs](Type_IDs.md), at the P2P specific data types. It prepends the 224bit sha3 hash of the destination session id and source session id; however, it drops the destination id and source id for just the P2P specific data types before arriving at the client. For all other data types (if a p2p connection is estabished), the source id is prepend-moved to the payload of the mrci frame before arriving at the client.
```ASYNC_CLOSE_P2P (25)```
This internal only async command carry a 224bit sha3 hash session id of a session that is about to close. This notifies all sessions that have matching hashes in p2p accepted and p2p pending list to remove them now. It also triggers a ```ASYNC_P2P``` to send a ```P2P_CLOSE``` for the session id in question so the clients can also be made aware of this.
```ASYNC_NEW_CH_MEMBER (26)```
```TEXT``` command line arguments when a new channel is created and the user that created it is added as the channel owner. Example: ```-user "user_name" -ch_name "new_channel" -ch_id 334 -level 1```.
```ASYNC_DEL_CH (27)```
```TEXT``` command line arguments when a channel is deleted. Example: ```-ch_name "channel_name" -ch_id 426```. The host will automatically close all sub-channels related to this channel for all sessions that have them open.
```ASYNC_RENAME_CH (28)```
```TEXT``` command line arguments when a channel is renamed. Example: ```-ch_name "old_name" -new_name "new_name"```.
```ASYNC_CH_ACT_FLAG (29)```
```TEXT``` command line arguments when sub-channel's active update flag has been updated. Example: ```-ch_name "channel_name" -sub_name "sub_channel" -state 1```. (```-state``` 1 is true or ```-state``` 0 is false).
```ASYNC_NEW_SUB_CH (30)```
```TEXT``` command line arguments when a new sub-channel is created. Example: ```-ch_name "channel_name" -sub_name "new_sub_channel" -ch_id 987 -sub_id 5 -level 2```.
```ASYNC_RM_SUB_CH (31)```
```TEXT``` command line arguments when a sub-channel is deleted. Example: ```-ch_name "channel_name" -sub_name "sub_channel" -ch_id 987 -sub_id 5```. The host will automatically close this sub-channel for sessions that currently have it open.
```ASYNC_RENAME_SUB_CH (32)```
```TEXT``` command line arguments when a sub-channel is renamed. Example: ```-ch_name "channel_name" -sub_name "sub_channel" -new_name "new_sub_name"```.
```ASYNC_INVITED_TO_CH (33)```
```TEXT``` command line arguments when a new user is invited to join a channel. Example: ```-ch_name "channel_name" -user "user_name"```.
```ASYNC_RM_CH_MEMBER (34)```
```TEXT``` command line arguments when a user is kicked from a channel, uninvited or has left the channel. Example: ```-ch_name "channel_name" -user "user_name" -ch_id 746```.
```ASYNC_INVITE_ACCEPTED (35)```
```TEXT``` command line arguments when a user that was previously invite to join the channel has accepted the invite and should now be considered full member of the channel starting off at level ```REGULAR```. Example: ```-ch_name "channel_name" -user "user_name"```.
```ASYNC_MEM_LEVEL_CHANGED (36)```
```TEXT``` command line arguments when a channel member's privilege level is changed. Example: ```-ch_name "channel_name" -user "user_name" -ch_id 774 -level 2```. The host automatically closes all sub-channels related to the channel for the affected user. It will be will up to the client to re-open the sub-channel(s) if the user still have access to it/them.
```ASYNC_SUB_CH_LEVEL_CHG (37)```
```TEXT``` command line arguments when a sub-channel's lowest level of access is changed. Example: ```-ch_name "channel_name" -sub_name "sub_channel" -level 3 -ch_id 645 -sub_id 5```. The host will automatically close this sub-channel for sessions that currently have it open. It will be up to the client to reopen it if the current user still have access to it.
```ASYNC_ADD_RDONLY (38)```
```TEXT``` command line arguments when a read only flag is added to a sub-channel's access level. Example: ```-ch_name "channel_name" -sub_id 5 -level 4```. The host will automatically close this sub-channel for sessions that currently have it open. It will be up to the client to reopen it if the current user still have access to it.
```ASYNC_RM_RDONLY (39)```
```TEXT``` command line arguments when a read only flag is removed from a sub-channel's access level. Example: ```-ch_name "channel_name" -sub_id 5 -level 4```. The host will automatically close this sub-channel for sessions that currently have it open. It will be up to the client to reopen it if the current user still have access to it.
```ASYNC_ADD_CMD (40)```
This async command carry ```NEW_CMD``` when the session's command executor loads a new command object and wants to notify the client of it.
```ASYNC_RM_CMD (41)```
This async command carry ```CMD_ID``` when the session's command executor deletes a command object and wants to notify the client of it.
```ASYNC_USER_RENAMED (42)```
```TEXT``` command line arguments when a user changes it's user name. Example: ```-old "old_user_name" -new_name "new_user_name"```.
```ASYNC_PUBLIC_AUTH (43)```
This internal only async commmand doesn't carry any data. It just tells the command executor to load or reload commands without an authorized user so only public commands will be available until the client authorizes into a user account. This is used by the host when starting a session for the first time or if restoring the session after a crash and no user was logged in at the time.

View File

@ -4,63 +4,61 @@ Cmdr is a command line terminal emulator client for MRCI host using text input/o
### Usage ###
Cmdr has it's own user interface and terminal display so there is no command line switches to pass on start up. Instead, all commands are parsed and processed within it's own terminal interface. Any commands not seen as an internal command for the client itself is passed through to the MRCI host if connected. Users have the option to start the command line with a '#' to bypass any of the client's internal commands in cases when there is overlap with a host command.
Cmdr have it's own terminal display so there is no command line switches to pass on start up. Instead, all commands are parsed and processed within it's own terminal interface. Any commands not seen as an internal command for the client itself is passed through to the MRCI host if connected. The client will add a numeric identifer to the end of all host command names that clash with the client commands so there is no chance a client and host command can conflict with each other.
### Versioning System ###
This application uses the typical 3 number versioning system: [major].[minor].[patch]
* Major - this indicates any major changes to the code of the application that renders versions of different majors incompatible with each other.
* Minor - this indicates only minor changes to the code that may require a few conditional blocks to maintain compatibility.
* Patch - this indicates changes that won't require any behaviour changes at all to maintain compatibility.
* Major - this indicates any major changes to the application or changes that render user data of different majors incompatible.
* Minor - this indicates changes to the code that still maintains compatibility with existing user data.
* Patch - this indicates changes that won't require any behaviour changes at all.
Any increments to major resets minor and patch to 0. The MRCI host/protocol also uses the 3 number version system seperate from this client's own version number.
Any increments to major resets minor and patch to 0.
### The Protocol ###
This application being a MRCI client uses the MRCI protocol to transport data to and from the host using TCP in a binary data format called MRCI frames. In general, local connections with the host are not encrypted but all connects outside of the host must be encrypted using TLS/SSL.
This application being a MRCI client uses the MRCI protocol to transport data to and from the host using TCP in a binary data format called MRCI frames. In general, local connections with the host are not encrypted but all connections outside of the host must be encrypted using TLS/SSL (including the local network).
Before any MRCI frames can be transported, the host need to be made aware of the version of the host that this application supports and this application also needs to be made aware of the host current version. This is done by sending a fixed length header containing information about this application to the host when it successfully connects. the host will then reply with it's own fixed length host header.
Before any MRCI frames can be transported, both the host and client need basic information about each other. This is done by having the client send a fixed length client header when it successfully connects to the host and the host will reply with it's own fixed length host header, described below.
### MRCI Frame Format ###
```
Format:
[type_id][cmd_id][branch_id][data_len][payload]
[type_id][cmd_id][data_len][payload]
type_id - 1byte - 8bit little endian integer type id of the payload.
cmd_id - 2bytes - 16bit little endian integer command object id.
data_len - 3bytes - 24bit little endian integer size of the payload.
payload - variable - the actual data to be processed.
type_id - 1byte - 8bit little endian integer type id of the payload.
cmd_id - 2bytes - 16bit little endian integer command id.
branch_id - 2bytes - 16bit little endian integer branch id.
data_len - 3bytes - 24bit little endian integer size of the payload.
payload - variable - the actual data to be processed.
```
A full description of the type id's can be found in the [Type_IDs.md](Type_IDs.md) document.
notes:
* A full description of the type id's can be found in the [Type_IDs.md](type_ids.md) document.
* This client call commands by name but the host will assign unique command ids for it's command names and will require the client to use the command id on calling. To track this command id to command name relationship for the host commands, this client will rely on the [ASYNC_ADD_CMD](async.md) and [ASYNC_RM_CMD](async.md) async commands.
* The branch id is an id that can be used to run muliple instances of the same command on the host. Commands sent by a certain branch id will result in data sent back with that same branch id. For now, this client does not do branching; instead all commands sent to the host using branch id 0 only.
### Client Header (This Application) ###
```
Format:
[tag][major][minor][patch][appName][coName]
[tag][appName][coName]
tag - 4bytes - 0x4D, 0x52, 0x43, 0x49 (MRCI)
major - 2bytes - 16bit little endian unsigned int
minor - 2bytes - 16bit little endian unsigned int
patch - 2bytes - 16bit little endian unsigned int
appName - 128bytes - UTF16LE string (padded with spaces)
coName - 272bytes - UTF16LE string (padded with spaces)
appName - 134bytes - UTF16LE string (padded with 0x00)
coName - 272bytes - UTF16LE string (padded with 0x00)
```
notes:
* The **tag** is just a fixed ascii string "MRCI" that indicates to the host that the client is indeed attempting to use the MRCI protocol.
* **major**, **minor**, **patch** is the version number of the MRCI protocol that the client understands. Do not send the client's own version number.
* The **appName** is the name of the client application that is connected to the host. It can also contain the client's app version if needed because it doesn't follow any particular standard.
* The **coName** is the common name of a SSL certificate that is currently installed in the host. Depending on how the host is configured, it can contain more than one installed SSL cert so coName can be used by clients as a way to request which one of the certs to use during the SSL handshake.
* The **coName** is the common name of a SSL certificate that is currently installed in the host. Depending on how the host is configured, it can contain more than one installed SSL cert so coName can be used by clients as a way to request which one of the SSL certs to use during the SSL handshake.
### Host Header ###
@ -80,16 +78,17 @@ notes:
* **reply** is a numeric value that the host returns in it's header to communicate to the client the result of it's evaluation of the client's header.
* reply = 1, means the client version is acceptable and it does not need to take any further action.
* reply = 2, means the client version is acceptable but the host will now send it's Pem formatted SSL cert data in a ```HOST_CERT``` mrci frame just after sending it's header. After receiving the cert, the client will then need to send a STARTTLS signal using this cert.
* reply = 3, means the client version is not supported by the host and the session will end shortly.
* reply = 4, means the host was unable to load the SSL cert associated with the common name sent by the client. The session will auto close at this point.
* reply = 1, means the client is acceptable and it does not need to take any further action.
* reply = 2, means the client is acceptable but the host will now send it's Pem formatted SSL cert data in a ```HOST_CERT``` mrci frame just after sending it's header. After receiving the cert, the client will then need to send a STARTTLS signal using this cert.
* reply = 4, means the host was unable to find the SSL cert associated with the common name sent by the client. The session will auto close at this point.
* **sesId** is the session id. It is a unique 224bit sha3 hash generated against the current date of session creation (down to the msec) and the machine id. This can be used to uniquely identify the current session and past sessions.
* **major**, **minor**, **path** these 3 numeric values are the host version number that also use a 3 number versioning system. This can be used by the client to setup backwards compatibility or determine of if supports the host at all. If not supported, the client can simply disconnect form the host.
### ASync Commands ###
* **sesId** is the session id. It is a unique 224bit sha3 hash that can be used by the host and client to uniquely identify the current session or past sessions.
Async commands are host only 'virtual commands' that this application can encounter at any time while connected to the host. More information about this can be found in the [Async.md](Async.md) document. This application does act on some of the data carried by the async commands but not all of them.
### Async Commands ###
Async commands are 'virtual commands' that this application can encounter at any time while connected to the host. More information about this can be found in the [Async.md](Async.md) document. This application does act on some of the data carried by the async commands but not all of them.
### Development Setup ###

314
docs/async.md Normal file
View File

@ -0,0 +1,314 @@
### 5.1 Async Commands ###
An async command is a virtual command that the host can use to send data to the client at any time while connected to the host. As the name implies, the occurance of a client receiving data from an async command is not always the result of running a regular command in the current session. This can occur for example when information in your account is changed by another client connected to the host; your client would not know about this change until an async command is sent notify it of the change. These commands can be called directly or indirectly by a module and are considered "virtual" commands because there is no defined objects attached to them. Instead, async commands are best identified by command id values 1-255.
Async commands are not only used to send data to the client but also used internally within the host to help objects operating in different processes to communicate with each other. Some async commands in fact are considered internal only because the client should never see any data come from them at anytime. There is also data flow contriants for aysnc commands, meaning some gets blocked if sent from the module or has no effect if sent with the unexpected [IPC](type_ids.md) type id. The list below shows the various data flow contriants each of these async commands have.
Here is a describtion of what the keywords in the list mean:
```
client - this means the async command id will be used to forward
data of any type to client if needed.
internal - this means the async command will be responded by the
session object but the data will not be forwarded to the
client or converted to an entirely different async
command before sending to the client.
public - this means the session objects will respond to this async
command if sent with PUB_IPC or PUB_IPC_WITH_FEEDBACK
from the module.
private - this means only the session object that has a direct IPC
connection with the module that sends the async command
via PRIV_IPC will respond to it.
none - this means none of the session objects will respond to
this async command no matter which of the IPC data types
are used. it is resevered for just the session object to
send to the client.
retricted - this means the session object will actively block this
async command from being sent from the module (any mode).
```
```
enum AsyncCommands : quint16
{
ASYNC_RDY = 1, // client | none
ASYNC_SYS_MSG = 2, // client | none
ASYNC_EXIT = 3, // internal | private
ASYNC_CAST = 4, // client | public
ASYNC_MAXSES = 5, // internal | private
ASYNC_LOGOUT = 6, // internal | private
ASYNC_USER_DELETED = 7, // client | public
ASYNC_DISP_RENAMED = 8, // internal | public
ASYNC_USER_RANK_CHANGED = 9, // internal | public
ASYNC_CMD_RANKS_CHANGED = 10, // internal | public
ASYNC_RESTART = 11, // internal | private
ASYNC_ENABLE_MOD = 12, // internal | public
ASYNC_DISABLE_MOD = 13, // internal | public
ASYNC_END_SESSION = 14, // internal | private
ASYNC_USER_LOGIN = 15, // internal | private
ASYNC_TO_PEER = 16, // client | public | retricted
ASYNC_LIMITED_CAST = 17, // client | public
ASYNC_RW_MY_INFO = 18, // internal | public
ASYNC_P2P = 19, // client | public
ASYNC_CLOSE_P2P = 20, // internal | public
ASYNC_NEW_CH_MEMBER = 21, // client | public
ASYNC_DEL_CH = 22, // client | public
ASYNC_RENAME_CH = 23, // client | public
ASYNC_CH_ACT_FLAG = 24, // internal | public
ASYNC_NEW_SUB_CH = 25, // client | public
ASYNC_RM_SUB_CH = 26, // client | public
ASYNC_RENAME_SUB_CH = 27, // client | public
ASYNC_INVITED_TO_CH = 28, // client | public
ASYNC_RM_CH_MEMBER = 29, // client | public
ASYNC_INVITE_ACCEPTED = 30, // client | public
ASYNC_MEM_LEVEL_CHANGED = 31, // client | public
ASYNC_SUB_CH_LEVEL_CHG = 32, // client | public
ASYNC_ADD_RDONLY = 33, // client | public
ASYNC_RM_RDONLY = 34, // client | public
ASYNC_ADD_CMD = 35, // client | none
ASYNC_RM_CMD = 36, // client | none
ASYNC_USER_RENAMED = 37, // internal | public
ASYNC_PING_PEERS = 38, // internal | private
ASYNC_OPEN_SUBCH = 39, // internal | private
ASYNC_CLOSE_SUBCH = 40, // internal | private
ASYNC_UPDATE_BANS = 41, // internal | private
ASYNC_KEEP_ALIVE = 42, // internal | private
ASYNC_SET_DIR = 43, // internal | private
ASYNC_DEBUG_TEXT = 44 // internal | private
};
```
### 5.2 Async Data ###
```ASYNC_RDY (1)```
This command signals to the client that your current session is now ready to start running commands. This is sent to the client after successfully setting up the tcp connection ([protocol](protocol.md)). It can carry [TEXT](type_ids.md) data that can be displayed directly to the user if needed.
```ASYNC_SYS_MSG (2)```
This command carry [TEXT](type_ids.md) or [ERR](type_ids.md) data that are system messages that can be directly displayed to the user of needed. It is also used to carry [HOST_CERT](type_ids.md) data during the tcp connection setup and MY_INFO when local user account information has changed.
```ASYNC_EXIT (3)```
This is an internal async command that doesn't carry any data. It is used to send a ```closeServer()``` signal to the TCPServer object in the main process. This will cause it stop listing for clients, close all sessions and then close the main process.
```ASYNC_CAST (4)```
This is an internal only command that carries a 54byte open sub-channels list described in section 5.3 and an embedded frame that can then be sent to clients that have any of the matching open sub-channels. It drops that sub-channel list before arriving at the client so it will apppear like a regular [mrci frame](protocol.md) of any data type.
```
from_module: [54bytes(sub_channel_list)][1byte(type_id)][rest-of-bytes(pay_load)]
to_client: [type_id][cmd_id(4)][branch_id(0)][size_of_payload][payload]
```
```ASYNC_MAXSES (5)```
Internal only async command can used by modules to send a 4byte unsigned 32bit int to the session object to change the maximum amount the concurrent sessions for the TCPServer object.
```ASYNC_LOGOUT (6)```
This internal only async command doesn't carry any data. This can be used by modules to tell the session object to logout the current user.
```ASYNC_USER_DELETED (7)```
This command carries a 32byte user id hash of the user account that was delete from the host database. All sessions that are currently logged into this account will force logout.
```ASYNC_DISP_RENAMED (8)```
This command carries a combination of the 32byte user id hash and the 64byte new display name (UTF-16LE, padded with 0x00) of the user account that changed it's display name. This will trigger all sessions that are currently logged into this account to send an updated [MY_INFO](type_ids.md) frame via ASYNC_SYS_MSG to the clients.
```
from_module: [32bytes(user_id)][64bytes(new_disp_name)]
to_client: [type_id(9)][cmd_id(2)][branch_id(0)][size_of_payload][payload(MY_INFO)]
```
```ASYNC_USER_RANK_CHANGED (9)```
This command carries a combination of the 32byte user id hash and 4byte new rank (32bit uint_le) of the user account that changed it's host rank. This will trigger all sessions that are currently logged into this account to send an updated [MY_INFO](type_ids.md) frame via ASYNC_SYS_MSG to the clients.
```
from_module: [32bytes(userId)][4bytes(newRank)]
to_client: [type_id(9)][cmd_id(2)][branch_id(0)][size_of_payload][payload(MY_INFO)]
```
```ASYNC_CMD_RANKS_CHANGED (10)```
This internal only command doesn't carry any data, it just triggers all sessions to re-load runable commands.
```ASYNC_RESTART (11)```
This internal only async commmand doesn't carry any data. It is used to send a ```resServer()``` signal to the TCPServer object in the main process. This will cause it stop listing for clients, close all sessions, reload the host settings and start listening for clients again.
```ASYNC_ENABLE_MOD (12)```
This internal only async commmand that carry a [TEXT](type_ids.md) path to a module executable. All session objects that receive this will then attempt to load the module.
```ASYNC_DISABLE_MOD (13)```
This is the other half to ASYNC_ENABLE_MOD. All session objects that receive this will remove and terminate all commands associated with this module.
```ASYNC_END_SESSION (14)```
This internal only async commmand doesn't carry any data. It is used by modules to logout the current user.
```ASYNC_USER_LOGIN (15)```
This command carries a 32byte user id hash. This can be used by modules to tell the session object to login as this user.
```ASYNC_TO_PEER (16)```
This is an async command that carry an embedded data frame directly to/from peer sessions without any restrictions. It is prepended with the 224bit hash of the target session id; however, it drops that session id before arriving at the client so it will apppear as a regular mrci frame of any data type. Modules do not have direct access to this, the host internal objects will handle this.
```
from_module: [28bytes(sessionId)][1byte(typeId)][rest-of-bytes(payload)]
to_client: [type_id][cmd_id(16)][branch_id(0)][size_of_payload][payload]
```
```ASYNC_LIMITED_CAST (17)```
This operate exactly like ASYNC_CAST except only sessions with active update sub-channels open will respond to it.
```ASYNC_RW_MY_INFO (18)```
This command carries a 32byte user id hash of a user account who's data has been overwritten by another user. This will trigger all sessions that are currently logged into this account to send an updated [MY_INFO](type_ids.md) frame via ASYNC_SYS_MSG to the clients.
```
from_module: [32bytes(user_id)]
to_client: [type_id(9)][cmd_id(2)][branch_id(0)][size_of_payload][payload(MY_INFO)]
```
```ASYNC_P2P (19)```
This async command carry an embedded data frames directly to/from peer sessions following the p2p negotiation process as described in [Type_IDs](type_ids.md), section 3.2 at the P2P specific data types. It prepends the 28byte hash of the destination session id and source session id; however, it drops the destination id and source id for just the P2P specific data types before arriving at the client. For all other data types (if a p2p connection is estabished), the source id is prepend-moved to the payload of the mrci frame before arriving at the client.
```
from_module: [28bytes(dst_sessionId)][28bytes(src_sessionId)][1byte(typeId)][rest-of-bytes(payload)]
to_client (P2P): [type_id][cmd_id(19)][branch_id(0)][size_of_payload][payload(src_sessionId)]
to_client: [type_id][cmd_id(19)][branch_id(0)][size_of_payload + 28][src_sessionId + payload]
```
```ASYNC_CLOSE_P2P (20)```
This internal only async command carry the 28byte hash session id of a session that is about to close. All sessions that receive this will close the p2p connection with this session if such a connection exists. If such a connection did exist, the session will convert the frame into a P2P_CLOSE mrci frame and send it to the client via ASYNC_P2P.
```
from_module: [28bytes(src_sessionId)]
to_client: [type_id(12)][cmd_id(19)][branch_id(0)][size_of_payload][payload(src_sessionId)]
```
```ASYNC_NEW_CH_MEMBER (21)```
This async command carries a [CH_MEMBER_INFO](type_ids.md) frame containing public information about a user that has been added to a channel member list. All sessions that are logged in as a member of the channel will forward the frame (unchanged) to the clients.
```
to_client: [type_id(25)][cmd_id(21)][branch_id(0)][size_of_payload][payload(CH_MEMBER_INFO)]
```
```ASYNC_DEL_CH (22)```
This async command carries a [CH_ID](type_ids.md) frame of a channel that has been deleted. All sessions that receive will forward the frame to the client (unchanged) and close all sub-channels related to the channel.
```
to_client: [type_id(26)][cmd_id(22)][branch_id(0)][size_of_payload][payload(CH_ID)]
```
```ASYNC_RENAME_CH (23)```
This async command carries a combination of the channel id and a 16bit null terminated UTF-16LE string containing the new name of the channel that has been renamed. This command desn't use any of the standard frame formats so it sends a [BYTES](type_ids.md) frame to the client.
```
to_client: [type_id(14)][cmd_id(28)][branch_id(0)][size_of_payload][payload(channel_id + new_channel_name)]
```
```ASYNC_CH_ACT_FLAG (24)```
This internal only async command carries a combination of the channel id and and sub-channel id to tell sessions that the sub-channel has changed it's active update flag. All sessions that have this sub-channel open will re-check this flag and determine if it should continue active updating.
```
format: [8bytes(64bit_ch_id)][1byte(8bit_sub_ch_id)]
```
```ASYNC_NEW_SUB_CH (25)```
This async command carries a comination of the channel id, sub-channel id, access level and a 16bit null terminated UTF-16LE string containing the sub-channel name when a new sub-channel is created. All sessions that are logged in as a member of the channel forwards the data to the clients as a [BYTES](type_ids.md) frame.
```
to_client: [type_id(14)][cmd_id(25)][branch_id(0)][8bytes(64bit_ch_id)][1byte(8bit_sub_ch_id)]
[1byte(8bit_access_level)][16bit_null_term_sub-channel_name]
```
```ASYNC_RM_SUB_CH (26)```
This is the other half to ASYNC_NEW_SUB_CH. It carries just a combination of the channel id and and sub-channel id to tell all sessions that the sub-channel was deleted. All sessions that have the sub-channel open will close it.
```
to_client: [type_id(14)][cmd_id(26)][branch_id(0)][8bytes(64bit_ch_id)][1byte(8bit_sub_ch_id)]
```
```ASYNC_RENAME_SUB_CH (27)```
This async command carries a combination of the channel id, sub-channel id, access level and a 16bit null terminated UTF-16LE string containing the new sub-channel name. All sessions that are logged in as a member of the channel forwards the data to the clients as a [BYTES](type_ids.md) frame.
```
to_client: [type_id(14)][cmd_id(27)][branch_id(0)][8bytes(64bit_ch_id)][1byte(8bit_sub_ch_id)]
[16bit_null_term_sub-channel_name]
```
```ASYNC_INVITED_TO_CH (28)```
This async command carries a [CH_MEMBER_INFO](type_ids.md) frame containing public information about a user that has been invited to a channel. All sessions that are logged in as a member of the channel will forward the frame (unchanged) to the clients.
```
to_client: [type_id(25)][cmd_id(28)][branch_id(0)][size_of_payload][payload(CH_MEMBER_INFO)]
```
```ASYNC_RM_CH_MEMBER (29)```
This async command carries a combination of the channel id and the 32byte user id of the channel member that has left or kicked from the channel member list. All sessions that are logged in as a member of the channel forwards the data to the clients as a [BYTES](type_ids.md) frame.
```
from_module: [8bytes(chId)][32bytes(userId)]
to_client: [type_id(14)][cmd_id(29)][branch_id(0)][size_of_payload][payload(64bit_ch_id + 256bit_user_id)]
```
```ASYNC_INVITE_ACCEPTED (30)```
This async command carries a [CH_MEMBER_INFO](type_ids.md) frame containing public information about a user that has accepted an invite to a channel. All sessions that are logged in as a member of the channel will forward the frame (unchanged) to the clients.
```
to_client: [type_id(25)][cmd_id(30)][branch_id(0)][size_of_payload][payload(CH_MEMBER_INFO)]
```
```ASYNC_MEM_LEVEL_CHANGED (31)```
This async command carries a combination of the channel id, new level and the 32byte user id of the channel member that changed it's level of privilege in the channel. All sessions that are logged in as a member of the channel forwards the data to the clients as a [BYTES](type_ids.md) frame. All sessions that are currently logged in as this user also closes all sub-channels related to the channel.
```
from_module: [8bytes(chId)][32bytes(userId)][1byte(newLevel)]
to_client: [type_id(14)][cmd_id(31)][branch_id(0)][size_of_payload][payload(64bit_ch_id + 256bit_user_id + 8bit_new_level)]
```
```ASYNC_SUB_CH_LEVEL_CHG (32)```
This async command carries a combination of the channel id and sub-channel id of a sub-channel that has changed it's level of access. All sessions that have this sub-channel open will close the sub-channel and/or all sessions that are logged in as a member of the channel forwards the data to the clients as a [BYTES](type_ids.md) frame.
```
from_module: [8bytes(chId)][1byte(subId)]
to_client: [type_id(14)][cmd_id(32)][branch_id(0)][size_of_payload][payload(64bit_ch_id + 8bit_sub_id)]
```
```ASYNC_ADD_RDONLY (33)```
This async command carries exactly the same data as ASYNC_SUB_CH_LEVEL_CHG and has the same effect on the session sub-channels except it tells the clients that a read-only flag was added to the sub-channel.
```ASYNC_RM_RDONLY (34)```
This is the other half to ASYNC_ADD_RDONLY except if tells the clients that a read-only was removed from the sub-channel.
```ASYNC_ADD_CMD (35)```
This async command is used indirectly by modules to send [NEW_CMD](type_ids.md) frames to the session object. The session object will decide which frame gets forwarded to the client and which doesn't. Both this or ASYNC_RM_CMD sent directly to the session object does nothing. Instead, for modules called in listing mode [2.3](modules.md) (public_cmds, exempt_cmds or user_cmds); the session will detect the [NEW_CMD](type_ids.md) frames from the module and forward them to the client with this async command.
```
to_client: [type_id(16)][cmd_id(35)][branch_id(0)][size_of_payload][payload(NEW_CMD)]
```
```ASYNC_RM_CMD (36)```
This is the other half to ASYNC_ADD_CMD expect it is used to tell the client that the command was removed from the session and it carries a [CMD_ID](type_ids.md) frame instead.
```
to_client: [type_id(17)][cmd_id(36)][branch_id(0)][size_of_payload][payload(CMD_ID)]
```
```ASYNC_USER_RENAMED (37)```
This command carries a combination of the 32byte user id hash and the 48byte new user name (UTF-16LE, padded with 0x00) of the user account that changed it's user name. This will trigger all sessions that are currently logged into this account to send an updated [MY_INFO](type_ids.md) frame via ASYNC_SYS_MSG to the clients.
```
from_module: [32bytes(user_id)][48bytes(new_user_name)]
to_client: [type_id(9)][cmd_id(2)][branch_id(0)][size_of_payload][payload(MY_INFO)]
```
```ASYNC_PING_PEERS (38)```
This internal only async commmand doesn't carry any data. It can be used by modules to request all peer sessions that have matching active update sub-channels open to return [PEER_INFO](type_ids.md) frames via ASYNC_TO_PEER directly to the session that has sent it. The session that receives these frames will in turn forward them to the client via ASYNC_LIMITED_CAST.
```
to_client: [type_id(8)][cmd_id(17)][branch_id(0)][size_of_payload][payload(PEER_INFO)]
```
```ASYNC_OPEN_SUBCH (39)```
This internal only async command carries a combination of the channel id and sub-channel id. it can be used by modules to tell the session object open a sub-channel.
```
format: [8bytes(64bit_ch_id)][1byte(8bit_sub_ch_id)]
```
```ASYNC_CLOSE_SUBCH (40)```
This is the other half to ASYNC_OPEN_SUBCH that tells the session to close the requested sub-channel.
```ASYNC_UPDATE_BANS (41)```
This internal only async command doesn't carry any data. It can be use by modules to tell the TCPServer object to update it's ip ban list cache from the database. This generally only needs to be used if the ip ban list in the database has changed in anyway.
```ASYNC_KEEP_ALIVE (42)```
This internal only async command doesn't carry any data. The session object normally sends a [KILL_CMD](type_ids.md) to the module when it detects that the module process has not sent an IPC frame in 2 minutes to terminate the module process. If desired, the module can send this async command in regular intervals to reset this 2 minute idle timer to prevent auto termination.
```ASYNC_SET_DIR (43)```
This internal only async command carries a [TEXT](type_ids.md) path that sets the working directory for the session object. All module processes started by the session will use this directory as the working directory and it is not shared among peer sessions. nothing happens if the path is invalid or does not exists.
```ASYNC_DEBUG_TEXT (44)```
This internal only async command carries a [TEXT](type_ids.md) debug message to be logged into the host debug log from the module. Modules can use this to help with debugging issues if it doesn't have direct access to the host database.
### 5.3 Open Sub-Channel List ###
An open sub-channel list is a binary data structure that string togeather up to 6 channel-sub combinations that indicate which channel id and sub id combinations are currently open. Each sub-channel are 9bytes long and the list itself maintians a fixed length of 54bytes so it is padded with 0x00 chars to maintain the fixed length (this padding can appear anywhere in 9byte increments within the list). Each sub-channel is formatted like this:
```
bytes[0-7] - 64bit LE unsigned int (channel id)
bytes[8] - 8bit LE unsigned int (sub id)
note: channel id 0 is not a valid id and the sub id cannot
be valid without a valid channel id.
```

View File

@ -1,46 +1,59 @@
### Type IDs ###
### 3.1 Type IDs ###
All mrci frames transferred to or from this application have an 8bit numeric value to indicate the type of data being passed with the binary data. The type id enum values are as follows:
```
enum TypeID
enum TypeID : quint8
{
GEN_FILE = 30,
TEXT = 31,
ERR = 32,
PRIV_TEXT = 33,
IDLE = 34,
HOST_CERT = 35,
FILE_INFO = 36,
PEER_INFO = 37,
MY_INFO = 38,
PEER_STAT = 39,
P2P_REQUEST = 40,
P2P_CLOSE = 41,
P2P_OPEN = 42,
BYTES = 43,
SESSION_ID = 44,
NEW_CMD = 45,
CMD_ID = 46,
BIG_TEXT = 47
GEN_FILE = 1,
TEXT = 2,
ERR = 3,
PRIV_TEXT = 4,
IDLE = 5,
HOST_CERT = 6,
FILE_INFO = 7,
PEER_INFO = 8,
MY_INFO = 9,
PEER_STAT = 10,
P2P_REQUEST = 11,
P2P_CLOSE = 12,
P2P_OPEN = 13,
BYTES = 14,
SESSION_ID = 15,
NEW_CMD = 16,
CMD_ID = 17,
BIG_TEXT = 18,
TERM_CMD = 19,
HOST_VER = 20,
PRIV_IPC = 21,
PUB_IPC = 22,
PUB_IPC_WITH_FEEDBACK = 23,
PING_PEERS = 24,
CH_MEMBER_INFO = 25,
CH_ID = 26,
KILL_CMD = 27,
HALT_CMD = 28,
RESUME_CMD = 29
};
```
### Type Descriptions ###
### 3.2 Type Descriptions ###
```TEXT```
This is text that can be displayed directly to the user or pass arguments for command objects to process.
This is text that can be displayed directly to the user, passed as command line arguments to be processed or used to carry text data within other data types.
format: ```[UTF-16LE_string] (no BOM)```
```GEN_FILE```
This is a file transfer type id that can be used to transfer any type of file type (music, photos, documents, etc...). It operates in its own protocol of sorts. The 1st GEN_FILE frame received by the host or client is TEXT parameters similar to what you see in terminal command lines with at least one of the arguments listed below. The next set of GEN_FILE frames received by the host or client is then the binary data that needs to be written to an open file or streamed until the limit defined in **-len** is meet.
This is a file transfer type id that can be used to transfer any file type (music, photos, documents, etc...). It operates in its own protocol of sorts. The 1st GEN_FILE frame received by the host or client is TEXT parameters similar to what you see in terminal command lines with at least one of the arguments listed below. The next set of GEN_FILE frames received by the host or client is then the binary data that needs to be written to an open file or streamed until the limit defined in -len is meet.
The host or the client can be set as the sender or receiver of the GEN_FILE binary data. Which ever is designated as the receiver by the TEXT parameters need to send an empty GEN_FILE frame to start the process. An example if this can be found at the buttom of this document.
The host or the client can be set as the sender or receiver of the GEN_FILE binary data. Which ever is designated as the receiver by the TEXT parameters need to send an empty GEN_FILE frame to start the process. An example of this can be found in section 3.3.
arguments:
* **-len (int)** | this is the integer value of the file size or amount of bytes to read/write.
* **-offset (int)** | this integer position tells where in the source or destination file to start reading/writing.
* **-offset (int)** | this is a integer position that indicates where in the source or destination file to start reading/writing.
* **-client_file** (string) | this is the file path to the source/destination file in the client's file system.
@ -48,7 +61,7 @@ arguments:
* **-single_step** | the presents of this argument tells both the client and host to operate in single step mode. single step mode causes the receiver of the binary data whether host or client to send an empty GEN_FILE frame after successfully receiving the data. this then tells the sender to send the next GEN_FILE frame containing binary data for the file and the cycle continues until len is meet. if this argument is not found, the sender can simply send all GEN_FILE data without waiting for an empty GEN_FILE from the receiver.
* **-to_host** | this argument should only come from host and it will define the client as the sender and the host as the receiver.
* **-to_host** | this argument should only come from the host and it will define the client as the sender and the host as the receiver.
* **-from_host** | opposite affect to *-to_host*. it defines the host as the sender and the client as the receiver.
@ -66,22 +79,56 @@ This id can be treated exactly like TEXT except this should tell the client to h
Also formatted exactly like TEXT but this indicates to the client that this is a large body of text that is recommended to be word wrapped when displaying to the user. It can contain line breaks so clients are also recommended to honor those line breaks.
```IDLE```
This doesn't carry any actual data, instead this indicates that the command id that sent it has finished it's task.
This doesn't carry any actual data, instead this indicates that the command id and branch id that sent it has finished it's task. Modules that send this doesn't need to terminate it's process.
```KILL_CMD```
This doesn't carry any actual data, instead can be sent by the client or session object to tell the command-branch id sent in the frame to terminate the module process. Modules that receive this need to send a IDLE frame if a command is still running and then terminate itself. The module will have 3 seconds to do this before it is force killed by the session.
```HALT_CMD```
This doesn't carry any actual data, instead can be sent by the client or session object to tell the command-branch id sent in the frame to pause/halt the current task that the command is currently running. All modules are not obligated to support this feature but highly recommended.
```RESUME_CMD```
This is the other half of HALT_CMD that tells the module to resume the command task it was running.
```HOST_CERT```
Just as the name implies, this data type is used by the host to send the host SSL certificate while setting up an SSL connection.
```HOST_VER```
This data structure carries 3 numeric values that represent the host version as described in section [1.3](protocol.md).
```
format:
1. bytes[0-1] - version major (16bit little endian uint)
2. bytes[2-3] - version minor (16bit little endian uint)
3. bytes[4-5] - version patch (16bit little endian uint)
```
```PRIV_IPC```
This is a data structure used to by modules to run async commands on the local session object only.
```
format:
1. bytes[0-1] - async command id (16bit little endian uint)
2. bytes[2-n] - payload (data to be processed by async command)
```
```PUB_IPC```
This is formatted exactly like PRIV_IPC except it is used by modules to run async commands on all peer session objects in the host while avoiding a run on the local session object.
```PUB_IPC_WITH_FEEDBACK```
This has the same functionality as PUB_IPC except it is also feedback into the local session object.
```FILE_INFO```
This is a data structure that carries information about a file system object (file,dir,link).
```
format:
1. bytes[0] - flags (8bit little endian uint)
2. bytes[1-8] - creation time in msec since Epoch UTC (64bit little endian uint)
3. bytes[9-16] - modification time in msec since Epoch UTC (64bit little endian uint)
4. bytes[17-24] - file size (64bit little endian uint)
5. bytes[25-variable] - file name (UTF16-LE string, 16bit terminated)
6. bytes[variable] - symmlink target if it is a symmlink (UTF16-LE string, 16bit terminated)
1. bytes[0] - flags (8bit little endian uint)
2. bytes[1-8] - creation time in msec since Epoch UTC (64bit little endian uint)
3. bytes[9-16] - modification time in msec since Epoch UTC (64bit little endian uint)
4. bytes[17-24] - file size (64bit little endian uint)
5. bytes[25-n] - file name (UTF16-LE string, 16bit terminated)
6. bytes[n-n] - symmlink target if it is a symmlink (UTF16-LE string, 16bit terminated)
notes:
1. 16bit terminated UTF-16LE strings are basically
@ -107,9 +154,9 @@ This carry some user account and session information about a peer client connect
format:
1. bytes[0-27] 28bytes - session id (224bit hash)
2. bytes[28-59] 32bytes - user id (256bit hash)
3. bytes[60-107] 48bytes - user name (TEXT - padded with empty spaces)
4. bytes[108-235] 128bytes - app name (TEXT - padded with empty spaces)
5. bytes[236-299] 64bytes - disp name (TEXT - padded with empty spaces)
3. bytes[60-107] 48bytes - user name (TEXT - padded with 0x00)
4. bytes[108-235] 128bytes - app name (TEXT - padded with 0x00)
5. bytes[236-299] 64bytes - disp name (TEXT - padded with 0x00)
notes:
1. the session id is unique to the peer's session connection only. it
@ -123,14 +170,17 @@ This carry some user account and session information about a peer client connect
name.
```
```PING_PEERS```
This is formatted extactly as PEER_INFO except it can be used the ASYNC_LIMITED_CAST [async](async.md) command to tell all peer sessions that receive it to send a PEER_INFO frame about you to their own clients and return PEER_INFO frames about themselves to you.
```MY_INFO```
This contains all of the information found in ```PEER_INFO``` for the local session but also includes the following:
```
format:
1. bytes[300-427] 128bytes - email (TEXT - padded with empty spaces)
2. bytes[428-451] 24bytes - group name (TEXT - padded with empty spaces)
3. bytes[452] 1byte - is email confirmed? (0x00 false, 0x01 true)
1. bytes[300-427] 128bytes - email (TEXT - padded with 0x00)
2. bytes[428-431] 4bytes - host rank (32bit unsigned int)
3. bytes[432] 1byte - is email confirmed? (0x00 false, 0x01 true)
```
```NEW_CMD```
@ -140,17 +190,19 @@ This contains information about a new command that was added to the current sess
format:
1. bytes[0-1] 2bytes - 16bit LE unsigned int (command id)
2. bytes[2] 1byte - bool (0x01 or 0x00) (handles gen file)
3. bytes[3-130] 128bytes - command name (TEXT - padded with empty spaces)
4. bytes[131-258] 128bytes - library name (TEXT - padded with empty spaces)
3. bytes[3-130] 128bytes - command name (TEXT - padded with 0x00)
4. bytes[131-258] 128bytes - library name (TEXT - padded with 0x00)
5. bytes[259-n] variable - short text (16bit null terminated)
6. bytes[n-n] variable - io text (16bit null terminated)
7. bytes[n-n] variable - long text (16bit null terminated)
notes:
1. the handles gen file flag is a single byte 0x01 to indicate true and
0x00 to indicate false. clients need to be aware of which command
handles the GEN_FILE mini protocol because it requires user input at
both ends (host and client).
2. the library name is the lirbary that the new command belongs to. this
can be used by any client that may expect or depend on certain commands
that do certain task.
2. the library name can contain the module name and/or extra informaion
the client can use to identify the library the command is a part of.
```
```CMD_ID```
@ -158,6 +210,16 @@ This type id carries a 16bit unsigned LE int representing a command id.
format: ```2bytes - 16bit LE unsigned int (command id)```
```CH_ID```
This type id carries a 64bit unsighed LE int indicating the channel id.
format: ```8bytes - 64bit LE unsigned int (channel id)```
```SESSION_ID```
This is a fixed length 28byte(224bit) sha3 hash of a client's session id connected to the host. This is unique to just the client's tcp connection with the host. This can change upon re-connection.
format: ```28bytes - session id (224bit sha3 hash)```
```PEER_STAT```
This contain status information of a peer client when the peer changes sub-channels or disconnects from the host.
@ -178,35 +240,54 @@ This contain status information of a peer client when the peer changes sub-chann
disconnected since you will no longer send/receive data with this peer.
```
```SESSION_ID```
This is a fixed length 28byte(224bit) sha3 hash of a client's session id connected to the host. This is unique to just the client's tcp connection with the host. This can change upon re-connection.
format: ```28bytes - session id (224bit sha3 hash)```
```P2P_REQUEST```
This is formatted extactly like ```PEER_INFO``` except this is allowed to be sent directly to the target peer without restriction using the **to_peer** host cmd. It will be up to the target peer to respond with a ```P2P_OPEN``` for the host to then unrestrict **to_peer** so it will then be able to send/received other TypeIDs with this peer until ```P2P_CLOSE``` is sent/received. ```P2P_CLOSE``` can also be sent to decline the request.
This is formatted extactly like PEER_INFO except it is allowed to be sent directly to a peer session without retriction via the ASYNC_P2P [async](async.md) command. It will be up to the target peer to respond with a P2P_OPEN for the session to then unrestrict ASYNC_P2P so it will then be able to send/received other TypeIDs with this peer until P2P_CLOSE is sent/received. P2P_CLOSE can also be sent to decline the request.
```P2P_OPEN```
This contains a 28byte session id hash of the peer session that you or the peer will allow direct communication with when using the **to_peer** host cmd.
This contains a 28byte session id hash of the peer session that you or the peer will allow direct communication with ASYNC_P2P.
format: ```28bytes - session id (224bit sha3 hash)```
```P2P_CLOSE```
This contains a 28byte session id hash of the peer session that you or the peer want to close direct communication with when using the **to_peer** host cmd.
This is the other half of P2P_OPEN that will close direct communication with ASYNC_P2P.
format: ```28bytes - session id (224bit sha3 hash)```
```BYTES```
This contains arbitrary binary data of any format that is not specialized for any internal objects in the host.
This contains arbitrary binary data of any format.
### GEN_FILE Example ###
```CH_MEMBER_INFO```
This contains public information about a channel member.
```
format:
1. bytes[0-7] 8bytes - channel id (64bit unsigned int)
2. bytes[8-39] 32bytes - user id (256bit hash)
3. bytes[40] 1byte - is invite? (0x00=false, 0x01=true)
4. bytes[41] 1byte - member's channel privilege level (8bit unsigned int)
5. bytes[42-n] variable - user name (TEXT - 16bit null terminated)
6. bytes[n-n] variable - display name (TEXT - 16bit null terminated)
7. bytes[n-n] variable - channel name (TEXT - 16bit null terminated)
notes:
1. a 16bit null terminated TEXT formatted string ended with 2 bytes of
(0x00) to indicate the end of the string data.
2. the member's privilege level can be any of the values discribed in
section [4.3](host_features.md).
3. is invite? indicates if this user has received an invite to join
that channel by has not accepted yet. if, accepted the user will
become a full member of the channel at the level indicated by this
data type.
```
### 3.3 GEN_FILE Example ###
Setup:
* The host has a command called *upload_file* with a command id of *768* and handles the ```GEN_FILE``` data type.
* The client has a file called */home/foo/bar.mp3* and wants to upload it to the host file */home/host/music/bar.mp3* and the client knows the file size is 512bytes.
To upload the file, the client calls command id *768* with the following TEXT arguments (must still be sent as a GEN_FILE):
To upload the file, the client calls command id *768* with the following text arguments (must still be sent as a GEN_FILE):
```-client_file "/home/foo/bar.mp3" -remote_file "/home/host/music/bar.mp3" -len 512```
The host will then return the following the text arguments to the client (also sent as a GEN_FILE):

View File

@ -215,10 +215,6 @@ void CmdLine::procCmdLine(const QString &line)
{
emit dataToHookedGenFile(toTEXT(argsLine));
}
else if (cmdName.startsWith(CMD_ESCAPE))
{
toHost(cmdName.mid(1), argsLine);
}
else if (Shared::clientCmds->contains(cmdName))
{
toLocalCmd(cmdName, argsLine);

View File

@ -61,6 +61,8 @@ signals:
void unsetUserIO(int flgs);
void setMaxLines(int value);
void termHostCmd();
void haltHostcmd();
void resumeHostCmd();
void connectToHost();
void quitApp();
void disconnectHost();

View File

@ -35,7 +35,7 @@ Quit::Quit(QObject *parent) : Command(parent)
Shared::clientCmds->insert(objectName(), this);
}
QString Quit::shortText() {return tr("close the application.");}
QString Quit::shortText() {return tr("close the client application.");}
QString Quit::ioText() {return tr("[none]/[none]");}
QString Quit::longText() {return TXT_Quit;}
@ -59,10 +59,34 @@ Term::Term(QObject *parent) : Command(parent)
Shared::clientCmds->insert(objectName(), this);
}
QString Term::shortText() {return tr("terminate the currently running command.");}
QString Term::shortText() {return tr("terminate the currently running host command.");}
QString Term::ioText() {return tr("[none]/[CMD_ID]");}
QString Term::longText() {return TXT_Term;}
Halt::Halt(QObject *parent) : Command(parent)
{
setObjectName("halt");
Shared::hookBypass->append(objectName());
Shared::clientCmds->insert(objectName(), this);
}
QString Halt::shortText() {return tr("halt/pause the currently running host command.");}
QString Halt::ioText() {return tr("[none]/[CMD_ID]");}
QString Halt::longText() {return TXT_Term;}
Resume::Resume(QObject *parent) : Command(parent)
{
setObjectName("resume");
Shared::hookBypass->append(objectName());
Shared::clientCmds->insert(objectName(), this);
}
QString Resume::shortText() {return tr("resume the currently halted/paused host command.");}
QString Resume::ioText() {return tr("[none]/[CMD_ID]");}
QString Resume::longText() {return TXT_Term;}
void Connect::dataIn(const QString &argsLine)
{
QStringList args = parseArgs(argsLine);
@ -117,14 +141,14 @@ void Connect::dataIn(const QString &argsLine)
void Quit::dataIn(const QString &argsLine)
{
Q_UNUSED(argsLine);
Q_UNUSED(argsLine)
emit quitApp();
}
void EndSession::dataIn(const QString &argsLine)
{
Q_UNUSED(argsLine);
Q_UNUSED(argsLine)
if (*Shared::connectedToHost)
{
@ -134,7 +158,21 @@ void EndSession::dataIn(const QString &argsLine)
void Term::dataIn(const QString &argsLine)
{
Q_UNUSED(argsLine);
Q_UNUSED(argsLine)
emit termHostCmd();
}
void Halt::dataIn(const QString &argsLine)
{
Q_UNUSED(argsLine)
emit haltHostcmd();
}
void Resume::dataIn(const QString &argsLine)
{
Q_UNUSED(argsLine)
emit resumeHostCmd();
}

View File

@ -96,4 +96,42 @@ public slots:
void dataIn(const QString &argsLine);
};
//----------------------------------------
class Halt : public Command
{
Q_OBJECT
public:
QString shortText();
QString ioText();
QString longText();
explicit Halt(QObject *parent = nullptr);
public slots:
void dataIn(const QString &argsLine);
};
//----------------------------------------
class Resume : public Command
{
Q_OBJECT
public:
QString shortText();
QString ioText();
QString longText();
explicit Resume(QObject *parent = nullptr);
public slots:
void dataIn(const QString &argsLine);
};
#endif // EXEC_H

123
src/cmd_objs/host_doc.cpp Normal file
View File

@ -0,0 +1,123 @@
#include "host_doc.h"
// This file is part of Cmdr.
// Cmdr is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Cmdr is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Cmdr under the LICENSE.md file. If not, see
// <http://www.gnu.org/licenses/>.
HostDoc::HostDoc(const QByteArray &import, QObject *parent) : Command(parent)
{
valid = false;
if (import.size() >= 259)
{
cmdId = static_cast<quint16>(rdInt(import.mid(0, 2)));
QString cmdName = fromTEXT(import.mid(3, 128)).toLower();
QString num;
for (int i = 1; Shared::clientCmds->contains(cmdName + num); ++i)
{
num = "_" + QString::number(i);
}
setObjectName(cmdName + num);
if (import[2] == 0x01)
{
Shared::genfileCmds->insert(cmdId, objectName());
}
quint32 offs = 259;
libTxt = fromTEXT(import.mid(131, 128));
shortTxt = readNullTermText(import, &offs);
ioTxt = readNullTermText(import, &offs);
longTxt = readNullTermText(import, &offs);
valid = true;
Shared::hostCmds->insert(cmdId, objectName());
Shared::hostDocs->insert(objectName(), this);
}
}
QString HostDoc::readNullTermText(const QByteArray &data, quint32 *offs)
{
static const QByteArray null16(2, 0x00);
QString ret;
quint32 len = static_cast<quint32>(data.size());
for (; *offs < len; *offs += 2)
{
if ((*offs + 2) < len)
{
QByteArray chr = QByteArray::fromRawData(data.data() + *offs, 2);
if (chr == null16)
{
break;
}
else
{
ret.append(fromTEXT(chr));
}
}
}
*offs += 2;
return ret;
}
QString HostDoc::shortText()
{
return shortTxt;
}
QString HostDoc::ioText()
{
return ioTxt;
}
QString HostDoc::libText()
{
return libTxt;
}
QString HostDoc::longText()
{
return longTxt;
}
bool HostDoc::isValid()
{
return valid;
}
void HostDoc::cmdRemoved(quint16 id)
{
if (id == cmdId)
{
Shared::hostCmds->remove(cmdId);
Shared::hostDocs->remove(objectName());
Shared::genfileCmds->remove(cmdId);
deleteLater();
}
}
void HostDoc::sessionEnded()
{
cmdRemoved(cmdId);
}

54
src/cmd_objs/host_doc.h Normal file
View File

@ -0,0 +1,54 @@
#ifndef HOST_DOC_H
#define HOST_DOC_H
// This file is part of Cmdr.
// Cmdr is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Cmdr is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Cmdr under the LICENSE.md file. If not, see
// <http://www.gnu.org/licenses/>.
#include "command.h"
class HostDoc : public Command
{
Q_OBJECT
private:
QString shortTxt;
QString ioTxt;
QString longTxt;
QString libTxt;
quint16 cmdId;
bool valid;
QString readNullTermText(const QByteArray &data, quint32 *offs);
public:
QString shortText();
QString ioText();
QString longText();
QString libText();
explicit HostDoc(const QByteArray &import, QObject *parent = nullptr);
bool isValid();
public slots:
void cmdRemoved(quint16 id);
void sessionEnded();
};
#endif // HOST_DOC_H

View File

@ -24,10 +24,80 @@ About::About(QObject *parent) : Command(parent)
}
void About::onStartup() {dataIn(QString());}
QString About::shortText() {return tr("display information about this MRCI client and display available client specific commands.");}
QString About::shortText() {return tr("display information about this MRCI client and display all available commands.");}
QString About::ioText() {return tr("[{cmd_name}]/[text]");}
QString About::longText() {return TXT_About;}
void About::dispInfo(Command *cmdObj)
{
QString txt;
QTextStream txtOut(&txt);
wordWrap("i/o: ", txtOut, cmdObj->ioText(), Shared::mainWidget);
txtOut << "" << endl;
wordWrap("library: ", txtOut, cmdObj->libText(), Shared::mainWidget);
txtOut << "" << endl;
wordWrap("usage: ", txtOut, cmdObj->longText(), Shared::mainWidget);
emit mainTxtOut(txt);
}
bool About::dispClientCmd(const QString &cmdName)
{
bool ret = false;
if (Shared::clientCmds->contains(cmdName))
{
dispInfo(Shared::clientCmds->value(cmdName));
ret = true;
}
return ret;
}
bool About::dispHostCmd(const QString &cmdName)
{
bool ret = false;
if (Shared::hostDocs->contains(cmdName))
{
dispInfo(Shared::hostDocs->value(cmdName));
ret = true;
}
return ret;
}
bool About::dispInfo(const QString &cmdName)
{
if (dispClientCmd(cmdName))
{
return true;
}
else if (dispHostCmd(cmdName))
{
return true;
}
else
{
return false;
}
}
void About::listCmds(QHash<QString, Command *> *cmdObjs, QTextStream &txtOut, int largestCmd)
{
QStringList cmdNames = cmdObjs->keys();
cmdNames.sort(Qt::CaseInsensitive);
for (int i = 0; i < cmdNames.size(); ++i)
{
wordWrap(cmdNames[i].leftJustified(largestCmd, ' ') + " - ", txtOut, cmdObjs->value(cmdNames[i])->shortText(), Shared::mainWidget);
}
}
void About::dataIn(const QString &argsLine)
{
QStringList args = parseArgs(argsLine);
@ -36,22 +106,7 @@ void About::dataIn(const QString &argsLine)
{
QString cmdName = args[0].toLower().trimmed();
if (Shared::clientCmds->contains(cmdName))
{
Command *obj = Shared::clientCmds->value(cmdName);
QString txt;
QTextStream txtOut(&txt);
wordWrap("i/o: ", txtOut, obj->ioText(), Shared::mainWidget);
txtOut << "" << endl;
wordWrap("library: ", txtOut, obj->libText(), Shared::mainWidget);
txtOut << "" << endl;
wordWrap("usage: ", txtOut, obj->longText(), Shared::mainWidget);
emit mainTxtOut(txt);
}
else
if (!dispInfo(cmdName))
{
emit errTxtOut("err: No such command: '" + cmdName + "'\n");
}
@ -61,18 +116,14 @@ void About::dataIn(const QString &argsLine)
QString txt;
QTextStream txtOut(&txt);
txtOut << libText() << endl << endl;
txtOut << "Based on QT " << QT_VERSION_STR << " " << 8 * QT_POINTER_SIZE << "bit" << endl << endl;
txtOut << "MRCI host support: " << MRCI_VERSION << endl << endl;
txtOut << "The program is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE" << endl;
txtOut << "WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE." << endl << endl;
txtOut << "note: In special cases when an internal client command is the same as a" << endl;
txtOut << " remote command, start your command line with a '" << CMD_ESCAPE << "' to bypass the" << endl;
txtOut << " client's parsing." << endl << endl;
txtOut << "usage: <command> <arguments>" << endl << endl;
txtOut << "<command>" << endl << endl;
txtOut << libText() << endl << endl;
txtOut << "Based on QT " << QT_VERSION_STR << " " << 8 * QT_POINTER_SIZE << "bit" << endl << endl;
txtOut << "The program is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE" << endl;
txtOut << "WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE." << endl << endl;
txtOut << "usage: <command> <arguments>" << endl << endl;
txtOut << "<command>" << endl << endl;
QStringList cmdNames = Shared::clientCmds->keys();
QStringList cmdNames = Shared::clientCmds->keys() + Shared::hostDocs->keys();
int largestCmd = 0;
for (int i = 0; i < cmdNames.size(); ++i)
@ -80,12 +131,8 @@ void About::dataIn(const QString &argsLine)
if (cmdNames[i].size() > largestCmd) largestCmd = cmdNames[i].size();
}
cmdNames.sort(Qt::CaseInsensitive);
for (int i = 0; i < cmdNames.size(); ++i)
{
wordWrap(cmdNames[i].leftJustified(largestCmd, ' ') + " - ", txtOut, Shared::clientCmds->value(cmdNames[i])->shortText(), Shared::mainWidget);
}
listCmds(Shared::clientCmds, txtOut, largestCmd);
listCmds(Shared::hostDocs, txtOut, largestCmd);
txtOut << endl << endl << "for more detailed information about a command type: about <command>" << endl << endl;

View File

@ -25,6 +25,14 @@ class About : public Command
{
Q_OBJECT
private:
void dispInfo(Command *cmdObj);
void listCmds(QHash<QString, Command *> *cmdObjs, QTextStream &txtOut, int largestCmd);
bool dispInfo(const QString &cmdName);
bool dispClientCmd(const QString &cmdName);
bool dispHostCmd(const QString &cmdName);
public:
QString shortText();

View File

@ -41,8 +41,14 @@ view the current session's parameters and connection status information (as seen
application)."
#define TXT_Term "\
this will terminate all actions currently being performed by any command; both remotely and \
locally."
this will terminate all actions currently being performed by the host command."
#define TXT_Halt "\
this will halt all actions currently being performed by the host command. this will not \
terminate the command; just stop what it is currently doing until told to resume."
#define TXT_Resume "\
this will resume the actions the previously halted host command is currently doing."
#define TXT_EndSession "\
this diconnects the client from the currently connected host. this will inturn end the session \

View File

@ -23,13 +23,13 @@ Status::Status(QObject *parent) : Command(parent)
Shared::clientCmds->insert(objectName(), this);
}
QString Status::shortText() {return tr("view the current session parameters.");}
QString Status::shortText() {return tr("view the current session parameters at the client's perspective.");}
QString Status::ioText() {return tr("[none]/[text]");}
QString Status::longText() {return TXT_SeeParams;}
void Status::dataIn(const QString &argsLine)
{
Q_UNUSED(argsLine);
Q_UNUSED(argsLine)
QString txt;
QTextStream txtOut(&txt);

View File

@ -124,9 +124,10 @@ QByteArray wrFrame(quint16 cmdId, const QByteArray &data, uchar dType)
{
QByteArray typeBa = wrInt(dType, 8);
QByteArray cmdBa = wrInt(cmdId, 16);
QByteArray sizeBa = wrInt(data.size(), 24);
QByteArray branBa = wrInt(0, 16);
QByteArray sizeBa = wrInt(static_cast<quint64>(data.size()), 24);
return typeBa + cmdBa + sizeBa + data;
return typeBa + cmdBa + branBa + sizeBa + data;
}
QByteArray toTEXT(const QString &txt)
@ -136,9 +137,18 @@ QByteArray toTEXT(const QString &txt)
return ret.mid(2);
}
QByteArray fixedToTEXT(const QString &txt, int len)
{
return toTEXT(txt).leftJustified(len, 0, true);
}
QString fromTEXT(const QByteArray &txt)
{
return QTextCodec::codecForName(TXT_CODEC)->toUnicode(txt);
QByteArray ba = txt;
ba.replace(QByteArray(2, 0x00), QByteArray());
return QTextCodec::codecForName(TXT_CODEC)->toUnicode(ba);
}
QByteArray wrInt(quint64 num, int numOfBits)
@ -170,9 +180,10 @@ quint64 rdInt(const QByteArray &bytes)
// convert little endian QByteArrays into local sytem
// endianness uints.
QByteArray ba = bytes.leftJustified(sizeof(quint64), 0);
QByteArray ba = bytes.leftJustified(sizeof(quint64), 0);
void *vod = reinterpret_cast<void*>(ba.data());
return qFromLittleEndian<quint64>((void*) ba.data());
return qFromLittleEndian<quint64>(vod);
}
QString extractCmdName(const QByteArray &data)
@ -376,6 +387,7 @@ QJsonObject *Shared::localData = nullptr;
QHash<QString, Command*> *Shared::clientCmds = nullptr;
QHash<quint16, QString> *Shared::hostCmds = nullptr;
QHash<quint16, QString> *Shared::genfileCmds = nullptr;
QHash<QString, Command*> *Shared::hostDocs = nullptr;
QStringList *Shared::hookBypass = nullptr;
QByteArray *Shared::sessionId = nullptr;
QString *Shared::clientHookedCmd = nullptr;

View File

@ -68,35 +68,70 @@
#define DEFAULT_HIST_LIMIT 100
#define DEFAULT_MAX_LINES 5000
#define RDBUFF 128000
#define CMD_ESCAPE '#'
#define TXT_CODEC "UTF-16LE"
#define BOOKMARK_FOLDER "bookmarks"
#define CONFIG_FILENAME "config.json"
#define APP_NAME "Cmdr"
#define APP_TARGET "cmdr"
#define APP_VERSION "1.0.0"
#define MRCI_VERSION "1.0.0"
#define APP_VERSION "2.0.0"
enum TypeID
enum TypeID : quint8
{
GEN_FILE = 30,
TEXT = 31,
ERR = 32,
PRIV_TEXT = 33,
IDLE = 34,
HOST_CERT = 35,
FILE_INFO = 36,
PEER_INFO = 37,
MY_INFO = 38,
PEER_STAT = 39,
P2P_REQUEST = 40,
P2P_CLOSE = 41,
P2P_OPEN = 42,
BYTES = 43,
SESSION_ID = 44,
NEW_CMD = 45,
CMD_ID = 46,
BIG_TEXT = 47
GEN_FILE = 1,
TEXT = 2,
ERR = 3,
PRIV_TEXT = 4,
IDLE = 5,
HOST_CERT = 6,
FILE_INFO = 7,
PEER_INFO = 8,
MY_INFO = 9,
PEER_STAT = 10,
P2P_REQUEST = 11,
P2P_CLOSE = 12,
P2P_OPEN = 13,
BYTES = 14,
SESSION_ID = 15,
NEW_CMD = 16,
CMD_ID = 17,
BIG_TEXT = 18,
TERM_CMD = 19,
HOST_VER = 20,
PRIV_IPC = 21,
PUB_IPC = 22,
PUB_IPC_WITH_FEEDBACK = 23,
PING_PEERS = 24,
CH_MEMBER_INFO = 25,
CH_ID = 26,
KILL_CMD = 27,
HALT_CMD = 28,
RESUME_CMD = 29
};
enum AsyncCommands : quint16
{
ASYNC_RDY = 1, // client | none
ASYNC_SYS_MSG = 2, // client | none
ASYNC_CAST = 4, // client | public
ASYNC_USER_DELETED = 7, // client | public
ASYNC_TO_PEER = 16, // client | public | retricted
ASYNC_LIMITED_CAST = 17, // client | public
ASYNC_P2P = 19, // client | public
ASYNC_NEW_CH_MEMBER = 21, // client | public
ASYNC_DEL_CH = 22, // client | public
ASYNC_RENAME_CH = 23, // client | public
ASYNC_NEW_SUB_CH = 25, // client | public
ASYNC_RM_SUB_CH = 26, // client | public
ASYNC_RENAME_SUB_CH = 27, // client | public
ASYNC_INVITED_TO_CH = 28, // client | public
ASYNC_RM_CH_MEMBER = 29, // client | public
ASYNC_INVITE_ACCEPTED = 30, // client | public
ASYNC_MEM_LEVEL_CHANGED = 31, // client | public
ASYNC_SUB_CH_LEVEL_CHG = 32, // client | public
ASYNC_ADD_RDONLY = 33, // client | public
ASYNC_RM_RDONLY = 34, // client | public
ASYNC_ADD_CMD = 35, // client | none
ASYNC_RM_CMD = 36, // client | none
};
enum ChannelMemberLevel
@ -125,6 +160,7 @@ bool argExists(const QString &key, const QStringList &args);
QByteArray wrInt(quint64 num, int numOfBits);
QByteArray wrFrame(quint16 cmdId, const QByteArray &data, uchar dType);
QByteArray toTEXT(const QString &txt);
QByteArray fixedToTEXT(const QString &txt, int len);
QStringList parseArgs(const QByteArray &data, int maxArgs);
QStringList parseArgs(const QString &line);
QString fromTEXT(const QByteArray &txt);
@ -142,6 +178,7 @@ class MainWindow;
class Session;
class TextBody;
class ContextReloader;
class HostDoc;
class Shared : public QObject
{
@ -162,6 +199,7 @@ public:
static QHash<QString, Command*> *clientCmds;
static QHash<quint16, QString> *hostCmds;
static QHash<quint16, QString> *genfileCmds;
static QHash<QString, Command*> *hostDocs;
static QJsonObject *localData;
static quint16 *termCmdId;
static CmdLine *cmdLine;

View File

@ -26,6 +26,7 @@
#include "cmd_objs/info.h"
#include "cmd_objs/exec.h"
#include "cmd_objs/style.h"
#include "cmd_objs/host_doc.h"
#include "main_ui.h"
#include "session.h"
#include "gen_file.h"
@ -56,6 +57,7 @@ int main(int argc, char *argv[])
QStringList hookBypass;
QHash<quint16, QString> genfileCmds;
QHash<QString, Command*> clientCmds;
QHash<QString, Command*> hostDocs;
QHash<quint16, QString> hostCmds;
QJsonObject localData;
@ -66,6 +68,7 @@ int main(int argc, char *argv[])
Shared::genfileCmds = &genfileCmds;
Shared::clientCmds = &clientCmds;
Shared::hostCmds = &hostCmds;
Shared::hostDocs = &hostDocs;
Shared::sessionId = &sessionId;
Shared::servMajor = &servMajor;
Shared::servMinor = &servMinor;
@ -95,6 +98,8 @@ int main(int argc, char *argv[])
new About(&app);
new EndSession(&app);
new Term(&app);
new Halt(&app);
new Resume(&app);
new SetColors(&app);
new SetFont(&app);
new SetMaxLines(&app);
@ -153,6 +158,8 @@ void setupClientCmds()
QObject::connect(command, &Command::unsetUserIO, cmdLine, &CmdLine::unsetFlags);
QObject::connect(command, &Command::termHostCmd, session, &Session::termHostCmd);
QObject::connect(command, &Command::haltHostcmd, session, &Session::haltHostCmd);
QObject::connect(command, &Command::resumeHostCmd, session, &Session::resumeHostCmd);
QObject::connect(command, &Command::connectToHost, session, &Session::connectToServ);
QObject::connect(command, &Command::quitApp, session, &Session::disconnectFromServ);
QObject::connect(command, &Command::disconnectHost, session, &Session::disconnectFromServ);
@ -214,6 +221,9 @@ void setupSession()
CmdLine *cmdLine = Shared::cmdLine;
TextBody *textBody = Shared::textBody;
Genfile *genFile = Shared::genFile;
QThread *sesThr = new QThread(nullptr);
QObject::connect(session, &Session::destroyed, sesThr, &QThread::quit);
QObject::connect(session, &Session::hostFinished, genFile, &Genfile::finished);
QObject::connect(session, &Session::toGenFile, genFile, &Genfile::hookedDataIn);
@ -224,4 +234,9 @@ void setupSession()
QObject::connect(session, &Session::bigTxtOut, textBody, &TextBody::addBigTxt);
QObject::connect(session, &Session::mainTxtOut, textBody, &TextBody::addMainTxt);
QObject::connect(session, &Session::errTxtOut, textBody, &TextBody::addErrTxt);
QObject::connect(sesThr, &QThread::finished, sesThr, &QThread::deleteLater);
session->moveToThread(sesThr);
sesThr->start();
}

View File

@ -16,7 +16,7 @@
// along with Cmdr under the LICENSE.md file. If not, see
// <http://www.gnu.org/licenses/>.
Session::Session(QObject *parent) : QSslSocket(parent)
Session::Session(QObject *parent) : QSslSocket(nullptr)
{
dSize = 0;
hook = 0;
@ -24,14 +24,17 @@ Session::Session(QObject *parent) : QSslSocket(parent)
flags = 0;
reconnect = false;
connect(this, SIGNAL(encrypted()), this, SLOT(handShakeDone()));
connect(this, SIGNAL(connected()), this, SLOT(isConnected()));
connect(this, SIGNAL(disconnected()), this, SLOT(isDisconnected()));
connect(this, SIGNAL(readyRead()), this, SLOT(dataIn()));
connect(parent, &QSslSocket::destroyed, this, &Session::deleteLater);
connect(this, &Session::encrypted, this, &Session::handShakeDone);
connect(this, &Session::connected, this, &Session::isConnected);
connect(this, &Session::disconnected, this, &Session::isDisconnected);
connect(this, &Session::readyRead, this, &Session::dataIn);
connect(this, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(sockerr(QAbstractSocket::SocketError)));
}
void Session::hookedBinToServer(const QByteArray &data, uchar typeId)
void Session::hookedBinToServer(const QByteArray &data, quint8 typeId)
{
if (*Shared::connectedToHost)
{
@ -43,7 +46,7 @@ void Session::hookedBinToServer(const QByteArray &data, uchar typeId)
}
}
void Session::binToServer(quint16 cmdId, const QByteArray &data, uchar typeId)
void Session::binToServer(quint16 cmdId, const QByteArray &data, quint8 typeId)
{
if (*Shared::connectedToHost)
{
@ -78,26 +81,20 @@ void Session::sockerr(QAbstractSocket::SocketError err)
void Session::isConnected()
{
// client header format: [4bytes(tag)][2bytes(major)][2bytes(minor)][2bytes(patch)][128bytes(appName)][272bytes(coName)]
// client header format: [4bytes(tag)][134bytes(appName)][272bytes(coName)]
// tag = 0x4D, 0x52, 0x43, 0x49 (MRCI)
// major = 16bit little endian int
// minor = 16bit little endian int
// patch = 16bit little endian int
// appName = UTF16LE string
// coName = UTF16LE string
// appName = UTF16LE string (padded with 0x00)
// coName = UTF16LE string (padded with 0x00)
emit mainTxtOut("Connected.\n");
QByteArray header;
QStringList ver = QString(MRCI_VERSION).split('.');
QByteArray header;
QString appName = QString(APP_NAME) + " v" + QString(APP_VERSION);
header.append(SERVER_HEADER_TAG);
header.append(wrInt(ver[0].toULongLong(), 16));
header.append(wrInt(ver[1].toULongLong(), 16));
header.append(wrInt(ver[2].toULongLong(), 16));
header.append(toTEXT(QString(QString(APP_NAME) + " v" + QString(APP_VERSION)).leftJustified(64, ' ', true)));
header.append(toTEXT(Shared::hostAddress->leftJustified(136, ' ', true)));
header.append(fixedToTEXT(appName, 134));
header.append(fixedToTEXT(*Shared::hostAddress, 272));
if (header.size() == CLIENT_HEADER_LEN)
{
@ -189,7 +186,23 @@ void Session::termHostCmd()
{
if (hook != 0)
{
binToServer(*Shared::termCmdId, wrInt(hook, 16), CMD_ID);
binToServer(hook, QByteArray(), TERM_CMD);
}
}
void Session::haltHostCmd()
{
if (hook != 0)
{
binToServer(hook, QByteArray(), HALT_CMD);
}
}
void Session::resumeHostCmd()
{
if (hook != 0)
{
binToServer(hook, QByteArray(), RESUME_CMD);
}
}
@ -205,9 +218,31 @@ void Session::enableGenFile(bool state)
}
}
void Session::dataFromHost(const QByteArray &data)
bool Session::isAsync(quint16 id)
{
if (cmdId == ASYNC_SYS_MSG)
return id < 256;
}
void Session::procAsync(const QByteArray &data)
{
if (dType == TEXT)
{
if (cmdId == ASYNC_RDY)
{
hook = 0;
}
emit mainTxtOut(fromTEXT(data));
}
else if (dType == BIG_TEXT)
{
emit bigTxtOut(fromTEXT(data));
}
else if (dType == ERR)
{
emit errTxtOut(fromTEXT(data));
}
else if (cmdId == ASYNC_SYS_MSG)
{
if (dType == HOST_CERT)
{
@ -225,39 +260,21 @@ void Session::dataFromHost(const QByteArray &data)
addCaCertificate(cert);
startClientEncryption();
}
else if (dType == TEXT)
{
emit mainTxtOut(fromTEXT(data));
}
else if (dType == BIG_TEXT)
{
emit bigTxtOut(fromTEXT(data));
}
else if (dType == ERR)
{
emit errTxtOut(fromTEXT(data));
}
}
else if (cmdId == ASYNC_ADD_CMD)
{
if ((dType == NEW_CMD) && (data.size() >= 131))
if (dType == NEW_CMD)
{
quint16 id = static_cast<quint16>(rdInt(data.mid(0, 2)));
QString cmdName = fromTEXT(data.mid(3, 128)).trimmed();
auto *hostDoc = new HostDoc(data, this);
if (cmdName.toLower() == "term")
if (hostDoc->isValid())
{
*Shared::termCmdId = id;
connect(this, &Session::disconnected, hostDoc, &HostDoc::sessionEnded);
connect(this, &Session::hostCmdRemoved, hostDoc, &HostDoc::cmdRemoved);
}
if (!Shared::hostCmds->contains(id))
else
{
Shared::hostCmds->insert(id, cmdName);
if (data[2])
{
Shared::genfileCmds->insert(id, cmdName);
}
hostDoc->deleteLater();
}
}
}
@ -269,27 +286,18 @@ void Session::dataFromHost(const QByteArray &data)
if (id == hook) hook = 0;
if (Shared::hostCmds->contains(id))
{
Shared::genfileCmds->remove(id);
Shared::hostCmds->remove(id);
}
emit hostCmdRemoved(id);
}
}
else if ((cmdId == ASYNC_RDY) || (cmdId == ASYNC_EXE_CRASH))
{
hook = 0;
}
if (dType == TEXT)
{
emit mainTxtOut(fromTEXT(data));
}
else if (dType == ERR)
{
emit errTxtOut(fromTEXT(data));
}
void Session::dataFromHost(const QByteArray &data)
{
if (isAsync(cmdId))
{
procAsync(data);
}
else if ((cmdId == hook) && (hook != 0))
else if ((cmdId == hook) && (branId == 0) && (hook != 0))
{
if ((dType == TEXT) || (dType == PRIV_TEXT))
{
@ -349,9 +357,10 @@ void Session::dataIn()
{
QByteArray header = read(FRAME_HEADER_LEN);
dType = static_cast<uchar>(header[0]);
dType = static_cast<quint8>(header[0]);
cmdId = static_cast<quint16>(rdInt(header.mid(1, 2)));
dSize = static_cast<uint>(rdInt(header.mid(3, 3)));
branId = static_cast<quint16>(rdInt(header.mid(3, 2)));
dSize = static_cast<quint32>(rdInt(header.mid(5, 3)));
flags |= DSIZE_RDY;
dataIn();
@ -369,17 +378,17 @@ void Session::dataIn()
// minor = 16bit little endian uint (host ver minor)
// patch = 16bit little endian uint (host ver patch)
uchar reply = static_cast<uchar>(rdInt(read(1)));
quint8 reply = static_cast<quint8>(rdInt(read(1)));
if ((reply == 1) || (reply == 2))
{
*Shared::servMajor = static_cast<ushort>(rdInt(read(2)));
*Shared::servMinor = static_cast<ushort>(rdInt(read(2)));
*Shared::servPatch = static_cast<ushort>(rdInt(read(2)));
*Shared::servMajor = static_cast<quint16>(rdInt(read(2)));
*Shared::servMinor = static_cast<quint16>(rdInt(read(2)));
*Shared::servPatch = static_cast<quint16>(rdInt(read(2)));
emit mainTxtOut("Detected host version: " + verText(*Shared::servMajor, *Shared::servMinor, *Shared::servPatch) + "\n");
if (*Shared::servMajor == 1)
if (*Shared::servMajor == 2)
{
*Shared::sessionId = read(28);
*Shared::connectedToHost = true;
@ -404,10 +413,6 @@ void Session::dataIn()
disconnectFromHost();
}
}
else if (reply == 3)
{
emit errTxtOut("err: Client version rejected by the host.\n");
}
else if (reply == 4)
{
emit errTxtOut("err: The host was unable to find a SSL cert for common name: " + *Shared::hostAddress + ".\n");

View File

@ -37,35 +37,12 @@
#include "cmd_objs/command.h"
#include "cmd_objs/bookmarks.h"
#include "cmd_objs/host_doc.h"
#define SERVER_HEADER_TAG "MRCI"
#define CLIENT_HEADER_LEN 410
#define SERVER_HEADER_LEN 35
#define FRAME_HEADER_LEN 6
#define ASYNC_RDY 1
#define ASYNC_SYS_MSG 2
#define ASYNC_EXE_CRASH 3
#define ASYNC_TO_PEER 21
#define ASYNC_LIMITED_CAST 22
#define ASYNC_P2P 24
#define ASYNC_NEW_CH_MEMBER 26
#define ASYNC_DEL_CH 27
#define ASYNC_RENAME_CH 28
#define ASYNC_CH_ACT_FLAG 29
#define ASYNC_NEW_SUB_CH 30
#define ASYNC_RM_SUB_CH 31
#define ASYNC_RENAME_SUB_CH 32
#define ASYNC_INVITED_TO_CH 33
#define ASYNC_RM_CH_MEMBER 34
#define ASYNC_INVITE_ACCEPTED 35
#define ASYNC_MEM_LEVEL_CHANGED 36
#define ASYNC_SUB_CH_LEVEL_CHG 37
#define ASYNC_ADD_RDONLY 38
#define ASYNC_RM_RDONLY 39
#define ASYNC_ADD_CMD 40
#define ASYNC_RM_CMD 41
#define ASYNC_USER_RENAMED 42
#define FRAME_HEADER_LEN 8
class Session : public QSslSocket
{
@ -73,14 +50,17 @@ class Session : public QSslSocket
private:
uint flags;
uint dSize;
quint32 flags;
quint32 dSize;
quint16 cmdId;
quint16 branId;
quint16 hook;
uchar dType;
quint8 dType;
bool reconnect;
void dataFromHost(const QByteArray &data);
void procAsync(const QByteArray &data);
bool isAsync(quint16 id);
private slots:
@ -103,10 +83,12 @@ public:
public slots:
void hookedBinToServer(const QByteArray &data, uchar typeId);
void binToServer(quint16 cmdId, const QByteArray &data, uchar typeId);
void hookedBinToServer(const QByteArray &data, quint8 typeId);
void binToServer(quint16 cmdId, const QByteArray &data, quint8 typeId);
void enableGenFile(bool state);
void termHostCmd();
void haltHostCmd();
void resumeHostCmd();
void connectToServ();
void disconnectFromServ();
@ -115,6 +97,7 @@ signals:
void hostFinished();
void setUserIO(int flgs);
void unsetUserIO(int flgs);
void hostCmdRemoved(quint16 id);
void toGenFile(const QByteArray &data);
void mainTxtOut(const QString &txt);
void errTxtOut(const QString &txt);

View File

@ -18,10 +18,7 @@
TextBody::TextBody(QWidget *parent) : QTextEdit(parent)
{
localData = Shared::localData;
reloading = false;
lineCount = 0;
localData = Shared::localData;
txtDocument = new QTextDocument(this);
txtCursor = new QTextCursor(txtDocument);
@ -30,15 +27,14 @@ TextBody::TextBody(QWidget *parent) : QTextEdit(parent)
setWordWrapMode(QTextOption::NoWrap);
loadTextSettings(localData, this);
loadTextBodySettings();
txtDocument->setMaximumBlockCount(maxLines);
}
void TextBody::loadTextBodySettings()
{
mainColor = localData->value("text_settings").toObject().value("text_color").toString();
errColor = localData->value("text_settings").toObject().value("err_color").toString();
maxLines = localData->value("max_lines").toInt();
txtDocument->setMaximumBlockCount(localData->value("max_lines").toInt());
}
QString TextBody::htmlEsc(const QString &txt)
@ -51,58 +47,38 @@ QString TextBody::htmlEsc(const QString &txt)
return ret;
}
void TextBody::addTextBlock(const QString &txt, const QString &color, TextType tType)
void TextBody::addTextBlock(const QString &txt, const QString &color)
{
QStringList lines = htmlEsc(txt).split("\n");
moveCursor(QTextCursor::End);
lineCount += lines.size();
QStringList lines = htmlEsc(txt).split("\n");
for (int i = 0; i < lines.size(); ++i)
{
txtCursor->insertHtml("<div style=\"white-space: pre; color:" + color + ";\">" + lines[i] + "</div>");
if (i == lines.size() - 1)
if (i != lines.size() - 1)
{
if (!reloading) textCache.append(QPair<TextType,QString>(tType, lines[i]));
}
else
{
if (!reloading) textCache.append(QPair<TextType,QString>(tType, lines[i] + "\n"));
txtCursor->insertBlock();
}
ensureCursorVisible();
}
ensureCursorVisible();
}
void TextBody::setMaxLines(int value)
{
txtDocument->setMaximumBlockCount(value);
trimCache();
}
void TextBody::trimCache()
{
for (;lineCount > maxLines; --lineCount)
{
textCache.removeFirst();
}
}
void TextBody::addMainTxt(const QString &txt)
{
moveCursor(QTextCursor::End);
addTextBlock(txt, mainColor, MAIN);
trimCache();
addTextBlock(txt, mainColor);
}
void TextBody::addErrTxt(const QString &txt)
{
moveCursor(QTextCursor::End);
addTextBlock(txt, errColor, ERROR);
trimCache();
addTextBlock(txt, errColor);
}
void TextBody::addBigTxt(const QString &txt)
@ -115,13 +91,6 @@ void TextBody::addBigTxt(const QString &txt)
addMainTxt(lines);
}
void TextBody::clearCache()
{
lineCount = 0;
textCache.clear();
}
void TextBody::reload()
{
clear();
@ -129,31 +98,13 @@ void TextBody::reload()
loadTextSettings(localData, this);
saveLocalData(localData);
loadTextBodySettings();
reloading = true;
for (int i = 0; i < textCache.size(); ++i)
{
if (textCache[i].first == ERROR)
{
addErrTxt(textCache[i].second);
}
else
{
addMainTxt(textCache[i].second);
}
}
reloading = false;
}
void TextBody::contextMenuEvent(QContextMenuEvent *event)
{
QMenu *menu = createStandardContextMenu();
QAction *action = menu->addAction("Clear", this, SLOT(clear()));
connect(action, SIGNAL(triggered()), this, SLOT(clearCache()));
QMenu *menu = createStandardContextMenu();
menu->addAction("Clear", this, SLOT(clear()));
menu->exec(event->globalPos());
menu->deleteLater();
}

View File

@ -31,25 +31,16 @@ private:
MAIN
};
QList<QPair<TextType,QString> > textCache;
QTextDocument *txtDocument;
QTextCursor *txtCursor;
QJsonObject *localData;
QString errColor;
QString mainColor;
bool reloading;
int maxLines;
int lineCount;
QTextDocument *txtDocument;
QTextCursor *txtCursor;
QJsonObject *localData;
QString errColor;
QString mainColor;
QString htmlEsc(const QString &txt);
void trimCache();
void loadTextBodySettings();
void contextMenuEvent(QContextMenuEvent *event);
void addTextBlock(const QString &txt, const QString &color, TextType tType);
private slots:
void clearCache();
void addTextBlock(const QString &txt, const QString &color);
public: