diff --git a/.abapgit.xml b/.abapgit.xml
new file mode 100644
index 0000000..7c0506a
--- /dev/null
+++ b/.abapgit.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ E
+ /src/
+ PREFIX
+
+
+
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..823791d
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,18 @@
+name: test
+
+on:
+ pull_request:
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v2
+ with:
+ node-version: '16'
+ - name: npm install
+ run: npm ci
+ - name: npm test
+ run: npm test
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fe03b2b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+output
\ No newline at end of file
diff --git a/abap_transpile.json b/abap_transpile.json
new file mode 100644
index 0000000..ba856a4
--- /dev/null
+++ b/abap_transpile.json
@@ -0,0 +1,19 @@
+{
+ "input_folder": "src",
+ "input_filter": [],
+ "output_folder": "output",
+ "libs": [
+ {
+ "url": "https://github.com/open-abap/open-abap"
+ }
+ ],
+ "write_unit_tests": true,
+ "write_source_map": true,
+ "options": {
+ "ignoreSyntaxCheck": false,
+ "addFilenames": true,
+ "addCommonJS": true,
+ "unknownTypes": "runtimeError",
+ "skip": []
+ }
+}
\ No newline at end of file
diff --git a/abaplint.jsonc b/abaplint.jsonc
new file mode 100644
index 0000000..70597ea
--- /dev/null
+++ b/abaplint.jsonc
@@ -0,0 +1,342 @@
+{
+ "global": {
+ "files": "/src/**/*.*"
+ },
+ "dependencies": [
+ {
+ "url": "https://github.com/open-abap/open-abap",
+ "files": "/src/**/*.*"
+ }
+ ],
+ "syntax": {
+ "version": "open-abap",
+ "errorNamespace": "."
+ },
+ "rules": {
+ "7bit_ascii": true,
+ "abapdoc": false,
+ "align_parameters": true,
+ "allowed_object_naming": true,
+ "allowed_object_types": false,
+ "ambiguous_statement": true,
+ "avoid_use": true,
+ "begin_end_names": true,
+ "begin_single_include": true,
+ "call_transaction_authority_check": true,
+ "cds_parser_error": true,
+ "chain_mainly_declarations": true,
+ "check_abstract": true,
+ "check_comments": false,
+ "check_ddic": true,
+ "check_include": true,
+ "check_no_handler_pragma": true,
+ "check_subrc": true,
+ "check_syntax": true,
+ "check_text_elements": true,
+ "check_transformation_exists": true,
+ "class_attribute_names": true,
+ "cloud_types": true,
+ "colon_missing_space": true,
+ "commented_code": true,
+ "constant_classes": {
+ "exclude": [],
+ "severity": "Error",
+ "mapping": []
+ },
+ "constructor_visibility_public": true,
+ "contains_tab": {
+ "exclude": [],
+ "severity": "Error",
+ "spaces": 1
+ },
+ "cyclic_oo": {
+ "exclude": [],
+ "severity": "Error",
+ "skip": []
+ },
+ "cyclomatic_complexity": {
+ "exclude": [],
+ "severity": "Error",
+ "max": 20
+ },
+ "dangerous_statement": {
+ "exclude": [],
+ "severity": "Error",
+ "execSQL": true,
+ "kernelCall": true,
+ "systemCall": true,
+ "insertReport": true,
+ "generateDynpro": true,
+ "generateReport": true,
+ "generateSubroutine": true,
+ "deleteReport": true,
+ "deleteTextpool": true,
+ "deleteDynpro": true,
+ "importDynpro": true,
+ "dynamicSQL": true
+ },
+ "db_operation_in_loop": true,
+ "definitions_top": true,
+ "description_empty": true,
+ "double_space": {
+ "exclude": [],
+ "severity": "Error",
+ "keywords": true,
+ "startParen": true,
+ "endParen": true,
+ "afterColon": true
+ },
+ "downport": true,
+ "empty_line_in_statement": {
+ "exclude": [],
+ "severity": "Error",
+ "allowChained": false
+ },
+ "empty_statement": true,
+ "empty_structure": {
+ "exclude": [],
+ "severity": "Error",
+ "loop": true,
+ "if": true,
+ "while": true,
+ "case": true,
+ "select": true,
+ "do": true,
+ "at": true,
+ "try": true
+ },
+ "exit_or_check": {
+ "exclude": [],
+ "severity": "Error",
+ "allowExit": false,
+ "allowCheck": false
+ },
+ "exporting": true,
+ "forbidden_identifier": {
+ "exclude": [],
+ "severity": "Error",
+ "check": []
+ },
+ "forbidden_pseudo_and_pragma": {
+ "exclude": [],
+ "severity": "Error",
+ "pseudo": [],
+ "pragmas": [],
+ "ignoreGlobalClassDefinition": false,
+ "ignoreGlobalInterface": false
+ },
+ "forbidden_void_type": {
+ "exclude": [],
+ "severity": "Error",
+ "check": []
+ },
+ "form_tables_obsolete": true,
+ "fully_type_constants": true,
+ "function_module_recommendations": true,
+ "functional_writing": {
+ "exclude": [],
+ "severity": "Error",
+ "ignoreExceptions": true
+ },
+ "global_class": true,
+ "identical_conditions": true,
+ "identical_contents": true,
+ "identical_descriptions": true,
+ "identical_form_names": true,
+ "if_in_if": true,
+ "implement_methods": true,
+ "in_statement_indentation": {
+ "exclude": [],
+ "severity": "Error",
+ "blockStatements": 2,
+ "ignoreExceptions": true
+ },
+ "indentation": {
+ "exclude": [],
+ "severity": "Error",
+ "ignoreExceptions": true,
+ "alignTryCatch": false,
+ "selectionScreenBlockIndentation": false,
+ "globalClassSkipFirst": false,
+ "ignoreGlobalClassDefinition": false,
+ "ignoreGlobalInterface": false
+ },
+ "inline_data_old_versions": true,
+ "intf_referencing_clas": {
+ "exclude": [],
+ "severity": "Error",
+ "allow": []
+ },
+ "keep_single_parameter_on_one_line": {
+ "exclude": [],
+ "severity": "Error",
+ "length": 120
+ },
+ "keyword_case": {
+ "exclude": [],
+ "severity": "Error",
+ "style": "upper",
+ "ignoreExceptions": true,
+ "ignoreLowerClassImplmentationStatement": true,
+ "ignoreGlobalClassDefinition": false,
+ "ignoreGlobalInterface": false,
+ "ignoreFunctionModuleName": false,
+ "ignoreGlobalClassBoundaries": false,
+ "ignoreKeywords": []
+ },
+ "line_break_multiple_parameters": {
+ "exclude": [],
+ "severity": "Error",
+ "count": 1
+ },
+ "line_break_style": false,
+ "line_length": {
+ "exclude": [],
+ "severity": "Error",
+ "length": 120
+ },
+ "line_only_punc": {
+ "exclude": [],
+ "severity": "Error",
+ "ignoreExceptions": true
+ },
+ "local_class_naming": {
+ "exclude": [],
+ "severity": "Error",
+ "patternKind": "required",
+ "ignoreNames": [],
+ "ignorePatterns": [],
+ "local": "^LCL_.+$",
+ "exception": "^LCX_.+$",
+ "test": "^LTCL_.+$"
+ },
+ "local_testclass_location": true,
+ "local_variable_names": {
+ "exclude": [],
+ "severity": "Error",
+ "patternKind": "required",
+ "ignoreNames": [],
+ "ignorePatterns": [],
+ "expectedData": "^L._.+$",
+ "expectedConstant": "^LC_.+$",
+ "expectedFS": "^$"
+ },
+ "main_file_contents": true,
+ "many_parentheses": true,
+ "max_one_method_parameter_per_line": true,
+ "max_one_statement": true,
+ "message_exists": true,
+ "method_implemented_twice": true,
+ "method_length": {
+ "exclude": [],
+ "severity": "Error",
+ "statements": 100,
+ "errorWhenEmpty": true,
+ "ignoreTestClasses": false,
+ "checkForms": true
+ },
+ "method_overwrites_builtin": true,
+ "method_parameter_names": true,
+ "mix_returning": true,
+ "modify_only_own_db_tables": true,
+ "msag_consistency": true,
+ "names_no_dash": true,
+ "nesting": true,
+ "newline_between_methods": true,
+ "no_aliases": true,
+ "no_chained_assignment": true,
+ "no_public_attributes": {
+ "exclude": [],
+ "severity": "Error",
+ "allowReadOnly": false
+ },
+ "no_yoda_conditions": {
+ "exclude": [],
+ "severity": "Error",
+ "onlyConstants": false
+ },
+ "object_naming": {
+ "exclude": [],
+ "severity": "Error",
+ "patternKind": "required",
+ "ignoreNames": [],
+ "ignorePatterns": [],
+ "clas": "^ZC(L|X)",
+ "intf": "^ZIF",
+ "prog": "^Z",
+ "fugr": "^Z",
+ "tabl": "^Z",
+ "ttyp": "^Z",
+ "dtel": "^Z",
+ "doma": "^Z",
+ "msag": "^Z",
+ "tran": "^Z",
+ "enqu": "^EZ",
+ "auth": "^Z",
+ "pinf": "^Z",
+ "idoc": "^Z",
+ "xslt": "^Z",
+ "ssfo": "^Z",
+ "ssst": "^Z",
+ "shlp": "^Z"
+ },
+ "obsolete_statement": true,
+ "omit_parameter_name": true,
+ "omit_preceding_zeros": true,
+ "omit_receiving": true,
+ "parser_702_chaining": true,
+ "parser_error": true,
+ "parser_missing_space": true,
+ "pragma_placement": true,
+ "prefer_corresponding": true,
+ "prefer_inline": true,
+ "prefer_is_not": true,
+ "prefer_raise_exception_new": true,
+ "prefer_returning_to_exporting": true,
+ "prefer_xsdbool": true,
+ "preferred_compare_operator": true,
+ "prefix_is_current_class": true,
+ "reduce_string_templates": true,
+ "release_idoc": true,
+ "remove_descriptions": true,
+ "rfc_error_handling": true,
+ "select_add_order_by": true,
+ "select_performance": true,
+ "selection_screen_naming": true,
+ "sequential_blank": true,
+ "short_case": true,
+ "sicf_consistency": true,
+ "space_before_colon": true,
+ "space_before_dot": {
+ "exclude": [],
+ "severity": "Error",
+ "ignoreGlobalDefinition": true,
+ "ignoreExceptions": true
+ },
+ "sql_escape_host_variables": true,
+ "start_at_tab": true,
+ "static_call_via_instance": true,
+ "superclass_final": true,
+ "sy_modification": true,
+ "tabl_enhancement_category": true,
+ "try_without_catch": true,
+ "type_form_parameters": true,
+ "types_naming": true,
+ "uncaught_exception": true,
+ "unknown_types": true,
+ "unnecessary_chaining": true,
+ "unreachable_code": true,
+ "unsecure_fae": true,
+ "unused_ddic": true,
+ "unused_methods": true,
+ "unused_types": true,
+ "unused_variables": true,
+ "use_bool_expression": true,
+ "use_class_based_exceptions": true,
+ "use_line_exists": true,
+ "use_new": true,
+ "when_others_last": true,
+ "whitespace_end": true,
+ "xml_consistency": true
+ }
+}
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..343414c
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,125 @@
+{
+ "name": "open-abap-ssh",
+ "version": "1.0.0",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "open-abap-ssh",
+ "version": "1.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "@abaplint/cli": "^2.85.32",
+ "@abaplint/runtime": "^1.8.11",
+ "@abaplint/transpiler-cli": "^1.8.11"
+ }
+ },
+ "node_modules/@abaplint/cli": {
+ "version": "2.85.32",
+ "resolved": "https://registry.npmjs.org/@abaplint/cli/-/cli-2.85.32.tgz",
+ "integrity": "sha512-GBtpn6JGNkwBH48g1ZhARW31ZQdn4hrohcYug16NZPUwfbkDOBCZEMDWOOnQO/wfmitlaRktUEID6W+lbkTgAQ==",
+ "bin": {
+ "abaplint": "abaplint"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/@abaplint/runtime": {
+ "version": "1.8.11",
+ "resolved": "https://registry.npmjs.org/@abaplint/runtime/-/runtime-1.8.11.tgz",
+ "integrity": "sha512-c35VaQ+vNEHRQ0RBVEvgceHbnRuRlPxUtrg73y5tFT3j34K7KF8k6nOIaZqotH4gnraWcDDEfjxUCWy0tQAVzQ==",
+ "dependencies": {
+ "hdb": "^0.19.1",
+ "sql.js": "^1.6.2"
+ }
+ },
+ "node_modules/@abaplint/transpiler-cli": {
+ "version": "1.8.11",
+ "resolved": "https://registry.npmjs.org/@abaplint/transpiler-cli/-/transpiler-cli-1.8.11.tgz",
+ "integrity": "sha512-JlENFEvzRraJxEPYKMXPgLZX/clH5hSGFyttKMPGUREX7qg6OS99lMSGCKF2TisyS0GAW38N+jIcs2dVTYn5jw==",
+ "bin": {
+ "abap_transpile": "abap_transpile"
+ }
+ },
+ "node_modules/hdb": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/hdb/-/hdb-0.19.1.tgz",
+ "integrity": "sha512-QqB7Srj/AaesB1bZPiKZMPXnjU4NRgP7ZheCHUWf4EKKqZXpWW2lJcb6BRnNfjxfCYIT+TR1wIaA0zLO1CeU6w==",
+ "dependencies": {
+ "iconv-lite": "^0.4.18"
+ },
+ "engines": {
+ "node": ">= 0.12"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "node_modules/sql.js": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.6.2.tgz",
+ "integrity": "sha512-9iucI5fXQa+Gspeqf/BNB20PxJIn5LhXDt4mjXoFPqXdR+NqtFs15SdKpSIJ6s529aGL9zFR9p2eSCIEiMsNGA=="
+ }
+ },
+ "dependencies": {
+ "@abaplint/cli": {
+ "version": "2.85.32",
+ "resolved": "https://registry.npmjs.org/@abaplint/cli/-/cli-2.85.32.tgz",
+ "integrity": "sha512-GBtpn6JGNkwBH48g1ZhARW31ZQdn4hrohcYug16NZPUwfbkDOBCZEMDWOOnQO/wfmitlaRktUEID6W+lbkTgAQ=="
+ },
+ "@abaplint/runtime": {
+ "version": "1.8.11",
+ "resolved": "https://registry.npmjs.org/@abaplint/runtime/-/runtime-1.8.11.tgz",
+ "integrity": "sha512-c35VaQ+vNEHRQ0RBVEvgceHbnRuRlPxUtrg73y5tFT3j34K7KF8k6nOIaZqotH4gnraWcDDEfjxUCWy0tQAVzQ==",
+ "requires": {
+ "hdb": "^0.19.1",
+ "sql.js": "^1.6.2"
+ }
+ },
+ "@abaplint/transpiler-cli": {
+ "version": "1.8.11",
+ "resolved": "https://registry.npmjs.org/@abaplint/transpiler-cli/-/transpiler-cli-1.8.11.tgz",
+ "integrity": "sha512-JlENFEvzRraJxEPYKMXPgLZX/clH5hSGFyttKMPGUREX7qg6OS99lMSGCKF2TisyS0GAW38N+jIcs2dVTYn5jw=="
+ },
+ "hdb": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/hdb/-/hdb-0.19.1.tgz",
+ "integrity": "sha512-QqB7Srj/AaesB1bZPiKZMPXnjU4NRgP7ZheCHUWf4EKKqZXpWW2lJcb6BRnNfjxfCYIT+TR1wIaA0zLO1CeU6w==",
+ "requires": {
+ "iconv-lite": "^0.4.18"
+ }
+ },
+ "iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "sql.js": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.6.2.tgz",
+ "integrity": "sha512-9iucI5fXQa+Gspeqf/BNB20PxJIn5LhXDt4mjXoFPqXdR+NqtFs15SdKpSIJ6s529aGL9zFR9p2eSCIEiMsNGA=="
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..bae75e4
--- /dev/null
+++ b/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "open-abap-ssh",
+ "version": "1.0.0",
+ "private": true,
+ "description": "open-abap-ssh",
+ "scripts": {
+ "test": "npx abaplint && npm run unit",
+ "unit": "rm -rf output && abap_transpile test/abap_transpile.json && echo RUNNING && node output/index.mjs"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/open-abap/open-abap-ssh.git"
+ },
+ "author": "",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/open-abap/open-abap-ssh/issues"
+ },
+ "homepage": "https://github.com/open-abap/open-abap-ssh#readme",
+ "dependencies": {
+ "@abaplint/cli": "^2.85.32",
+ "@abaplint/runtime": "^1.8.11",
+ "@abaplint/transpiler-cli": "^1.8.11"
+ }
+}
diff --git a/src/package.devc.xml b/src/package.devc.xml
new file mode 100644
index 0000000..3d9014b
--- /dev/null
+++ b/src/package.devc.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+ abapGit-ssh
+
+
+
+
diff --git a/src/zcl_oassh.clas.abap b/src/zcl_oassh.clas.abap
new file mode 100644
index 0000000..d58e844
--- /dev/null
+++ b/src/zcl_oassh.clas.abap
@@ -0,0 +1,161 @@
+CLASS zcl_oassh DEFINITION
+ PUBLIC
+ CREATE PRIVATE .
+
+ PUBLIC SECTION.
+
+ INTERFACES if_apc_wsp_event_handler .
+
+ CLASS-METHODS connect
+ IMPORTING
+ !iv_host TYPE string
+ !iv_port TYPE string
+ RAISING
+ cx_static_check .
+ PROTECTED SECTION.
+ PRIVATE SECTION.
+
+ CONSTANTS:
+ BEGIN OF gc_state,
+ protocol_version_exchange TYPE i VALUE 1,
+ key_exchange TYPE i VALUE 2,
+ END OF gc_state .
+ DATA mi_client TYPE REF TO if_apc_wsp_client .
+ DATA mv_buffer TYPE xstring .
+ DATA mv_state TYPE i .
+
+ METHODS handle .
+ METHODS send
+ IMPORTING
+ !iv_message TYPE xstring
+ RAISING
+ cx_apc_error .
+ENDCLASS.
+
+
+
+CLASS zcl_oassh IMPLEMENTATION.
+
+
+ METHOD connect.
+
+ DATA lo_ssh TYPE REF TO zcl_oassh.
+ DATA ls_frame TYPE if_abap_channel_types=>ty_apc_tcp_frame.
+
+ CREATE OBJECT lo_ssh.
+
+ ls_frame-frame_type = if_apc_tcp_frame_types=>co_frame_type_fixed_length.
+ ls_frame-fixed_length = 1.
+
+ lo_ssh->mi_client = cl_apc_tcp_client_manager=>create(
+ i_host = iv_host
+ i_port = iv_port
+ i_frame = ls_frame
+ i_event_handler = lo_ssh ).
+
+ lo_ssh->mi_client->connect( ).
+
+ ENDMETHOD.
+
+
+ METHOD handle.
+
+ DATA lv_remote_version TYPE string.
+ DATA lo_stream TYPE REF TO zcl_oassh_stream.
+ DATA lv_padding_length TYPE i.
+ DATA lv_length TYPE i.
+ DATA ls_kexinit TYPE zcl_oassh_message_20=>ty_data.
+
+ CASE mv_state.
+ WHEN gc_state-protocol_version_exchange.
+ IF mv_buffer CP |*{ cl_abap_codepage=>convert_to( |{ cl_abap_char_utilities=>cr_lf }| ) }|.
+ lv_remote_version = cl_abap_codepage=>convert_from( mv_buffer ).
+ CLEAR mv_buffer.
+ mv_state = gc_state-key_exchange.
+ ENDIF.
+ WHEN gc_state-key_exchange.
+* todo, check buffer contains a full packet, and return the packet payload
+* https://datatracker.ietf.org/doc/html/rfc4253#section-7
+
+ IF xstrlen( mv_buffer ) > 4.
+ CREATE OBJECT lo_stream EXPORTING iv_hex = mv_buffer.
+ lv_length = lo_stream->uint32_decode( ).
+ IF lo_stream->get_length( ) = lv_length.
+* there is no MAC negotiated at this point in time
+ lv_padding_length = lo_stream->take( 1 ).
+ ls_kexinit = zcl_oassh_message_20=>parse( lo_stream ).
+ lo_stream->take( lv_padding_length ).
+ ENDIF.
+ ENDIF.
+
+ ENDCASE.
+
+ ENDMETHOD.
+
+
+ METHOD if_apc_wsp_event_handler~on_close.
+ WRITE / 'on_close'.
+ ENDMETHOD.
+
+
+ METHOD if_apc_wsp_event_handler~on_error.
+ WRITE / 'on_error'.
+ ENDMETHOD.
+
+
+ METHOD if_apc_wsp_event_handler~on_message.
+ DATA lv_message TYPE xstring.
+
+ TRY.
+ lv_message = i_message->get_binary( ).
+ CATCH cx_root.
+ ENDTRY.
+ mv_buffer = mv_buffer && lv_message.
+
+ handle( ).
+
+ ENDMETHOD.
+
+
+ METHOD if_apc_wsp_event_handler~on_open.
+ DATA lv_xstr TYPE xstring.
+
+ WRITE / 'on_open'.
+
+* https://datatracker.ietf.org/doc/html/rfc4253#section-4.2
+
+ lv_xstr = cl_abap_codepage=>convert_to( 'SSH-2.0-abap' && cl_abap_char_utilities=>cr_lf ).
+
+ TRY.
+ send( lv_xstr ).
+ CATCH cx_apc_error.
+ ASSERT 1 = 2.
+ ENDTRY.
+
+ mv_state = gc_state-protocol_version_exchange.
+
+ ENDMETHOD.
+
+
+ METHOD send.
+
+ DATA li_message_manager TYPE REF TO if_apc_wsp_message_manager.
+ DATA li_message TYPE REF TO if_apc_wsp_message.
+ DATA lv_index TYPE i.
+ DATA lv_hex TYPE xstring.
+
+ li_message_manager ?= mi_client->get_message_manager( ).
+
+ li_message = li_message_manager->create_message( ).
+
+ ASSERT iv_message IS NOT INITIAL.
+
+ DO xstrlen( iv_message ) TIMES.
+ lv_index = sy-index - 1.
+ lv_hex = iv_message+lv_index(1).
+ li_message->set_binary( lv_hex ).
+ li_message_manager->send( li_message ).
+ ENDDO.
+
+ ENDMETHOD.
+ENDCLASS.
\ No newline at end of file
diff --git a/src/zcl_oassh.clas.xml b/src/zcl_oassh.clas.xml
new file mode 100644
index 0000000..0aa13ed
--- /dev/null
+++ b/src/zcl_oassh.clas.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ ZCL_OASSH
+ E
+ ssh test
+ 1
+ X
+ X
+ X
+
+
+
+
diff --git a/src/zcl_oassh_message_20.clas.abap b/src/zcl_oassh_message_20.clas.abap
new file mode 100644
index 0000000..f5909ba
--- /dev/null
+++ b/src/zcl_oassh_message_20.clas.abap
@@ -0,0 +1,89 @@
+CLASS zcl_oassh_message_20 DEFINITION
+ PUBLIC
+ FINAL
+ CREATE PUBLIC .
+
+ PUBLIC SECTION.
+
+ TYPES:
+ BEGIN OF ty_data,
+ message_id TYPE x LENGTH 1,
+ cookie TYPE x LENGTH 16,
+ kex_algorithms TYPE string_table,
+ server_host_key_algorithms TYPE string_table,
+ encryption_algorithms_c_to_s TYPE string_table,
+ encryption_algorithms_s_to_c TYPE string_table,
+ mac_algorithms_c_to_s TYPE string_table,
+ mac_algorithms_s_to_c TYPE string_table,
+ compression_algorithms_c_to_s TYPE string_table,
+ compression_algorithms_s_to_c TYPE string_table,
+ languages_c_to_s TYPE string_table,
+ languages_s_to_c TYPE string_table,
+ first_kex_packet_follows TYPE abap_bool,
+ reserved TYPE i,
+ END OF ty_data .
+
+ CLASS-METHODS parse
+ IMPORTING
+ !io_stream TYPE REF TO zcl_oassh_stream
+ RETURNING
+ VALUE(rs_data) TYPE ty_data .
+
+ CLASS-METHODS serialize
+ IMPORTING
+ is_data TYPE ty_data
+ RETURNING
+ VALUE(ro_stream) TYPE REF TO zcl_oassh_stream .
+
+ CONSTANTS gc_message_id TYPE x LENGTH 1 VALUE '14'. " is 20 in decimal
+
+ PROTECTED SECTION.
+ PRIVATE SECTION.
+ENDCLASS.
+
+
+
+CLASS ZCL_OASSH_MESSAGE_20 IMPLEMENTATION.
+
+
+ METHOD parse.
+
+ rs_data-message_id = io_stream->take( 1 ).
+ ASSERT rs_data-message_id = gc_message_id.
+ rs_data-cookie = io_stream->take( 16 ).
+ rs_data-kex_algorithms = io_stream->name_list_decode( ).
+ rs_data-server_host_key_algorithms = io_stream->name_list_decode( ).
+ rs_data-encryption_algorithms_c_to_s = io_stream->name_list_decode( ).
+ rs_data-encryption_algorithms_s_to_c = io_stream->name_list_decode( ).
+ rs_data-mac_algorithms_c_to_s = io_stream->name_list_decode( ).
+ rs_data-mac_algorithms_s_to_c = io_stream->name_list_decode( ).
+ rs_data-compression_algorithms_c_to_s = io_stream->name_list_decode( ).
+ rs_data-compression_algorithms_s_to_c = io_stream->name_list_decode( ).
+ rs_data-languages_c_to_s = io_stream->name_list_decode( ).
+ rs_data-languages_s_to_c = io_stream->name_list_decode( ).
+ rs_data-first_kex_packet_follows = io_stream->boolean_decode( ).
+ rs_data-reserved = io_stream->uint32_decode( ).
+
+ ENDMETHOD.
+
+
+ METHOD serialize.
+
+ CREATE OBJECT ro_stream.
+ ro_stream->append( gc_message_id ).
+ ro_stream->append( is_data-cookie ).
+ ro_stream->name_list_encode( is_data-kex_algorithms ).
+ ro_stream->name_list_encode( is_data-server_host_key_algorithms ).
+ ro_stream->name_list_encode( is_data-encryption_algorithms_c_to_s ).
+ ro_stream->name_list_encode( is_data-encryption_algorithms_s_to_c ).
+ ro_stream->name_list_encode( is_data-mac_algorithms_c_to_s ).
+ ro_stream->name_list_encode( is_data-mac_algorithms_s_to_c ).
+ ro_stream->name_list_encode( is_data-compression_algorithms_c_to_s ).
+ ro_stream->name_list_encode( is_data-compression_algorithms_s_to_c ).
+ ro_stream->name_list_encode( is_data-languages_c_to_s ).
+ ro_stream->name_list_encode( is_data-languages_s_to_c ).
+ ro_stream->boolean_encode( is_data-first_kex_packet_follows ).
+ ro_stream->uint32_encode( is_data-reserved ).
+
+ ENDMETHOD.
+ENDCLASS.
diff --git a/src/zcl_oassh_message_20.clas.xml b/src/zcl_oassh_message_20.clas.xml
new file mode 100644
index 0000000..500f03c
--- /dev/null
+++ b/src/zcl_oassh_message_20.clas.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ ZCL_OASSH_MESSAGE_20
+ E
+ SSH_MSG_KEXINIT - 20
+ 1
+ X
+ X
+ X
+
+
+
+
diff --git a/src/zcl_oassh_stream.clas.abap b/src/zcl_oassh_stream.clas.abap
new file mode 100644
index 0000000..226d263
--- /dev/null
+++ b/src/zcl_oassh_stream.clas.abap
@@ -0,0 +1,138 @@
+CLASS zcl_oassh_stream DEFINITION
+ PUBLIC
+ FINAL
+ CREATE PUBLIC .
+
+ PUBLIC SECTION.
+
+ METHODS constructor
+ IMPORTING
+ !iv_hex TYPE xstring OPTIONAL .
+ METHODS get
+ RETURNING
+ VALUE(rv_hex) TYPE xstring .
+ METHODS take
+ IMPORTING
+ !iv_length TYPE i
+ RETURNING
+ VALUE(rv_hex) TYPE xstring .
+ METHODS append
+ IMPORTING
+ !iv_hex TYPE xsequence .
+ METHODS name_list_encode
+ IMPORTING
+ !it_list TYPE string_table .
+ METHODS boolean_encode
+ IMPORTING
+ !iv_boolean TYPE abap_bool .
+ METHODS boolean_decode
+ RETURNING
+ VALUE(rv_boolean) TYPE abap_bool .
+ METHODS name_list_decode
+ RETURNING
+ VALUE(rt_list) TYPE string_table .
+ METHODS uint32_encode
+ IMPORTING
+ !iv_int TYPE i .
+ METHODS uint32_decode
+ RETURNING
+ VALUE(rv_int) TYPE i .
+ METHODS get_length
+ RETURNING
+ VALUE(rv_length) TYPE i .
+ PROTECTED SECTION.
+ PRIVATE SECTION.
+ DATA mv_hex TYPE xstring.
+ENDCLASS.
+
+
+
+CLASS ZCL_OASSH_STREAM IMPLEMENTATION.
+
+
+ METHOD append.
+ mv_hex = mv_hex && iv_hex.
+ ENDMETHOD.
+
+
+ METHOD boolean_decode.
+ rv_boolean = boolc( take( 1 ) = '00' ).
+ ENDMETHOD.
+
+
+ METHOD boolean_encode.
+ CASE iv_boolean.
+ WHEN abap_true.
+ append( '01' ).
+ WHEN abap_false.
+ append( '00' ).
+ WHEN OTHERS.
+ ASSERT 1 = 2.
+ ENDCASE.
+ ENDMETHOD.
+
+
+ METHOD constructor.
+ mv_hex = iv_hex.
+ ENDMETHOD.
+
+
+ METHOD get.
+ rv_hex = mv_hex.
+ ENDMETHOD.
+
+
+ METHOD get_length.
+ rv_length = xstrlen( mv_hex ).
+ ENDMETHOD.
+
+
+ METHOD name_list_decode.
+* https://datatracker.ietf.org/doc/html/rfc4251#section-5
+
+ DATA lv_length TYPE i.
+ DATA lv_hex TYPE xstring.
+ DATA lv_text TYPE string.
+
+ lv_length = uint32_decode( ).
+ lv_hex = mv_hex(lv_length).
+ lv_text = cl_abap_codepage=>convert_from( lv_hex ).
+ SPLIT lv_text AT ',' INTO TABLE rt_list.
+ take( lv_length ).
+
+ ENDMETHOD.
+
+
+ METHOD name_list_encode.
+* https://datatracker.ietf.org/doc/html/rfc4251#section-5
+
+ DATA lv_text TYPE string.
+ CONCATENATE LINES OF it_list INTO lv_text SEPARATED BY ','.
+
+ uint32_encode( strlen( lv_text ) ).
+ append( cl_abap_codepage=>convert_to( lv_text ) ).
+
+ ENDMETHOD.
+
+
+ METHOD take.
+ rv_hex = mv_hex(iv_length).
+ mv_hex = mv_hex+iv_length.
+ ENDMETHOD.
+
+
+ METHOD uint32_decode.
+
+ rv_int = take( 4 ).
+
+ ENDMETHOD.
+
+
+ METHOD uint32_encode.
+
+ DATA lv_hex TYPE x LENGTH 4.
+ lv_hex = iv_int.
+ append( lv_hex ).
+
+ ENDMETHOD.
+ENDCLASS.
diff --git a/src/zcl_oassh_stream.clas.testclasses.abap b/src/zcl_oassh_stream.clas.testclasses.abap
new file mode 100644
index 0000000..d11132a
--- /dev/null
+++ b/src/zcl_oassh_stream.clas.testclasses.abap
@@ -0,0 +1,51 @@
+CLASS ltcl_test DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS FINAL.
+
+ PRIVATE SECTION.
+ DATA mo_stream TYPE REF TO zcl_oassh_stream.
+ METHODS setup.
+ METHODS name_list FOR TESTING RAISING cx_static_check.
+ METHODS unit32 FOR TESTING RAISING cx_static_check.
+ENDCLASS.
+
+
+CLASS ltcl_test IMPLEMENTATION.
+
+ METHOD setup.
+ CREATE OBJECT mo_stream.
+ ENDMETHOD.
+
+ METHOD name_list.
+
+ DATA lt_list TYPE string_table.
+ APPEND 'zlib' TO lt_list.
+ APPEND 'none' TO lt_list.
+
+ mo_stream->name_list_encode( lt_list ).
+
+ cl_abap_unit_assert=>assert_equals(
+ act = mo_stream->get( )
+ exp = '000000097A6C69622C6E6F6E65' ).
+
+ cl_abap_unit_assert=>assert_equals(
+ act = lines( mo_stream->name_list_decode( ) )
+ exp = 2 ).
+
+ ENDMETHOD.
+
+ METHOD unit32.
+
+ DATA lv_int TYPE i VALUE 699921578.
+
+ mo_stream->uint32_encode( lv_int ).
+
+ cl_abap_unit_assert=>assert_equals(
+ act = mo_stream->get( )
+ exp = '29B7F4AA' ).
+
+ cl_abap_unit_assert=>assert_equals(
+ act = mo_stream->uint32_decode( )
+ exp = 699921578 ).
+
+ ENDMETHOD.
+
+ENDCLASS.
diff --git a/src/zcl_oassh_stream.clas.xml b/src/zcl_oassh_stream.clas.xml
new file mode 100644
index 0000000..b49d204
--- /dev/null
+++ b/src/zcl_oassh_stream.clas.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+ ZCL_OASSH_STREAM
+ E
+ Stream
+ 1
+ X
+ X
+ X
+ X
+
+
+
+
diff --git a/src/ztest_oassh.prog.abap b/src/ztest_oassh.prog.abap
new file mode 100644
index 0000000..157c01e
--- /dev/null
+++ b/src/ztest_oassh.prog.abap
@@ -0,0 +1,12 @@
+REPORT ztest_oassh.
+
+START-OF-SELECTION.
+ PERFORM run.
+
+FORM run RAISING cx_static_check.
+
+ zcl_oassh=>connect(
+ iv_host = 'github.com'
+ iv_port = '22' ).
+
+ENDFORM.
diff --git a/src/ztest_oassh.prog.xml b/src/ztest_oassh.prog.xml
new file mode 100644
index 0000000..bebbd41
--- /dev/null
+++ b/src/ztest_oassh.prog.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+ ZTEST_OASSH
+ 1
+ E
+ X
+ X
+
+
+ -
+ R
+ test
+ 4
+
+
+
+
+