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 + + + + +