# HG changeset patch # User Carl Byington # Date 1449170635 28800 # Node ID d29cce60f39375ff2634e6591b614f54a768b7d2 # Parent 208b310323182459b7ef05474914b15d4558561a migrate from Eclipse to Android Studio diff -r 208b31032318 -r d29cce60f393 .classpath --- a/.classpath Fri Jun 19 13:41:57 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ - - - - - - - - - diff -r 208b31032318 -r d29cce60f393 .hgignore --- a/.hgignore Fri Jun 19 13:41:57 2015 -0700 +++ b/.hgignore Thu Dec 03 11:23:55 2015 -0800 @@ -1,8 +1,11 @@ syntax: glob .git -bin -gen +.gradle +.idea +html +app/build +app/src/main/assets/help launchpad-*.tar.gz local.properties *~ diff -r 208b31032318 -r d29cce60f393 .project --- a/.project Fri Jun 19 13:41:57 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ - - - 510Connectbot - - - - - - com.android.ide.eclipse.adt.ResourceManagerBuilder - - - - - com.android.ide.eclipse.adt.PreCompilerBuilder - - - - - org.eclipse.jdt.core.javabuilder - - - - - com.android.ide.eclipse.adt.ApkBuilder - - - - - - com.android.ide.eclipse.adt.AndroidNature - org.eclipse.jdt.core.javanature - - diff -r 208b31032318 -r d29cce60f393 .settings/org.eclipse.jdt.core.prefs --- a/.settings/org.eclipse.jdt.core.prefs Fri Jun 19 13:41:57 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,284 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 -org.eclipse.jdt.core.compiler.compliance=1.5 -org.eclipse.jdt.core.compiler.source=1.5 -org.eclipse.jdt.core.formatter.align_type_members_on_columns=false -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_assignment=0 -org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 -org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 -org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 -org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 -org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 -org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 -org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 -org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 -org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 -org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 -org.eclipse.jdt.core.formatter.blank_lines_after_package=1 -org.eclipse.jdt.core.formatter.blank_lines_before_field=0 -org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 -org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 -org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 -org.eclipse.jdt.core.formatter.blank_lines_before_method=1 -org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 -org.eclipse.jdt.core.formatter.blank_lines_before_package=0 -org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 -org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 -org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false -org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false -org.eclipse.jdt.core.formatter.comment.format_block_comments=true -org.eclipse.jdt.core.formatter.comment.format_header=false -org.eclipse.jdt.core.formatter.comment.format_html=true -org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true -org.eclipse.jdt.core.formatter.comment.format_line_comments=true -org.eclipse.jdt.core.formatter.comment.format_source_code=true -org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true -org.eclipse.jdt.core.formatter.comment.indent_root_tags=true -org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert -org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert -org.eclipse.jdt.core.formatter.comment.line_length=80 -org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true -org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true -org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false -org.eclipse.jdt.core.formatter.compact_else_if=true -org.eclipse.jdt.core.formatter.continuation_indentation=2 -org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 -org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off -org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on -org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false -org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true -org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true -org.eclipse.jdt.core.formatter.indent_empty_lines=false -org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true -org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true -org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true -org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false -org.eclipse.jdt.core.formatter.indentation.size=4 -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert -org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert -org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert -org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert -org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert -org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert -org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert -org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert -org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.join_lines_in_comments=true -org.eclipse.jdt.core.formatter.join_wrapped_lines=true -org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false -org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false -org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false -org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false -org.eclipse.jdt.core.formatter.lineSplit=100 -org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false -org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false -org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 -org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 -org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true -org.eclipse.jdt.core.formatter.tabulation.char=tab -org.eclipse.jdt.core.formatter.tabulation.size=4 -org.eclipse.jdt.core.formatter.use_on_off_tags=false -org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false -org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true -org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true -org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true diff -r 208b31032318 -r d29cce60f393 .settings/org.eclipse.jdt.ui.prefs --- a/.settings/org.eclipse.jdt.ui.prefs Fri Jun 19 13:41:57 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,113 +0,0 @@ -cleanup.add_default_serial_version_id=true -cleanup.add_generated_serial_version_id=false -cleanup.add_missing_annotations=true -cleanup.add_missing_deprecated_annotations=true -cleanup.add_missing_methods=false -cleanup.add_missing_nls_tags=false -cleanup.add_missing_override_annotations=true -cleanup.add_serial_version_id=false -cleanup.always_use_blocks=false -cleanup.always_use_parentheses_in_expressions=false -cleanup.always_use_this_for_non_static_field_access=false -cleanup.always_use_this_for_non_static_method_access=false -cleanup.convert_to_enhanced_for_loop=false -cleanup.correct_indentation=true -cleanup.format_source_code=true -cleanup.format_source_code_changes_only=false -cleanup.make_local_variable_final=true -cleanup.make_parameters_final=false -cleanup.make_private_fields_final=true -cleanup.make_type_abstract_if_missing_method=false -cleanup.make_variable_declarations_final=false -cleanup.never_use_blocks=false -cleanup.never_use_parentheses_in_expressions=true -cleanup.organize_imports=true -cleanup.qualify_static_field_accesses_with_declaring_class=false -cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true -cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true -cleanup.qualify_static_member_accesses_with_declaring_class=true -cleanup.qualify_static_method_accesses_with_declaring_class=false -cleanup.remove_private_constructors=true -cleanup.remove_trailing_whitespaces=true -cleanup.remove_trailing_whitespaces_all=true -cleanup.remove_trailing_whitespaces_ignore_empty=false -cleanup.remove_unnecessary_casts=true -cleanup.remove_unnecessary_nls_tags=true -cleanup.remove_unused_imports=true -cleanup.remove_unused_local_variables=false -cleanup.remove_unused_private_fields=true -cleanup.remove_unused_private_members=false -cleanup.remove_unused_private_methods=true -cleanup.remove_unused_private_types=true -cleanup.sort_members=true -cleanup.sort_members_all=true -cleanup.use_blocks=true -cleanup.use_blocks_only_for_return_and_throw=true -cleanup.use_parentheses_in_expressions=false -cleanup.use_this_for_non_static_field_access=true -cleanup.use_this_for_non_static_field_access_only_if_necessary=true -cleanup.use_this_for_non_static_method_access=true -cleanup.use_this_for_non_static_method_access_only_if_necessary=true -cleanup_profile=_ConnectBot Light Cleanup -cleanup_settings_version=2 -eclipse.preferences.version=1 -editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true -formatter_profile=_ConnectBot -formatter_settings_version=12 -org.eclipse.jdt.ui.ignorelowercasenames=true -org.eclipse.jdt.ui.importorder=java;javax;org;com; -org.eclipse.jdt.ui.javadoc=true -org.eclipse.jdt.ui.ondemandthreshold=99 -org.eclipse.jdt.ui.staticondemandthreshold=99 -org.eclipse.jdt.ui.text.custom_code_templates= -sp_cleanup.add_default_serial_version_id=true -sp_cleanup.add_generated_serial_version_id=false -sp_cleanup.add_missing_annotations=true -sp_cleanup.add_missing_deprecated_annotations=true -sp_cleanup.add_missing_methods=false -sp_cleanup.add_missing_nls_tags=false -sp_cleanup.add_missing_override_annotations=true -sp_cleanup.add_serial_version_id=false -sp_cleanup.always_use_blocks=true -sp_cleanup.always_use_parentheses_in_expressions=false -sp_cleanup.always_use_this_for_non_static_field_access=false -sp_cleanup.always_use_this_for_non_static_method_access=false -sp_cleanup.convert_to_enhanced_for_loop=false -sp_cleanup.correct_indentation=false -sp_cleanup.format_source_code=false -sp_cleanup.format_source_code_changes_only=true -sp_cleanup.make_local_variable_final=false -sp_cleanup.make_parameters_final=false -sp_cleanup.make_private_fields_final=true -sp_cleanup.make_type_abstract_if_missing_method=false -sp_cleanup.make_variable_declarations_final=false -sp_cleanup.never_use_blocks=false -sp_cleanup.never_use_parentheses_in_expressions=true -sp_cleanup.on_save_use_additional_actions=true -sp_cleanup.organize_imports=true -sp_cleanup.qualify_static_field_accesses_with_declaring_class=false -sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true -sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true -sp_cleanup.qualify_static_member_accesses_with_declaring_class=false -sp_cleanup.qualify_static_method_accesses_with_declaring_class=false -sp_cleanup.remove_private_constructors=true -sp_cleanup.remove_trailing_whitespaces=true -sp_cleanup.remove_trailing_whitespaces_all=true -sp_cleanup.remove_trailing_whitespaces_ignore_empty=false -sp_cleanup.remove_unnecessary_casts=true -sp_cleanup.remove_unnecessary_nls_tags=false -sp_cleanup.remove_unused_imports=false -sp_cleanup.remove_unused_local_variables=false -sp_cleanup.remove_unused_private_fields=true -sp_cleanup.remove_unused_private_members=false -sp_cleanup.remove_unused_private_methods=true -sp_cleanup.remove_unused_private_types=true -sp_cleanup.sort_members=false -sp_cleanup.sort_members_all=false -sp_cleanup.use_blocks=false -sp_cleanup.use_blocks_only_for_return_and_throw=false -sp_cleanup.use_parentheses_in_expressions=false -sp_cleanup.use_this_for_non_static_field_access=false -sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true -sp_cleanup.use_this_for_non_static_method_access=false -sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff -r 208b31032318 -r d29cce60f393 AndroidManifest.xml --- a/AndroidManifest.xml Fri Jun 19 13:41:57 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,122 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff -r 208b31032318 -r d29cce60f393 Makefile --- a/Makefile Fri Jun 19 13:41:57 2015 -0700 +++ b/Makefile Thu Dec 03 11:23:55 2015 -0800 @@ -1,13 +1,22 @@ #mc40 is "On Device Storage" #tc55 is "Internal Storage" -style=release -dest=/run/user/1000/gvfs/mtp*/*torage/Download -apk='bin/510Connectbot-$(style).apk' +style:=release +dest:=/run/user/1000/gvfs/mtp*/*torage/Download +apk:='app/build/outputs/apk/510Connectbot-$(style).apk' +ver:=$(shell grep versionName app/src/main/AndroidManifest.xml | cut -d'"' -f2) +id:=$(shell hg id --id) +da:=$(shell date +%Y-%m-%d) +version:=\ +\n\ +\n\ + 510Connectbot $(ver) ($(id) $(da))\n\ +\n + ifeq ($(style),release) - debuggable=false + task=assembleRelease else - debuggable=true + task=assembleDebug endif @@ -16,11 +25,12 @@ make builder builder: prep - sed -i -e 's/android:debuggable=".*"/android:debuggable="$(debuggable)"/g' AndroidManifest.xml - rm -rf gen bin - ndk-build clean; V=1 ndk-build - android update project -p . -t android-16 - ant $(style) + rm -rf app/build/* + echo -e "$(version)" >app/src/main/res/values/version.xml + cat app/src/main/res/values/version.xml + ./gradlew $(task) + mv app/build/outputs/apk/app-arm-$(style).apk $(apk) + ls -al app/build/outputs/apk prep: (cd help; make) @@ -38,19 +48,19 @@ buildicon: convert base.510.icon.png -background white -resize 500x500 -extent 1024x500 google.play.store/feature.510.icon.png - cp -a base.510.icon.png res/drawable-xxxhdpi/icon.png - convert base.510.icon.png -resize 144x144 res/drawable-xxhdpi/icon.png - convert base.510.icon.png -resize 96x96 res/drawable-xhdpi/icon.png - convert base.510.icon.png -resize 72x72 res/drawable-hdpi/icon.png - convert base.510.icon.png -resize 48x48 res/drawable-mdpi/icon.png - convert base.510.icon.png -resize 36x36 res/drawable-ldpi/icon.png + cp -a base.510.icon.png app/src/main/res/drawable-xxxhdpi/icon.png + convert base.510.icon.png -resize 144x144 app/src/main/res/drawable-xxhdpi/icon.png + convert base.510.icon.png -resize 96x96 app/src/main/res/drawable-xhdpi/icon.png + convert base.510.icon.png -resize 72x72 app/src/main/res/drawable-hdpi/icon.png + convert base.510.icon.png -resize 48x48 app/src/main/res/drawable-mdpi/icon.png + convert base.510.icon.png -resize 36x36 app/src/main/res/drawable-ldpi/icon.png - convert res/drawable-xxxhdpi/icon.png -resize 50% -colorspace Gray res/drawable-xxxhdpi/notification_icon.png - convert res/drawable-xxhdpi/icon.png -resize 50% -colorspace Gray res/drawable-xxhdpi/notification_icon.png - convert res/drawable-xhdpi/icon.png -resize 50% -colorspace Gray res/drawable-xhdpi/notification_icon.png - convert res/drawable-hdpi/icon.png -resize 50% -colorspace Gray res/drawable-hdpi/notification_icon.png - convert res/drawable-mdpi/icon.png -resize 50% -colorspace Gray res/drawable-mdpi/notification_icon.png - convert res/drawable-ldpi/icon.png -resize 50% -colorspace Gray res/drawable-ldpi/notification_icon.png + convert app/src/main/res/drawable-xxxhdpi/icon.png -resize 50% -colorspace Gray app/src/main/res/drawable-xxxhdpi/notification_icon.png + convert app/src/main/res/drawable-xxhdpi/icon.png -resize 50% -colorspace Gray app/src/main/res/drawable-xxhdpi/notification_icon.png + convert app/src/main/res/drawable-xhdpi/icon.png -resize 50% -colorspace Gray app/src/main/res/drawable-xhdpi/notification_icon.png + convert app/src/main/res/drawable-hdpi/icon.png -resize 50% -colorspace Gray app/src/main/res/drawable-hdpi/notification_icon.png + convert app/src/main/res/drawable-mdpi/icon.png -resize 50% -colorspace Gray app/src/main/res/drawable-mdpi/notification_icon.png + convert app/src/main/res/drawable-ldpi/icon.png -resize 50% -colorspace Gray app/src/main/res/drawable-ldpi/notification_icon.png indentc: indent --line-length100 \ diff -r 208b31032318 -r d29cce60f393 ant.properties --- a/ant.properties Fri Jun 19 13:41:57 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -# This file is used to override default values used by the Ant build system. -# -# This file must be checked in Version Control Systems, as it is -# integral to the build system of your project. - -# This file is only used by the Ant script. - -# You can use this to override default values such as -# 'source.dir' for the location of your java source folder and -# 'out.dir' for the location of your output folder. - -# You can also use it define how the release builds are signed by declaring -# the following properties: -# 'key.store' for the location of your keystore and -# 'key.alias' for the name of the key to use. -# The password will be asked during the build when you use the 'release' target. - -source.dir=src -out.dir=bin -key.store=510Connectbot.keystore -key.alias=510Connectbot diff -r 208b31032318 -r d29cce60f393 app/build.gradle --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/build.gradle Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,75 @@ +tasks.whenTaskAdded { task -> + if (task.name.contains("lint")) { + task.enabled = false + } +} + +apply plugin: 'com.android.model.application' + +model { + def signConf + + android { + compileSdkVersion = 16 + buildToolsVersion = "23.0.2" + + defaultConfig.with { + applicationId = "com.five_ten_sg.connectbot" + minSdkVersion.apiLevel = 8 + targetSdkVersion.apiLevel = 15 + } + } + + compileOptions.with { + sourceCompatibility = JavaVersion.VERSION_1_7 + targetCompatibility = JavaVersion.VERSION_1_7 + } + + android.ndk { + moduleName = "com_google_ase_Exec" + cppFlags.add("-Werror") + ldLibs.add("log") + } + + android.sources { + main { + jni { + source { + srcDir "Exec" + } + } + } + } + + android.signingConfigs { + create("signed") { + storeFile = file("../510Connectbot.keystore") + storePassword = new String(System.console().readPassword("\n\$ Enter keystore password: ")) + storeType = "jks" + keyAlias = "510Connectbot" + keyPassword = new String(System.console().readPassword("\n\$ Enter key password: ")) + signConf = it + } + } + + android.buildTypes { + release { + minifyEnabled = false + signingConfig = signConf + //proguardFiles.add(file('proguard-rules.txt')) + } + debug { + debuggable = true + } + } + + android.productFlavors { + create("arm") { + ndk.abiFilters.add("armeabi") + } + create("x86") { + ndk.abiFilters.add("x86") + } + } + +} diff -r 208b31032318 -r d29cce60f393 app/lint.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/lint.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/AndroidManifest.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/AndroidManifest.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/AbstractSFTPClient.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/AbstractSFTPClient.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,690 @@ +package ch.ethz.ssh2; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.SocketException; +import java.nio.charset.Charset; +import java.nio.charset.UnsupportedCharsetException; +import java.util.HashMap; +import java.util.Map; + +import ch.ethz.ssh2.channel.Channel; +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.packets.TypesWriter; +import ch.ethz.ssh2.sftp.AttribFlags; +import ch.ethz.ssh2.sftp.ErrorCodes; +import ch.ethz.ssh2.sftp.Packet; +import ch.ethz.ssh2.util.StringEncoder; + +/** + * @version $Id: AbstractSFTPClient.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public abstract class AbstractSFTPClient implements SFTPClient { + + private static final Logger log = Logger.getLogger(SFTPv3Client.class); + + private Session sess; + + private InputStream is; + private OutputStream os; + + private int next_request_id = 1000; + + private String charset; + + /** + * Parallel read requests maximum size. + */ + private static final int DEFAULT_MAX_PARALLELISM = 64; + + /** + * Parallel read requests. + */ + private int parallelism = DEFAULT_MAX_PARALLELISM; + + public void setRequestParallelism(int parallelism) { + this.parallelism = Math.min(parallelism, DEFAULT_MAX_PARALLELISM); + } + + /** + * Mapping request ID to request. + */ + private Map pendingReadQueue + = new HashMap(); + + /** + * Mapping request ID to request. + */ + private Map pendingStatusQueue + = new HashMap(); + + private PacketListener listener; + + protected AbstractSFTPClient(final Connection conn, final int version, final PacketListener listener) throws IOException { + this.listener = listener; + log.debug("Opening session and starting SFTP subsystem."); + sess = conn.openSession(); + sess.startSubSystem("sftp"); + is = sess.getStdout(); + os = new BufferedOutputStream(sess.getStdin(), 2048); + init(version); + } + + private void init(final int client_version) throws IOException { + // Send SSH_FXP_INIT with client version + TypesWriter tw = new TypesWriter(); + tw.writeUINT32(client_version); + sendMessage(Packet.SSH_FXP_INIT, 0, tw.getBytes()); + /* Receive SSH_FXP_VERSION */ + log.debug("Waiting for SSH_FXP_VERSION..."); + TypesReader tr = new TypesReader(receiveMessage(34000)); /* Should be enough for any reasonable server */ + int t = tr.readByte(); + listener.read(Packet.forName(t)); + + if (t != Packet.SSH_FXP_VERSION) { + log.warning(String.format("The server did not send a SSH_FXP_VERSION but %d", t)); + throw new PacketTypeException(t); + } + + final int protocol_version = tr.readUINT32(); + log.debug("SSH_FXP_VERSION: protocol_version = " + protocol_version); + + if (protocol_version != client_version) { + throw new IOException(String.format("Server protocol version %d does not match %d", + protocol_version, client_version)); + } + + // Both parties should from then on adhere to particular version of the protocol + + // Read and save extensions (if any) for later use + while (tr.remain() != 0) { + String name = tr.readString(); + listener.read(name); + byte[] value = tr.readByteString(); + log.debug(String.format("SSH_FXP_VERSION: extension: %s = '%s'", name, StringEncoder.GetString(value))); + } + } + + /** + * Queries the channel state + * + * @return True if the underlying session is in open state + */ + public boolean isConnected() { + return sess.getState() == Channel.STATE_OPEN; + } + + /** + * Close this SFTP session. NEVER forget to call this method to free up + * resources - even if you got an exception from one of the other methods. + * Sometimes these other methods may throw an exception, saying that the + * underlying channel is closed (this can happen, e.g., if the other server + * sent a close message.) However, as long as you have not called the + * close() method, you are likely wasting resources. + */ + public void close() { + sess.close(); + } + + /** + * Set the charset used to convert between Java Unicode Strings and byte encodings + * used by the server for paths and file names. + * + * @param charset the name of the charset to be used or null to use UTF-8. + * @throws java.io.IOException + * @see #getCharset() + */ + public void setCharset(String charset) throws IOException { + if (charset == null) { + this.charset = null; + return; + } + + try { + Charset.forName(charset); + } + catch (UnsupportedCharsetException e) { + throw new IOException("This charset is not supported", e); + } + + this.charset = charset; + } + + /** + * The currently used charset for filename encoding/decoding. + * + * @return The name of the charset (null if UTF-8 is used). + * @see #setCharset(String) + */ + public String getCharset() { + return charset; + } + + public abstract SFTPFileHandle openFile(String fileName, int flags, SFTPFileAttributes attr) throws IOException; + + public void mkdir(String dirName, int posixPermissions) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(dirName, this.getCharset()); + tw.writeUINT32(AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS); + tw.writeUINT32(posixPermissions); + sendMessage(Packet.SSH_FXP_MKDIR, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } + + public void rm(String fileName) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(fileName, this.getCharset()); + sendMessage(Packet.SSH_FXP_REMOVE, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } + + public void rmdir(String dirName) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(dirName, this.getCharset()); + sendMessage(Packet.SSH_FXP_RMDIR, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } + + public void mv(String oldPath, String newPath) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(oldPath, this.getCharset()); + tw.writeString(newPath, this.getCharset()); + sendMessage(Packet.SSH_FXP_RENAME, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } + + public String readLink(String path) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(path, charset); + sendMessage(Packet.SSH_FXP_READLINK, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_NAME) { + int count = tr.readUINT32(); + + if (count != 1) { + throw new PacketTypeException(t); + } + + return tr.readString(charset); + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + public void setstat(String path, SFTPFileAttributes attr) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(path, charset); + tw.writeBytes(attr.toBytes()); + sendMessage(Packet.SSH_FXP_SETSTAT, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } + + public void fsetstat(SFTPFileHandle handle, SFTPFileAttributes attr) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(handle.getHandle(), 0, handle.getHandle().length); + tw.writeBytes(attr.toBytes()); + sendMessage(Packet.SSH_FXP_FSETSTAT, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } + + public void createSymlink(String src, String target) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(src, charset); + tw.writeString(target, charset); + sendMessage(Packet.SSH_FXP_SYMLINK, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } + + public void createHardlink(String src, String target) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString("hardlink@openssh.com", charset); + tw.writeString(target, charset); + tw.writeString(src, charset); + sendMessage(Packet.SSH_FXP_EXTENDED, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } + + public String canonicalPath(String path) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(path, charset); + sendMessage(Packet.SSH_FXP_REALPATH, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_NAME) { + int count = tr.readUINT32(); + + if (count != 1) { + throw new PacketFormatException("The server sent an invalid SSH_FXP_NAME packet."); + } + + final String name = tr.readString(charset); + listener.read(name); + return name; + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + private void sendMessage(int type, int requestId, byte[] msg, int off, int len) throws IOException { + if (log.isDebugEnabled()) { + log.debug(String.format("Send message of type %d with request id %d", type, requestId)); + } + + listener.write(Packet.forName(type)); + int msglen = len + 1; + + if (type != Packet.SSH_FXP_INIT) { + msglen += 4; + } + + os.write(msglen >> 24); + os.write(msglen >> 16); + os.write(msglen >> 8); + os.write(msglen); + os.write(type); + + if (type != Packet.SSH_FXP_INIT) { + os.write(requestId >> 24); + os.write(requestId >> 16); + os.write(requestId >> 8); + os.write(requestId); + } + + os.write(msg, off, len); + os.flush(); + } + + protected void sendMessage(int type, int requestId, byte[] msg) throws IOException { + sendMessage(type, requestId, msg, 0, msg.length); + } + + private void readBytes(byte[] buff, int pos, int len) throws IOException { + while (len > 0) { + int count = is.read(buff, pos, len); + + if (count < 0) { + throw new SocketException("Unexpected end of stream."); + } + + len -= count; + pos += count; + } + } + + /** + * Read a message and guarantee that the contents is not larger than + * maxlen bytes. + *

+ * Note: receiveMessage(34000) actually means that the message may be up to 34004 + * bytes (the length attribute preceding the contents is 4 bytes). + * + * @param maxlen + * @return the message contents + * @throws IOException + */ + protected byte[] receiveMessage(int maxlen) throws IOException { + byte[] msglen = new byte[4]; + readBytes(msglen, 0, 4); + int len = (((msglen[0] & 0xff) << 24) | ((msglen[1] & 0xff) << 16) | ((msglen[2] & 0xff) << 8) | (msglen[3] & 0xff)); + + if ((len > maxlen) || (len <= 0)) { + throw new PacketFormatException(String.format("Illegal SFTP packet length %d", len)); + } + + byte[] msg = new byte[len]; + readBytes(msg, 0, len); + return msg; + } + + protected int generateNextRequestID() { + synchronized (this) { + return next_request_id++; + } + } + + protected void closeHandle(byte[] handle) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(handle, 0, handle.length); + sendMessage(Packet.SSH_FXP_CLOSE, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } + + private void readStatus() throws IOException { + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + // Search the pending queue + OutstandingStatusRequest status = pendingStatusQueue.remove(tr.readUINT32()); + + if (null == status) { + throw new RequestMismatchException(); + } + + // Evaluate the answer + if (t == Packet.SSH_FXP_STATUS) { + // In any case, stop sending more packets + int code = tr.readUINT32(); + + if (log.isDebugEnabled()) { + String[] desc = ErrorCodes.getDescription(code); + log.debug("Got SSH_FXP_STATUS (" + status.req_id + ") (" + ((desc != null) ? desc[0] : "UNKNOWN") + ")"); + } + + if (code == ErrorCodes.SSH_FX_OK) { + return; + } + + String msg = tr.readString(); + listener.read(msg); + throw new SFTPException(msg, code); + } + + throw new PacketTypeException(t); + } + + private void readPendingReadStatus() throws IOException { + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + // Search the pending queue + OutstandingReadRequest status = pendingReadQueue.remove(tr.readUINT32()); + + if (null == status) { + throw new RequestMismatchException(); + } + + // Evaluate the answer + if (t == Packet.SSH_FXP_STATUS) { + // In any case, stop sending more packets + int code = tr.readUINT32(); + + if (log.isDebugEnabled()) { + String[] desc = ErrorCodes.getDescription(code); + log.debug("Got SSH_FXP_STATUS (" + status.req_id + ") (" + ((desc != null) ? desc[0] : "UNKNOWN") + ")"); + } + + if (code == ErrorCodes.SSH_FX_OK) { + return; + } + + if (code == ErrorCodes.SSH_FX_EOF) { + return; + } + + String msg = tr.readString(); + listener.read(msg); + throw new SFTPException(msg, code); + } + + throw new PacketTypeException(t); + } + + protected void expectStatusOKMessage(int id) throws IOException { + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != id) { + throw new RequestMismatchException(); + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + + if (errorCode == ErrorCodes.SSH_FX_OK) { + return; + } + + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + public void closeFile(SFTPFileHandle handle) throws IOException { + while (!pendingReadQueue.isEmpty()) { + this.readPendingReadStatus(); + } + + while (!pendingStatusQueue.isEmpty()) { + this.readStatus(); + } + + closeHandle(handle.getHandle()); + } + + public int read(SFTPFileHandle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException { + boolean errorOccured = false; + int remaining = len * parallelism; + //int clientOffset = dstoff; + long serverOffset = fileOffset; + + for (OutstandingReadRequest r : pendingReadQueue.values()) { + // Server offset should take pending requests into account. + serverOffset += r.len; + } + + while (true) { + // Stop if there was an error and no outstanding request + if ((pendingReadQueue.size() == 0) && errorOccured) { + break; + } + + // Send as many requests as we are allowed to + while (pendingReadQueue.size() < parallelism) { + if (errorOccured) { + break; + } + + // Send the next read request + OutstandingReadRequest req = new OutstandingReadRequest(); + req.req_id = generateNextRequestID(); + req.serverOffset = serverOffset; + req.len = (remaining > len) ? len : remaining; + req.buffer = dst; + req.dstOffset = dstoff; + serverOffset += req.len; + //clientOffset += req.len; + remaining -= req.len; + sendReadRequest(req.req_id, handle, req.serverOffset, req.len); + pendingReadQueue.put(req.req_id, req); + } + + if (pendingReadQueue.size() == 0) { + break; + } + + // Receive a single answer + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + // Search the pending queue + OutstandingReadRequest req = pendingReadQueue.remove(tr.readUINT32()); + + if (null == req) { + throw new RequestMismatchException(); + } + + // Evaluate the answer + if (t == Packet.SSH_FXP_STATUS) { + /* In any case, stop sending more packets */ + int code = tr.readUINT32(); + String msg = tr.readString(); + listener.read(msg); + + if (log.isDebugEnabled()) { + String[] desc = ErrorCodes.getDescription(code); + log.debug("Got SSH_FXP_STATUS (" + req.req_id + ") (" + ((desc != null) ? desc[0] : "UNKNOWN") + ")"); + } + + // Flag to read all pending requests but don't send any more. + errorOccured = true; + + if (pendingReadQueue.isEmpty()) { + if (ErrorCodes.SSH_FX_EOF == code) { + return -1; + } + + throw new SFTPException(msg, code); + } + } + else if (t == Packet.SSH_FXP_DATA) { + // OK, collect data + int readLen = tr.readUINT32(); + + if ((readLen < 0) || (readLen > req.len)) { + throw new PacketFormatException("The server sent an invalid length field in a SSH_FXP_DATA packet."); + } + + if (log.isDebugEnabled()) { + log.debug("Got SSH_FXP_DATA (" + req.req_id + ") " + req.serverOffset + "/" + readLen + + " (requested: " + req.len + ")"); + } + + // Read bytes into buffer + tr.readBytes(req.buffer, req.dstOffset, readLen); + + if (readLen < req.len) { + /* Send this request packet again to request the remaing data in this slot. */ + req.req_id = generateNextRequestID(); + req.serverOffset += readLen; + req.len -= readLen; + log.debug("Requesting again: " + req.serverOffset + "/" + req.len); + sendReadRequest(req.req_id, handle, req.serverOffset, req.len); + pendingReadQueue.put(req.req_id, req); + } + + return readLen; + } + else { + throw new PacketTypeException(t); + } + } + + // Should never reach here. + throw new SFTPException("No EOF reached", -1); + } + + private void sendReadRequest(int id, SFTPFileHandle handle, long offset, int len) throws IOException { + TypesWriter tw = new TypesWriter(); + tw.writeString(handle.getHandle(), 0, handle.getHandle().length); + tw.writeUINT64(offset); + tw.writeUINT32(len); + sendMessage(Packet.SSH_FXP_READ, id, tw.getBytes()); + } + + public void write(SFTPFileHandle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException { + while (len > 0) { + int writeRequestLen = len; + + if (writeRequestLen > 32768) { + writeRequestLen = 32768; + } + + // Send the next write request + OutstandingStatusRequest req = new OutstandingStatusRequest(); + req.req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(handle.getHandle(), 0, handle.getHandle().length); + tw.writeUINT64(fileOffset); + tw.writeString(src, srcoff, writeRequestLen); + sendMessage(Packet.SSH_FXP_WRITE, req.req_id, tw.getBytes()); + pendingStatusQueue.put(req.req_id, req); + + // Only read next status if parallelism reached + while (pendingStatusQueue.size() >= parallelism) { + this.readStatus(); + } + + fileOffset += writeRequestLen; + srcoff += writeRequestLen; + len -= writeRequestLen; + } + } + + + /** + * A read is divided into multiple requests sent sequentially before + * reading any status from the server + */ + private static class OutstandingReadRequest { + int req_id; + /** + * Read offset to request on server starting at the file offset for the first request. + */ + long serverOffset; + /** + * Length of requested data + */ + int len; + /** + * Offset in destination buffer + */ + int dstOffset; + /** + * Temporary buffer + */ + byte[] buffer; + } + + /** + * A read is divided into multiple requests sent sequentially before + * reading any status from the server + */ + private static final class OutstandingStatusRequest { + int req_id; + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/AuthAgentCallback.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/AuthAgentCallback.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,64 @@ +package ch.ethz.ssh2; + +import java.security.KeyPair; +import java.util.Map; + +/** + * AuthAgentCallback. + * + * @author Kenny Root + * @version $Id$ + */ +public interface AuthAgentCallback { + + /** + * @return array of blobs containing the OpenSSH-format encoded public keys + */ + Map retrieveIdentities(); + + /** + * @param pair A RSAPrivateKey or DSAPrivateKey or ECPrivateKey + * containing a DSA or RSA or EC private key of + * the user in standard java key format. + * @param comment comment associated with this key + * @param confirmUse whether to prompt before using this key + * @param lifetime lifetime in seconds for key to be remembered + * @return success or failure + */ + boolean addIdentity(KeyPair pair, String comment, boolean confirmUse, int lifetime); + + /** + * @param publicKey byte blob containing the OpenSSH-format encoded public key + * @return success or failure + */ + boolean removeIdentity(byte[] publicKey); + + /** + * @return success or failure + */ + boolean removeAllIdentities(); + + /** + * @param publicKey byte blob containing the OpenSSH-format encoded public key + * @return A RSAPrivateKey or DSAPrivateKey + * containing a DSA or RSA or EC private key of + * the user in standard java key format. + */ + KeyPair getKeyPair(byte[] publicKey); + + /** + * @return + */ + boolean isAgentLocked(); + + /** + * @param lockPassphrase + */ + boolean setAgentLock(String lockPassphrase); + + /** + * @param unlockPassphrase + * @return + */ + boolean requestAgentUnlock(String unlockPassphrase); +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/AuthenticationResult.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/AuthenticationResult.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,21 @@ + +package ch.ethz.ssh2; + +public enum AuthenticationResult { + + /** + * + */ + SUCCESS, + /** + * The authentication request to which this is a response was successful, however, more + * authentication requests are needed (multi-method authentication sequence). + * + * @see ServerAuthenticationCallback#getRemainingAuthMethods(ServerConnection) + */ + PARTIAL_SUCCESS, + /** + * The server rejected the authentication request. + */ + FAILURE +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/ChannelCondition.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/ChannelCondition.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * Contains constants that can be used to specify what conditions to wait for on + * a SSH-2 channel (e.g., represented by a {@link Session}). + * + * @see Session#waitForCondition(int, long) + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public abstract interface ChannelCondition { + /** + * A timeout has occurred, none of your requested conditions is fulfilled. + * However, other conditions may be true - therefore, NEVER use the "==" + * operator to test for this (or any other) condition. Always use + * something like ((cond & ChannelCondition.CLOSED) != 0). + */ + public static final int TIMEOUT = 1; + + /** + * The underlying SSH-2 channel, however not necessarily the whole connection, + * has been closed. This implies EOF. Note that there may still + * be unread stdout or stderr data in the local window, i.e, STDOUT_DATA + * or/and STDERR_DATA may be set at the same time. + */ + public static final int CLOSED = 2; + + /** + * There is stdout data available that is ready to be consumed. + */ + public static final int STDOUT_DATA = 4; + + /** + * There is stderr data available that is ready to be consumed. + */ + public static final int STDERR_DATA = 8; + + /** + * EOF on has been reached, no more _new_ stdout or stderr data will arrive + * from the remote server. However, there may be unread stdout or stderr + * data, i.e, STDOUT_DATA or/and STDERR_DATA + * may be set at the same time. + */ + public static final int EOF = 16; + + /** + * The exit status of the remote process is available. + * Some servers never send the exist status, or occasionally "forget" to do so. + */ + public static final int EXIT_STATUS = 32; + + /** + * The exit signal of the remote process is available. + */ + public static final int EXIT_SIGNAL = 64; + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/Connection.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/Connection.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,1452 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2; + +import java.io.CharArrayWriter; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.security.KeyPair; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import ch.ethz.ssh2.auth.AgentProxy; +import ch.ethz.ssh2.auth.AuthenticationManager; +import ch.ethz.ssh2.channel.ChannelManager; +import ch.ethz.ssh2.compression.CompressionFactory; +import ch.ethz.ssh2.crypto.CryptoWishList; +import ch.ethz.ssh2.crypto.SecureRandomFix; +import ch.ethz.ssh2.crypto.cipher.BlockCipherFactory; +import ch.ethz.ssh2.crypto.digest.MAC; +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.PacketIgnore; +import ch.ethz.ssh2.transport.ClientTransportManager; +import ch.ethz.ssh2.transport.HTTPProxyClientTransportManager; +import ch.ethz.ssh2.transport.KexManager; +import ch.ethz.ssh2.util.TimeoutService.TimeoutToken; +import ch.ethz.ssh2.util.TimeoutService; + +/** + * A Connection is used to establish an encrypted TCP/IP + * connection to a SSH-2 server. + *

+ * Typically, one + *

    + *
  1. creates a {@link #Connection(String) Connection} object.
  2. + *
  3. calls the {@link #connect() connect()} method.
  4. + *
  5. calls some of the authentication methods (e.g., {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()}).
  6. + *
  7. calls one or several times the {@link #openSession() openSession()} method.
  8. + *
  9. finally, one must close the connection and release resources with the {@link #close() close()} method.
  10. + *
+ * + * @author Christian Plattner + * @version $Id: Connection.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ + +public class Connection { + protected static final Logger log = Logger.getLogger(Connection.class); + + /** + * The identifier presented to the SSH-2 server. This is the same + * as the "softwareversion" defined in RFC 4253. + *

+ * NOTE: As per the RFC, the "softwareversion" string MUST consist of printable + * US-ASCII characters, with the exception of whitespace characters and the minus sign (-). + */ + private String softwareversion + = String.format("Ganymed_%s", Version.getSpecification()); + + /* Will be used to generate all random data needed for the current connection. + * Note: SecureRandom.nextBytes() is thread safe. + */ + + private SecureRandomFix generator; + + /** + * Unless you know what you are doing, you will never need this. + * + * @return The list of supported cipher algorithms by this implementation. + */ + + public static synchronized String[] getAvailableCiphers() { + return BlockCipherFactory.getDefaultCipherList(); + } + + /** + * Unless you know what you are doing, you will never need this. + * + * @return The list of supported MAC algorthims by this implementation. + */ + + public static synchronized String[] getAvailableMACs() { + return MAC.getMacList(); + } + + /** + * Unless you know what you are doing, you will never need this. + * + * @return The list of supported server host key algorthims by this implementation. + */ + + public static synchronized String[] getAvailableServerHostKeyAlgorithms() { + return KexManager.getDefaultServerHostkeyAlgorithmList(); + } + + private AuthenticationManager am; + + private boolean authenticated; + private ChannelManager cm; + + private CryptoWishList cryptoWishList + = new CryptoWishList(); + + private DHGexParameters dhgexpara + = new DHGexParameters(); + + private final String hostname; + + private final int port; + + private ClientTransportManager tm; + + private boolean tcpNoDelay = false; + + private HTTPProxyData proxy; + + private List connectionMonitors + = new ArrayList(); + + /** + * Prepares a fresh Connection object which can then be used + * to establish a connection to the specified SSH-2 server. + *

+ * Same as {@link #Connection(String, int) Connection(hostname, 22)}. + * + * @param hostname the hostname of the SSH-2 server. + */ + public Connection(String hostname) { + this(hostname, 22); + } + + /** + * Prepares a fresh Connection object which can then be used + * to establish a connection to the specified SSH-2 server. + * + * @param hostname the host where we later want to connect to. + * @param port port on the server, normally 22. + */ + public Connection(String hostname, int port) { + this.hostname = hostname; + this.port = port; + } + + /** + * Prepares a fresh Connection object which can then be used + * to establish a connection to the specified SSH-2 server. + * + * @param hostname the host where we later want to connect to. + * @param port port on the server, normally 22. + * @param softwareversion Allows you to set a custom "softwareversion" string as defined in RFC 4253. + * NOTE: As per the RFC, the "softwareversion" string MUST consist of printable + * US-ASCII characters, with the exception of whitespace characters and the minus sign (-). + */ + public Connection(String hostname, int port, String softwareversion) { + this.hostname = hostname; + this.port = port; + this.softwareversion = softwareversion; + } + + public Connection(String hostname, int port, final HTTPProxyData proxy) { + this.hostname = hostname; + this.port = port; + this.proxy = proxy; + } + + public Connection(String hostname, int port, String softwareversion, final HTTPProxyData proxy) { + this.hostname = hostname; + this.port = port; + this.softwareversion = softwareversion; + this.proxy = proxy; + } + + /** + * After a successful connect, one has to authenticate oneself. This method + * is based on DSA (it uses DSA to sign a challenge sent by the server). + *

+ * If the authentication phase is complete, true will be + * returned. If the server does not accept the request (or if further + * authentication steps are needed), false is returned and + * one can retry either by using this or any other authentication method + * (use the getRemainingAuthMethods method to get a list of + * the remaining possible methods). + * + * @param user A String holding the username. + * @param pem A String containing the DSA private key of the + * user in OpenSSH key format (PEM, you can't miss the + * "-----BEGIN DSA PRIVATE KEY-----" tag). The string may contain + * linefeeds. + * @param password If the PEM string is 3DES encrypted ("DES-EDE3-CBC"), then you + * must specify the password. Otherwise, this argument will be + * ignored and can be set to null. + * @return whether the connection is now authenticated. + * @throws IOException + * @deprecated You should use one of the {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()} + * methods, this method is just a wrapper for it and will + * disappear in future builds. + */ + @Deprecated + + public synchronized boolean authenticateWithDSA(String user, String pem, String password) throws IOException { + if (tm == null) { + throw new IllegalStateException("Connection is not established!"); + } + + if (authenticated) { + throw new IllegalStateException("Connection is already authenticated!"); + } + + if (am == null) { + am = new AuthenticationManager(tm); + } + + if (cm == null) { + cm = new ChannelManager(tm); + } + + if (user == null) { + throw new IllegalArgumentException("user argument is null"); + } + + if (pem == null) { + throw new IllegalArgumentException("pem argument is null"); + } + + authenticated = am.authenticatePublicKey(user, pem.toCharArray(), password, getOrCreateSecureRND()); + return authenticated; + } + + /** + * A wrapper that calls {@link #authenticateWithKeyboardInteractive(String, String[], InteractiveCallback) + * authenticateWithKeyboardInteractivewith} a null submethod list. + * + * @param user A String holding the username. + * @param cb An InteractiveCallback which will be used to + * determine the responses to the questions asked by the server. + * @return whether the connection is now authenticated. + * @throws IOException + */ + + public synchronized boolean authenticateWithKeyboardInteractive(String user, InteractiveCallback cb) + throws IOException { + return authenticateWithKeyboardInteractive(user, null, cb); + } + + /** + * After a successful connect, one has to authenticate oneself. This method + * is based on "keyboard-interactive", specified in + * draft-ietf-secsh-auth-kbdinteract-XX. Basically, you have to define a + * callback object which will be feeded with challenges generated by the + * server. Answers are then sent back to the server. It is possible that the + * callback will be called several times during the invocation of this + * method (e.g., if the server replies to the callback's answer(s) with + * another challenge...) + *

+ * If the authentication phase is complete, true will be + * returned. If the server does not accept the request (or if further + * authentication steps are needed), false is returned and + * one can retry either by using this or any other authentication method + * (use the getRemainingAuthMethods method to get a list of + * the remaining possible methods). + *

+ * Note: some SSH servers advertise "keyboard-interactive", however, any + * interactive request will be denied (without having sent any challenge to + * the client). + * + * @param user A String holding the username. + * @param submethods An array of submethod names, see + * draft-ietf-secsh-auth-kbdinteract-XX. May be null + * to indicate an empty list. + * @param cb An InteractiveCallback which will be used to + * determine the responses to the questions asked by the server. + * @return whether the connection is now authenticated. + * @throws IOException + */ + + public synchronized boolean authenticateWithKeyboardInteractive(String user, String[] submethods, + InteractiveCallback cb) throws IOException { + if (cb == null) { + throw new IllegalArgumentException("Callback may not ne NULL!"); + } + + if (tm == null) { + throw new IllegalStateException("Connection is not established!"); + } + + if (authenticated) { + throw new IllegalStateException("Connection is already authenticated!"); + } + + if (am == null) { + am = new AuthenticationManager(tm); + } + + if (cm == null) { + cm = new ChannelManager(tm); + } + + if (user == null) { + throw new IllegalArgumentException("user argument is null"); + } + + authenticated = am.authenticateInteractive(user, submethods, cb); + return authenticated; + } + + public synchronized boolean authenticateWithAgent(String user, AgentProxy proxy) throws IOException { + if (tm == null) { + throw new IllegalStateException("Connection is not established!"); + } + + if (authenticated) { + throw new IllegalStateException("Connection is already authenticated!"); + } + + if (am == null) { + am = new AuthenticationManager(tm); + } + + if (cm == null) { + cm = new ChannelManager(tm); + } + + if (user == null) { + throw new IllegalArgumentException("user argument is null"); + } + + authenticated = am.authenticatePublicKey(user, proxy); + return authenticated; + } + + /** + * After a successful connect, one has to authenticate oneself. This method + * sends username and password to the server. + *

+ * If the authentication phase is complete, true will be + * returned. If the server does not accept the request (or if further + * authentication steps are needed), false is returned and + * one can retry either by using this or any other authentication method + * (use the getRemainingAuthMethods method to get a list of + * the remaining possible methods). + *

+ * Note: if this method fails, then please double-check that it is actually + * offered by the server (use {@link #getRemainingAuthMethods(String) getRemainingAuthMethods()}. + *

+ * Often, password authentication is disabled, but users are not aware of it. + * Many servers only offer "publickey" and "keyboard-interactive". However, + * even though "keyboard-interactive" *feels* like password authentication + * (e.g., when using the putty or openssh clients) it is *not* the same mechanism. + * + * @param user + * @param password + * @return if the connection is now authenticated. + * @throws IOException + */ + + public synchronized boolean authenticateWithPassword(String user, String password) throws IOException { + if (tm == null) { + throw new IllegalStateException("Connection is not established!"); + } + + if (authenticated) { + throw new IllegalStateException("Connection is already authenticated!"); + } + + if (am == null) { + am = new AuthenticationManager(tm); + } + + if (cm == null) { + cm = new ChannelManager(tm); + } + + if (user == null) { + throw new IllegalArgumentException("user argument is null"); + } + + if (password == null) { + throw new IllegalArgumentException("password argument is null"); + } + + authenticated = am.authenticatePassword(user, password); + return authenticated; + } + + /** + * After a successful connect, one has to authenticate oneself. + * This method can be used to explicitly use the special "none" + * authentication method (where only a username has to be specified). + *

+ * Note 1: The "none" method may always be tried by clients, however as by + * the specs, the server will not explicitly announce it. In other words, + * the "none" token will never show up in the list returned by + * {@link #getRemainingAuthMethods(String)}. + *

+ * Note 2: no matter which one of the authenticateWithXXX() methods + * you call, the library will always issue exactly one initial "none" + * authentication request to retrieve the initially allowed list of + * authentication methods by the server. Please read RFC 4252 for the + * details. + *

+ * If the authentication phase is complete, true will be + * returned. If further authentication steps are needed, false + * is returned and one can retry by any other authentication method + * (use the getRemainingAuthMethods method to get a list of + * the remaining possible methods). + * + * @param user + * @return if the connection is now authenticated. + * @throws IOException + */ + + public synchronized boolean authenticateWithNone(String user) throws IOException { + if (tm == null) { + throw new IllegalStateException("Connection is not established!"); + } + + if (authenticated) { + throw new IllegalStateException("Connection is already authenticated!"); + } + + if (am == null) { + am = new AuthenticationManager(tm); + } + + if (cm == null) { + cm = new ChannelManager(tm); + } + + if (user == null) { + throw new IllegalArgumentException("user argument is null"); + } + + /* Trigger the sending of the PacketUserauthRequestNone packet */ + /* (if not already done) */ + authenticated = am.authenticateNone(user); + return authenticated; + } + + /** + * After a successful connect, one has to authenticate oneself. + * The authentication method "publickey" works by signing a challenge + * sent by the server. The signature is either DSA or RSA based - it + * just depends on the type of private key you specify, either a DSA + * or RSA private key in PEM format. And yes, this is may seem to be a + * little confusing, the method is called "publickey" in the SSH-2 protocol + * specification, however since we need to generate a signature, you + * actually have to supply a private key =). + *

+ * The private key contained in the PEM file may also be encrypted ("Proc-Type: 4,ENCRYPTED"). + * The library supports DES-CBC and DES-EDE3-CBC encryption, as well + * as the more exotic PEM encrpytions AES-128-CBC, AES-192-CBC and AES-256-CBC. + *

+ * If the authentication phase is complete, true will be + * returned. If the server does not accept the request (or if further + * authentication steps are needed), false is returned and + * one can retry either by using this or any other authentication method + * (use the getRemainingAuthMethods method to get a list of + * the remaining possible methods). + *

+ * NOTE PUTTY USERS: Event though your key file may start with "-----BEGIN..." + * it is not in the expected format. You have to convert it to the OpenSSH + * key format by using the "puttygen" tool (can be downloaded from the Putty + * website). Simply load your key and then use the "Conversions/Export OpenSSH key" + * functionality to get a proper PEM file. + * + * @param user A String holding the username. + * @param pemPrivateKey A char[] containing a DSA or RSA private key of the + * user in OpenSSH key format (PEM, you can't miss the + * "-----BEGIN DSA PRIVATE KEY-----" or "-----BEGIN RSA PRIVATE KEY-----" + * tag). The char array may contain linebreaks/linefeeds. + * @param password If the PEM structure is encrypted ("Proc-Type: 4,ENCRYPTED") then + * you must specify a password. Otherwise, this argument will be ignored + * and can be set to null. + * @return whether the connection is now authenticated. + * @throws IOException + */ + + public synchronized boolean authenticateWithPublicKey(String user, char[] pemPrivateKey, String password) + throws IOException { + if (tm == null) { + throw new IllegalStateException("Connection is not established!"); + } + + if (authenticated) { + throw new IllegalStateException("Connection is already authenticated!"); + } + + if (am == null) { + am = new AuthenticationManager(tm); + } + + if (cm == null) { + cm = new ChannelManager(tm); + } + + if (user == null) { + throw new IllegalArgumentException("user argument is null"); + } + + if (pemPrivateKey == null) { + throw new IllegalArgumentException("pemPrivateKey argument is null"); + } + + authenticated = am.authenticatePublicKey(user, pemPrivateKey, password, getOrCreateSecureRND()); + return authenticated; + } + + /** + * After a successful connect, one has to authenticate oneself. The + * authentication method "publickey" works by signing a challenge sent by + * the server. The signature is either DSA or RSA based - it just depends on + * the type of private key you specify, either a DSA or RSA private key in + * PEM format. And yes, this is may seem to be a little confusing, the + * method is called "publickey" in the SSH-2 protocol specification, however + * since we need to generate a signature, you actually have to supply a + * private key =). + *

+ * If the authentication phase is complete, true will be + * returned. If the server does not accept the request (or if further + * authentication steps are needed), false is returned and + * one can retry either by using this or any other authentication method + * (use the getRemainingAuthMethods method to get a list of + * the remaining possible methods). + * + * @param user + * A String holding the username. + * @param pair + * A RSAPrivateKey or DSAPrivateKey + * containing a DSA or RSA private key of + * the user in Trilead object format. + * + * @return whether the connection is now authenticated. + * @throws IOException + */ + + public synchronized boolean authenticateWithPublicKey(String user, KeyPair pair) + throws IOException { + if (tm == null) + throw new IllegalStateException("Connection is not established!"); + + if (authenticated) + throw new IllegalStateException("Connection is already authenticated!"); + + if (am == null) + am = new AuthenticationManager(tm); + + if (cm == null) + cm = new ChannelManager(tm); + + if (user == null) + throw new IllegalArgumentException("user argument is null"); + + if (pair == null) + throw new IllegalArgumentException("Key pair argument is null"); + + authenticated = am.authenticatePublicKey(user, pair, getOrCreateSecureRND()); + return authenticated; + } + + /** + * A convenience wrapper function which reads in a private key (PEM format, either DSA or RSA) + * and then calls authenticateWithPublicKey(String, char[], String). + *

+ * NOTE PUTTY USERS: Event though your key file may start with "-----BEGIN..." + * it is not in the expected format. You have to convert it to the OpenSSH + * key format by using the "puttygen" tool (can be downloaded from the Putty + * website). Simply load your key and then use the "Conversions/Export OpenSSH key" + * functionality to get a proper PEM file. + * + * @param user A String holding the username. + * @param pemFile A File object pointing to a file containing a DSA or RSA + * private key of the user in OpenSSH key format (PEM, you can't miss the + * "-----BEGIN DSA PRIVATE KEY-----" or "-----BEGIN RSA PRIVATE KEY-----" + * tag). + * @param password If the PEM file is encrypted then you must specify the password. + * Otherwise, this argument will be ignored and can be set to null. + * @return whether the connection is now authenticated. + * @throws IOException + */ + + public synchronized boolean authenticateWithPublicKey(String user, File pemFile, String password) + throws IOException { + if (pemFile == null) { + throw new IllegalArgumentException("pemFile argument is null"); + } + + char[] buff = new char[256]; + CharArrayWriter cw = new CharArrayWriter(); + FileReader fr = new FileReader(pemFile); + + while (true) { + int len = fr.read(buff); + + if (len < 0) { + break; + } + + cw.write(buff, 0, len); + } + + fr.close(); + return authenticateWithPublicKey(user, cw.toCharArray(), password); + } + + /** + * Add a {@link ConnectionMonitor} to this connection. Can be invoked at any time, + * but it is best to add connection monitors before invoking + * connect() to avoid glitches (e.g., you add a connection monitor after + * a successful connect(), but the connection has died in the mean time. Then, + * your connection monitor won't be notified.) + *

+ * You can add as many monitors as you like. If a monitor has already been added, then + * this method does nothing. + * + * @param cmon An object implementing the {@link ConnectionMonitor} interface. + * @see ConnectionMonitor + */ + + public synchronized void addConnectionMonitor(ConnectionMonitor cmon) { + if (!connectionMonitors.contains(cmon)) { + connectionMonitors.add(cmon); + + if (tm != null) { + tm.setConnectionMonitors(connectionMonitors); + } + } + } + + /** + * Remove a {@link ConnectionMonitor} from this connection. + * + * @param cmon + * @return whether the monitor could be removed + */ + + public synchronized boolean removeConnectionMonitor(ConnectionMonitor cmon) { + boolean existed = connectionMonitors.remove(cmon); + + if (tm != null) { + tm.setConnectionMonitors(connectionMonitors); + } + + return existed; + } + + /** + * Controls whether compression is used on the link or not. + *

+ * Note: This can only be called before connect() + * @param enabled whether to enable compression + * @throws IOException + */ + + public synchronized void setCompression(boolean enabled) throws IOException { + if (tm != null) + throw new IOException("Connection to " + hostname + " is already in connected state!"); + + if (enabled) enableCompression(); + else disableCompression(); + } + + /** + * Close the connection to the SSH-2 server. All assigned sessions will be + * closed, too. Can be called at any time. Don't forget to call this once + * you don't need a connection anymore - otherwise the receiver thread may + * run forever. + */ + + // cannot be synchronized, since Connection.connect() is synchronized, and + // if the key exchange fails, another thread will try to close(). + + public void close() { + log.debug("Connection.close()"); + Throwable t = new Throwable("Closed due to user request."); + close(t, false); + } + + public void close(Throwable t, boolean hard) { + log.debug(String.format("Connection.close(%s hard=%b)", t.getMessage(), hard)); + if (cm != null) { + cm.closeAllChannels(); + } + + if (tm != null) { + tm.close(t, hard == false); + tm = null; + } + + am = null; + cm = null; + authenticated = false; + } + + /** + * Same as {@link #connect(ServerHostKeyVerifier, int, int) connect(null, 0, 0)}. + * + * @return see comments for the {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)} method. + * @throws IOException + */ + + public synchronized ConnectionInfo connect() throws IOException { + return connect(null, 0, 0); + } + + /** + * Same as {@link #connect(ServerHostKeyVerifier, int, int) connect(verifier, 0, 0)}. + * + * @return see comments for the {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)} method. + * @throws IOException + */ + + public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier) throws IOException { + return connect(verifier, 0, 0); + } + + /** + * Connect to the SSH-2 server and, as soon as the server has presented its + * host key, use the {@link ServerHostKeyVerifier#verifyServerHostKey(String, + * int, String, byte[]) ServerHostKeyVerifier.verifyServerHostKey()} + * method of the verifier to ask for permission to proceed. + * If verifier is null, then any host key will be + * accepted - this is NOT recommended, since it makes man-in-the-middle attackes + * VERY easy (somebody could put a proxy SSH server between you and the real server). + *

+ * Note: The verifier will be called before doing any crypto calculations + * (i.e., diffie-hellman). Therefore, if you don't like the presented host key then + * no CPU cycles are wasted (and the evil server has less information about us). + *

+ * However, it is still possible that the server presented a fake host key: the server + * cheated (typically a sign for a man-in-the-middle attack) and is not able to generate + * a signature that matches its host key. Don't worry, the library will detect such + * a scenario later when checking the signature (the signature cannot be checked before + * having completed the diffie-hellman exchange). + *

+ * Note 2: The {@link ServerHostKeyVerifier#verifyServerHostKey(String, + * int, String, byte[]) ServerHostKeyVerifier.verifyServerHostKey()} method + * will *NOT* be called from the current thread, the call is being made from a + * background thread (there is a background dispatcher thread for every + * established connection). + *

+ * Note 3: This method will block as long as the key exchange of the underlying connection + * has not been completed (and you have not specified any timeouts). + *

+ * Note 4: If you want to re-use a connection object that was successfully connected, + * then you must call the {@link #close()} method before invoking connect() again. + * + * @param verifier An object that implements the + * {@link ServerHostKeyVerifier} interface. Pass null + * to accept any server host key - NOT recommended. + * @param connectTimeout Connect the underlying TCP socket to the server with the given timeout + * value (non-negative, in milliseconds). Zero means no timeout. + * @param kexTimeout Timeout for complete connection establishment (non-negative, + * in milliseconds). Zero means no timeout. The timeout counts from the + * moment you invoke the connect() method and is cancelled as soon as the + * first key-exchange round has finished. It is possible that + * the timeout event will be fired during the invocation of the + * verifier callback, but it will only have an effect after + * the verifier returns. + * @return A {@link ConnectionInfo} object containing the details of + * the established connection. + * @throws IOException If any problem occurs, e.g., the server's host key is not + * accepted by the verifier or there is problem during + * the initial crypto setup (e.g., the signature sent by the server is wrong). + *

+ * In case of a timeout (either connectTimeout or kexTimeout) + * a SocketTimeoutException is thrown. + *

+ * An exception may also be thrown if the connection was already successfully + * connected (no matter if the connection broke in the mean time) and you invoke + * connect() again without having called {@link #close()} first. + *

+ * If a HTTP proxy is being used and the proxy refuses the connection, + * then a {@link HTTPProxyException} may be thrown, which + * contains the details returned by the proxy. If the proxy is buggy and does + * not return a proper HTTP response, then a normal IOException is thrown instead. + */ + + public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier, int connectTimeout, int kexTimeout) + throws IOException { + final class TimeoutState { + boolean isCancelled = false; + boolean timeoutSocketClosed = false; + } + + if (tm != null) { + throw new IllegalStateException(String.format("Connection to %s is already in connected state", hostname)); + } + + if (connectTimeout < 0) { + throw new IllegalArgumentException("connectTimeout must be non-negative!"); + } + + if (kexTimeout < 0) { + throw new IllegalArgumentException("kexTimeout must be non-negative!"); + } + + final TimeoutState state = new TimeoutState(); + + if (null == proxy) { + tm = new ClientTransportManager(new Socket()); + } + else { + tm = new HTTPProxyClientTransportManager(new Socket(), proxy); + } + + tm.setSoTimeout(connectTimeout); + tm.setTcpNoDelay(tcpNoDelay); + tm.setConnectionMonitors(connectionMonitors); + + try { + TimeoutToken token = null; + + if (kexTimeout > 0) { + final Runnable timeoutHandler = new Runnable() { + public void run() { + synchronized (state) { + if (state.isCancelled) { + return; + } + + state.timeoutSocketClosed = true; + tm.close(new SocketTimeoutException("The connect timeout expired"), false); + } + } + }; + long timeoutHorizont = System.currentTimeMillis() + kexTimeout; + token = TimeoutService.addTimeoutHandler(timeoutHorizont, timeoutHandler); + } + + tm.connect(hostname, port, softwareversion, cryptoWishList, verifier, dhgexpara, connectTimeout, + getOrCreateSecureRND()); + /* Wait until first KEX has finished */ + ConnectionInfo ci = tm.getConnectionInfo(1); + + /* Now try to cancel the timeout, if needed */ + + if (token != null) { + TimeoutService.cancelTimeoutHandler(token); + + /* Were we too late? */ + + synchronized (state) { + if (state.timeoutSocketClosed) { + throw new IOException("This exception will be replaced by the one below =)"); + } + + /* Just in case the "cancelTimeoutHandler" invocation came just a little bit + * too late but the handler did not enter the semaphore yet - we can + * still stop it. + */ + state.isCancelled = true; + } + } + + return ci; + } + catch (SocketTimeoutException e) { + throw e; + } + catch (HTTPProxyException e) { + throw e; + } + catch (IOException e) { + // This will also invoke any registered connection monitors + close(e, false); + + synchronized (state) { + /* Show a clean exception, not something like "the socket is closed!?!" */ + if (state.timeoutSocketClosed) { + throw new SocketTimeoutException(String.format("The kexTimeout (%d ms) expired.", kexTimeout)); + } + } + + throw e; + } + } + + /** + * Creates a new {@link LocalPortForwarder}. + * A LocalPortForwarder forwards TCP/IP connections that arrive at a local + * port via the secure tunnel to another host (which may or may not be + * identical to the remote SSH-2 server). + *

+ * This method must only be called after one has passed successfully the authentication step. + * There is no limit on the number of concurrent forwardings. + * + * @param local_port the local port the LocalPortForwarder shall bind to. + * @param host_to_connect target address (IP or hostname) + * @param port_to_connect target port + * @return A {@link LocalPortForwarder} object. + * @throws IOException + */ + + public synchronized LocalPortForwarder createLocalPortForwarder(int local_port, String host_to_connect, + int port_to_connect) throws IOException { + this.checkConnection(); + return new LocalPortForwarder(cm, local_port, host_to_connect, port_to_connect); + } + + /** + * Creates a new {@link LocalPortForwarder}. + * A LocalPortForwarder forwards TCP/IP connections that arrive at a local + * port via the secure tunnel to another host (which may or may not be + * identical to the remote SSH-2 server). + *

+ * This method must only be called after one has passed successfully the authentication step. + * There is no limit on the number of concurrent forwardings. + * + * @param addr specifies the InetSocketAddress where the local socket shall be bound to. + * @param host_to_connect target address (IP or hostname) + * @param port_to_connect target port + * @return A {@link LocalPortForwarder} object. + * @throws IOException + */ + + public synchronized LocalPortForwarder createLocalPortForwarder(InetSocketAddress addr, String host_to_connect, + int port_to_connect) throws IOException { + this.checkConnection(); + return new LocalPortForwarder(cm, addr, host_to_connect, port_to_connect); + } + + /** + * Creates a new {@link LocalStreamForwarder}. + * A LocalStreamForwarder manages an Input/Outputstream pair + * that is being forwarded via the secure tunnel into a TCP/IP connection to another host + * (which may or may not be identical to the remote SSH-2 server). + * + * @param host_to_connect + * @param port_to_connect + * @return A {@link LocalStreamForwarder} object. + * @throws IOException + */ + + public synchronized LocalStreamForwarder createLocalStreamForwarder(String host_to_connect, int port_to_connect) + throws IOException { + this.checkConnection(); + return new LocalStreamForwarder(cm, host_to_connect, port_to_connect); + } + + /** + * Creates a new {@link DynamicPortForwarder}. A + * DynamicPortForwarder forwards TCP/IP connections that arrive + * at a local port via the secure tunnel to another host that is chosen via + * the SOCKS protocol. + *

+ * This method must only be called after one has passed successfully the + * authentication step. There is no limit on the number of concurrent + * forwardings. + * + * @param local_port + * @return A {@link DynamicPortForwarder} object. + * @throws IOException + */ + + public synchronized DynamicPortForwarder createDynamicPortForwarder(int local_port) throws IOException { + if (tm == null) + throw new IllegalStateException("Cannot forward ports, you need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("Cannot forward ports, connection is not authenticated."); + + return new DynamicPortForwarder(cm, local_port); + } + + /** + * Creates a new {@link DynamicPortForwarder}. A + * DynamicPortForwarder forwards TCP/IP connections that arrive + * at a local port via the secure tunnel to another host that is chosen via + * the SOCKS protocol. + *

+ * This method must only be called after one has passed successfully the + * authentication step. There is no limit on the number of concurrent + * forwardings. + * + * @param addr + * specifies the InetSocketAddress where the local socket shall + * be bound to. + * @return A {@link DynamicPortForwarder} object. + * @throws IOException + */ + + public synchronized DynamicPortForwarder createDynamicPortForwarder(InetSocketAddress addr) throws IOException { + if (tm == null) + throw new IllegalStateException("Cannot forward ports, you need to establish a connection first."); + + if (!authenticated) + throw new IllegalStateException("Cannot forward ports, connection is not authenticated."); + + return new DynamicPortForwarder(cm, addr); + } + + /** + * Create a very basic {@link SCPClient} that can be used to copy + * files from/to the SSH-2 server. + *

+ * Works only after one has passed successfully the authentication step. + * There is no limit on the number of concurrent SCP clients. + *

+ * Note: This factory method will probably disappear in the future. + * + * @return A {@link SCPClient} object. + * @throws IOException + */ + + public synchronized SCPClient createSCPClient() throws IOException { + this.checkConnection(); + return new SCPClient(this); + } + + /** + * Force an asynchronous key re-exchange (the call does not block). The + * latest values set for MAC, Cipher and DH group exchange parameters will + * be used. If a key exchange is currently in progress, then this method has + * the only effect that the so far specified parameters will be used for the + * next (server driven) key exchange. + *

+ * Note: This implementation will never start a key exchange (other than the initial one) + * unless you or the SSH-2 server ask for it. + * + * @throws IOException In case of any failure behind the scenes. + */ + + public synchronized void forceKeyExchange() throws IOException { + this.checkConnection(); + tm.forceKeyExchange(cryptoWishList, dhgexpara, null, null, null); + } + + /** + * Returns the hostname that was passed to the constructor. + * + * @return the hostname + */ + + public synchronized String getHostname() { + return hostname; + } + + /** + * Returns the port that was passed to the constructor. + * + * @return the TCP port + */ + + public synchronized int getPort() { + return port; + } + + /** + * Returns a {@link ConnectionInfo} object containing the details of + * the connection. Can be called as soon as the connection has been + * established (successfully connected). + * + * @return A {@link ConnectionInfo} object. + * @throws IOException In case of any failure behind the scenes. + */ + + public synchronized ConnectionInfo getConnectionInfo() throws IOException { + this.checkConnection(); + return tm.getConnectionInfo(1); + } + + /** + * After a successful connect, one has to authenticate oneself. This method + * can be used to tell which authentication methods are supported by the + * server at a certain stage of the authentication process (for the given + * username). + *

+ * Note 1: the username will only be used if no authentication step was done + * so far (it will be used to ask the server for a list of possible + * authentication methods by sending the initial "none" request). Otherwise, + * this method ignores the user name and returns a cached method list + * (which is based on the information contained in the last negative server response). + *

+ * Note 2: the server may return method names that are not supported by this + * implementation. + *

+ * After a successful authentication, this method must not be called + * anymore. + * + * @param user A String holding the username. + * @return a (possibly emtpy) array holding authentication method names. + * @throws IOException + */ + + public synchronized String[] getRemainingAuthMethods(String user) throws IOException { + if (user == null) { + throw new IllegalArgumentException("user argument may not be NULL!"); + } + + if (tm == null) { + throw new IllegalStateException("Connection is not established!"); + } + + if (authenticated) { + throw new IllegalStateException("Connection is already authenticated!"); + } + + if (am == null) { + am = new AuthenticationManager(tm); + } + + if (cm == null) { + cm = new ChannelManager(tm); + } + + final Set remainingMethods = am.getRemainingMethods(user); + return remainingMethods.toArray(new String[remainingMethods.size()]); + } + + /** + * Determines if the authentication phase is complete. Can be called at any + * time. + * + * @return true if no further authentication steps are + * needed. + */ + + public synchronized boolean isAuthenticationComplete() { + return authenticated; + } + + /** + * Returns true if there was at least one failed authentication request and + * the last failed authentication request was marked with "partial success" + * by the server. This is only needed in the rare case of SSH-2 server setups + * that cannot be satisfied with a single successful authentication request + * (i.e., multiple authentication steps are needed.) + *

+ * If you are interested in the details, then have a look at RFC4252. + * + * @return if the there was a failed authentication step and the last one + * was marked as a "partial success". + */ + + public synchronized boolean isAuthenticationPartialSuccess() { + if (am == null) { + return false; + } + + return am.getPartialSuccess(); + } + + /** + * Checks if a specified authentication method is available. This method is + * actually just a wrapper for {@link #getRemainingAuthMethods(String) + * getRemainingAuthMethods()}. + * + * @param user A String holding the username. + * @param method An authentication method name (e.g., "publickey", "password", + * "keyboard-interactive") as specified by the SSH-2 standard. + * @return if the specified authentication method is currently available. + * @throws IOException + */ + + public synchronized boolean isAuthMethodAvailable(String user, String method) throws IOException { + String methods[] = getRemainingAuthMethods(user); + + for (final String m : methods) { + if (m.compareTo(method) == 0) { + return true; + } + } + + return false; + } + + private SecureRandomFix getOrCreateSecureRND() { + if (generator == null) { + generator = new SecureRandomFix(); + } + + return generator; + } + + /** + * Open a new {@link Session} on this connection. Works only after one has passed + * successfully the authentication step. There is no limit on the number of + * concurrent sessions. + * + * @return A {@link Session} object. + * @throws IOException + */ + + public synchronized Session openSession() throws IOException { + this.checkConnection(); + return new Session(cm, getOrCreateSecureRND()); + } + + /** + * Send an SSH_MSG_IGNORE packet. This method will generate a random data attribute + * (length between 0 (invlusive) and 16 (exclusive) bytes, contents are random bytes). + *

+ * This method must only be called once the connection is established. + * + * @throws IOException + */ + + public synchronized void sendIgnorePacket() throws IOException { + SecureRandomFix rnd = getOrCreateSecureRND(); + byte[] data = new byte[rnd.nextInt(16)]; + rnd.nextBytes(data); + sendIgnorePacket(data); + } + + /** + * Send an SSH_MSG_IGNORE packet with the given data attribute. + *

+ * This method must only be called once the connection is established. + * + * @throws IOException + */ + + public synchronized void sendIgnorePacket(byte[] data) throws IOException { + this.checkConnection(); + PacketIgnore pi = new PacketIgnore(data); + tm.sendMessage(pi.getPayload()); + } + + /** + * Controls whether compression is used on the link or not. + */ + + public synchronized void setCompression(String[] algorithms) { + CompressionFactory.checkCompressorList(algorithms); + cryptoWishList.c2s_comp_algos = algorithms; + } + + public synchronized void enableCompression() { + cryptoWishList.c2s_comp_algos = CompressionFactory.getDefaultCompressorList(); + cryptoWishList.s2c_comp_algos = CompressionFactory.getDefaultCompressorList(); + } + + public synchronized void disableCompression() { + cryptoWishList.c2s_comp_algos = new String[] {"none"}; + cryptoWishList.s2c_comp_algos = new String[] {"none"}; + } + + /** + * Unless you know what you are doing, you will never need this. + */ + + public synchronized void setClient2ServerCiphers(final String[] ciphers) { + if ((ciphers == null) || (ciphers.length == 0)) { + throw new IllegalArgumentException(); + } + + BlockCipherFactory.checkCipherList(ciphers); + cryptoWishList.c2s_enc_algos = ciphers; + } + + /** + * Unless you know what you are doing, you will never need this. + */ + + public synchronized void setClient2ServerMACs(final String[] macs) { + MAC.checkMacList(macs); + cryptoWishList.c2s_mac_algos = macs; + } + + /** + * Sets the parameters for the diffie-hellman group exchange. Unless you + * know what you are doing, you will never need this. Default values are + * defined in the {@link DHGexParameters} class. + * + * @param dgp {@link DHGexParameters}, non null. + */ + + public synchronized void setDHGexParameters(DHGexParameters dgp) { + if (dgp == null) { + throw new IllegalArgumentException(); + } + + dhgexpara = dgp; + } + + /** + * Unless you know what you are doing, you will never need this. + */ + + public synchronized void setServer2ClientCiphers(final String[] ciphers) { + BlockCipherFactory.checkCipherList(ciphers); + cryptoWishList.s2c_enc_algos = ciphers; + } + + /** + * Unless you know what you are doing, you will never need this. + */ + + public synchronized void setServer2ClientMACs(final String[] macs) { + MAC.checkMacList(macs); + cryptoWishList.s2c_mac_algos = macs; + } + + /** + * Define the set of allowed server host key algorithms to be used for + * the following key exchange operations. + *

+ * Unless you know what you are doing, you will never need this. + * + * @param algos An array of allowed server host key algorithms. + * SSH-2 defines ssh-dss and ssh-rsa. + * The entries of the array must be ordered after preference, i.e., + * the entry at index 0 is the most preferred one. You must specify + * at least one entry. + */ + + public synchronized void setServerHostKeyAlgorithms(final String[] algos) { + KexManager.checkServerHostkeyAlgorithmsList(algos); + cryptoWishList.serverHostKeyAlgorithms = algos; + } + + /** + * Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm) on the underlying socket. + *

+ * Can be called at any time. If the connection has not yet been established + * then the passed value will be stored and set after the socket has been set up. + * The default value that will be used is false. + * + * @param enable the argument passed to the Socket.setTCPNoDelay() method. + * @throws IOException + */ + + public synchronized void setTCPNoDelay(boolean enable) throws IOException { + tcpNoDelay = enable; + + if (tm != null) { + tm.setTcpNoDelay(enable); + } + } + + /** + * Used to tell the library that the connection shall be established through + * a proxy server. It only makes sense to call this method before calling + * the {@link #connect() connect()} method. + *

+ * At the moment, only HTTP proxies are supported. + *

+ * Note: This method can be called any number of times. The + * {@link #connect() connect()} method will use the value set in the last + * preceding invocation of this method. + * + * @see HTTPProxyData + * + * @param proxy + * Connection information about the proxy. If null, + * then no proxy will be used (non surprisingly, this is also the + * default). + */ + + public synchronized void setProxyData(HTTPProxyData proxy) { + this.proxy = proxy; + } + + /** + * Request a remote port forwarding. + * If successful, then forwarded connections will be redirected to the given target address. + * You can cancle a requested remote port forwarding by calling + * {@link #cancelRemotePortForwarding(int) cancelRemotePortForwarding()}. + *

+ * A call of this method will block until the peer either agreed or disagreed to your request- + *

+ * Note 1: this method typically fails if you + *

    + *
  • pass a port number for which the used remote user has not enough permissions (i.e., port + * < 1024)
  • + *
  • or pass a port number that is already in use on the remote server
  • + *
  • or if remote port forwarding is disabled on the server.
  • + *
+ *

+ * Note 2: (from the openssh man page): By default, the listening socket on the server will be + * bound to the loopback interface only. This may be overriden by specifying a bind address. + * Specifying a remote bind address will only succeed if the server's GatewayPorts option + * is enabled (see sshd_config(5)). + * + * @param bindAddress address to bind to on the server: + *

    + *
  • "" means that connections are to be accepted on all protocol families + * supported by the SSH implementation
  • + *
  • "0.0.0.0" means to listen on all IPv4 addresses
  • + *
  • "::" means to listen on all IPv6 addresses
  • + *
  • "localhost" means to listen on all protocol families supported by the SSH + * implementation on loopback addresses only, [RFC3330] and RFC3513]
  • + *
  • "127.0.0.1" and "::1" indicate listening on the loopback interfaces for + * IPv4 and IPv6 respectively
  • + *
+ * @param bindPort port number to bind on the server (must be > 0) + * @param targetAddress the target address (IP or hostname) + * @param targetPort the target port + * @throws IOException + */ + + public synchronized void requestRemotePortForwarding(String bindAddress, int bindPort, String targetAddress, + int targetPort) throws IOException { + this.checkConnection(); + + if ((bindAddress == null) || (targetAddress == null) || (bindPort <= 0) || (targetPort <= 0)) { + throw new IllegalArgumentException(); + } + + cm.requestGlobalForward(bindAddress, bindPort, targetAddress, targetPort); + } + + /** + * Cancel an earlier requested remote port forwarding. + * Currently active forwardings will not be affected (e.g., disrupted). + * Note that further connection forwarding requests may be received until + * this method has returned. + * + * @param bindPort the allocated port number on the server + * @throws IOException if the remote side refuses the cancel request or another low + * level error occurs (e.g., the underlying connection is closed) + */ + + public synchronized void cancelRemotePortForwarding(int bindPort) throws IOException { + this.checkConnection(); + cm.requestCancelGlobalForward(bindPort); + } + + /** + * Provide your own instance of SecureRandom. Can be used, e.g., if you + * want to seed the used SecureRandom generator manually. + *

+ * The SecureRandom instance is used during key exchanges, public key authentication, + * x11 cookie generation and the like. + * + * @param rnd a SecureRandom instance + */ + + public synchronized void setSecureRandom(SecureRandomFix rnd) { + if (rnd == null) { + throw new IllegalArgumentException(); + } + + this.generator = rnd; + } + + private void checkConnection() throws IllegalStateException { + if (tm == null) { + throw new IllegalStateException("You need to establish a connection first."); + } + + if (!authenticated) { + throw new IllegalStateException("The connection is not authenticated."); + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/ConnectionInfo.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/ConnectionInfo.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * In most cases you probably do not need the information contained in here. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class ConnectionInfo { + /** + * The used key exchange (KEX) algorithm in the latest key exchange. + */ + public String keyExchangeAlgorithm; + + /** + * The currently used crypto algorithm for packets from to the client to the + * server. + */ + public String clientToServerCryptoAlgorithm; + /** + * The currently used crypto algorithm for packets from to the server to the + * client. + */ + public String serverToClientCryptoAlgorithm; + + /** + * The currently used MAC algorithm for packets from to the client to the + * server. + */ + public String clientToServerMACAlgorithm; + /** + * The currently used MAC algorithm for packets from to the server to the + * client. + */ + public String serverToClientMACAlgorithm; + + /** + * The type of the server host key (currently either "ssh-dss" or + * "ssh-rsa"). + */ + public String serverHostKeyAlgorithm; + + /** + * The server host key that was sent during the latest key exchange. + */ + public byte[] serverHostKey; + + /** + * Number of kex exchanges performed on this connection so far. + */ + public int keyExchangeCounter = 0; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/ConnectionMonitor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/ConnectionMonitor.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * A ConnectionMonitor is used to get notified when the + * underlying socket of a connection is closed. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public interface ConnectionMonitor { + /** + * This method is called after the connection's underlying + * socket has been closed. E.g., due to the {@link Connection#close()} request of the + * user, if the peer closed the connection, due to a fatal error during connect() + * (also if the socket cannot be established) or if a fatal error occured on + * an established connection. + *

+ * This is an experimental feature. + *

+ * You MUST NOT make any assumption about the thread that invokes this method. + *

+ * Please note: if the connection is not connected (e.g., there was no successful + * connect() call), then the invocation of {@link Connection#close()} will NOT trigger + * this method. + * + * @see Connection#addConnectionMonitor(ConnectionMonitor) + * + * @param reason Includes an indication why the socket was closed. + */ + public void connectionLost(Throwable reason); +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/DHGexParameters.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/DHGexParameters.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * A DHGexParameters object can be used to specify parameters for + * the diffie-hellman group exchange. + *

+ * Depending on which constructor is used, either the use of a + * SSH_MSG_KEX_DH_GEX_REQUEST or SSH_MSG_KEX_DH_GEX_REQUEST_OLD + * can be forced. + * + * @see Connection#setDHGexParameters(DHGexParameters) + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public class DHGexParameters { + private final int min_group_len; + private final int pref_group_len; + private final int max_group_len; + + private static final int MIN_ALLOWED = 1024; + private static final int MAX_ALLOWED = 8192; + + /** + * Same as calling {@link #DHGexParameters(int, int, int) DHGexParameters(1024, 1024, 4096)}. + * This is also the default used by the Connection class. + * + */ + public DHGexParameters() { + this(1024, 1024, 4096); + } + + /** + * This constructor can be used to force the sending of a + * SSH_MSG_KEX_DH_GEX_REQUEST_OLD request. + * Internally, the minimum and maximum group lengths will + * be set to zero. + * + * @param pref_group_len has to be >= 1024 and <= 8192 + */ + public DHGexParameters(int pref_group_len) { + if ((pref_group_len < MIN_ALLOWED) || (pref_group_len > MAX_ALLOWED)) + throw new IllegalArgumentException("pref_group_len out of range!"); + + this.pref_group_len = pref_group_len; + this.min_group_len = 0; + this.max_group_len = 0; + } + + /** + * This constructor can be used to force the sending of a + * SSH_MSG_KEX_DH_GEX_REQUEST request. + *

+ * Note: older OpenSSH servers don't understand this request, in which + * case you should use the {@link #DHGexParameters(int)} constructor. + *

+ * All values have to be >= 1024 and <= 8192. Furthermore, + * min_group_len <= pref_group_len <= max_group_len. + * + * @param min_group_len + * @param pref_group_len + * @param max_group_len + */ + public DHGexParameters(int min_group_len, int pref_group_len, int max_group_len) { + if ((min_group_len < MIN_ALLOWED) || (min_group_len > MAX_ALLOWED)) + throw new IllegalArgumentException("min_group_len out of range!"); + + if ((pref_group_len < MIN_ALLOWED) || (pref_group_len > MAX_ALLOWED)) + throw new IllegalArgumentException("pref_group_len out of range!"); + + if ((max_group_len < MIN_ALLOWED) || (max_group_len > MAX_ALLOWED)) + throw new IllegalArgumentException("max_group_len out of range!"); + + if ((pref_group_len < min_group_len) || (pref_group_len > max_group_len)) + throw new IllegalArgumentException("pref_group_len is incompatible with min and max!"); + + if (max_group_len < min_group_len) + throw new IllegalArgumentException("max_group_len must not be smaller than min_group_len!"); + + this.min_group_len = min_group_len; + this.pref_group_len = pref_group_len; + this.max_group_len = max_group_len; + } + + /** + * Get the maximum group length. + * + * @return the maximum group length, may be zero if + * SSH_MSG_KEX_DH_GEX_REQUEST_OLD should be requested + */ + public int getMax_group_len() { + return max_group_len; + } + + /** + * Get the minimum group length. + * + * @return minimum group length, may be zero if + * SSH_MSG_KEX_DH_GEX_REQUEST_OLD should be requested + */ + public int getMin_group_len() { + return min_group_len; + } + + /** + * Get the preferred group length. + * + * @return the preferred group length + */ + public int getPref_group_len() { + return pref_group_len; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/DynamicPortForwarder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/DynamicPortForwarder.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,63 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.ethz.ssh2; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import ch.ethz.ssh2.channel.ChannelManager; +import ch.ethz.ssh2.channel.DynamicAcceptThread; + +/** + * A DynamicPortForwarder forwards TCP/IP connections to a local + * port via the secure tunnel to another host which is selected via the + * SOCKS protocol. Checkout {@link Connection#createDynamicPortForwarder(int)} + * on how to create one. + * + * @author Kenny Root + * @version $Id: $ + */ +public class DynamicPortForwarder { + ChannelManager cm; + + DynamicAcceptThread dat; + + DynamicPortForwarder(ChannelManager cm, int local_port) + throws IOException { + this.cm = cm; + dat = new DynamicAcceptThread(cm, local_port); + dat.setDaemon(true); + dat.start(); + } + + DynamicPortForwarder(ChannelManager cm, InetSocketAddress addr) throws IOException { + this.cm = cm; + dat = new DynamicAcceptThread(cm, addr); + dat.setDaemon(true); + dat.start(); + } + + /** + * Stop TCP/IP forwarding of newly arriving connections. + * + * @throws IOException + */ + public void close() throws IOException { + dat.stopWorking(); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/HTTPProxyData.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/HTTPProxyData.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * A HTTPProxyData object is used to specify the needed connection data + * to connect through a HTTP proxy. + * + * @see Connection#setProxyData(ProxyData) + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public class HTTPProxyData implements ProxyData { + public final String proxyHost; + public final int proxyPort; + public final String proxyUser; + public final String proxyPass; + public final String[] requestHeaderLines; + + /** + * Same as calling {@link #HTTPProxyData(String, int, String, String) HTTPProxyData(proxyHost, proxyPort, null, null)} + * + * @param proxyHost Proxy hostname. + * @param proxyPort Proxy port. + */ + public HTTPProxyData(String proxyHost, int proxyPort) { + this(proxyHost, proxyPort, null, null); + } + + /** + * Same as calling {@link #HTTPProxyData(String, int, String, String, String[]) HTTPProxyData(proxyHost, proxyPort, null, null, null)} + * + * @param proxyHost Proxy hostname. + * @param proxyPort Proxy port. + * @param proxyUser Username for basic authentication (null if no authentication is needed). + * @param proxyPass Password for basic authentication (null if no authentication is needed). + */ + public HTTPProxyData(String proxyHost, int proxyPort, String proxyUser, String proxyPass) { + this(proxyHost, proxyPort, proxyUser, proxyPass, null); + } + + /** + * Connection data for a HTTP proxy. It is possible to specify a username and password + * if the proxy requires basic authentication. Also, additional request header lines can + * be specified (e.g., "User-Agent: CERN-LineMode/2.15 libwww/2.17b3"). + *

+ * Please note: if you want to use basic authentication, then both proxyUser + * and proxyPass must be non-null. + *

+ * Here is an example: + *

+ * + * new HTTPProxyData("192.168.1.1", "3128", "proxyuser", "secret", new String[] {"User-Agent: GanymedBasedClient/1.0", "X-My-Proxy-Option: something"}); + * + * + * @param proxyHost Proxy hostname. + * @param proxyPort Proxy port. + * @param proxyUser Username for basic authentication (null if no authentication is needed). + * @param proxyPass Password for basic authentication (null if no authentication is needed). + * @param requestHeaderLines An array with additional request header lines (without end-of-line markers) + * that have to be sent to the server. May be null. + */ + + public HTTPProxyData(String proxyHost, int proxyPort, String proxyUser, String proxyPass, + String[] requestHeaderLines) { + if (proxyHost == null) + throw new IllegalArgumentException("proxyHost must be non-null"); + + if (proxyPort < 0) + throw new IllegalArgumentException("proxyPort must be non-negative"); + + this.proxyHost = proxyHost; + this.proxyPort = proxyPort; + this.proxyUser = proxyUser; + this.proxyPass = proxyPass; + this.requestHeaderLines = requestHeaderLines; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/HTTPProxyException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/HTTPProxyException.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.IOException; + +/** + * May be thrown upon connect() if a HTTP proxy is being used. + * + * @see Connection#connect() + * @see Connection#setProxyData(ProxyData) + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public class HTTPProxyException extends IOException { + private static final long serialVersionUID = 2241537397104426186L; + + public final String httpResponse; + public final int httpErrorCode; + + public HTTPProxyException(String httpResponse, int httpErrorCode) { + super("HTTP Proxy Error (" + httpErrorCode + " " + httpResponse + ")"); + this.httpResponse = httpResponse; + this.httpErrorCode = httpErrorCode; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/InteractiveCallback.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/InteractiveCallback.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * An InteractiveCallback is used to respond to challenges sent + * by the server if authentication mode "keyboard-interactive" is selected. + * + * @see Connection#authenticateWithKeyboardInteractive(String, + * String[], InteractiveCallback) + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public interface InteractiveCallback { + /** + * This callback interface is used during a "keyboard-interactive" + * authentication. Every time the server sends a set of challenges (however, + * most often just one challenge at a time), this callback function will be + * called to give your application a chance to talk to the user and to + * determine the response(s). + *

+ * Some copy-paste information from the standard: a command line interface + * (CLI) client SHOULD print the name and instruction (if non-empty), adding + * newlines. Then for each prompt in turn, the client SHOULD display the + * prompt and read the user input. The name and instruction fields MAY be + * empty strings, the client MUST be prepared to handle this correctly. The + * prompt field(s) MUST NOT be empty strings. + *

+ * Please refer to draft-ietf-secsh-auth-kbdinteract-XX.txt for the details. + *

+ * Note: clients SHOULD use control character filtering as discussed in + * RFC4251 to avoid attacks by including + * terminal control characters in the fields to be displayed. + * + * @param name + * the name String sent by the server. + * @param instruction + * the instruction String sent by the server. + * @param numPrompts + * number of prompts - may be zero (in this case, you should just + * return a String array of length zero). + * @param prompt + * an array (length numPrompts) of Strings + * @param echo + * an array (length numPrompts) of booleans. For + * each prompt, the corresponding echo field indicates whether or + * not the user input should be echoed as characters are typed. + * @return an array of reponses - the array size must match the parameter + * numPrompts. + */ + public String[] replyToChallenge(String name, String instruction, int numPrompts, String[] prompt, boolean[] echo) + throws Exception; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/KnownHosts.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/KnownHosts.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,814 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2; + +import java.io.BufferedReader; +import java.io.CharArrayReader; +import java.io.CharArrayWriter; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.DigestException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import ch.ethz.ssh2.crypto.Base64; +import ch.ethz.ssh2.crypto.SecureRandomFix; +import ch.ethz.ssh2.crypto.digest.Digest; +import ch.ethz.ssh2.crypto.digest.HMAC; +import ch.ethz.ssh2.crypto.digest.MD5; +import ch.ethz.ssh2.crypto.digest.SHA1; + +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPublicKey; +import ch.ethz.ssh2.signature.DSASHA1Verify; +import ch.ethz.ssh2.signature.ECDSASHA2Verify; +import ch.ethz.ssh2.signature.RSASHA1Verify; +import ch.ethz.ssh2.util.StringEncoder; + +/** + * The KnownHosts class is a handy tool to verify received server hostkeys + * based on the information in known_hosts files (the ones used by OpenSSH). + *

+ * It offers basically an in-memory database for known_hosts entries, as well as some + * helper functions. Entries from a known_hosts file can be loaded at construction time. + * It is also possible to add more keys later (e.g., one can parse different + * known_hosts files). + *

+ * It is a thread safe implementation, therefore, you need only to instantiate one + * KnownHosts for your whole application. + * + * @author Christian Plattner + * @version $Id: KnownHosts.java 152 2014-04-28 11:02:23Z dkocher@sudo.ch $ + */ + +public class KnownHosts { + public static final int HOSTKEY_IS_OK = 0; + public static final int HOSTKEY_IS_NEW = 1; + public static final int HOSTKEY_HAS_CHANGED = 2; + + private class KnownHostsEntry { + String[] patterns; + PublicKey key; + + KnownHostsEntry(String[] patterns, PublicKey key) { + this.patterns = patterns; + this.key = key; + } + } + + private final LinkedList publicKeys = new LinkedList(); + + public KnownHosts() { + } + + public KnownHosts(char[] knownHostsData) throws IOException { + initialize(knownHostsData); + } + + public KnownHosts(String knownHosts) throws IOException { + initialize(new File(knownHosts)); + } + + public KnownHosts(File knownHosts) throws IOException { + initialize(knownHosts); + } + + /** + * Adds a single public key entry to the database. Note: this will NOT add the public key + * to any physical file (e.g., "~/.ssh/known_hosts") - use addHostkeyToFile() for that purpose. + * This method is designed to be used in a {@link ServerHostKeyVerifier}. + * + * @param hostnames a list of hostname patterns - at least one most be specified. Check out the + * OpenSSH sshd man page for a description of the pattern matching algorithm. + * @param serverHostKeyAlgorithm as passed to the {@link ServerHostKeyVerifier}. + * @param serverHostKey as passed to the {@link ServerHostKeyVerifier}. + * @throws IOException + */ + public void addHostkey(String hostnames[], String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException { + if (hostnames == null) { + throw new IllegalArgumentException("hostnames may not be null"); + } + + if ("ssh-rsa".equals(serverHostKeyAlgorithm)) { + RSAPublicKey rpk = RSASHA1Verify.decodeSSHRSAPublicKey(serverHostKey); + + synchronized (publicKeys) { + publicKeys.add(new KnownHostsEntry(hostnames, rpk)); + } + } + else if ("ssh-dss".equals(serverHostKeyAlgorithm)) { + DSAPublicKey dpk = DSASHA1Verify.decodeSSHDSAPublicKey(serverHostKey); + + synchronized (publicKeys) { + publicKeys.add(new KnownHostsEntry(hostnames, dpk)); + } + } + else if (serverHostKeyAlgorithm.startsWith("ecdsa-sha2-")) { + ECPublicKey epk = ECDSASHA2Verify.decodeSSHECDSAPublicKey(serverHostKey); + + synchronized (publicKeys) { + publicKeys.add(new KnownHostsEntry(hostnames, epk)); + } + } + else { + throw new IOException(String.format("Unknown host key type %s", serverHostKeyAlgorithm)); + } + } + + /** + * Parses the given known_hosts data and adds entries to the database. + * + * @param knownHostsData + * @throws IOException + */ + public void addHostkeys(char[] knownHostsData) throws IOException { + initialize(knownHostsData); + } + + /** + * Parses the given known_hosts file and adds entries to the database. + * + * @param knownHosts + * @throws IOException + */ + public void addHostkeys(File knownHosts) throws IOException { + initialize(knownHosts); + } + + /** + * Generate the hashed representation of the given hostname. Useful for adding entries + * with hashed hostnames to a known_hosts file. (see -H option of OpenSSH key-gen). + * + * @param hostname + * @return the hashed representation, e.g., "|1|cDhrv7zwEUV3k71CEPHnhHZezhA=|Xo+2y6rUXo2OIWRAYhBOIijbJMA=" + */ + public static String createHashedHostname(String hostname) throws IOException { + SHA1 sha1 = new SHA1(); + byte[] salt = new byte[sha1.getDigestLength()]; + new SecureRandomFix().nextBytes(salt); + byte[] hash; + + try { + hash = hmacSha1Hash(salt, hostname); + } + catch (IOException e) { + throw new IOException(e); + } + + String base64_salt = new String(Base64.encode(salt)); + String base64_hash = new String(Base64.encode(hash)); + return String.format("|1|%s|%s", base64_salt, base64_hash); + } + + private static byte[] hmacSha1Hash(byte[] salt, String hostname) throws IOException { + SHA1 sha1 = new SHA1(); + + if (salt.length != sha1.getDigestLength()) { + throw new IllegalArgumentException("Salt has wrong length (" + salt.length + ")"); + } + + try { + HMAC hmac = new HMAC(sha1, salt, salt.length); + hmac.update(StringEncoder.GetBytes(hostname)); + byte[] dig = new byte[hmac.getDigestLength()]; + hmac.digest(dig); + return dig; + } + catch (DigestException e) { + throw new IOException(e); + } + } + + private boolean checkHashed(String entry, String hostname) { + if (entry.startsWith("|1|") == false) { + return false; + } + + int delim_idx = entry.indexOf('|', 3); + + if (delim_idx == -1) { + return false; + } + + String salt_base64 = entry.substring(3, delim_idx); + String hash_base64 = entry.substring(delim_idx + 1); + byte[] salt; + byte[] hash; + + try { + salt = Base64.decode(salt_base64.toCharArray()); + hash = Base64.decode(hash_base64.toCharArray()); + } + catch (IOException e) { + return false; + } + + SHA1 sha1 = new SHA1(); + + if (salt.length != sha1.getDigestLength()) { + return false; + } + + byte[] dig = new byte[0]; + + try { + dig = hmacSha1Hash(salt, hostname); + } + catch (IOException e) { + return false; + } + + for (int i = 0; i < dig.length; i++) { + if (dig[i] != hash[i]) { + return false; + } + } + + return true; + } + + private int checkKey(String remoteHostname, PublicKey remoteKey) { + int result = HOSTKEY_IS_NEW; + + synchronized (publicKeys) { + for (KnownHostsEntry ke : publicKeys) { + if (hostnameMatches(ke.patterns, remoteHostname) == false) { + continue; + } + + boolean res = matchKeys(ke.key, remoteKey); + + if (res == true) { + return HOSTKEY_IS_OK; + } + + result = HOSTKEY_HAS_CHANGED; + } + } + + return result; + } + + private List getAllKeys(String hostname) { + List keys = new ArrayList(); + + synchronized (publicKeys) { + for (KnownHostsEntry ke : publicKeys) { + if (hostnameMatches(ke.patterns, hostname) == false) { + continue; + } + + keys.add(ke.key); + } + } + + return keys; + } + + /** + * Try to find the preferred order of hostkey algorithms for the given hostname. + * Based on the type of hostkey that is present in the internal database + * (i.e., either ssh-rsa or ssh-dss) + * an ordered list of hostkey algorithms is returned which can be passed + * to Connection.setServerHostKeyAlgorithms. + * + * @param hostname + * @return null if no key for the given hostname is present or + * there are keys of multiple types present for the given hostname. Otherwise, + * an array with hostkey algorithms is returned (i.e., an array of length 2). + */ + public String[] getPreferredServerHostkeyAlgorithmOrder(String hostname) { + String[] algos = recommendHostkeyAlgorithms(hostname); + + if (algos != null) { + return algos; + } + + InetAddress[] ipAdresses; + + try { + ipAdresses = InetAddress.getAllByName(hostname); + } + catch (UnknownHostException e) { + return null; + } + + for (int i = 0; i < ipAdresses.length; i++) { + algos = recommendHostkeyAlgorithms(ipAdresses[i].getHostAddress()); + + if (algos != null) { + return algos; + } + } + + return null; + } + + private boolean hostnameMatches(String[] hostpatterns, String hostname) { + boolean isMatch = false; + boolean negate; + hostname = hostname.toLowerCase(); + + for (int k = 0; k < hostpatterns.length; k++) { + if (hostpatterns[k] == null) { + continue; + } + + String pattern; + + /* In contrast to OpenSSH we also allow negated hash entries (as well as hashed + * entries in lines with multiple entries). + */ + + if ((hostpatterns[k].length() > 0) && (hostpatterns[k].charAt(0) == '!')) { + pattern = hostpatterns[k].substring(1); + negate = true; + } + else { + pattern = hostpatterns[k]; + negate = false; + } + + /* Optimize, no need to check this entry */ + + if ((isMatch) && (negate == false)) { + continue; + } + + /* Now compare */ + + if (pattern.charAt(0) == '|') { + if (checkHashed(pattern, hostname)) { + if (negate) { + return false; + } + + isMatch = true; + } + } + else { + pattern = pattern.toLowerCase(); + + if ((pattern.indexOf('?') != -1) || (pattern.indexOf('*') != -1)) { + if (pseudoRegex(pattern.toCharArray(), 0, hostname.toCharArray(), 0)) { + if (negate) { + return false; + } + + isMatch = true; + } + } + else if (pattern.compareTo(hostname) == 0) { + if (negate) { + return false; + } + + isMatch = true; + } + } + } + + return isMatch; + } + + private void initialize(char[] knownHostsData) throws IOException { + BufferedReader br = new BufferedReader(new CharArrayReader(knownHostsData)); + + while (true) { + String line = br.readLine(); + + if (line == null) { + break; + } + + line = line.trim(); + + if (line.startsWith("#")) { + continue; + } + + String[] arr = line.split(" "); + + if (arr.length >= 3) { + if ((arr[1].compareTo("ssh-rsa") == 0) || + (arr[1].compareTo("ssh-dss") == 0) || + (arr[1].startsWith("ecdsa-sha2-"))) { + String[] hostnames = arr[0].split(","); + byte[] msg = Base64.decode(arr[2].toCharArray()); + + try { + addHostkey(hostnames, arr[1], msg); + } + catch (IOException e) { + continue; + } + } + } + } + } + + private void initialize(File knownHosts) throws IOException { + char[] buff = new char[512]; + CharArrayWriter cw = new CharArrayWriter(); + knownHosts.createNewFile(); + FileReader fr = new FileReader(knownHosts); + + while (true) { + int len = fr.read(buff); + + if (len < 0) { + break; + } + + cw.write(buff, 0, len); + } + + fr.close(); + initialize(cw.toCharArray()); + } + + private final boolean matchKeys(PublicKey key1, PublicKey key2) { + return key1.equals(key2); + } + + private boolean pseudoRegex(char[] pattern, int i, char[] match, int j) { + /* This matching logic is equivalent to the one present in OpenSSH 4.1 */ + while (true) { + /* Are we at the end of the pattern? */ + if (pattern.length == i) { + return (match.length == j); + } + + if (pattern[i] == '*') { + i++; + + if (pattern.length == i) { + return true; + } + + if ((pattern[i] != '*') && (pattern[i] != '?')) { + while (true) { + if ((pattern[i] == match[j]) && pseudoRegex(pattern, i + 1, match, j + 1)) { + return true; + } + + j++; + + if (match.length == j) { + return false; + } + } + } + + while (true) { + if (pseudoRegex(pattern, i, match, j)) { + return true; + } + + j++; + + if (match.length == j) { + return false; + } + } + } + + if (match.length == j) { + return false; + } + + if ((pattern[i] != '?') && (pattern[i] != match[j])) { + return false; + } + + i++; + j++; + } + } + + private String[] recommendHostkeyAlgorithms(String hostname) { + String preferredAlgo = null; + List keys = getAllKeys(hostname); + + for (Object key : keys) { + String thisAlgo; + + if (key instanceof RSAPublicKey) { + thisAlgo = "ssh-rsa"; + } + else if (key instanceof DSAPublicKey) { + thisAlgo = "ssh-dss"; + } + else if (key instanceof ECPublicKey) { + ECPublicKey ecPub = (ECPublicKey) key; + String keyType = ECDSASHA2Verify.getCurveName(ecPub.getParams().getCurve().getField().getFieldSize()); + thisAlgo = ECDSASHA2Verify.ECDSA_SHA2_PREFIX + keyType; + } + else { + continue; + } + + if (preferredAlgo != null) { + /* If we find different key types, then return null */ + if (preferredAlgo.compareTo(thisAlgo) != 0) { + return null; + } + } + else { + preferredAlgo = thisAlgo; + } + } + + /* If we did not find anything that we know of, return null */ + + if (preferredAlgo == null) { + return null; + } + + /* Now put the preferred algo to the start of the array. + * You may ask yourself why we do it that way - basically, we could just + * return only the preferred algorithm: since we have a saved key of that + * type (sent earlier from the remote host), then that should work out. + * However, imagine that the server is (for whatever reasons) not offering + * that type of hostkey anymore (e.g., "ssh-rsa" was disabled and + * now "ssh-dss" is being used). If we then do not let the server send us + * a fresh key of the new type, then we shoot ourself into the foot: + * the connection cannot be established and hence the user cannot decide + * if he/she wants to accept the new key. + */ + + if (preferredAlgo.equals("ssh-rsa")) { + return new String[] {"ssh-rsa", "ssh-dss", "ecdsa-sha2-nistp256"}; + } + + return new String[] {"ssh-dss", "ssh-rsa", "ecdsa-sha2-nistp256"}; + } + + /** + * Checks the internal hostkey database for the given hostkey. + * If no matching key can be found, then the hostname is resolved to an IP address + * and the search is repeated using that IP address. + * + * @param hostname the server's hostname, will be matched with all hostname patterns + * @param serverHostKeyAlgorithm type of hostkey, either ssh-rsa or ssh-dss + * @param serverHostKey the key blob + * @return
    + *
  • HOSTKEY_IS_OK: the given hostkey matches an entry for the given hostname
  • + *
  • HOSTKEY_IS_NEW: no entries found for this hostname and this type of hostkey
  • + *
  • HOSTKEY_HAS_CHANGED: hostname is known, but with another key of the same type + * (man-in-the-middle attack?)
  • + *
+ * @throws IOException if the supplied key blob cannot be parsed or does not match the given hostkey type. + */ + public int verifyHostkey(String hostname, String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException { + PublicKey remoteKey; + + if ("ssh-rsa".equals(serverHostKeyAlgorithm)) { + remoteKey = RSASHA1Verify.decodeSSHRSAPublicKey(serverHostKey); + } + else if ("ssh-dss".equals(serverHostKeyAlgorithm)) { + remoteKey = DSASHA1Verify.decodeSSHDSAPublicKey(serverHostKey); + } + else if (serverHostKeyAlgorithm.startsWith("ecdsa-sha2-")) { + remoteKey = ECDSASHA2Verify.decodeSSHECDSAPublicKey(serverHostKey); + } + else { + throw new IllegalArgumentException("Unknown hostkey type " + serverHostKeyAlgorithm); + } + + int result = checkKey(hostname, remoteKey); + + if (result == HOSTKEY_IS_OK) { + return result; + } + + InetAddress[] ipAdresses; + + try { + ipAdresses = InetAddress.getAllByName(hostname); + } + catch (UnknownHostException e) { + return result; + } + + for (int i = 0; i < ipAdresses.length; i++) { + int newresult = checkKey(ipAdresses[i].getHostAddress(), remoteKey); + + if (newresult == HOSTKEY_IS_OK) { + return newresult; + } + + if (newresult == HOSTKEY_HAS_CHANGED) { + result = HOSTKEY_HAS_CHANGED; + } + } + + return result; + } + + /** + * Adds a single public key entry to the a known_hosts file. + * This method is designed to be used in a {@link ServerHostKeyVerifier}. + * + * @param knownHosts the file where the publickey entry will be appended. + * @param hostnames a list of hostname patterns - at least one most be specified. Check out the + * OpenSSH sshd man page for a description of the pattern matching algorithm. + * @param serverHostKeyAlgorithm as passed to the {@link ServerHostKeyVerifier}. + * @param serverHostKey as passed to the {@link ServerHostKeyVerifier}. + * @throws IOException + */ + public static void addHostkeyToFile(File knownHosts, String[] hostnames, String serverHostKeyAlgorithm, + byte[] serverHostKey) throws IOException { + if ((hostnames == null) || (hostnames.length == 0)) { + throw new IllegalArgumentException("Need at least one hostname specification"); + } + + if ((serverHostKeyAlgorithm == null) || (serverHostKey == null)) { + throw new IllegalArgumentException(); + } + + CharArrayWriter writer = new CharArrayWriter(); + + for (int i = 0; i < hostnames.length; i++) { + if (i != 0) { + writer.write(','); + } + + writer.write(hostnames[i]); + } + + writer.write(' '); + writer.write(serverHostKeyAlgorithm); + writer.write(' '); + writer.write(Base64.encode(serverHostKey)); + writer.write("\n"); + char[] entry = writer.toCharArray(); + RandomAccessFile raf = new RandomAccessFile(knownHosts, "rw"); + long len = raf.length(); + + if (len > 0) { + raf.seek(len - 1); + int last = raf.read(); + + if (last != '\n') { + raf.write('\n'); + } + } + + raf.write(StringEncoder.GetBytes(new String(entry))); + raf.close(); + } + + /** + * Generates a "raw" fingerprint of a hostkey. + * + * @param type either "md5" or "sha1" + * @param keyType either "ssh-rsa" or "ssh-dss" or "ecdsa-sha2..." + * @param hostkey the hostkey + * @return the raw fingerprint + */ + static private byte[] rawFingerPrint(String type, String keyType, byte[] hostkey) throws IOException { + Digest dig; + + if ("md5".equals(type)) { + dig = new MD5(); + } + else if ("sha1".equals(type)) { + dig = new SHA1(); + } + else { + throw new IllegalArgumentException("Unknown hash type " + type); + } + + if ("ssh-rsa".equals(keyType)) { + } + else if ("ssh-dss".equals(keyType)) { + } + else if (keyType.startsWith("ecdsa-sha2-")) { + } + else { + throw new IllegalArgumentException("Unknown key type " + keyType); + } + + if (hostkey == null) { + throw new IllegalArgumentException("hostkey is null"); + } + + dig.update(hostkey); + byte[] res = new byte[dig.getDigestLength()]; + + try { + dig.digest(res); + } + catch (DigestException e) { + throw new IOException(e); + } + + return res; + } + + /** + * Convert a raw fingerprint to hex representation (XX:YY:ZZ...). + * + * @param fingerprint raw fingerprint + * @return the hex representation + */ + static private String rawToHexFingerprint(byte[] fingerprint) { + final char[] alpha = "0123456789abcdef".toCharArray(); + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < fingerprint.length; i++) { + if (i != 0) { + sb.append(':'); + } + + int b = fingerprint[i] & 0xff; + sb.append(alpha[b >> 4]); + sb.append(alpha[b & 15]); + } + + return sb.toString(); + } + + /** + * Convert a raw fingerprint to bubblebabble representation. + * + * @param raw raw fingerprint + * @return the bubblebabble representation + */ + static private String rawToBubblebabbleFingerprint(byte[] raw) { + final char[] v = "aeiouy".toCharArray(); + final char[] c = "bcdfghklmnprstvzx".toCharArray(); + StringBuilder sb = new StringBuilder(); + int seed = 1; + int rounds = (raw.length / 2) + 1; + sb.append('x'); + + for (int i = 0; i < rounds; i++) { + if (((i + 1) < rounds) || ((raw.length) % 2 != 0)) { + sb.append(v[(((raw[2 * i] >> 6) & 3) + seed) % 6]); + sb.append(c[(raw[2 * i] >> 2) & 15]); + sb.append(v[((raw[2 * i] & 3) + (seed / 6)) % 6]); + + if ((i + 1) < rounds) { + sb.append(c[(((raw[(2 * i) + 1])) >> 4) & 15]); + sb.append('-'); + sb.append(c[(((raw[(2 * i) + 1]))) & 15]); + // As long as seed >= 0, seed will be >= 0 afterwards + seed = ((seed * 5) + (((raw[2 * i] & 0xff) * 7) + (raw[(2 * i) + 1] & 0xff))) % 36; + } + } + else { + sb.append(v[seed % 6]); // seed >= 0, therefore index positive + sb.append('x'); + sb.append(v[seed / 6]); + } + } + + sb.append('x'); + return sb.toString(); + } + + /** + * Convert a ssh2 key-blob into a human readable hex fingerprint. + * Generated fingerprints are identical to those generated by OpenSSH. + *

+ * Example fingerprint: d0:cb:76:19:99:5a:03:fc:73:10:70:93:f2:44:63:47. + * + * @param keytype either "ssh-rsa" or "ssh-dss" or "ecdsa-sha2..." + * @param publickey key blob + * @return Hex fingerprint + */ + public static String createHexFingerprint(String keytype, byte[] publickey) throws IOException { + byte[] raw = rawFingerPrint("md5", keytype, publickey); + return rawToHexFingerprint(raw); + } + + /** + * Convert a ssh2 key-blob into a human readable bubblebabble fingerprint. + * The used bubblebabble algorithm (taken from OpenSSH) generates fingerprints + * that are easier to remember for humans. + *

+ * Example fingerprint: xofoc-bubuz-cazin-zufyl-pivuk-biduk-tacib-pybur-gonar-hotat-lyxux. + * + * @param keytype either "ssh-rsa" or "ssh-dss" + * @param publickey key data + * @return Bubblebabble fingerprint + */ + public static String createBubblebabbleFingerprint(String keytype, byte[] publickey) throws IOException { + byte[] raw = rawFingerPrint("sha1", keytype, publickey); + return rawToBubblebabbleFingerprint(raw); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/LocalPortForwarder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/LocalPortForwarder.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; + +import ch.ethz.ssh2.channel.ChannelManager; +import ch.ethz.ssh2.channel.LocalAcceptThread; + +/** + * A LocalPortForwarder forwards TCP/IP connections to a local + * port via the secure tunnel to another host (which may or may not be identical + * to the remote SSH-2 server). Checkout {@link Connection#createLocalPortForwarder(int, String, int)} + * on how to create one. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class LocalPortForwarder { + final ChannelManager cm; + + final String host_to_connect; + + final int port_to_connect; + + final LocalAcceptThread lat; + + LocalPortForwarder(ChannelManager cm, int local_port, String host_to_connect, int port_to_connect) + throws IOException { + this.cm = cm; + this.host_to_connect = host_to_connect; + this.port_to_connect = port_to_connect; + lat = new LocalAcceptThread(cm, local_port, host_to_connect, port_to_connect); + lat.setDaemon(true); + lat.start(); + } + + LocalPortForwarder(ChannelManager cm, InetSocketAddress addr, String host_to_connect, int port_to_connect) + throws IOException { + this.cm = cm; + this.host_to_connect = host_to_connect; + this.port_to_connect = port_to_connect; + lat = new LocalAcceptThread(cm, addr, host_to_connect, port_to_connect); + lat.setDaemon(true); + lat.start(); + } + + /** + * Return the local socket address of the {@link ServerSocket} used to accept connections. + * @return + */ + public InetSocketAddress getLocalSocketAddress() { + return (InetSocketAddress) lat.getServerSocket().getLocalSocketAddress(); + } + + /** + * Stop TCP/IP forwarding of newly arriving connections. + * + * @throws IOException + */ + public void close() throws IOException { + lat.stopWorking(); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/LocalStreamForwarder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/LocalStreamForwarder.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; + +import ch.ethz.ssh2.channel.Channel; +import ch.ethz.ssh2.channel.ChannelManager; + +/** + * A LocalStreamForwarder forwards an Input- and Outputstream + * pair via the secure tunnel to another host (which may or may not be identical + * to the remote SSH-2 server). + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class LocalStreamForwarder { + private ChannelManager cm; + + private Channel cn; + + LocalStreamForwarder(ChannelManager cm, String host_to_connect, int port_to_connect) throws IOException { + this.cm = cm; + cn = cm.openDirectTCPIPChannel(host_to_connect, port_to_connect, + InetAddress.getLocalHost().getHostAddress(), 0); + } + + /** + * @return An InputStream object. + * @throws IOException + */ + public InputStream getInputStream() throws IOException { + return cn.getStdoutStream(); + } + + /** + * Get the OutputStream. Please be aware that the implementation MAY use an + * internal buffer. To make sure that the buffered data is sent over the + * tunnel, you have to call the flush method of the + * OutputStream. To signal EOF, please use the + * close method of the OutputStream. + * + * @return An OutputStream object. + * @throws IOException + */ + public OutputStream getOutputStream() throws IOException { + return cn.getStdinStream(); + } + + /** + * Close the underlying SSH forwarding channel and free up resources. + * You can also use this method to force the shutdown of the underlying + * forwarding channel. Pending output (OutputStream not flushed) will NOT + * be sent. Pending input (InputStream) can still be read. If the shutdown + * operation is already in progress (initiated from either side), then this + * call is a no-op. + * + * @throws IOException + */ + public void close() throws IOException { + cm.closeChannel(cn, "Closed due to user request.", true); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/PacketFormatException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/PacketFormatException.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,13 @@ +package ch.ethz.ssh2; + +import java.io.IOException; + +/** + * @version $Id: PacketFormatException.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class PacketFormatException extends IOException { + + public PacketFormatException(String message) { + super(message); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/PacketListener.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/PacketListener.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2011 David Kocher. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * @version $Id: PacketListener.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public interface PacketListener { + void read(String packet); + + void write(String packet); +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/PacketTypeException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/PacketTypeException.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,24 @@ +package ch.ethz.ssh2; + +import java.io.IOException; + +/** + * @version $Id: PacketTypeException.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class PacketTypeException extends IOException { + + public PacketTypeException() { + } + + public PacketTypeException(final String message) { + super(message); + } + + public PacketTypeException(final int packet) { + super(String.format("The SFTP server sent an unexpected packet type (%d)", packet)); + } + + public PacketTypeException(final int packet, final String message) { + super(String.format("The SFTP server sent an invalid packet type (%d). %s", packet, message)); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/ProxyData.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/ProxyData.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * An abstract marker interface implemented by all proxy data implementations. + * + * @see HTTPProxyData + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public abstract interface ProxyData { +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/PtySettings.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/PtySettings.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2012-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * PTY settings for a SSH session. Zero dimension parameters are ignored. The character/row dimensions + * override the pixel dimensions (when nonzero). Pixel dimensions refer to + * the drawable area of the window. The dimension parameters are only + * informational. The encoding of terminal modes (parameter + * terminal_modes) is described in RFC4254. + * + * @author Christian + */ +public class PtySettings { + /** + * TERM environment variable value (e.g., vt100) + */ + public String term; + + /** + * Terminal width, characters (e.g., 80) + */ + public int term_width_characters; + + /** + * Terminal height, rows (e.g., 24) + */ + public int term_height_characters; + + /** + * Terminal width, pixels (e.g., 640) + */ + public int term_width_pixels; + + /** + * Terminal height, pixels (e.g., 480) + */ + public int term_height_pixels; + + /** + * Encoded terminal modes + */ + public byte[] terminal_modes; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/RequestMismatchException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/RequestMismatchException.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,17 @@ +package ch.ethz.ssh2; + +import java.io.IOException; + +/** + * @version $Id: RequestMismatchException.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class RequestMismatchException extends IOException { + + public RequestMismatchException() { + super("The server sent an invalid id field."); + } + + public RequestMismatchException(final String message) { + super(message); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/SCPClient.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/SCPClient.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,742 @@ +package ch.ethz.ssh2; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.nio.charset.UnsupportedCharsetException; + +/** + * A very basic SCPClient that can be used to copy files from/to + * the SSH-2 server. On the server side, the "scp" program must be in the PATH. + *

+ * This scp client is thread safe - you can download (and upload) different sets + * of files concurrently without any troubles. The SCPClient is + * actually mapping every request to a distinct {@link ch.ethz.ssh2.Session}. + * + * @author Christian Plattner, plattner@inf.ethz.ch + * @version $Id: SCPClient.java 85 2014-04-07 14:05:09Z dkocher@sudo.ch $ + */ + +public class SCPClient { + Connection conn; + + String charsetName = null; + + /** + * Set the charset used to convert between Java Unicode Strings and byte encodings + * used by the server for paths and file names. + * + * @param charset the name of the charset to be used or null to use the platform's + * default encoding. + * @throws IOException + * @see #getCharset() + */ + public void setCharset(String charset) throws IOException { + if (charset == null) { + charsetName = charset; + return; + } + + try { + Charset.forName(charset); + } + catch (UnsupportedCharsetException e) { + throw new IOException("This charset is not supported", e); + } + + charsetName = charset; + } + + /** + * The currently used charset for filename encoding/decoding. + * + * @return The name of the charset (null if the platform's default charset is being used) + * @see #setCharset(String) + */ + public String getCharset() { + return charsetName; + } + + public class LenNamePair { + public long length; + String filename; + } + + public SCPClient(Connection conn) { + if (conn == null) + throw new IllegalArgumentException("Cannot accept null argument!"); + + this.conn = conn; + } + + protected void readResponse(InputStream is) throws IOException { + int c = is.read(); + + if (c == 0) + return; + + if (c == -1) + throw new IOException("Remote scp terminated unexpectedly."); + + if ((c != 1) && (c != 2)) + throw new IOException("Remote scp sent illegal error code."); + + if (c == 2) + throw new IOException("Remote scp terminated with error."); + + String err = receiveLine(is); + throw new IOException("Remote scp terminated with error (" + err + ")."); + } + + protected String receiveLine(InputStream is) throws IOException { + StringBuilder sb = new StringBuilder(30); + + while (true) { + /* This is a random limit - if your path names are longer, then adjust it */ + if (sb.length() > 8192) + throw new IOException("Remote scp sent a too long line"); + + int c = is.read(); + + if (c < 0) + throw new IOException("Remote scp terminated unexpectedly."); + + if (c == '\n') + break; + + sb.append((char) c); + } + + return sb.toString(); + } + + protected LenNamePair parseCLine(String line) throws IOException { + /* Minimum line: "xxxx y z" ---> 8 chars */ + if (line.length() < 8) + throw new IOException("Malformed C line sent by remote SCP binary, line too short."); + + if ((line.charAt(4) != ' ') || (line.charAt(5) == ' ')) + throw new IOException("Malformed C line sent by remote SCP binary."); + + int length_name_sep = line.indexOf(' ', 5); + + if (length_name_sep == -1) + throw new IOException("Malformed C line sent by remote SCP binary."); + + String length_substring = line.substring(5, length_name_sep); + String name_substring = line.substring(length_name_sep + 1); + + if ((length_substring.length() <= 0) || (name_substring.length() <= 0)) + throw new IOException("Malformed C line sent by remote SCP binary."); + + if ((6 + length_substring.length() + name_substring.length()) != line.length()) + throw new IOException("Malformed C line sent by remote SCP binary."); + + final long len; + + try { + len = Long.parseLong(length_substring); + } + catch (NumberFormatException e) { + throw new IOException("Malformed C line sent by remote SCP binary, cannot parse file length."); + } + + if (len < 0) + throw new IOException("Malformed C line sent by remote SCP binary, illegal file length."); + + LenNamePair lnp = new LenNamePair(); + lnp.length = len; + lnp.filename = name_substring; + return lnp; + } + + /** + * The session for opened for this SCP transfer must be closed using + * SCPOutputStream#close + * + * @param remoteFile + * @param length The size of the file to send + * @param remoteTargetDirectory + * @param mode + * @return + * @throws IOException + */ + public SCPOutputStream put(final String remoteFile, long length, String remoteTargetDirectory, String mode) + throws IOException { + Session sess = null; + + if (null == remoteFile) + throw new IllegalArgumentException("Null argument."); + + if (null == remoteTargetDirectory) + remoteTargetDirectory = ""; + + if (null == mode) + mode = "0600"; + + if (mode.length() != 4) + throw new IllegalArgumentException("Invalid mode."); + + for (int i = 0; i < mode.length(); i++) + if (Character.isDigit(mode.charAt(i)) == false) + throw new IllegalArgumentException("Invalid mode."); + + remoteTargetDirectory = (remoteTargetDirectory.length() > 0) ? remoteTargetDirectory : "."; + String cmd = "scp -t -d \"" + remoteTargetDirectory + "\""; + sess = conn.openSession(); + sess.execCommand(cmd, charsetName); + return new SCPOutputStream(this, sess, remoteFile, length, mode); + } + + /** + * The session for opened for this SCP transfer must be closed using + * SCPInputStream#close + * + * @param remoteFile + * @return + * @throws IOException + */ + public SCPInputStream get(final String remoteFile) throws IOException { + Session sess = null; + + if (null == remoteFile) + throw new IllegalArgumentException("Null argument."); + + if (remoteFile.length() == 0) + throw new IllegalArgumentException("Cannot accept empty filename."); + + String cmd = "scp -f"; + cmd += (" \"" + remoteFile + "\""); + sess = conn.openSession(); + sess.execCommand(cmd, charsetName); + return new SCPInputStream(this, sess); + } + private void sendBytes(Session sess, byte[] data, String fileName, String mode) throws IOException { + OutputStream os = sess.getStdin(); + InputStream is = new BufferedInputStream(sess.getStdout(), 512); + readResponse(is); + String cline = "C" + mode + " " + data.length + " " + fileName + "\n"; + os.write(cline.getBytes("ISO-8859-1")); + os.flush(); + readResponse(is); + os.write(data, 0, data.length); + os.write(0); + os.flush(); + readResponse(is); + os.write("E\n".getBytes("ISO-8859-1")); + os.flush(); + } + + private void sendFiles(Session sess, String[] files, String[] remoteFiles, String mode) throws IOException { + byte[] buffer = new byte[8192]; + OutputStream os = new BufferedOutputStream(sess.getStdin(), 40000); + InputStream is = new BufferedInputStream(sess.getStdout(), 512); + readResponse(is); + + for (int i = 0; i < files.length; i++) { + File f = new File(files[i]); + long remain = f.length(); + String remoteName; + + if ((remoteFiles != null) && (remoteFiles.length > i) && (remoteFiles[i] != null)) + remoteName = remoteFiles[i]; + else + remoteName = f.getName(); + + String cline = "C" + mode + " " + remain + " " + remoteName + "\n"; + os.write(cline.getBytes("ISO-8859-1")); + os.flush(); + readResponse(is); + FileInputStream fis = null; + + try { + fis = new FileInputStream(f); + + while (remain > 0) { + int trans; + + if (remain > buffer.length) + trans = buffer.length; + else + trans = (int) remain; + + if (fis.read(buffer, 0, trans) != trans) + throw new IOException("Cannot read enough from local file " + files[i]); + + os.write(buffer, 0, trans); + remain -= trans; + } + } + finally { + if (fis != null) + fis.close(); + } + + os.write(0); + os.flush(); + readResponse(is); + } + + os.write("E\n".getBytes("ISO-8859-1")); + os.flush(); + } + + private void receiveFiles(Session sess, OutputStream[] targets) throws IOException { + byte[] buffer = new byte[8192]; + OutputStream os = new BufferedOutputStream(sess.getStdin(), 512); + InputStream is = new BufferedInputStream(sess.getStdout(), 40000); + os.write(0x0); + os.flush(); + + for (int i = 0; i < targets.length; i++) { + LenNamePair lnp = null; + + while (true) { + int c = is.read(); + + if (c < 0) + throw new IOException("Remote scp terminated unexpectedly."); + + String line = receiveLine(is); + + if (c == 'T') { + /* Ignore modification times */ + continue; + } + + if ((c == 1) || (c == 2)) + throw new IOException("Remote SCP error: " + line); + + if (c == 'C') { + lnp = parseCLine(line); + break; + } + + throw new IOException("Remote SCP error: " + ((char) c) + line); + } + + os.write(0x0); + os.flush(); + long remain = lnp.length; + + while (remain > 0) { + int trans; + + if (remain > buffer.length) + trans = buffer.length; + else + trans = (int) remain; + + int this_time_received = is.read(buffer, 0, trans); + + if (this_time_received < 0) { + throw new IOException("Remote scp terminated connection unexpectedly"); + } + + targets[i].write(buffer, 0, this_time_received); + remain -= this_time_received; + } + + readResponse(is); + os.write(0x0); + os.flush(); + } + } + + private void receiveFiles(Session sess, String[] files, String target) throws IOException { + byte[] buffer = new byte[8192]; + OutputStream os = new BufferedOutputStream(sess.getStdin(), 512); + InputStream is = new BufferedInputStream(sess.getStdout(), 40000); + os.write(0x0); + os.flush(); + + for (int i = 0; i < files.length; i++) { + LenNamePair lnp = null; + + while (true) { + int c = is.read(); + + if (c < 0) + throw new IOException("Remote scp terminated unexpectedly."); + + String line = receiveLine(is); + + if (c == 'T') { + /* Ignore modification times */ + continue; + } + + if ((c == 1) || (c == 2)) + throw new IOException("Remote SCP error: " + line); + + if (c == 'C') { + lnp = parseCLine(line); + break; + } + + throw new IOException("Remote SCP error: " + ((char) c) + line); + } + + os.write(0x0); + os.flush(); + File f = new File(target + File.separatorChar + lnp.filename); + FileOutputStream fop = null; + + try { + fop = new FileOutputStream(f); + long remain = lnp.length; + + while (remain > 0) { + int trans; + + if (remain > buffer.length) + trans = buffer.length; + else + trans = (int) remain; + + int this_time_received = is.read(buffer, 0, trans); + + if (this_time_received < 0) { + throw new IOException("Remote scp terminated connection unexpectedly"); + } + + fop.write(buffer, 0, this_time_received); + remain -= this_time_received; + } + } + finally { + if (fop != null) + fop.close(); + } + + readResponse(is); + os.write(0x0); + os.flush(); + } + } + + /** + * Copy a local file to a remote directory, uses mode 0600 when creating the + * file on the remote side. + * + * @param localFile + * Path and name of local file. + * @param remoteTargetDirectory + * Remote target directory. Use an empty string to specify the + * default directory. + * + * @throws IOException + */ + public void put(String localFile, String remoteTargetDirectory) throws IOException { + put(new String[] { localFile }, remoteTargetDirectory, "0600"); + } + + /** + * Copy a set of local files to a remote directory, uses mode 0600 when + * creating files on the remote side. + * + * @param localFiles + * Paths and names of local file names. + * @param remoteTargetDirectory + * Remote target directory. Use an empty string to specify the + * default directory. + * + * @throws IOException + */ + + public void put(String[] localFiles, String remoteTargetDirectory) throws IOException { + put(localFiles, remoteTargetDirectory, "0600"); + } + + /** + * Copy a local file to a remote directory, uses the specified mode when + * creating the file on the remote side. + * + * @param localFile + * Path and name of local file. + * @param remoteTargetDirectory + * Remote target directory. Use an empty string to specify the + * default directory. + * @param mode + * a four digit string (e.g., 0644, see "man chmod", "man open") + * @throws IOException + */ + public void put(String localFile, String remoteTargetDirectory, String mode) throws IOException { + put(new String[] { localFile }, remoteTargetDirectory, mode); + } + + /** + * Copy a local file to a remote directory, uses the specified mode and + * remote filename when creating the file on the remote side. + * + * @param localFile + * Path and name of local file. + * @param remoteFileName + * The name of the file which will be created in the remote + * target directory. + * @param remoteTargetDirectory + * Remote target directory. Use an empty string to specify the + * default directory. + * @param mode + * a four digit string (e.g., 0644, see "man chmod", "man open") + * @throws IOException + */ + public void put(String localFile, String remoteFileName, String remoteTargetDirectory, String mode) + throws IOException { + put(new String[] { localFile }, new String[] { remoteFileName }, remoteTargetDirectory, mode); + } + + /** + * Create a remote file and copy the contents of the passed byte array into + * it. Uses mode 0600 for creating the remote file. + * + * @param data + * the data to be copied into the remote file. + * @param remoteFileName + * The name of the file which will be created in the remote + * target directory. + * @param remoteTargetDirectory + * Remote target directory. Use an empty string to specify the + * default directory. + * @throws IOException + */ + + public void put(byte[] data, String remoteFileName, String remoteTargetDirectory) throws IOException { + put(data, remoteFileName, remoteTargetDirectory, "0600"); + } + + /** + * Create a remote file and copy the contents of the passed byte array into + * it. The method use the specified mode when creating the file on the + * remote side. + * + * @param data + * the data to be copied into the remote file. + * @param remoteFileName + * The name of the file which will be created in the remote + * target directory. + * @param remoteTargetDirectory + * Remote target directory. Use an empty string to specify the + * default directory. + * @param mode + * a four digit string (e.g., 0644, see "man chmod", "man open") + * @throws IOException + */ + public void put(byte[] data, String remoteFileName, String remoteTargetDirectory, String mode) throws IOException { + Session sess = null; + + if ((remoteFileName == null) || (remoteTargetDirectory == null) || (mode == null)) + throw new IllegalArgumentException("Null argument."); + + if (mode.length() != 4) + throw new IllegalArgumentException("Invalid mode."); + + for (int i = 0; i < mode.length(); i++) + if (Character.isDigit(mode.charAt(i)) == false) + throw new IllegalArgumentException("Invalid mode."); + + remoteTargetDirectory = remoteTargetDirectory.trim(); + remoteTargetDirectory = (remoteTargetDirectory.length() > 0) ? remoteTargetDirectory : "."; + String cmd = "scp -t -d " + remoteTargetDirectory; + + try { + sess = conn.openSession(); + sess.execCommand(cmd); + sendBytes(sess, data, remoteFileName, mode); + } + catch (IOException e) { + throw(IOException) new IOException("Error during SCP transfer.").initCause(e); + } + finally { + if (sess != null) + sess.close(); + } + } + + /** + * Copy a set of local files to a remote directory, uses the specified mode + * when creating the files on the remote side. + * + * @param localFiles + * Paths and names of the local files. + * @param remoteTargetDirectory + * Remote target directory. Use an empty string to specify the + * default directory. + * @param mode + * a four digit string (e.g., 0644, see "man chmod", "man open") + * @throws IOException + */ + public void put(String[] localFiles, String remoteTargetDirectory, String mode) throws IOException { + put(localFiles, null, remoteTargetDirectory, mode); + } + + public void put(String[] localFiles, String[] remoteFiles, String remoteTargetDirectory, String mode) + throws IOException { + Session sess = null; + + /* + * remoteFiles may be null, indicating that the local filenames shall be + * used + */ + + if ((localFiles == null) || (remoteTargetDirectory == null) || (mode == null)) + throw new IllegalArgumentException("Null argument."); + + if (mode.length() != 4) + throw new IllegalArgumentException("Invalid mode."); + + for (int i = 0; i < mode.length(); i++) + if (Character.isDigit(mode.charAt(i)) == false) + throw new IllegalArgumentException("Invalid mode."); + + if (localFiles.length == 0) + return; + + remoteTargetDirectory = remoteTargetDirectory.trim(); + remoteTargetDirectory = (remoteTargetDirectory.length() > 0) ? remoteTargetDirectory : "."; + String cmd = "scp -t -d " + remoteTargetDirectory; + + for (int i = 0; i < localFiles.length; i++) { + if (localFiles[i] == null) + throw new IllegalArgumentException("Cannot accept null filename."); + } + + try { + sess = conn.openSession(); + sess.execCommand(cmd); + sendFiles(sess, localFiles, remoteFiles, mode); + } + catch (IOException e) { + throw(IOException) new IOException("Error during SCP transfer.").initCause(e); + } + finally { + if (sess != null) + sess.close(); + } + } + + /** + * Download a file from the remote server to a local directory. + * + * @param remoteFile + * Path and name of the remote file. + * @param localTargetDirectory + * Local directory to put the downloaded file. + * + * @throws IOException + */ + public void get(String remoteFile, String localTargetDirectory) throws IOException { + get(new String[] { remoteFile }, localTargetDirectory); + } + + /** + * Download a file from the remote server and pipe its contents into an + * OutputStream. Please note that, to enable flexible usage + * of this method, the OutputStream will not be closed nor + * flushed. + * + * @param remoteFile + * Path and name of the remote file. + * @param target + * OutputStream where the contents of the file will be sent to. + * @throws IOException + */ + public void get(String remoteFile, OutputStream target) throws IOException { + get(new String[] { remoteFile }, new OutputStream[] { target }); + } + + private void get(String remoteFiles[], OutputStream[] targets) throws IOException { + Session sess = null; + + if ((remoteFiles == null) || (targets == null)) + throw new IllegalArgumentException("Null argument."); + + if (remoteFiles.length != targets.length) + throw new IllegalArgumentException("Length of arguments does not match."); + + if (remoteFiles.length == 0) + return; + + String cmd = "scp -f"; + + for (int i = 0; i < remoteFiles.length; i++) { + if (remoteFiles[i] == null) + throw new IllegalArgumentException("Cannot accept null filename."); + + String tmp = remoteFiles[i].trim(); + + if (tmp.length() == 0) + throw new IllegalArgumentException("Cannot accept empty filename."); + + cmd += (" " + tmp); + } + + try { + sess = conn.openSession(); + sess.execCommand(cmd); + receiveFiles(sess, targets); + } + catch (IOException e) { + throw(IOException) new IOException("Error during SCP transfer.").initCause(e); + } + finally { + if (sess != null) + sess.close(); + } + } + + /** + * Download a set of files from the remote server to a local directory. + * + * @param remoteFiles + * Paths and names of the remote files. + * @param localTargetDirectory + * Local directory to put the downloaded files. + * + * @throws IOException + */ + public void get(String remoteFiles[], String localTargetDirectory) throws IOException { + Session sess = null; + + if ((remoteFiles == null) || (localTargetDirectory == null)) + throw new IllegalArgumentException("Null argument."); + + if (remoteFiles.length == 0) + return; + + String cmd = "scp -f"; + + for (int i = 0; i < remoteFiles.length; i++) { + if (remoteFiles[i] == null) + throw new IllegalArgumentException("Cannot accept null filename."); + + String tmp = remoteFiles[i].trim(); + + if (tmp.length() == 0) + throw new IllegalArgumentException("Cannot accept empty filename."); + + cmd += (" " + tmp); + } + + try { + sess = conn.openSession(); + sess.execCommand(cmd); + receiveFiles(sess, remoteFiles, localTargetDirectory); + } + catch (IOException e) { + throw(IOException) new IOException("Error during SCP transfer.").initCause(e); + } + finally { + if (sess != null) + sess.close(); + } + } +} + + diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/SCPInputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/SCPInputStream.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2011 David Kocher. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * @version $Id: SCPInputStream.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class SCPInputStream extends BufferedInputStream { + private Session session; + + /** + * Bytes remaining to be read from the stream + */ + private long remaining; + + public SCPInputStream(SCPClient client, Session session) throws IOException { + super(session.getStdout()); + this.session = session; + OutputStream os = new BufferedOutputStream(session.getStdin(), 512); + os.write(0x0); + os.flush(); + final SCPClient.LenNamePair lnp; + + while (true) { + int c = session.getStdout().read(); + + if (c < 0) { + throw new IOException("Remote scp terminated unexpectedly."); + } + + String line = client.receiveLine(session.getStdout()); + + if (c == 'T') { + /* Ignore modification times */ + continue; + } + + if ((c == 1) || (c == 2)) { + throw new IOException("Remote SCP error: " + line); + } + + if (c == 'C') { + lnp = client.parseCLine(line); + break; + } + + throw new IOException("Remote SCP error: " + ((char) c) + line); + } + + os.write(0x0); + os.flush(); + this.remaining = lnp.length; + } + + @Override + public int read() throws IOException { + if (!(remaining > 0)) { + return -1; + } + + int b = super.read(); + + if (b < 0) { + throw new IOException("Remote scp terminated connection unexpectedly"); + } + + remaining -= 1; + return b; + } + + @Override + public int read(byte b[], int off, int len) throws IOException { + if (!(remaining > 0)) { + return -1; + } + + int trans = (int) remaining; + + if (remaining > len) { + trans = len; + } + + int read = super.read(b, off, trans); + + if (read < 0) { + throw new IOException("Remote scp terminated connection unexpectedly"); + } + + remaining -= read; + return read; + } + + @Override + public void close() throws IOException { + try { + session.getStdin().write(0x0); + session.getStdin().flush(); + } + finally { + if (session != null) { + session.close(); + } + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/SCPOutputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/SCPOutputStream.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2011 David Kocher. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import ch.ethz.ssh2.util.StringEncoder; + +/** + * @version $Id: SCPOutputStream.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class SCPOutputStream extends BufferedOutputStream { + + private Session session; + + private SCPClient scp; + + public SCPOutputStream(SCPClient client, Session session, final String remoteFile, long length, String mode) throws IOException { + super(session.getStdin(), 40000); + this.session = session; + this.scp = client; + InputStream is = new BufferedInputStream(session.getStdout(), 512); + scp.readResponse(is); + String cline = "C" + mode + " " + length + " " + remoteFile + "\n"; + super.write(StringEncoder.GetBytes(cline)); + this.flush(); + scp.readResponse(is); + } + + @Override + public void close() throws IOException { + try { + this.write(0); + this.flush(); + scp.readResponse(session.getStdout()); + this.write(StringEncoder.GetBytes("E\n")); + this.flush(); + } + finally { + if (session != null) + session.close(); + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/SFTPClient.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/SFTPClient.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,225 @@ +package ch.ethz.ssh2; + +import java.io.IOException; +import java.util.List; + +/** + * @version $Id: SFTPClient.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public interface SFTPClient { + + /** + * Retrieve the file attributes of an open file. + * + * @param handle a SFTPv3FileHandle handle. + * @return a SFTPv3FileAttributes object. + * @throws IOException + */ + SFTPFileAttributes fstat(SFTPFileHandle handle) throws IOException; + + /** + * Retrieve the file attributes of a file. This method + * follows symbolic links on the server. + * + * @param path See the {@link SFTPClient comment} for the class for more details. + * @return a SFTPv3FileAttributes object. + * @throws IOException + * @see #lstat(String) + */ + SFTPFileAttributes stat(String path) throws IOException; + + /** + * Retrieve the file attributes of a file. This method + * does NOT follow symbolic links on the server. + * + * @param path See the {@link SFTPClient comment} for the class for more details. + * @return a SFTPv3FileAttributes object. + * @throws IOException + * @see #stat(String) + */ + SFTPFileAttributes lstat(String path) throws IOException; + + /** + * Read the target of a symbolic link. Note: OpenSSH (as of version 4.4) gets very upset + * (SSH_FX_BAD_MESSAGE error) if you want to read the target of a file that is not a + * symbolic link. Better check first with {@link #lstat(String)}. + * + * @param path See the {@link SFTPClient comment} for the class for more details. + * @return The target of the link. + * @throws IOException + */ + String readLink(String path) throws IOException; + + /** + * Modify the attributes of a file. Used for operations such as changing + * the ownership, permissions or access times, as well as for truncating a file. + * + * @param path See the {@link SFTPClient comment} for the class for more details. + * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be + * made to the attributes of the file. Empty fields will be ignored. + * @throws IOException + */ + void setstat(String path, SFTPFileAttributes attr) throws IOException; + + /** + * Modify the attributes of a file. Used for operations such as changing + * the ownership, permissions or access times, as well as for truncating a file. + * + * @param handle a SFTPv3FileHandle handle + * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be + * made to the attributes of the file. Empty fields will be ignored. + * @throws IOException + */ + void fsetstat(SFTPFileHandle handle, SFTPFileAttributes attr) throws IOException; + + /** + * Create a symbolic link on the server. Creates a link "src" that points + * to "target". + * + * @param src See the {@link SFTPClient comment} for the class for more details. + * @param target See the {@link SFTPClient comment} for the class for more details. + * @throws IOException + */ + void createSymlink(String src, String target) throws IOException; + + /** + * Create a symbolic link on the server. Creates a link "src" that points + * to "target". + * + * @param src See the {@link SFTPClient comment} for the class for more details. + * @param target See the {@link SFTPClient comment} for the class for more details. + * @throws IOException + */ + void createHardlink(String src, String target) throws IOException; + + /** + * Have the server canonicalize any given path name to an absolute path. + * This is useful for converting path names containing ".." components or + * relative pathnames without a leading slash into absolute paths. + * + * @param path See the {@link SFTPClient comment} for the class for more details. + * @return An absolute path. + * @throws IOException + */ + String canonicalPath(String path) throws IOException; + + void setCharset(String charset) throws IOException; + + String getCharset(); + + SFTPFileHandle openDirectory(String path) throws IOException; + + boolean isConnected(); + + void close(); + + List ls(String dirName) throws IOException; + + /** + * Create a new directory. + * + * @param name See the {@link SFTPClient comment} for the class for more details. + * @param posixPermissions the permissions for this directory, e.g., "0700" (remember that + * this is octal noation). The server will likely apply a umask. + * @throws IOException + */ + void mkdir(String name, int posixPermissions) throws IOException; + + /** + * Remove a file. + * + * @param filename See the {@link SFTPClient comment} for the class for more details. + * @throws IOException + */ + void rm(String filename) throws IOException; + + /** + * Remove an empty directory. + * + * @param dirName See the {@link SFTPClient comment} for the class for more details. + * @throws IOException + */ + void rmdir(String dirName) throws IOException; + + /** + * Move a file or directory. + * + * @param oldPath See the {@link SFTPClient comment} for the class for more details. + * @param newPath See the {@link SFTPClient comment} for the class for more details. + * @throws IOException + */ + void mv(String oldPath, String newPath) throws IOException; + + /** + * Create a file and open it for reading and writing. + * Same as {@link #createFile(String, SFTPFileAttributes) createFile(filename, null)}. + * + * @param filename See the {@link SFTPClient comment} for the class for more details. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + SFTPFileHandle createFile(String filename) throws IOException; + + /** + * Create a file and open it for reading and writing. + * You can specify the default attributes of the file (the server may or may + * not respect your wishes). + * + * @param filename See the {@link SFTPv3Client comment} for the class for more details. + * @param attr may be null to use server defaults. Probably only + * the uid, gid and permissions + * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle} + * structure make sense. You need only to set those fields where you want + * to override the server's defaults. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + SFTPFileHandle createFile(String filename, SFTPFileAttributes attr) throws IOException; + + SFTPFileHandle openFile(String filename, int flags) throws IOException; + + /** + * Read bytes from a file in a parallel fashion. As many bytes as you want will be read. + *

+ *

    + *
  • The server will read as many bytes as it can from the file (up to len), + * and return them.
  • + *
  • If EOF is encountered before reading any data, -1 is returned. + *
  • If an error occurs, an exception is thrown
  • . + *
  • For normal disk files, it is guaranteed that the server will return the specified + * number of bytes, or up to end of file. For, e.g., device files this may return + * fewer bytes than requested.
  • + *
+ * + * @param handle a SFTPv3FileHandle handle + * @param fileOffset offset (in bytes) in the file + * @param dst the destination byte array + * @param dstoff offset in the destination byte array + * @param len how many bytes to read, 0 < len + * @return the number of bytes that could be read, may be less than requested if + * the end of the file is reached, -1 is returned in case of EOF + * @throws IOException + */ + int read(SFTPFileHandle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException; + + /** + * Write bytes to a file. If len > 32768, then the write operation will + * be split into multiple writes. + * + * @param handle a SFTPv3FileHandle handle. + * @param fileOffset offset (in bytes) in the file. + * @param src the source byte array. + * @param srcoff offset in the source byte array. + * @param len how many bytes to write. + * @throws IOException + */ + void write(SFTPFileHandle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException; + + /** + * Close a file. + * + * @param handle a file handle + * @throws IOException + */ + void closeFile(SFTPFileHandle handle) throws IOException; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/SFTPDirectoryEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/SFTPDirectoryEntry.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,12 @@ +package ch.ethz.ssh2; + +/** + * @version $Id: SFTPDirectoryEntry.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public interface SFTPDirectoryEntry { + + public String getFilename(); + + public SFTPFileAttributes getAttributes(); + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/SFTPException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/SFTPException.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please see the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.IOException; + +import ch.ethz.ssh2.sftp.ErrorCodes; + +/** + * Used in combination with the SFTPv3Client. This exception wraps + * error messages sent by the SFTP server. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public class SFTPException extends IOException { + private static final long serialVersionUID = 578654644222421811L; + + private final String sftpErrorMessage; + private final int sftpErrorCode; + + private static String constructMessage(String s, int errorCode) { + String[] detail = ErrorCodes.getDescription(errorCode); + + if (detail == null) + return s + " (UNKNOWN SFTP ERROR CODE)"; + + return s + " (" + detail[0] + ": " + detail[1] + ")"; + } + + SFTPException(String msg, int errorCode) { + super(constructMessage(msg, errorCode)); + sftpErrorMessage = msg; + sftpErrorCode = errorCode; + } + + /** + * Get the error message sent by the server. Often, this + * message does not help a lot (e.g., "failure"). + * + * @return the plain string as sent by the server. + */ + public String getServerErrorMessage() { + return sftpErrorMessage; + } + + /** + * Get the error code sent by the server. + * + * @return an error code as defined in the SFTP specs. + */ + public int getServerErrorCode() { + return sftpErrorCode; + } + + /** + * Get the symbolic name of the error code as given in the SFTP specs. + * + * @return e.g., "SSH_FX_INVALID_FILENAME". + */ + public String getServerErrorCodeSymbol() { + String[] detail = ErrorCodes.getDescription(sftpErrorCode); + + if (detail == null) + return "UNKNOWN SFTP ERROR CODE " + sftpErrorCode; + + return detail[0]; + } + + /** + * Get the description of the error code as given in the SFTP specs. + * + * @return e.g., "The filename is not valid." + */ + public String getServerErrorCodeVerbose() { + String[] detail = ErrorCodes.getDescription(sftpErrorCode); + + if (detail == null) + return "The error code " + sftpErrorCode + " is unknown."; + + return detail[1]; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/SFTPFileAttributes.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/SFTPFileAttributes.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,15 @@ +package ch.ethz.ssh2; + +/** + * @version $Id: SFTPFileAttributes.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public interface SFTPFileAttributes { + + boolean isDirectory(); + + boolean isRegularFile(); + + boolean isSymlink(); + + byte[] toBytes(); +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/SFTPFileHandle.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/SFTPFileHandle.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,35 @@ +package ch.ethz.ssh2; + +import java.io.IOException; + +/** + * @version $Id: SFTPFileHandle.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class SFTPFileHandle { + + private final SFTPClient client; + + private final byte[] handle; + + protected SFTPFileHandle(final SFTPClient client, final byte[] handle) { + this.client = client; + this.handle = handle; + } + + /** + * Get the SFTPv3Client instance which created this handle. + * + * @return A SFTPv3Client instance. + */ + public SFTPClient getClient() { + return client; + } + + public byte[] getHandle() { + return handle; + } + + public void close() throws IOException { + client.closeFile(this); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/SFTPInputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/SFTPInputStream.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2011 David Kocher. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.IOException; +import java.io.InputStream; + +/** + * @version $Id: SFTPInputStream.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class SFTPInputStream extends InputStream { + + private SFTPFileHandle handle; + + /** + * Offset (in bytes) in the file to read + */ + private long readOffset = 0; + + public SFTPInputStream(SFTPFileHandle handle) { + this.handle = handle; + } + + /** + * Reads up to len bytes of data from the input stream into + * an array of bytes. An attempt is made to read as many as + * len bytes, but a smaller number may be read, possibly + * zero. The number of bytes actually read is returned as an integer. + * + * @see SFTPClient#read(SFTPFileHandle, long, byte[], int, int) + */ + @Override + public int read(byte[] buffer, int offset, int len) throws IOException { + int read = handle.getClient().read(handle, readOffset, buffer, offset, len); + + if (read > 0) { + readOffset += read; + } + + return read; + } + + /** + * Reads the next byte of data from the input stream. The value byte is + * returned as an int in the range 0 to + * 255. If no byte is available because the end of the stream + * has been reached, the value -1 is returned. This method + * blocks until input data is available, the end of the stream is detected, + * or an exception is thrown. + *

+ *

A subclass must provide an implementation of this method. + * + * @return the next byte of data, or -1 if the end of the + * stream is reached. + * @throws IOException if an I/O error occurs. + */ + @Override + public int read() throws IOException { + byte[] buffer = new byte[1]; + int b = handle.getClient().read(handle, readOffset, buffer, 0, 1); + + if (b > 0) { + readOffset += 1; + } + + return b; + } + + /** + * Skips over and discards n bytes of data from this input + * stream. + * + * @param n the number of bytes to be skipped. + * @return the actual number of bytes skipped. + */ + @Override + public long skip(long n) { + readOffset += n; + return n; + } + + @Override + public void close() throws IOException { + handle.getClient().closeFile(handle); + } +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/SFTPOutputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/SFTPOutputStream.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2011 David Kocher. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * @version $Id: SFTPOutputStream.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class SFTPOutputStream extends OutputStream { + + private SFTPFileHandle handle; + + /** + * Offset (in bytes) in the file to write + */ + private long writeOffset = 0; + + public SFTPOutputStream(SFTPFileHandle handle) { + this.handle = handle; + } + + /** + * Writes len bytes from the specified byte array + * starting at offset off to this output stream. + * The general contract for write(b, off, len) is that + * some of the bytes in the array b are written to the + * output stream in order; element b[off] is the first + * byte written and b[off+len-1] is the last byte written + * by this operation. + * + * @see SFTPClient#write(SFTPFileHandle, long, byte[], int, int) + */ + @Override + public void write(byte[] buffer, int offset, int len) throws IOException { + // We can just blindly write the whole buffer at once. + // if len > 32768, then the write operation will + // be split into multiple writes in SFTPv3Client#write. + handle.getClient().write(handle, writeOffset, buffer, offset, len); + writeOffset += len; + } + + @Override + public void write(int b) throws IOException { + byte[] buffer = new byte[1]; + buffer[0] = (byte) b; + handle.getClient().write(handle, writeOffset, buffer, 0, 1); + writeOffset += 1; + } + + public long skip(long n) { + writeOffset += n; + return n; + } + + @Override + public void close() throws IOException { + handle.getClient().closeFile(handle); + } +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/SFTPv3Client.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/SFTPv3Client.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,433 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.packets.TypesWriter; +import ch.ethz.ssh2.sftp.ErrorCodes; +import ch.ethz.ssh2.sftp.Packet; + +/** + * A SFTPv3Client represents a SFTP (protocol version 3) + * client connection tunnelled over a SSH-2 connection. This is a very simple + * (synchronous) implementation. + *

+ * Basically, most methods in this class map directly to one of + * the packet types described in draft-ietf-secsh-filexfer-02.txt. + *

+ * Note: this is experimental code. + *

+ * Error handling: the methods of this class throw IOExceptions. However, unless + * there is catastrophic failure, exceptions of the type {@link SFTPv3Client} will + * be thrown (a subclass of IOException). Therefore, you can implement more verbose + * behavior by checking if a thrown exception if of this type. If yes, then you + * can cast the exception and access detailed information about the failure. + *

+ * Notes about file names, directory names and paths, copy-pasted + * from the specs: + *

    + *
  • SFTP v3 represents file names as strings. File names are + * assumed to use the slash ('/') character as a directory separator.
  • + *
  • File names starting with a slash are "absolute", and are relative to + * the root of the file system. Names starting with any other character + * are relative to the user's default directory (home directory).
  • + *
  • Servers SHOULD interpret a path name component ".." as referring to + * the parent directory, and "." as referring to the current directory. + * If the server implementation limits access to certain parts of the + * file system, it must be extra careful in parsing file names when + * enforcing such restrictions. There have been numerous reported + * security bugs where a ".." in a path name has allowed access outside + * the intended area.
  • + *
  • An empty path name is valid, and it refers to the user's default + * directory (usually the user's home directory).
  • + *
+ *

+ * If you are still not tired then please go on and read the comment for + * {@link #setCharset(String)}. + * + * @author Christian Plattner, plattner@inf.ethz.ch + * @version $Id: SFTPv3Client.java 133 2014-04-14 12:26:29Z dkocher@sudo.ch $ + */ +public class SFTPv3Client extends AbstractSFTPClient { + private static final Logger log = Logger.getLogger(SFTPv3Client.class); + + /** + * Open the file for reading. + */ + public static final int SSH_FXF_READ = 0x00000001; + /** + * Open the file for writing. If both this and SSH_FXF_READ are + * specified, the file is opened for both reading and writing. + */ + public static final int SSH_FXF_WRITE = 0x00000002; + /** + * Force all writes to append data at the end of the file. + */ + public static final int SSH_FXF_APPEND = 0x00000004; + /** + * If this flag is specified, then a new file will be created if one + * does not alread exist (if O_TRUNC is specified, the new file will + * be truncated to zero length if it previously exists). + */ + public static final int SSH_FXF_CREAT = 0x00000008; + /** + * Forces an existing file with the same name to be truncated to zero + * length when creating a file by specifying SSH_FXF_CREAT. + * SSH_FXF_CREAT MUST also be specified if this flag is used. + */ + public static final int SSH_FXF_TRUNC = 0x00000010; + /** + * Causes the request to fail if the named file already exists. + */ + public static final int SSH_FXF_EXCL = 0x00000020; + + private PacketListener listener; + + /** + * Create a SFTP v3 client. + * + * @param conn The underlying SSH-2 connection to be used. + * @throws IOException + */ + public SFTPv3Client(Connection conn) throws IOException { + this(conn, new PacketListener() { + public void read(String packet) { + log.debug("Read packet " + packet); + } + public void write(String packet) { + log.debug("Write packet " + packet); + } + }); + } + + /** + * Create a SFTP v3 client. + * + * @param conn The underlying SSH-2 connection to be used. + * @throws IOException + */ + public SFTPv3Client(Connection conn, PacketListener listener) throws IOException { + super(conn, 3, listener); + this.listener = listener; + } + + public SFTPv3FileAttributes fstat(SFTPFileHandle handle) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(handle.getHandle(), 0, handle.getHandle().length); + sendMessage(Packet.SSH_FXP_FSTAT, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_ATTRS) { + return new SFTPv3FileAttributes(tr); + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + private SFTPv3FileAttributes statBoth(String path, int statMethod) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(path, this.getCharset()); + sendMessage(statMethod, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_ATTRS) { + return new SFTPv3FileAttributes(tr); + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + public SFTPv3FileAttributes stat(String path) throws IOException { + return statBoth(path, Packet.SSH_FXP_STAT); + } + + public SFTPv3FileAttributes lstat(String path) throws IOException { + return statBoth(path, Packet.SSH_FXP_LSTAT); + } + + + private List scanDirectory(byte[] handle) throws IOException { + List files = new ArrayList(); + + while (true) { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(handle, 0, handle.length); + sendMessage(Packet.SSH_FXP_READDIR, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_NAME) { + int count = tr.readUINT32(); + + if (log.isDebugEnabled()) { + log.debug(String.format("Parsing %d name entries", count)); + } + + while (count > 0) { + SFTPv3DirectoryEntry file = new SFTPv3DirectoryEntry(); + file.filename = tr.readString(this.getCharset()); + file.longEntry = tr.readString(this.getCharset()); + listener.read(file.longEntry); + file.attributes = new SFTPv3FileAttributes(tr); + + if (log.isDebugEnabled()) { + log.debug(String.format("Adding file %s", file)); + } + + files.add(file); + count--; + } + + continue; + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + + if (errorCode == ErrorCodes.SSH_FX_EOF) { + return files; + } + + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + } + + public final SFTPv3FileHandle openDirectory(String path) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(path, this.getCharset()); + sendMessage(Packet.SSH_FXP_OPENDIR, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_HANDLE) { + return new SFTPv3FileHandle(this, tr.readByteString()); + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + /** + * List the contents of a directory. + * + * @param dirName See the {@link SFTPv3Client comment} for the class for more details. + * @return A Vector containing {@link SFTPv3DirectoryEntry} objects. + * @throws IOException + */ + public List ls(String dirName) throws IOException { + SFTPv3FileHandle handle = openDirectory(dirName); + List result = scanDirectory(handle.getHandle()); + closeFile(handle); + return result; + } + + /** + * Open a file for reading. + * + * @param filename See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle openFileRO(String filename) throws IOException { + return openFile(filename, SSH_FXF_READ, new SFTPv3FileAttributes()); + } + + /** + * Open a file for reading and writing. + * + * @param filename See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle openFileRW(String filename) throws IOException { + return openFile(filename, SSH_FXF_READ | SSH_FXF_WRITE, new SFTPv3FileAttributes()); + } + + /** + * Open a file in append mode. The SFTP v3 draft says nothing but assuming normal POSIX + * behavior, all writes will be appendend to the end of the file, no matter which offset + * one specifies. + *

+ * A side note for the curious: OpenSSH does an lseek() to the specified writing offset before each write(), + * even for writes to files opened in O_APPEND mode. However, bear in mind that when working + * in the O_APPEND mode, each write() includes an implicit lseek() to the end of the file + * (well, this is what the newsgroups say). + * + * @param filename See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle openFileRWAppend(String filename) throws IOException { + return openFile(filename, SSH_FXF_READ | SSH_FXF_WRITE | SSH_FXF_APPEND, new SFTPv3FileAttributes()); + } + + /** + * Open a file in append mode. The SFTP v3 draft says nothing but assuming normal POSIX + * behavior, all writes will be appendend to the end of the file, no matter which offset + * one specifies. + *

+ * A side note for the curious: OpenSSH does an lseek() to the specified writing offset before each write(), + * even for writes to files opened in O_APPEND mode. However, bear in mind that when working + * in the O_APPEND mode, each write() includes an implicit lseek() to the end of the file + * (well, this is what the newsgroups say). + * + * @param filename See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle openFileWAppend(String filename) throws IOException { + return openFile(filename, SSH_FXF_WRITE | SSH_FXF_APPEND, new SFTPv3FileAttributes()); + } + + public SFTPv3FileHandle createFile(String filename) throws IOException { + return createFile(filename, new SFTPv3FileAttributes()); + } + + public SFTPv3FileHandle createFile(String filename, SFTPFileAttributes attr) throws IOException { + return openFile(filename, SSH_FXF_CREAT | SSH_FXF_READ | SSH_FXF_WRITE, attr); + } + + /** + * Create a file (truncate it if it already exists) and open it for writing. + * Same as {@link #createFileTruncate(String, SFTPFileAttributes) createFileTruncate(filename, null)}. + * + * @param filename See the {@link SFTPv3Client comment} for the class for more details. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle createFileTruncate(String filename) throws IOException { + return createFileTruncate(filename, new SFTPv3FileAttributes()); + } + + /** + * reate a file (truncate it if it already exists) and open it for writing. + * You can specify the default attributes of the file (the server may or may + * not respect your wishes). + * + * @param filename See the {@link SFTPv3Client comment} for the class for more details. + * @param attr may be null to use server defaults. Probably only + * the uid, gid and permissions + * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle} + * structure make sense. You need only to set those fields where you want + * to override the server's defaults. + * @return a SFTPv3FileHandle handle + * @throws IOException + */ + public SFTPv3FileHandle createFileTruncate(String filename, SFTPFileAttributes attr) throws IOException { + return openFile(filename, SSH_FXF_CREAT | SSH_FXF_TRUNC | SSH_FXF_WRITE, attr); + } + + public SFTPv3FileHandle openFile(String filename, int flags) throws IOException { + return openFile(filename, flags, new SFTPv3FileAttributes()); + } + + @Override + public SFTPv3FileHandle openFile(String filename, int flags, SFTPFileAttributes attr) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(filename, this.getCharset()); + tw.writeUINT32(flags); + tw.writeBytes(attr.toBytes()); + sendMessage(Packet.SSH_FXP_OPEN, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_HANDLE) { + return new SFTPv3FileHandle(this, tr.readByteString()); + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + @Override + public void createSymlink(String src, String target) throws IOException { + int req_id = generateNextRequestID(); + // Changed semantics of src and target. The bug is known on SFTP servers shipped with all + // versions of OpenSSH (Bug #861). + TypesWriter tw = new TypesWriter(); + tw.writeString(target, this.getCharset()); + tw.writeString(src, this.getCharset()); + sendMessage(Packet.SSH_FXP_SYMLINK, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/SFTPv3DirectoryEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/SFTPv3DirectoryEntry.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * A SFTPv3DirectoryEntry as returned by {@link SFTPv3Client#ls(String)}. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class SFTPv3DirectoryEntry implements SFTPDirectoryEntry { + /** + * A relative name within the directory, without any path components. + */ + public String filename; + + /** + * An expanded format for the file name, similar to what is returned by + * "ls -l" on Un*x systems. + *

+ * The format of this field is unspecified by the SFTP v3 protocol. + * It MUST be suitable for use in the output of a directory listing + * command (in fact, the recommended operation for a directory listing + * command is to simply display this data). However, clients SHOULD NOT + * attempt to parse the longname field for file attributes; they SHOULD + * use the attrs field instead. + *

+ * The recommended format for the longname field is as follows:
+ * -rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer + */ + public String longEntry; + + /** + * The attributes of this entry. + */ + public SFTPv3FileAttributes attributes; + + public String getFilename() { + return filename; + } + + public SFTPv3FileAttributes getAttributes() { + return attributes; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("SFTPv3DirectoryEntry{"); + sb.append("filename='").append(filename).append('\''); + sb.append(", attributes=").append(attributes); + sb.append('}'); + return sb.toString(); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/SFTPv3FileAttributes.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/SFTPv3FileAttributes.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.IOException; + +import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.packets.TypesWriter; +import ch.ethz.ssh2.sftp.AttribFlags; + +/** + * A SFTPv3FileAttributes object represents detail information + * about a file on the server. Not all fields may/must be present. + * + * @author Christian Plattner, plattner@inf.ethz.ch + * @version $Id: SFTPv3FileAttributes.java 133 2014-04-14 12:26:29Z dkocher@sudo.ch $ + */ +public class SFTPv3FileAttributes implements SFTPFileAttributes { + /** + * The SIZE attribute. NULL if not present. + */ + public Long size = null; + + /** + * The UID attribute. NULL if not present. + */ + public Integer uid = null; + + /** + * The GID attribute. NULL if not present. + */ + public Integer gid = null; + + /** + * The POSIX permissions. NULL if not present. + *

+ * Here is a list: + *

+ *

Note: these numbers are all OCTAL.
+     * 

+ * S_IFMT 0170000 bitmask for the file type bitfields + * S_IFSOCK 0140000 socket + * S_IFLNK 0120000 symbolic link + * S_IFREG 0100000 regular file + * S_IFBLK 0060000 block device + * S_IFDIR 0040000 directory + * S_IFCHR 0020000 character device + * S_IFIFO 0010000 fifo + * S_ISUID 0004000 set UID bit + * S_ISGID 0002000 set GID bit + * S_ISVTX 0001000 sticky bit + *

+ * S_IRWXU 00700 mask for file owner permissions + * S_IRUSR 00400 owner has read permission + * S_IWUSR 00200 owner has write permission + * S_IXUSR 00100 owner has execute permission + * S_IRWXG 00070 mask for group permissions + * S_IRGRP 00040 group has read permission + * S_IWGRP 00020 group has write permission + * S_IXGRP 00010 group has execute permission + * S_IRWXO 00007 mask for permissions for others (not in group) + * S_IROTH 00004 others have read permission + * S_IWOTH 00002 others have write permisson + * S_IXOTH 00001 others have execute permission + *

+ */ + public Integer permissions = null; + + /** + * Last access time of the file. + *

+ * The atime attribute. Represented as seconds from Jan 1, 1970 in UTC. + * NULL if not present. + */ + public Integer atime = null; + + /** + * The mtime attribute. Represented as seconds from Jan 1, 1970 in UTC. + * NULL if not present. + */ + public Integer mtime = null; + + /** + * Checks if this entry is a directory. + * + * @return Returns true if permissions are available and they indicate + * that this entry represents a directory. + */ + public boolean isDirectory() { + if (permissions == null) { + return false; + } + + return ((permissions & 0040000) == 0040000); + } + + /** + * Checks if this entry is a regular file. + * + * @return Returns true if permissions are available and they indicate + * that this entry represents a regular file. + */ + public boolean isRegularFile() { + if (permissions == null) { + return false; + } + + return ((permissions & 0100000) == 0100000); + } + + /** + * Checks if this entry is a a symlink. + * + * @return Returns true if permissions are available and they indicate + * that this entry represents a symlink. + */ + public boolean isSymlink() { + if (permissions == null) { + return false; + } + + return ((permissions & 0120000) == 0120000); + } + + /** + * Turn the POSIX permissions into a 7 digit octal representation. + * Note: the returned value is first masked with 0177777. + * + * @return NULL if permissions are not available. + */ + public String getOctalPermissions() { + if (permissions == null) { + return null; + } + + String res = Integer.toString(permissions.intValue() & 0177777, 8); + StringBuilder sb = new StringBuilder(); + int leadingZeros = 7 - res.length(); + + while (leadingZeros > 0) { + sb.append('0'); + leadingZeros--; + } + + sb.append(res); + return sb.toString(); + } + + public SFTPv3FileAttributes() { + // + } + + /** + * uint32 valid-attribute-flags + * byte type always present + * uint64 size if flag SIZE + * uint64 allocation-size if flag ALLOCATION_SIZE + * string owner if flag OWNERGROUP + * string group if flag OWNERGROUP + * uint32 permissions if flag PERMISSIONS + * int64 atime if flag ACCESSTIME + * uint32 atime-nseconds if flag SUBSECOND_TIMES + * int64 createtime if flag CREATETIME + * uint32 createtime-nseconds if flag SUBSECOND_TIMES + * int64 mtime if flag MODIFYTIME + * uint32 mtime-nseconds if flag SUBSECOND_TIMES + * int64 ctime if flag CTIME + * uint32 ctime-nseconds if flag SUBSECOND_TIMES + * string acl if flag ACL + * uint32 attrib-bits if flag BITS + * uint32 attrib-bits-valid if flag BITS + * byte text-hint if flag TEXT_HINT + * string mime-type if flag MIME_TYPE + * uint32 link-count if flag LINK_COUNT + * string untranslated-name if flag UNTRANSLATED_NAME + * uint32 extended-count if flag EXTENDED + * extension-pair extensions + */ + public SFTPv3FileAttributes(final TypesReader tr) throws IOException { + int flags = tr.readUINT32(); + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SIZE) != 0) { + this.size = tr.readUINT64(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID) != 0) { + this.uid = tr.readUINT32(); + this.gid = tr.readUINT32(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) { + this.permissions = tr.readUINT32(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME) != 0) { + this.atime = tr.readUINT32(); + this.mtime = tr.readUINT32(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_EXTENDED) != 0) { + int count = tr.readUINT32(); + + // Read it anyway to detect corrupt packets + while (count > 0) { + tr.readByteString(); + tr.readByteString(); + count--; + } + } + } + + /** + * The same encoding is used both when returning file + * attributes from the server and when sending file attributes to the + * server. + * + * @return Encoded attributes + */ + public byte[] toBytes() { + TypesWriter tw = new TypesWriter(); + int attrFlags = 0; + + if (this.size != null) { + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_SIZE; + } + + if ((this.uid != null) && (this.gid != null)) { + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID; + } + + if (this.permissions != null) { + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS; + } + + if ((this.atime != null) && (this.mtime != null)) { + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME; + } + + tw.writeUINT32(attrFlags); + + if (this.size != null) { + tw.writeUINT64(this.size); + } + + if ((this.uid != null) && (this.gid != null)) { + tw.writeUINT32(this.uid); + tw.writeUINT32(this.gid); + } + + if (this.permissions != null) { + tw.writeUINT32(this.permissions); + } + + if ((this.atime != null) && (this.mtime != null)) { + tw.writeUINT32(this.atime); + tw.writeUINT32(this.mtime); + } + + return tw.getBytes(); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("SFTPv3FileAttributes{"); + sb.append("size=").append(size); + sb.append(", uid=").append(uid); + sb.append(", gid=").append(gid); + sb.append(", permissions=").append(permissions); + sb.append(", atime=").append(atime); + sb.append(", mtime=").append(mtime); + sb.append('}'); + return sb.toString(); + } +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/SFTPv3FileHandle.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/SFTPv3FileHandle.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * A SFTPv3FileHandle. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class SFTPv3FileHandle extends SFTPFileHandle { + protected SFTPv3FileHandle(final SFTPv3Client client, final byte[] handle) { + super(client, handle); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/SFTPv6Client.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/SFTPv6Client.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,300 @@ +package ch.ethz.ssh2; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.packets.TypesWriter; +import ch.ethz.ssh2.sftp.AceMask; +import ch.ethz.ssh2.sftp.ErrorCodes; +import ch.ethz.ssh2.sftp.OpenFlags; +import ch.ethz.ssh2.sftp.Packet; + +/** + * @version $Id: SFTPv6Client.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class SFTPv6Client extends AbstractSFTPClient { + private static final Logger log = Logger.getLogger(SFTPv6Client.class); + + private PacketListener listener; + + public SFTPv6Client(Connection conn) throws IOException { + this(conn, new PacketListener() { + public void read(String packet) { + log.debug("Read packet " + packet); + } + public void write(String packet) { + log.debug("Write packet " + packet); + } + }); + } + + public SFTPv6Client(Connection conn, PacketListener listener) throws IOException { + super(conn, 6, listener); + this.listener = listener; + } + + public SFTPv6FileAttributes fstat(SFTPFileHandle handle) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(handle.getHandle(), 0, handle.getHandle().length); + sendMessage(Packet.SSH_FXP_FSTAT, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_ATTRS) { + return new SFTPv6FileAttributes(tr); + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + private SFTPv6FileAttributes statBoth(String path, int statMethod) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(path, this.getCharset()); + sendMessage(statMethod, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_ATTRS) { + return new SFTPv6FileAttributes(tr); + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + public SFTPv6FileAttributes stat(String path) throws IOException { + return statBoth(path, Packet.SSH_FXP_STAT); + } + + public SFTPv6FileAttributes lstat(String path) throws IOException { + return statBoth(path, Packet.SSH_FXP_LSTAT); + } + + + private List scanDirectory(byte[] handle) throws IOException { + List files = new ArrayList(); + + while (true) { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(handle, 0, handle.length); + sendMessage(Packet.SSH_FXP_READDIR, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_NAME) { + int count = tr.readUINT32(); + + if (log.isDebugEnabled()) { + log.debug(String.format("Parsing %d name entries", count)); + } + + while (count > 0) { + SFTPv6DirectoryEntry file = new SFTPv6DirectoryEntry(); + file.filename = tr.readString(this.getCharset()); + listener.read(file.filename); + file.attributes = new SFTPv6FileAttributes(tr); + + if (log.isDebugEnabled()) { + log.debug(String.format("Adding file %s", file)); + } + + files.add(file); + count--; + } + + continue; + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + + if (errorCode == ErrorCodes.SSH_FX_EOF) { + return files; + } + + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + } + + public final SFTPFileHandle openDirectory(String path) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(path, this.getCharset()); + sendMessage(Packet.SSH_FXP_OPENDIR, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_HANDLE) { + return new SFTPFileHandle(this, tr.readByteString()); + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + /** + * List the contents of a directory. + * + * @param dirName See the {@link SFTPv6Client comment} for the class for more details. + * @return A Vector containing {@link SFTPv6DirectoryEntry} objects. + * @throws IOException + */ + public List ls(String dirName) throws IOException { + SFTPFileHandle handle = openDirectory(dirName); + List result = scanDirectory(handle.getHandle()); + closeFile(handle); + return result; + } + + /** + * Create a file and open it for reading and writing. + * Same as {@link #createFile(String, SFTPFileAttributes) createFile(fileName, null)}. + * + * @param filename See the {@link SFTPv6Client comment} for the class for more details. + * @return a SFTPFileHandle handle + * @throws IOException + */ + public SFTPFileHandle createFile(String filename) throws IOException { + return createFile(filename, new SFTPv6FileAttributes()); + } + + /** + * Create a file and open it for reading and writing. + * You can specify the default attributes of the file (the server may or may + * not respect your wishes). + * + * @param filename See the {@link SFTPv6Client comment} for the class for more details. + * @param attr may be null to use server defaults. Probably only + * the uid, gid and permissions + * (remember the server may apply a umask) entries of the {@link SFTPFileHandle} + * structure make sense. You need only to set those fields where you want + * to override the server's defaults. + * @return a SFTPFileHandle handle + * @throws IOException + */ + public SFTPFileHandle createFile(String filename, SFTPFileAttributes attr) throws IOException { + return openFile(filename, OpenFlags.SSH_FXF_CREATE_NEW, attr); + } + + public SFTPFileHandle openFile(String filename, int flags) throws IOException { + return this.openFile(filename, flags, new SFTPv6FileAttributes()); + } + + @Override + public SFTPFileHandle openFile(String filename, int flags, SFTPFileAttributes attr) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + tw.writeString(filename, this.getCharset()); + tw.writeUINT32(AceMask.ACE4_READ_DATA | AceMask.ACE4_READ_ATTRIBUTES | AceMask.ACE4_READ_ACL | AceMask.ACE4_READ_NAMED_ATTRS + | AceMask.ACE4_WRITE_DATA | AceMask.ACE4_APPEND_DATA | AceMask.ACE4_WRITE_ATTRIBUTES | AceMask.ACE4_WRITE_ACL | AceMask.ACE4_WRITE_NAMED_ATTRS); + tw.writeUINT32(flags); + tw.writeBytes(attr.toBytes()); + sendMessage(Packet.SSH_FXP_OPEN, req_id, tw.getBytes()); + byte[] resp = receiveMessage(34000); + TypesReader tr = new TypesReader(resp); + int t = tr.readByte(); + listener.read(Packet.forName(t)); + int rep_id = tr.readUINT32(); + + if (rep_id != req_id) { + throw new RequestMismatchException(); + } + + if (t == Packet.SSH_FXP_HANDLE) { + return new SFTPFileHandle(this, tr.readByteString()); + } + + if (t != Packet.SSH_FXP_STATUS) { + throw new PacketTypeException(t); + } + + int errorCode = tr.readUINT32(); + String errorMessage = tr.readString(); + listener.read(errorMessage); + throw new SFTPException(errorMessage, errorCode); + } + + @Override + public void createSymlink(String src, String target) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + // new-link-path + tw.writeString(src, this.getCharset()); + // existing-path + tw.writeString(target, this.getCharset()); + tw.writeBoolean(true); + sendMessage(Packet.SSH_FXP_LINK, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } + + @Override + public void createHardlink(String src, String target) throws IOException { + int req_id = generateNextRequestID(); + TypesWriter tw = new TypesWriter(); + // new-link-path + tw.writeString(src, this.getCharset()); + // existing-path + tw.writeString(target, this.getCharset()); + tw.writeBoolean(false); + sendMessage(Packet.SSH_FXP_LINK, req_id, tw.getBytes()); + expectStatusOKMessage(req_id); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/SFTPv6DirectoryEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/SFTPv6DirectoryEntry.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,33 @@ +package ch.ethz.ssh2; + +/** + * @version $Id: SFTPv6DirectoryEntry.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class SFTPv6DirectoryEntry implements SFTPDirectoryEntry { + /** + * A relative name within the directory, without any path components. + */ + public String filename; + + /** + * The attributes of this entry. + */ + public SFTPv6FileAttributes attributes; + + public String getFilename() { + return filename; + } + + public SFTPv6FileAttributes getAttributes() { + return attributes; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("SFTPv6DirectoryEntry{"); + sb.append("filename='").append(filename).append('\''); + sb.append(", attributes=").append(attributes); + sb.append('}'); + return sb.toString(); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/SFTPv6FileAttributes.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/SFTPv6FileAttributes.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,371 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.IOException; + +import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.packets.TypesWriter; +import ch.ethz.ssh2.sftp.AttribFlags; +import ch.ethz.ssh2.sftp.AttribTypes; + +/** + * A SFTPv3FileAttributes object represents detail information + * about a file on the server. Not all fields may/must be present. + * + * @author Christian Plattner, plattner@inf.ethz.ch + * @version $Id: SFTPv6FileAttributes.java 133 2014-04-14 12:26:29Z dkocher@sudo.ch $ + */ + +public class SFTPv6FileAttributes implements SFTPFileAttributes { + + /** + * The type field is always present + * + * @see ch.ethz.ssh2.sftp.AttribTypes + */ + private Integer type = null; + + /** + * The SIZE attribute. NULL if not present. + */ + public Long size = null; + + /** + * The POSIX permissions. NULL if not present. + *

+ * Here is a list: + *

+ *

Note: these numbers are all OCTAL.
+     * 

+ * S_IFMT 0170000 bitmask for the file type bitfields + * S_IFSOCK 0140000 socket + * S_IFLNK 0120000 symbolic link + * S_IFREG 0100000 regular file + * S_IFBLK 0060000 block device + * S_IFDIR 0040000 directory + * S_IFCHR 0020000 character device + * S_IFIFO 0010000 fifo + * S_ISUID 0004000 set UID bit + * S_ISGID 0002000 set GID bit + * S_ISVTX 0001000 sticky bit + *

+ * S_IRWXU 00700 mask for file owner permissions + * S_IRUSR 00400 owner has read permission + * S_IWUSR 00200 owner has write permission + * S_IXUSR 00100 owner has execute permission + * S_IRWXG 00070 mask for group permissions + * S_IRGRP 00040 group has read permission + * S_IWGRP 00020 group has write permission + * S_IXGRP 00010 group has execute permission + * S_IRWXO 00007 mask for permissions for others (not in group) + * S_IROTH 00004 others have read permission + * S_IWOTH 00002 others have write permisson + * S_IXOTH 00001 others have execute permission + *

+ */ + public Integer permissions = null; + + /** + * Creation time of the file. + *

+ * The createtime attribute. Represented as seconds from Jan 1, 1970 in UTC. + * NULL if not present. + */ + public Long createtime = null; + + /** + * Last access time of the file. + *

+ * The atime attribute. Represented as seconds from Jan 1, 1970 in UTC. + * NULL if not present. + */ + public Long atime = null; + + /** + * The mtime attribute. Represented as seconds from Jan 1, 1970 in UTC. + * NULL if not present. + */ + public Long mtime = null; + + /** + * Last time the file attributes were changed. The exact meaning of this field depends on the server. + *

+ * The ctime attribute. Represented as seconds from Jan 1, 1970 in UTC. + * NULL if not present. + */ + public Long ctime = null; + + /** + * The 'owner' and 'group' fields are represented as UTF-8 strings. user@localhost represents + * a user in the context of the server. + */ + public String owner = null; + + /** + * The 'owner' and 'group' fields are represented as UTF-8 strings + */ + public String group = null; + + /** + * Checks if this entry is a directory. + * + * @return Returns true if permissions are available and they indicate + * that this entry represents a directory. + */ + public boolean isDirectory() { + return (type & AttribTypes.SSH_FILEXFER_TYPE_DIRECTORY) == AttribTypes.SSH_FILEXFER_TYPE_DIRECTORY; + } + + /** + * Checks if this entry is a regular file. + * + * @return Returns true if permissions are available and they indicate + * that this entry represents a regular file. + */ + public boolean isRegularFile() { + return (type & AttribTypes.SSH_FILEXFER_TYPE_REGULAR) == AttribTypes.SSH_FILEXFER_TYPE_REGULAR; + } + + /** + * Checks if this entry is a a symlink. + * + * @return Returns true if permissions are available and they indicate + * that this entry represents a symlink. + */ + public boolean isSymlink() { + return (type & AttribTypes.SSH_FILEXFER_TYPE_SYMLINK) == AttribTypes.SSH_FILEXFER_TYPE_SYMLINK; + } + + public SFTPv6FileAttributes() { + // + } + + /** + * uint32 valid-attribute-flags + * byte type always present + * uint64 size if flag SIZE + * uint64 allocation-size if flag ALLOCATION_SIZE + * string owner if flag OWNERGROUP + * string group if flag OWNERGROUP + * uint32 permissions if flag PERMISSIONS + * int64 atime if flag ACCESSTIME + * uint32 atime-nseconds if flag SUBSECOND_TIMES + * int64 createtime if flag CREATETIME + * uint32 createtime-nseconds if flag SUBSECOND_TIMES + * int64 mtime if flag MODIFYTIME + * uint32 mtime-nseconds if flag SUBSECOND_TIMES + * int64 ctime if flag CTIME + * uint32 ctime-nseconds if flag SUBSECOND_TIMES + * string acl if flag ACL + * uint32 attrib-bits if flag BITS + * uint32 attrib-bits-valid if flag BITS + * byte text-hint if flag TEXT_HINT + * string mime-type if flag MIME_TYPE + * uint32 link-count if flag LINK_COUNT + * string untranslated-name if flag UNTRANSLATED_NAME + * uint32 extended-count if flag EXTENDED + * extension-pair extensions + */ + public SFTPv6FileAttributes(final TypesReader tr) throws IOException { + int flags = tr.readUINT32(); + // The type field is always present + this.type = tr.readByte(); + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SIZE) != 0) { + this.size = tr.readUINT64(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_ALLOCATION_SIZE) != 0) { + // Ignore + tr.readUINT64(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_OWNERGROUP) != 0) { + this.owner = tr.readString(); + this.group = tr.readString(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) { + this.permissions = tr.readUINT32(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_ACCESSTIME) != 0) { + this.atime = tr.readUINT64(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) { + // Ignore + tr.readUINT32(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_CREATETIME) != 0) { + this.createtime = tr.readUINT64(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) { + // Ignore + tr.readUINT32(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_MODIFYTIME) != 0) { + this.mtime = tr.readUINT64(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) { + // Ignore + tr.readUINT32(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_CTIME) != 0) { + this.ctime = tr.readUINT64(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) { + // Ignore + tr.readUINT32(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_ACL) != 0) { + // Ignore + tr.readString(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_BITS) != 0) { + // Ignore attrib-bits + tr.readUINT32(); + // Ignore attrib-bits-valid + tr.readUINT32(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_TEXT_HINT) != 0) { + // Ignore + tr.readByte(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_MIME_TYPE) != 0) { + // Ignore + tr.readString(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_LINK_COUNT) != 0) { + // Ignore + tr.readUINT32(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_UNTRANSLATED_NAME) != 0) { + // Ignore + tr.readString(); + } + + if ((flags & AttribFlags.SSH_FILEXFER_ATTR_EXTENDED) != 0) { + int count = tr.readUINT32(); + + // Read it anyway to detect corrupt packets + while (count > 0) { + // extension-name + tr.readByteString(); + // extension-data + tr.readByteString(); + count--; + } + } + } + + /** + * The same encoding is used both when returning file + * attributes from the server and when sending file attributes to the + * server. + * + * @return Encoded attributes + */ + public byte[] toBytes() { + TypesWriter tw = new TypesWriter(); + // The 'valid-attribute-flags' specifies which of the fields are present. Those fields + // for which the corresponding flag is not set are not present + int attrFlags = 0; + + if (this.size != null) { + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_SIZE; + } + + if ((this.owner != null) && (this.group != null)) { + // If either the owner or group field is zero length, the field should + // be considered absent, and no change should be made to that specific + // field during a modification operation. + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_OWNERGROUP; + } + + if (this.permissions != null) { + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS; + } + + if (this.atime != null) { + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_ACCESSTIME; + } + + if (this.createtime != null) { + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_CREATETIME; + } + + if (this.mtime != null) { + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_MODIFYTIME; + } + + if (this.ctime != null) { + attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_CTIME; + } + + tw.writeUINT32(attrFlags); + + // The type field is always present. + if (this.size != null) { + tw.writeUINT64(this.size); + } + + if ((this.owner != null) && (this.group != null)) { + tw.writeString(owner); + tw.writeString(group); + } + + if (this.permissions != null) { + tw.writeUINT32(this.permissions); + } + + if (this.atime != null) { + tw.writeUINT64(this.atime); + } + + if (this.createtime != null) { + tw.writeUINT64(this.createtime); + } + + if (this.mtime != null) { + tw.writeUINT64(this.mtime); + } + + if (this.ctime != null) { + tw.writeUINT64(this.ctime); + } + + return tw.getBytes(); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("SFTPv6FileAttributes{"); + sb.append("type=").append(type); + sb.append(", size=").append(size); + sb.append(", permissions=").append(permissions); + sb.append(", createtime=").append(createtime); + sb.append(", atime=").append(atime); + sb.append(", mtime=").append(mtime); + sb.append(", ctime=").append(ctime); + sb.append(", owner='").append(owner).append('\''); + sb.append(", group='").append(group).append('\''); + sb.append('}'); + return sb.toString(); + } +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/ServerAuthenticationCallback.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/ServerAuthenticationCallback.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2012-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2; + +/** + * A callback used during the authentication phase (see RFC 4252) when + * implementing a SSH server. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public interface ServerAuthenticationCallback { + /** + * The method name for host-based authentication. + */ + public final String METHOD_HOSTBASED = "hostbased"; + + /** + * The method name for public-key authentication. + */ + public final String METHOD_PUBLICKEY = "publickey"; + + /** + * The method name for password authentication. + */ + public final String METHOD_PASSWORD = "password"; + + /** + * Called when the client enters authentication. + * This gives you the chance to set a custom authentication banner + * for this SSH-2 session. This is the first method called in this interface. + * It will only called at most once per ServerConnection. + * + * @param sc The corresponding ServerConnection + * @return The authentication banner or NULL in case no banner should be send. + */ + public String initAuthentication(ServerConnection sc); + + /** + * Return the authentication methods that are currently available to the client. + * Be prepared to return this information at any time during the authentication procedure. + *

+ * The returned name-list of 'method names' (see RFC4252) indicate the authentication methods + * that may productively continue the authentication dialog. + *

+ * It is RECOMMENDED that servers only include those 'method name' + * values in the name-list that are actually useful. However, it is not + * illegal to include 'method name' values that cannot be used to + * authenticate the user. + *

+ * Already successfully completed authentications SHOULD NOT be included + * in the name-list, unless they should be performed again for some reason. + * + * @see #METHOD_HOSTBASED + * @see #METHOD_PASSWORD + * @see #METHOD_PUBLICKEY + * + * @param sc + * @return A list of method names. + */ + public String[] getRemainingAuthMethods(ServerConnection sc); + + /** + * Typically, this will be called be the client to get the list of + * authentication methods that can continue. You should simply return + * {@link AuthenticationResult#FAILURE}. + * + * @param sc + * @param username Name of the user that wants to log in with the "none" method. + * @return + */ + public AuthenticationResult authenticateWithNone(ServerConnection sc, String username); + + public AuthenticationResult authenticateWithPassword(ServerConnection sc, String username, String password); + + /** + * NOTE: Not implemented yet. + * + * @param sc + * @param username + * @param algorithm + * @param publickey + * @param signature + * @return + */ + public AuthenticationResult authenticateWithPublicKey(ServerConnection sc, String username, String algorithm, + byte[] publickey, byte[] signature); +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/ServerConnection.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/ServerConnection.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,376 @@ +/* + * Copyright (c) 2012-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2; + +import java.io.CharArrayWriter; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.net.Socket; +import java.util.List; +import java.util.ArrayList; + +import ch.ethz.ssh2.crypto.CryptoWishList; +import ch.ethz.ssh2.crypto.PEMDecoder; +import ch.ethz.ssh2.server.ServerConnectionState; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.RSAPrivateKey; +import ch.ethz.ssh2.transport.ServerTransportManager; + +/** + * A server-side SSH-2 connection. + * + * @author Christian + * + */ +public class ServerConnection { + /** + * The softwareversion presented to the SSH-2 client. + */ + private String softwareversion = String.format("Ganymed_SSHD_%s", Version.getSpecification()); + + private final ServerConnectionState state = new ServerConnectionState(this); + + /** + * Creates a new ServerConnection that will communicate + * with the client over the given Socket. + *

+ * Note: you need to call {@link #connect()} or {@link #connect(int)} to + * perform the initial handshake and establish the encrypted communication. + * + * @see #connect(int) + * + * @param s The socket + */ + public ServerConnection(Socket s) { + this(s, null, null, null); + } + + public ServerConnection(Socket s, String softwareversion) { + this(s, null, null, null); + this.softwareversion = softwareversion; + } + + /** + * Creates a new ServerConnection that will communicate + * with the client over the given Socket. + *

+ * Note: you need to call {@link #connect()} or {@link #connect(int)} to + * perform the initial handshake and establish the encrypted communication. + *

+ * Please read the javadoc for the {@link #connect(int)} method. + * + * @see #connect(int) + * + * @param s The socket + * @param dsa_key The DSA hostkey, may be NULL + * @param rsa_key The RSA hostkey, may be NULL + * @param ec_key The EC hostkey, may be NULL + */ + public ServerConnection(Socket s, KeyPair dsa_key, KeyPair rsa_key, KeyPair ec_key) { + state.s = s; + state.softwareversion = softwareversion; + state.next_dsa_key = dsa_key; + state.next_rsa_key = rsa_key; + state.next_ec_key = ec_key; + fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key, state.next_ec_key); + } + + /** + * Establish the connection and block until the first handshake has completed. + *

+ * Note: this is a wrapper that calls connect(0) (i.e., connect with no timeout). + *

+ * Please read the javadoc for the {@link #connect(int)} method. + * + * @see #connect(int) + * + * @throws IOException + */ + + public synchronized void connect() throws IOException { + connect(0); + } + + /** + * Establish the connection and block until the first handshake has completed. + *

+ * Note 1: at least one DSA, RSA or EC hostkey must be set before calling this method. + *

+ * Note 2: You must set the callbacks for authentication ({@link #setAuthenticationCallback(ServerAuthenticationCallback)}) + * and connection events ({@link #setServerConnectionCallback(ServerConnectionCallback)}). + * + * @see #setPEMHostKey(char[], String) + * @see #setPEMHostKey(File, String) + * @see #setRsaHostKey(RSAPrivateKey) + * @see #setDsaHostKey(DSAPrivateKey) + * + * @param timeout_milliseconds Timeout in milliseconds, 0 means no timeout. + * @throws IOException + */ + + public synchronized void connect(int timeout_milliseconds) throws IOException { + synchronized (state) { + if (state.cb_conn == null) + throw new IllegalStateException("The callback for connection events has not been set."); + + if (state.cb_auth == null) + throw new IllegalStateException("The callback for authentication events has not been set."); + + if (state.tm != null) + throw new IllegalStateException("The initial handshake has already been started."); + + if ((state.next_dsa_key == null) && (state.next_rsa_key == null) && (state.next_ec_key == null)) + throw new IllegalStateException("Neither an RSA nor a DSA nor an EC host key has been specified!"); + + state.tm = new ServerTransportManager(state.s); + } + + state.tm.connect(state); + /* Wait until first KEX has finished */ + state.tm.getConnectionInfo(1); + } + + /** + * Retrieve the underlying socket. + * + * @return the socket that has been passed to the constructor. + */ + public Socket getSocket() { + return state.s; + } + + /** + * Force an asynchronous key re-exchange (the call does not block). The + * latest values set for MAC, Cipher and DH group exchange parameters will + * be used. If a key exchange is currently in progress, then this method has + * the only effect that the so far specified parameters will be used for the + * next (client driven) key exchange. You may call this method only after + * the initial key exchange has been established. + *

+ * Note: This implementation will never start automatically a key exchange (other than the initial one) + * unless you or the connected SSH-2 client ask for it. + * + * @throws IOException + * In case of any failure behind the scenes. + */ + + public synchronized void forceKeyExchange() throws IOException { + synchronized (state) { + if (state.tm == null) + throw new IllegalStateException( + "Cannot force another key exchange, you need to start the key exchange first."); + + state.tm.forceKeyExchange(state.next_cryptoWishList, null, state.next_dsa_key, state.next_rsa_key, state.next_ec_key); + } + } + + /** + * Returns a {@link ConnectionInfo} object containing the details of + * the connection. May be called as soon as the first key exchange has been + * started. The method blocks in case the first key exchange has not been completed. + *

+ * Note: upon return of this method, authentication may still be pending. + * + * @return A {@link ConnectionInfo} object. + * @throws IOException + * In case of any failure behind the scenes; e.g., first key exchange was aborted. + */ + + public synchronized ConnectionInfo getConnectionInfo() throws IOException { + synchronized (state) { + if (state.tm == null) + throw new IllegalStateException( + "Cannot get details of connection, you need to start the key exchange first."); + } + + return state.tm.getConnectionInfo(1); + } + + /** + * Change the current DSA hostkey. Either a DSA or RSA or EC private key must be set for a successful handshake with + * the client. + *

+ * Note: You can change an existing DSA hostkey after the initial kex exchange (the new value will + * be used during the next server initiated key exchange), but you cannot remove (i.e., set to null) the + * current DSA key, otherwise the next key exchange may fail in case the client supports only DSA hostkeys. + * + * @param dsa_hostkey + */ + + public synchronized void setDsaHostKey(KeyPair dsa_hostkey) { + synchronized (state) { + if ((dsa_hostkey == null) && (state.next_dsa_key != null) && (state.tm != null)) + throw new IllegalStateException("Cannot remove DSA hostkey after first key exchange."); + + state.next_dsa_key = dsa_hostkey; + fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key, state.next_ec_key); + } + } + + /** + * Change the current RSA hostkey. Either a DSA or RSA or EC private key must be set for a successful handshake with + * the client. + *

+ * Note: You can change an existing RSA hostkey after the initial kex exchange (the new value will + * be used during the next server initiated key exchange), but you cannot remove (i.e., set to null) the + * current RSA key, otherwise the next key exchange may fail in case the client supports only RSA hostkeys. + * + * @param rsa_hostkey + */ + + public synchronized void setRsaHostKey(KeyPair rsa_hostkey) { + synchronized (state) { + if ((rsa_hostkey == null) && (state.next_rsa_key != null) && (state.tm != null)) + throw new IllegalStateException("Cannot remove RSA hostkey after first key exchange."); + + state.next_rsa_key = rsa_hostkey; + fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key, state.next_ec_key); + } + } + + /** + * Change the current EC hostkey. Either a DSA or RSA or EC private key must be set for a successful handshake with + * the client. + *

+ * Note: You can change an existing EC hostkey after the initial kex exchange (the new value will + * be used during the next server initiated key exchange), but you cannot remove (i.e., set to null) the + * current EC key, otherwise the next key exchange may fail in case the client supports only EC hostkeys. + * + * @param rsa_hostkey + */ + + public synchronized void setEcHostKey(KeyPair ec_hostkey) { + synchronized (state) { + if ((ec_hostkey == null) && (state.next_ec_key != null) && (state.tm != null)) + throw new IllegalStateException("Cannot remove EC hostkey after first key exchange."); + + state.next_ec_key = ec_hostkey; + fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key, state.next_ec_key); + } + } + + /** + * Utility method that loads a PEM based hostkey (either RSA or DSA based) and + * calls either setRsaHostKey() or setDsaHostKey(). + * + * @param pemdata The PEM data + * @param password Password, may be null in case the PEM data is not password protected + * @throws IOException In case of any error. + */ + public void setPEMHostKey(char[] pemdata, String password) throws IOException { + KeyPair pair = PEMDecoder.decode(pemdata, password); + PrivateKey key = pair.getPrivate(); + + if (key instanceof DSAPrivateKey) setDsaHostKey(pair); + + if (key instanceof RSAPrivateKey) setRsaHostKey(pair); + + if (key instanceof ECPrivateKey) setEcHostKey(pair); + } + + /** + * Utility method that loads a hostkey from a PEM file (either RSA or DSA based) and + * calls either setRsaHostKey() or setDsaHostKey(). + * + * @param pemFile The PEM file + * @param password Password, may be null in case the PEM file is not password protected + * @throws IOException + */ + public void setPEMHostKey(File pemFile, String password) throws IOException { + if (pemFile == null) + throw new IllegalArgumentException("pemfile argument is null"); + + char[] buff = new char[256]; + CharArrayWriter cw = new CharArrayWriter(); + FileReader fr = new FileReader(pemFile); + + while (true) { + int len = fr.read(buff); + + if (len < 0) + break; + + cw.write(buff, 0, len); + } + + fr.close(); + setPEMHostKey(cw.toCharArray(), password); + } + + private void fixCryptoWishList(CryptoWishList next_cryptoWishList, KeyPair next_dsa_key, KeyPair next_rsa_key, KeyPair next_ec_key) { + List algos = new ArrayList(); + + if (next_rsa_key != null) algos.add("ssh-rsa"); + if (next_dsa_key != null) algos.add("ssh-dss"); + if (next_ec_key != null) algos.add("ecdsa-sha2-nistp521"); + if (next_ec_key != null) algos.add("ecdsa-sha2-nistp384"); + if (next_ec_key != null) algos.add("ecdsa-sha2-nistp256"); + + next_cryptoWishList.serverHostKeyAlgorithms = new String[algos.size()]; + algos.toArray(next_cryptoWishList.serverHostKeyAlgorithms); + } + + /** + * Callback interface with methods that will be called upon events + * generated by the client (e.g., client opens a new Session which results in a ServerSession). + *

+ * Note: This must be set before the first handshake. + * + * @param cb The callback implementation + */ + + public synchronized void setServerConnectionCallback(ServerConnectionCallback cb) { + synchronized (state) { + state.cb_conn = cb; + } + } + + /** + * Callback interface with methods that will be called upon authentication events. + *

+ * Note: This must be set before the first handshake. + * + * @param cb The callback implementation + */ + + public synchronized void setAuthenticationCallback(ServerAuthenticationCallback cb) { + synchronized (state) { + state.cb_auth = cb; + } + } + + /** + * Close the connection to the SSH-2 server. All assigned sessions will be + * closed, too. Can be called at any time. Don't forget to call this once + * you don't need a connection anymore - otherwise the receiver thread may + * run forever. + */ + public void close() { + synchronized (state) { + if (state.cm != null) + state.cm.closeAllChannels(); + + if (state.tm != null) { + state.tm.close(new Throwable("Closed due to user request."), false); + } + } + } + + public void close(IOException t) { + synchronized (state) { + if (state.cm != null) + state.cm.closeAllChannels(); + + if (state.tm != null) { + state.tm.close(t, false); + } + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/ServerConnectionCallback.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/ServerConnectionCallback.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2012-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +public interface ServerConnectionCallback { + public ServerSessionCallback acceptSession(ServerSession session); +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/ServerHostKeyVerifier.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/ServerHostKeyVerifier.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +/** + * A callback interface used to implement a client specific method of checking + * server host keys. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public interface ServerHostKeyVerifier { + /** + * The actual verifier method, it will be called by the key exchange code + * on EVERY key exchange - this can happen several times during the lifetime + * of a connection. + *

+ * Note: SSH-2 servers are allowed to change their hostkey at ANY time. + * + * @param hostname the hostname used to create the {@link Connection} object + * @param port the remote TCP port + * @param serverHostKeyAlgorithm the public key algorithm (ssh-rsa or ssh-dss) + * @param serverHostKey the server's public key blob + * @return if the client wants to accept the server's host key - if not, the + * connection will be closed. + * @throws Exception Will be wrapped with an IOException, extended version of returning false =) + */ + public boolean verifyServerHostKey(String hostname, int port, String serverHostKeyAlgorithm, byte[] serverHostKey) + throws Exception; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/ServerSession.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/ServerSession.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2012-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.InputStream; +import java.io.OutputStream; + +/** + * A ServerSession represents the server-side of a session channel. + * + * @see Session + * + * @author Christian + * + */ +public interface ServerSession { + public InputStream getStdout(); + + public InputStream getStderr(); + + public OutputStream getStdin(); + + public void close(); +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/ServerSessionCallback.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/ServerSessionCallback.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2012-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2; + +import java.io.IOException; + +/** + * The interface for an object that receives events based on requests that + * a client sends on a session channel. Once the session channel has been set up, + * a program is started at the server end. The program can be a shell, an application + * program, or a subsystem with a host-independent name. Only one of these requests + * can succeed per channel. + *

+ * CAUTION: These event methods are being called from the receiver thread. The receiving of + * messages will be blocked until your event handler returns. To signal to the client that you + * are willing to accept its request, return a {@link Runnable} object which will be executed + * in a new thread after the acknowledgment has been sent back to the client. + *

+ * If a method does not allow you to return a {@link Runnable}, then the + * SSH protocol does not allow to send a status back to the client (more exactly, the client cannot + * request an acknowledgment). In these cases, if you need to invoke + * methods on the {@link ServerSession} or plan to execute long-running activity, then please do this from within a new {@link Thread}. + *

+ * If you want to signal a fatal error, then please throw an IOException. Currently, this will + * tear down the whole SSH connection. + * + * @see ServerSession + * + * @author Christian + * + */ +public interface ServerSessionCallback { + public Runnable requestPtyReq(ServerSession ss, PtySettings pty) throws IOException; + + public Runnable requestEnv(ServerSession ss, String name, String value) throws IOException; + + public Runnable requestShell(ServerSession ss) throws IOException; + + public Runnable requestExec(ServerSession ss, String command) throws IOException; + + public Runnable requestSubsystem(ServerSession ss, String subsystem) throws IOException; + + /** + * When the window (terminal) size changes on the client side, it MAY send a message to the other side to inform it of the new dimensions. + * + * @param ss the corresponding session + * @param term_width_columns + * @param term_height_rows + * @param term_width_pixels + */ + public void requestWindowChange(ServerSession ss, int term_width_columns, int term_height_rows, + int term_width_pixels, int term_height_pixels) throws IOException; + + /** + * A signal can be delivered to the remote process/service. Some systems may not implement signals, in which case they SHOULD ignore this message. + * + * @param ss the corresponding session + * @param signal (a string without the "SIG" prefix) + * @return + * @throws IOException + */ + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/Session.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/Session.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,476 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SecureRandom; + +import ch.ethz.ssh2.channel.Channel; +import ch.ethz.ssh2.channel.ChannelManager; +import ch.ethz.ssh2.channel.X11ServerData; + +/** + * A Session is a remote execution of a program. "Program" means + * in this context either a shell, an application or a system command. The + * program may or may not have a tty. Only one single program can be started on + * a session. However, multiple sessions can be active simultaneously. + * + * @author Christian Plattner + * @version $Id: Session.java 96 2014-04-08 15:14:37Z dkocher@sudo.ch $ + */ +public class Session { + private ChannelManager cm; + private Channel cn; + + private boolean flag_pty_requested = false; + private boolean flag_x11_requested = false; + private boolean flag_execution_started = false; + private boolean flag_closed = false; + + private String x11FakeCookie = null; + + private final SecureRandom rnd; + + protected Session(ChannelManager cm, SecureRandom rnd) throws IOException { + this.cm = cm; + this.cn = cm.openSessionChannel(); + this.rnd = rnd; + } + + /** + * Basically just a wrapper for lazy people - identical to calling + * requestPTY("dumb", 0, 0, 0, 0, null). + * + * @throws IOException + */ + public void requestDumbPTY() throws IOException { + requestPTY("dumb", 0, 0, 0, 0, null); + } + + /** + * Basically just another wrapper for lazy people - identical to calling + * requestPTY(term, 0, 0, 0, 0, null). + * + * @throws IOException + */ + public void requestPTY(String term) throws IOException { + requestPTY(term, 0, 0, 0, 0, null); + } + + /** + * Allocate a pseudo-terminal for this session. + *

+ * This method may only be called before a program or shell is started in + * this session. + *

+ * Different aspects can be specified: + *

+ *

    + *
  • The TERM environment variable value (e.g., vt100)
  • + *
  • The terminal's dimensions.
  • + *
  • The encoded terminal modes.
  • + *
+ * Zero dimension parameters are ignored. The character/row dimensions + * override the pixel dimensions (when nonzero). Pixel dimensions refer to + * the drawable area of the window. The dimension parameters are only + * informational. The encoding of terminal modes (parameter + * terminal_modes) is described in RFC4254. + * + * @param term The TERM environment variable value (e.g., vt100) + * @param term_width_characters terminal width, characters (e.g., 80) + * @param term_height_characters terminal height, rows (e.g., 24) + * @param term_width_pixels terminal width, pixels (e.g., 640) + * @param term_height_pixels terminal height, pixels (e.g., 480) + * @param terminal_modes encoded terminal modes (may be null) + * @throws IOException + */ + public void requestPTY(String term, int term_width_characters, int term_height_characters, int term_width_pixels, + int term_height_pixels, byte[] terminal_modes) throws IOException { + if (term == null) + throw new IllegalArgumentException("TERM cannot be null."); + + if ((terminal_modes != null) && (terminal_modes.length > 0)) { + if (terminal_modes[terminal_modes.length - 1] != 0) + throw new IOException("Illegal terminal modes description, does not end in zero byte"); + } + else + terminal_modes = new byte[] {0}; + + synchronized (this) { + /* The following is just a nicer error, we would catch it anyway later in the channel code */ + if (flag_closed) + throw new IOException("This session is closed."); + + if (flag_pty_requested) + throw new IOException("A PTY was already requested."); + + if (flag_execution_started) + throw new IOException( + "Cannot request PTY at this stage anymore, a remote execution has already started."); + + flag_pty_requested = true; + } + + cm.requestPTY(cn, term, term_width_characters, term_height_characters, term_width_pixels, term_height_pixels, + terminal_modes); + } + + /** + * Tells the server that the size of the terminal has changed. + * + * See {@link #requestPTY(String, int, int, int, int, byte[])} for more details about how parameters are interpreted. + * + * @param term_width_characters + * terminal width, characters (e.g., 80) + * @param term_height_characters + * terminal height, rows (e.g., 24) + * @param term_width_pixels + * terminal width, pixels (e.g., 640) + * @param term_height_pixels + * terminal height, pixels (e.g., 480) + * @throws IOException + */ + public void resizePTY(int term_width_characters, int term_height_characters, int term_width_pixels, int term_height_pixels) throws IOException { + requestWindowChange(term_width_characters, term_height_characters, term_width_pixels, term_height_pixels); + } + + public void requestWindowChange(int term_width_characters, int term_height_characters, int term_width_pixels, + int term_height_pixels) throws IOException { + synchronized (this) { + /* The following is just a nicer error, we would catch it anyway later in the channel code */ + if (flag_closed) + throw new IOException("This session is closed."); + + if (!flag_pty_requested) + throw new IOException("A PTY was not requested."); + } + + cm.requestWindowChange(cn, term_width_characters, term_height_characters, term_width_pixels, term_height_pixels); + } + + /** + * Request X11 forwarding for the current session. + *

+ * You have to supply the name and port of your X-server. + *

+ * This method may only be called before a program or shell is started in + * this session. + * + * @param hostname the hostname of the real (target) X11 server (e.g., 127.0.0.1) + * @param port the port of the real (target) X11 server (e.g., 6010) + * @param cookie if non-null, then present this cookie to the real X11 server + * @param singleConnection if true, then the server is instructed to only forward one single + * connection, no more connections shall be forwarded after first, or after the session + * channel has been closed + * @throws IOException + */ + public void requestX11Forwarding(String hostname, int port, byte[] cookie, boolean singleConnection) + throws IOException { + if (hostname == null) + throw new IllegalArgumentException("hostname argument may not be null"); + + synchronized (this) { + /* The following is just a nicer error, we would catch it anyway later in the channel code */ + if (flag_closed) + throw new IOException("This session is closed."); + + if (flag_x11_requested) + throw new IOException("X11 forwarding was already requested."); + + if (flag_execution_started) + throw new IOException( + "Cannot request X11 forwarding at this stage anymore, a remote execution has already started."); + + flag_x11_requested = true; + } + + /* X11ServerData - used to store data about the target X11 server */ + X11ServerData x11data = new X11ServerData(); + x11data.hostname = hostname; + x11data.port = port; + x11data.x11_magic_cookie = cookie; /* if non-null, then present this cookie to the real X11 server */ + /* Generate fake cookie - this one is used between remote clients and the ganymed proxy */ + byte[] fakeCookie = new byte[16]; + String hexEncodedFakeCookie; + + /* Make sure that this fake cookie is unique for this connection */ + + while (true) { + rnd.nextBytes(fakeCookie); + /* Generate also hex representation of fake cookie */ + StringBuilder tmp = new StringBuilder(32); + + for (int i = 0; i < fakeCookie.length; i++) { + String digit2 = Integer.toHexString(fakeCookie[i] & 0xff); + tmp.append((digit2.length() == 2) ? digit2 : "0" + digit2); + } + + hexEncodedFakeCookie = tmp.toString(); + + /* Well, yes, chances are low, but we want to be on the safe side */ + + if (cm.checkX11Cookie(hexEncodedFakeCookie) == null) + break; + } + + /* Ask for X11 forwarding */ + cm.requestX11(cn, singleConnection, "MIT-MAGIC-COOKIE-1", hexEncodedFakeCookie, 0); + + /* OK, that went fine, get ready to accept X11 connections... */ + /* ... but only if the user has not called close() in the meantime =) */ + + synchronized (this) { + if (flag_closed == false) { + this.x11FakeCookie = hexEncodedFakeCookie; + cm.registerX11Cookie(hexEncodedFakeCookie, x11data); + } + } + + /* Now it is safe to start remote X11 programs */ + } + + /** + * Execute a command on the remote machine. + * + * @param cmd The command to execute on the remote host. + * @throws IOException + */ + public void execCommand(String cmd) throws IOException { + this.execCommand(cmd, null); + } + + /** + * Execute a command on the remote machine. + * + * @param cmd The command to execute on the remote host. + * @param charsetName The charset used to convert between Java Unicode Strings and byte encodings + * @throws IOException + */ + public void execCommand(String cmd, String charsetName) throws IOException { + if (cmd == null) + throw new IllegalArgumentException("cmd argument may not be null"); + + synchronized (this) { + /* The following is just a nicer error, we would catch it anyway later in the channel code */ + if (flag_closed) + throw new IOException("This session is closed."); + + if (flag_execution_started) + throw new IOException("A remote execution has already started."); + + flag_execution_started = true; + } + + cm.requestExecCommand(cn, cmd, charsetName); + } + + /** + * Start a shell on the remote machine. + * + * @throws IOException + */ + public void startShell() throws IOException { + synchronized (this) { + /* The following is just a nicer error, we would catch it anyway later in the channel code */ + if (flag_closed) + throw new IOException("This session is closed."); + + if (flag_execution_started) + throw new IOException("A remote execution has already started."); + + flag_execution_started = true; + } + + cm.requestShell(cn); + } + + /** + * Start a subsystem on the remote machine. + * Unless you know what you are doing, you will never need this. + * + * @param name the name of the subsystem. + * @throws IOException + */ + public void startSubSystem(String name) throws IOException { + if (name == null) + throw new IllegalArgumentException("name argument may not be null"); + + synchronized (this) { + /* The following is just a nicer error, we would catch it anyway later in the channel code */ + if (flag_closed) + throw new IOException("This session is closed."); + + if (flag_execution_started) + throw new IOException("A remote execution has already started."); + + flag_execution_started = true; + } + + cm.requestSubSystem(cn, name); + } + + /** + * Request authentication agent forwarding. + * @param agent object that implements the callbacks + * + * @throws IOException in case of any problem or when the session is closed + */ + + public synchronized void requestAuthAgentForwarding(AuthAgentCallback agent) throws IOException { + synchronized (this) { + /* + * The following is just a nicer error, we would catch it anyway + * later in the channel code + */ + if (flag_closed) + throw new IOException("This session is closed."); + } + + cm.requestChannelAgentForwarding(cn, agent); + } + + public int getState() { + return cn.getState(); + } + + public InputStream getStdout() { + return cn.getStdoutStream(); + } + + public InputStream getStderr() { + return cn.getStderrStream(); + } + + public OutputStream getStdin() { + return cn.getStdinStream(); + } + + /** + * This method blocks until there is more data available on either the + * stdout or stderr InputStream of this Session. Very useful + * if you do not want to use two parallel threads for reading from the two + * InputStreams. One can also specify a timeout. NOTE: do NOT call this + * method if you use concurrent threads that operate on either of the two + * InputStreams of this Session (otherwise this method may + * block, even though more data is available). + * + * @param timeout The (non-negative) timeout in ms. 0 means no + * timeout, the call may block forever. + * @return

    + *
  • 0 if no more data will arrive.
  • + *
  • 1 if more data is available.
  • + *
  • -1 if a timeout occurred.
  • + *
+ * @throws IOException + * @deprecated This method has been replaced with a much more powerful wait-for-condition + * interface and therefore acts only as a wrapper. + */ + public int waitUntilDataAvailable(long timeout) throws IOException { + if (timeout < 0) + throw new IllegalArgumentException("timeout must not be negative!"); + + int conditions = cm.waitForCondition(cn, timeout, ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA + | ChannelCondition.EOF); + + if ((conditions & ChannelCondition.TIMEOUT) != 0) + return -1; + + if ((conditions & (ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA)) != 0) + return 1; + + /* Here we do not need to check separately for CLOSED, since CLOSED implies EOF */ + + if ((conditions & ChannelCondition.EOF) != 0) + return 0; + + throw new IllegalStateException("Unexpected condition result (" + conditions + ")"); + } + + /** + * This method blocks until certain conditions hold true on the underlying SSH-2 channel. + *

+ * This method returns as soon as one of the following happens: + *

    + *
  • at least of the specified conditions (see {@link ChannelCondition}) holds true
  • + *
  • timeout > 0 and a timeout occured (TIMEOUT will be set in result conditions) + *
  • the underlying channel was closed (CLOSED will be set in result conditions) + *
+ *

+ * In any case, the result value contains ALL current conditions, which may be more + * than the specified condition set (i.e., never use the "==" operator to test for conditions + * in the bitmask, see also comments in {@link ChannelCondition}). + *

+ * Note: do NOT call this method if you want to wait for STDOUT_DATA or STDERR_DATA and + * there are concurrent threads (e.g., StreamGobblers) that operate on either of the two + * InputStreams of this Session (otherwise this method may + * block, even though more data is available in the StreamGobblers). + * + * @param condition_set a bitmask based on {@link ChannelCondition} values + * @param timeout non-negative timeout in ms, 0 means no timeout + * @return all bitmask specifying all current conditions that are true + */ + + public int waitForCondition(int condition_set, long timeout) throws IOException { + if (timeout < 0) + throw new IllegalArgumentException("timeout must be non-negative!"); + + return cm.waitForCondition(cn, timeout, condition_set); + } + + /** + * Get the exit code/status from the remote command - if available. Be + * careful - not all server implementations return this value. It is + * generally a good idea to call this method only when all data from the + * remote side has been consumed (see also the method). + * + * @return An Integer holding the exit code, or + * null if no exit code is (yet) available. + */ + public Integer getExitStatus() { + return cn.getExitStatus(); + } + + /** + * Get the name of the signal by which the process on the remote side was + * stopped - if available and applicable. Be careful - not all server + * implementations return this value. + * + * @return An String holding the name of the signal, or + * null if the process exited normally or is still + * running (or if the server forgot to send this information). + */ + public String getExitSignal() { + return cn.getExitSignal(); + } + + /** + * Close this session. NEVER forget to call this method to free up resources - + * even if you got an exception from one of the other methods (or when + * getting an Exception on the Input- or OutputStreams). Sometimes these other + * methods may throw an exception, saying that the underlying channel is + * closed (this can happen, e.g., if the other server sent a close message.) + * However, as long as you have not called the close() + * method, you may be wasting (local) resources. + */ + public void close() { + synchronized (this) { + if (flag_closed) + return; + + flag_closed = true; + + if (x11FakeCookie != null) + cm.unRegisterX11Cookie(x11FakeCookie, true); + + try { + cm.closeChannel(cn, "Closed due to user request", true); + } + catch (IOException ignored) { + } + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/SimpleServerSessionCallback.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/SimpleServerSessionCallback.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2; + +import java.io.IOException; + +/** + * A basic ServerSessionCallback implementation. + *

+ * Note: you should derive from this class instead of implementing + * the {@link ServerSessionCallback} interface directly. This way + * your code works also in case the interface gets extended in future + * versions. + * + * @author Christian + * + */ +public class SimpleServerSessionCallback implements ServerSessionCallback { + public Runnable requestShell(ServerSession ss) throws IOException { + return null; + } + + public Runnable requestExec(ServerSession ss, String command) throws IOException { + return null; + } + + public Runnable requestSubsystem(ServerSession ss, String subsystem) throws IOException { + return null; + } + + public Runnable requestPtyReq(ServerSession ss, PtySettings pty) throws IOException { + return null; + } + + /** + * By default, silently ignore passwd environment variables. + */ + public Runnable requestEnv(ServerSession ss, String name, String value) throws IOException { + return new Runnable() { + public void run() { + /* Do nothing */ + } + }; + } + + public void requestWindowChange(ServerSession ss, int term_width_columns, int term_height_rows, + int term_width_pixels, int term_height_pixels) throws IOException { + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/StreamGobbler.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/StreamGobbler.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; + +/** + * A StreamGobbler is an InputStream that uses an internal worker + * thread to constantly consume input from another InputStream. It uses a buffer + * to store the consumed data. The buffer size is automatically adjusted, if needed. + *

+ * This class is sometimes very convenient - if you wrap a session's STDOUT and STDERR + * InputStreams with instances of this class, then you don't have to bother about + * the shared window of STDOUT and STDERR in the low level SSH-2 protocol, + * since all arriving data will be immediatelly consumed by the worker threads. + * Also, as a side effect, the streams will be buffered (e.g., single byte + * read() operations are faster). + *

+ * Other SSH for Java libraries include this functionality by default in + * their STDOUT and STDERR InputStream implementations, however, please be aware + * that this approach has also a downside: + *

+ * If you do not call the StreamGobbler's read() method often enough + * and the peer is constantly sending huge amounts of data, then you will sooner or later + * encounter a low memory situation due to the aggregated data (well, it also depends on the Java heap size). + * Joe Average will like this class anyway - a paranoid programmer would never use such an approach. + *

+ * The term "StreamGobbler" was taken from an article called "When Runtime.exec() won't", + * see http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public class StreamGobbler extends InputStream { + class GobblerThread extends Thread { + @Override + public void run() { + byte[] buff = new byte[8192]; + + while (true) { + try { + int avail = is.read(buff); + + synchronized (synchronizer) { + if (avail <= 0) { + isEOF = true; + synchronizer.notifyAll(); + break; + } + + int space_available = buffer.length - write_pos; + + if (space_available < avail) { + /* compact/resize buffer */ + int unread_size = write_pos - read_pos; + int need_space = unread_size + avail; + byte[] new_buffer = buffer; + + if (need_space > buffer.length) { + int inc = need_space / 3; + inc = (inc < 256) ? 256 : inc; + inc = (inc > 8192) ? 8192 : inc; + new_buffer = new byte[need_space + inc]; + } + + if (unread_size > 0) + System.arraycopy(buffer, read_pos, new_buffer, 0, unread_size); + + buffer = new_buffer; + read_pos = 0; + write_pos = unread_size; + } + + System.arraycopy(buff, 0, buffer, write_pos, avail); + write_pos += avail; + synchronizer.notifyAll(); + } + } + catch (IOException e) { + synchronized (synchronizer) { + exception = e; + synchronizer.notifyAll(); + break; + } + } + } + } + } + + private InputStream is; + + private final Object synchronizer = new Object(); + + private boolean isEOF = false; + private boolean isClosed = false; + private IOException exception = null; + + private byte[] buffer = new byte[2048]; + private int read_pos = 0; + private int write_pos = 0; + + public StreamGobbler(InputStream is) { + this.is = is; + GobblerThread t = new GobblerThread(); + t.setDaemon(true); + t.start(); + } + + @Override + public int read() throws IOException { + synchronized (synchronizer) { + if (isClosed) + throw new IOException("This StreamGobbler is closed."); + + while (read_pos == write_pos) { + if (exception != null) + throw exception; + + if (isEOF) + return -1; + + try { + synchronizer.wait(); + } + catch (InterruptedException e) { + throw new InterruptedIOException(); + } + } + + return buffer[read_pos++] & 0xff; + } + } + + @Override + public int available() throws IOException { + synchronized (synchronizer) { + if (isClosed) + throw new IOException("This StreamGobbler is closed."); + + return write_pos - read_pos; + } + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public void close() throws IOException { + synchronized (synchronizer) { + if (isClosed) + return; + + isClosed = true; + isEOF = true; + synchronizer.notifyAll(); + is.close(); + } + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (b == null) + throw new NullPointerException(); + + if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) + throw new IndexOutOfBoundsException(); + + if (len == 0) + return 0; + + synchronized (synchronizer) { + if (isClosed) + throw new IOException("This StreamGobbler is closed."); + + while (read_pos == write_pos) { + if (exception != null) + throw exception; + + if (isEOF) + return -1; + + try { + synchronizer.wait(); + } + catch (InterruptedException e) { + throw new InterruptedIOException(); + } + } + + int avail = write_pos - read_pos; + avail = (avail > len) ? len : avail; + System.arraycopy(buffer, read_pos, b, off, avail); + read_pos += avail; + return avail; + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/Version.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/Version.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,26 @@ +package ch.ethz.ssh2; + +/** + * Provides version information from the manifest. + * + * @version $Id: Version.java 140 2014-04-15 13:16:25Z dkocher@sudo.ch $ + */ +public class Version { + public static String getSpecification() { + Package pkg = Version.class.getPackage(); + return (pkg == null) ? "SNAPSHOT" : pkg.getSpecificationVersion() == null ? "SNAPSHOT" : pkg.getSpecificationVersion(); + } + + public static String getImplementation() { + Package pkg = Version.class.getPackage(); + return (pkg == null) ? "SNAPSHOT" : pkg.getImplementationVersion() == null ? "SNAPSHOT" : pkg.getImplementationVersion(); + } + + /** + * A simple main method that prints the version and exits + */ + public static void main(String[] args) { + System.out.println("Version: " + getSpecification()); + System.out.println("Implementation: " + getImplementation()); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/auth/AgentIdentity.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/auth/AgentIdentity.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.auth; + +public interface AgentIdentity { + String getAlgName(); + byte[] getPublicKeyBlob(); + byte[] sign(byte[] data); +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/auth/AgentProxy.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/auth/AgentProxy.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.auth; + +import java.util.Collection; + +public interface AgentProxy { + public Collection getIdentities(); +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/auth/AuthenticationManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/auth/AuthenticationManager.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.auth; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +import ch.ethz.ssh2.InteractiveCallback; +import ch.ethz.ssh2.PacketTypeException; +import ch.ethz.ssh2.crypto.PEMDecoder; +import ch.ethz.ssh2.packets.PacketServiceAccept; +import ch.ethz.ssh2.packets.PacketServiceRequest; +import ch.ethz.ssh2.packets.PacketUserauthBanner; +import ch.ethz.ssh2.packets.PacketUserauthFailure; +import ch.ethz.ssh2.packets.PacketUserauthInfoRequest; +import ch.ethz.ssh2.packets.PacketUserauthInfoResponse; +import ch.ethz.ssh2.packets.PacketUserauthRequestInteractive; +import ch.ethz.ssh2.packets.PacketUserauthRequestNone; +import ch.ethz.ssh2.packets.PacketUserauthRequestPassword; +import ch.ethz.ssh2.packets.PacketUserauthRequestPublicKey; +import ch.ethz.ssh2.packets.Packets; +import ch.ethz.ssh2.packets.TypesWriter; +import ch.ethz.ssh2.signature.DSASHA1Verify; +import ch.ethz.ssh2.signature.ECDSASHA2Verify; +import ch.ethz.ssh2.signature.RSASHA1Verify; +import ch.ethz.ssh2.transport.ClientTransportManager; +import ch.ethz.ssh2.transport.MessageHandler; + +/** + * @author Christian Plattner + * @version $Id: AuthenticationManager.java 161 2014-05-01 18:01:55Z dkocher@sudo.ch $ + */ +public class AuthenticationManager implements MessageHandler { + private ClientTransportManager tm; + + private final BlockingQueue packets + = new ArrayBlockingQueue(5); + + private boolean connectionClosed = false; + + private String banner; + + private Set remainingMethods + = new HashSet(); + + private boolean isPartialSuccess = false; + + private boolean authenticated = false; + private boolean initDone = false; + + public AuthenticationManager(ClientTransportManager tm) { + this.tm = tm; + } + + private byte[] deQueue() throws IOException { + if (connectionClosed) { + throw(IOException) new IOException("The connection is closed.").initCause(tm.getReasonClosedCause()); + } + + // Wait for packet + try { + return packets.take(); + } + catch (InterruptedException e) { + throw new InterruptedIOException(e.getMessage()); + } + } + + byte[] getNextMessage() throws IOException { + while (true) { + byte[] message = deQueue(); + + switch (message[0]) { + case Packets.SSH_MSG_USERAUTH_BANNER: + // The server may send an SSH_MSG_USERAUTH_BANNER message at any + // time after this authentication protocol starts and before + // authentication is successful. + PacketUserauthBanner sb = new PacketUserauthBanner(message); + banner = sb.getBanner(); + break; + + default: + return message; + } + } + } + + public Set getRemainingMethods(String user) throws IOException { + initialize(user); + return remainingMethods; + } + + public String getBanner() { + return banner; + } + + public boolean getPartialSuccess() { + return isPartialSuccess; + } + + private boolean initialize(String user) throws IOException { + if (initDone == false) { + tm.registerMessageHandler(this, 0, 255); + PacketServiceRequest sr = new PacketServiceRequest("ssh-userauth"); + tm.sendMessage(sr.getPayload()); + final PacketServiceAccept accept = new PacketServiceAccept(this.getNextMessage()); + PacketUserauthRequestNone auth = new PacketUserauthRequestNone("ssh-connection", user); + tm.sendMessage(auth.getPayload()); + byte[] message = this.getNextMessage(); + initDone = true; + + switch (message[0]) { + case Packets.SSH_MSG_USERAUTH_SUCCESS: + authenticated = true; + tm.removeMessageHandler(this); + return true; + + case Packets.SSH_MSG_USERAUTH_FAILURE: + PacketUserauthFailure puf = new PacketUserauthFailure(message); + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + return false; + } + + throw new PacketTypeException(message[0]); + } + + return authenticated; + } + + public boolean authenticatePublicKey(String user, AgentProxy proxy) throws IOException { + initialize(user); + boolean success; + + for (AgentIdentity identity : proxy.getIdentities()) { + success = authenticatePublicKey(user, identity); + + if (success) { + return true; + } + } + + return false; + } + + private boolean authenticatePublicKey(String user, AgentIdentity identity) throws IOException { + if (!remainingMethods.contains("publickey")) { + throw new IOException("Authentication method not supported"); + } + + byte[] pubKeyBlob = identity.getPublicKeyBlob(); + + if (pubKeyBlob == null) { + return false; + } + + TypesWriter tw = new TypesWriter(); + byte[] H = tm.getSessionIdentifier(); + tw.writeString(H, 0, H.length); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString("ssh-connection"); + tw.writeString("publickey"); + tw.writeBoolean(true); + tw.writeString(identity.getAlgName()); + tw.writeString(pubKeyBlob, 0, pubKeyBlob.length); + byte[] msg = tw.getBytes(); + byte[] response = identity.sign(msg); + PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey( + "ssh-connection", user, identity.getAlgName(), pubKeyBlob, response); + tm.sendMessage(ua.getPayload()); + byte[] message = getNextMessage(); + final int type = message[0]; + + switch (type) { + case Packets.SSH_MSG_USERAUTH_SUCCESS: + authenticated = true; + tm.removeMessageHandler(this); + return true; + + case Packets.SSH_MSG_USERAUTH_FAILURE: + PacketUserauthFailure puf = new PacketUserauthFailure(message); + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + return false; + } + + throw new PacketTypeException(type); + } + + public boolean authenticatePublicKey(String user, char[] PEMPrivateKey, String password, SecureRandom rnd) + throws IOException { + KeyPair pair = PEMDecoder.decode(PEMPrivateKey, password); + return authenticatePublicKey(user, pair, rnd); + } + + public boolean authenticatePublicKey(String user, KeyPair pair, SecureRandom rnd) + throws IOException { + PrivateKey key = pair.getPrivate(); + + try { + initialize(user); + + if (!remainingMethods.contains("publickey")) { + throw new IOException("Authentication method publickey not supported by the server at this stage."); + } + + if (key instanceof DSAPrivateKey) { + DSAPrivateKey pk = (DSAPrivateKey) key; + byte[] pk_enc = DSASHA1Verify.encodeSSHDSAPublicKey((DSAPublicKey) pair.getPublic()); + TypesWriter tw = new TypesWriter(); + byte[] H = tm.getSessionIdentifier(); + tw.writeString(H, 0, H.length); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString("ssh-connection"); + tw.writeString("publickey"); + tw.writeBoolean(true); + tw.writeString("ssh-dss"); + tw.writeString(pk_enc, 0, pk_enc.length); + byte[] msg = tw.getBytes(); + byte[] ds = DSASHA1Verify.generateSignature(msg, pk, rnd); + byte[] ds_enc = DSASHA1Verify.encodeSSHDSASignature(ds); + PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user, + "ssh-dss", pk_enc, ds_enc); + tm.sendMessage(ua.getPayload()); + } + else if (key instanceof RSAPrivateKey) { + RSAPrivateKey pk = (RSAPrivateKey) key; + byte[] pk_enc = RSASHA1Verify.encodeSSHRSAPublicKey((RSAPublicKey) pair.getPublic()); + TypesWriter tw = new TypesWriter(); + { + byte[] H = tm.getSessionIdentifier(); + tw.writeString(H, 0, H.length); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString("ssh-connection"); + tw.writeString("publickey"); + tw.writeBoolean(true); + tw.writeString("ssh-rsa"); + tw.writeString(pk_enc, 0, pk_enc.length); + } + byte[] msg = tw.getBytes(); + byte[] ds = RSASHA1Verify.generateSignature(msg, pk); + byte[] rsa_sig_enc = RSASHA1Verify.encodeSSHRSASignature(ds); + PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user, + "ssh-rsa", pk_enc, rsa_sig_enc); + tm.sendMessage(ua.getPayload()); + } + else if (key instanceof ECPrivateKey) { + ECPrivateKey pk = (ECPrivateKey) key; + final String algo = ECDSASHA2Verify.ECDSA_SHA2_PREFIX + + ECDSASHA2Verify.getCurveName(pk.getParams()); + byte[] pk_enc = ECDSASHA2Verify.encodeSSHECDSAPublicKey((ECPublicKey) pair.getPublic()); + TypesWriter tw = new TypesWriter(); + { + byte[] H = tm.getSessionIdentifier(); + tw.writeString(H, 0, H.length); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString("ssh-connection"); + tw.writeString("publickey"); + tw.writeBoolean(true); + tw.writeString(algo); + tw.writeString(pk_enc, 0, pk_enc.length); + } + byte[] msg = tw.getBytes(); + byte[] ds = ECDSASHA2Verify.generateSignature(msg, pk); + byte[] ec_sig_enc = ECDSASHA2Verify.encodeSSHECDSASignature(ds, pk.getParams()); + PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user, + algo, pk_enc, ec_sig_enc); + tm.sendMessage(ua.getPayload()); + } + else { + throw new IOException("Unknown private key type returned by the PEM decoder."); + } + + byte[] message = getNextMessage(); + final int type = message[0]; + + switch (type) { + case Packets.SSH_MSG_USERAUTH_SUCCESS: + authenticated = true; + tm.removeMessageHandler(this); + return true; + + case Packets.SSH_MSG_USERAUTH_FAILURE: + PacketUserauthFailure puf = new PacketUserauthFailure(message); + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + return false; + } + + throw new PacketTypeException(type); + } + catch (IOException e) { + tm.close(e, false); + throw e; + } + } + + public boolean authenticateNone(String user) throws IOException { + try { + initialize(user); + return authenticated; + } + catch (IOException e) { + tm.close(e, false); + throw e; + } + } + + public boolean authenticatePassword(String user, String pass) throws IOException { + try { + initialize(user); + + if (!remainingMethods.contains("password")) { + throw new IOException("Authentication method not supported"); + } + + PacketUserauthRequestPassword ua = new PacketUserauthRequestPassword("ssh-connection", user, pass); + tm.sendMessage(ua.getPayload()); + byte[] message = getNextMessage(); + final int type = message[0]; + + switch (type) { + case Packets.SSH_MSG_USERAUTH_SUCCESS: + authenticated = true; + tm.removeMessageHandler(this); + return true; + + case Packets.SSH_MSG_USERAUTH_FAILURE: + PacketUserauthFailure puf = new PacketUserauthFailure(message); + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + return false; + } + + throw new PacketTypeException(type); + } + catch (IOException e) { + tm.close(e, false); + throw e; + } + } + + public boolean authenticateInteractive(String user, String[] submethods, InteractiveCallback cb) throws IOException { + try { + initialize(user); + + if (!remainingMethods.contains("keyboard-interactive")) { + throw new IOException( + "Authentication method keyboard-interactive not supported by the server at this stage."); + } + + PacketUserauthRequestInteractive ua = new PacketUserauthRequestInteractive("ssh-connection", user, + submethods); + tm.sendMessage(ua.getPayload()); + + while (true) { + byte[] message = getNextMessage(); + final int type = message[0]; + + switch (type) { + case Packets.SSH_MSG_USERAUTH_SUCCESS: + authenticated = true; + tm.removeMessageHandler(this); + return true; + + case Packets.SSH_MSG_USERAUTH_FAILURE: + PacketUserauthFailure puf = new PacketUserauthFailure(message); + remainingMethods = puf.getAuthThatCanContinue(); + isPartialSuccess = puf.isPartialSuccess(); + return false; + + case Packets.SSH_MSG_USERAUTH_INFO_REQUEST: + PacketUserauthInfoRequest info = new PacketUserauthInfoRequest(message); + String[] responses; + + try { + responses = cb.replyToChallenge(info.getName(), info.getInstruction(), info.getNumPrompts(), + info.getPrompt(), info.getEcho()); + } + catch (Exception e) { + throw new IOException("Exception in callback.", e); + } + + PacketUserauthInfoResponse puir = new PacketUserauthInfoResponse(responses); + tm.sendMessage(puir.getPayload()); + continue; + } + + throw new PacketTypeException(type); + } + } + catch (IOException e) { + tm.close(e, false); + throw e; + } + } + + public void handleFailure(final IOException failure) { + connectionClosed = true; + } + + public void handleMessage(byte[] message) throws IOException { + packets.add(message); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/auth/ServerAuthenticationManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/auth/ServerAuthenticationManager.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,128 @@ + +package ch.ethz.ssh2.auth; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import ch.ethz.ssh2.AuthenticationResult; +import ch.ethz.ssh2.PacketTypeException; +import ch.ethz.ssh2.ServerAuthenticationCallback; +import ch.ethz.ssh2.channel.ChannelManager; +import ch.ethz.ssh2.packets.PacketServiceAccept; +import ch.ethz.ssh2.packets.PacketServiceRequest; +import ch.ethz.ssh2.packets.PacketUserauthBanner; +import ch.ethz.ssh2.packets.PacketUserauthFailure; +import ch.ethz.ssh2.packets.PacketUserauthSuccess; +import ch.ethz.ssh2.packets.Packets; +import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.server.ServerConnectionState; +import ch.ethz.ssh2.transport.MessageHandler; + +public class ServerAuthenticationManager implements MessageHandler { + private final ServerConnectionState state; + + public ServerAuthenticationManager(ServerConnectionState state) { + this.state = state; + state.tm.registerMessageHandler(this, 0, 255); + } + + private void sendresult(AuthenticationResult result) throws IOException { + if (AuthenticationResult.SUCCESS == result) { + PacketUserauthSuccess pus = new PacketUserauthSuccess(); + state.tm.sendAsynchronousMessage(pus.getPayload()); + state.tm.removeMessageHandler(this); + state.tm.registerMessageHandler(this, 50, 79); + state.cm = new ChannelManager(state); + state.flag_auth_completed = true; + } + else { + Set remaining_methods = new HashSet(); + + if (state.cb_auth != null) { + remaining_methods.addAll(Arrays.asList( + state.cb_auth.getRemainingAuthMethods(state.conn))); + } + + PacketUserauthFailure puf = new PacketUserauthFailure(remaining_methods, + AuthenticationResult.PARTIAL_SUCCESS == result); + state.tm.sendAsynchronousMessage(puf.getPayload()); + } + } + + public void handleFailure(final IOException failure) { + // + } + + public void handleMessage(byte[] msg) throws IOException { + /* Ignore all authentication messages after successful auth */ + if (state.flag_auth_completed) { + return; + } + + if (!state.flag_auth_serviceRequested) { + /* Must be PacketServiceRequest */ + PacketServiceRequest psr = new PacketServiceRequest(msg); + + if (!"ssh-userauth".equals(psr.getServiceName())) { + throw new IOException("SSH protocol error, expected ssh-userauth service request"); + } + + PacketServiceAccept psa = new PacketServiceAccept("ssh-userauth"); + state.tm.sendAsynchronousMessage(psa.getPayload()); + String banner = state.cb_auth.initAuthentication(state.conn); + + if (banner != null) { + PacketUserauthBanner pub = new PacketUserauthBanner(banner); + state.tm.sendAsynchronousMessage(pub.getPayload()); + } + + state.flag_auth_serviceRequested = true; + return; + } + + ServerAuthenticationCallback cb = state.cb_auth; + TypesReader tr = new TypesReader(msg); + int packet_type = tr.readByte(); + + if (packet_type == Packets.SSH_MSG_USERAUTH_REQUEST) { + String username = tr.readString("UTF-8"); + String service = tr.readString(); + String method = tr.readString(); + + if (!"ssh-connection".equals(service)) { + sendresult(AuthenticationResult.FAILURE); + return; + } + + if ("none".equals(method)) { + if (cb != null) { + sendresult(cb.authenticateWithNone(state.conn, username)); + return; + } + } + + if ("password".equals(method)) { + boolean flag_change_pass = tr.readBoolean(); + + if (flag_change_pass) { + sendresult(AuthenticationResult.FAILURE); + return; + } + + String password = tr.readString("UTF-8"); + + if (cb != null) { + sendresult(cb.authenticateWithPassword(state.conn, username, password)); + return; + } + } + + sendresult(AuthenticationResult.FAILURE); + return; + } + + throw new PacketTypeException(packet_type); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/channel/AuthAgentForwardThread.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/channel/AuthAgentForwardThread.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,546 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.ethz.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.spec.DSAPrivateKeySpec; +import java.security.spec.DSAPublicKeySpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.util.Map; +import java.util.Map.Entry; + +import ch.ethz.ssh2.AuthAgentCallback; +import ch.ethz.ssh2.crypto.SecureRandomFix; +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.packets.TypesWriter; +import ch.ethz.ssh2.signature.DSASHA1Verify; +import ch.ethz.ssh2.signature.ECDSASHA2Verify; +import ch.ethz.ssh2.signature.RSASHA1Verify; + +/** + * AuthAgentForwardThread. + * + * @author Kenny Root + * @version $Id$ + */ +public class AuthAgentForwardThread extends Thread implements IChannelWorkerThread { + private static final byte[] SSH_AGENT_FAILURE = {0, 0, 0, 1, 5}; // 5 + private static final byte[] SSH_AGENT_SUCCESS = {0, 0, 0, 1, 6}; // 6 + + private static final int SSH2_AGENTC_REQUEST_IDENTITIES = 11; + private static final int SSH2_AGENT_IDENTITIES_ANSWER = 12; + + private static final int SSH2_AGENTC_SIGN_REQUEST = 13; + private static final int SSH2_AGENT_SIGN_RESPONSE = 14; + + private static final int SSH2_AGENTC_ADD_IDENTITY = 17; + private static final int SSH2_AGENTC_REMOVE_IDENTITY = 18; + private static final int SSH2_AGENTC_REMOVE_ALL_IDENTITIES = 19; + +// private static final int SSH_AGENTC_ADD_SMARTCARD_KEY = 20; +// private static final int SSH_AGENTC_REMOVE_SMARTCARD_KEY = 21; + + private static final int SSH_AGENTC_LOCK = 22; + private static final int SSH_AGENTC_UNLOCK = 23; + + private static final int SSH2_AGENTC_ADD_ID_CONSTRAINED = 25; +// private static final int SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED = 26; + + // Constraints for adding keys + private static final int SSH_AGENT_CONSTRAIN_LIFETIME = 1; + private static final int SSH_AGENT_CONSTRAIN_CONFIRM = 2; + + // Flags for signature requests +// private static final int SSH_AGENT_OLD_SIGNATURE = 1; + + private static final Logger log = Logger.getLogger(RemoteAcceptThread.class); + + AuthAgentCallback authAgent; + OutputStream os; + InputStream is; + Channel c; + + byte[] buffer = new byte[Channel.CHANNEL_BUFFER_SIZE]; + + public AuthAgentForwardThread(Channel c, AuthAgentCallback authAgent) { + this.c = c; + this.authAgent = authAgent; + log.debug("AuthAgentForwardThread started"); + } + + @Override + public void run() { + try { + c.cm.registerThread(this); + } + catch (IOException e) { + stopWorking(); + return; + } + + try { + c.cm.sendOpenConfirmation(c); + is = c.getStdoutStream(); + os = c.getStdinStream(); + int totalSize = 4; + int readSoFar = 0; + + while (true) { + int len; + + try { + len = is.read(buffer, readSoFar, buffer.length - readSoFar); + } + catch (IOException e) { + stopWorking(); + return; + } + + if (len <= 0) + break; + + readSoFar += len; + + if (readSoFar >= 4) { + TypesReader tr = new TypesReader(buffer, 0, 4); + totalSize = tr.readUINT32() + 4; + } + + if (totalSize == readSoFar) { + TypesReader tr = new TypesReader(buffer, 4, readSoFar - 4); + int messageType = tr.readByte(); + + switch (messageType) { + case SSH2_AGENTC_REQUEST_IDENTITIES: + sendIdentities(); + break; + + case SSH2_AGENTC_ADD_IDENTITY: + addIdentity(tr, false); + break; + + case SSH2_AGENTC_ADD_ID_CONSTRAINED: + addIdentity(tr, true); + break; + + case SSH2_AGENTC_REMOVE_IDENTITY: + removeIdentity(tr); + break; + + case SSH2_AGENTC_REMOVE_ALL_IDENTITIES: + removeAllIdentities(tr); + break; + + case SSH2_AGENTC_SIGN_REQUEST: + processSignRequest(tr); + break; + + case SSH_AGENTC_LOCK: + processLockRequest(tr); + break; + + case SSH_AGENTC_UNLOCK: + processUnlockRequest(tr); + break; + + default: + os.write(SSH_AGENT_FAILURE); + break; + } + + readSoFar = 0; + } + } + + c.cm.closeChannel(c, "EOF on both streams reached.", true); + } + catch (IOException e) { + log.debug("IOException in agent forwarder: " + e.getMessage()); + + try { + is.close(); + } + catch (IOException e1) { + } + + try { + os.close(); + } + catch (IOException e2) { + } + + try { + c.cm.closeChannel(c, "IOException in agent forwarder (" + e.getMessage() + ")", true); + } + catch (IOException e3) { + } + } + } + + public void stopWorking() { + try { + /* This will lead to an IOException in the is.read() call */ + is.close(); + } + catch (IOException e) { + } + } + + /** + * @return whether the agent is locked + */ + private boolean failWhenLocked() throws IOException { + if (authAgent.isAgentLocked()) { + os.write(SSH_AGENT_FAILURE); + return true; + } + else + return false; + } + + private void sendIdentities() throws IOException { + Map keys = null; + TypesWriter tw = new TypesWriter(); + tw.writeByte(SSH2_AGENT_IDENTITIES_ANSWER); + int numKeys = 0; + + if (!authAgent.isAgentLocked()) + keys = authAgent.retrieveIdentities(); + + if (keys != null) + numKeys = keys.size(); + + tw.writeUINT32(numKeys); + + if (keys != null) { + for (Entry entry : keys.entrySet()) { + byte[] keyBytes = entry.getValue(); + tw.writeString(keyBytes, 0, keyBytes.length); + tw.writeString(entry.getKey()); + } + } + + sendPacket(tw.getBytes()); + } + + /** + * @param tr + */ + private void addIdentity(TypesReader tr, boolean checkConstraints) { + try { + if (failWhenLocked()) + return; + + String type = tr.readString(); + String comment; + String keyType; + KeySpec pubSpec; + KeySpec privSpec; + + if (type.equals("ssh-rsa")) { + keyType = "RSA"; + BigInteger n = tr.readMPINT(); + BigInteger e = tr.readMPINT(); + BigInteger d = tr.readMPINT(); + BigInteger iqmp = tr.readMPINT(); + BigInteger p = tr.readMPINT(); + BigInteger q = tr.readMPINT(); + comment = tr.readString(); + // Derive the extra values Java needs. + BigInteger dmp1 = d.mod(p.subtract(BigInteger.ONE)); + BigInteger dmq1 = d.mod(q.subtract(BigInteger.ONE)); + pubSpec = new RSAPublicKeySpec(n, e); + privSpec = new RSAPrivateCrtKeySpec(n, e, d, p, q, dmp1, dmq1, iqmp); + } + else if (type.equals("ssh-dss")) { + keyType = "DSA"; + BigInteger p = tr.readMPINT(); + BigInteger q = tr.readMPINT(); + BigInteger g = tr.readMPINT(); + BigInteger y = tr.readMPINT(); + BigInteger x = tr.readMPINT(); + comment = tr.readString(); + pubSpec = new DSAPublicKeySpec(y, p, q, g); + privSpec = new DSAPrivateKeySpec(x, p, q, g); + } + else if (type.equals("ecdsa-sha2-nistp256")) { + keyType = "EC"; + String curveName = tr.readString(); + byte[] groupBytes = tr.readByteString(); + BigInteger exponent = tr.readMPINT(); + comment = tr.readString(); + + if (!"nistp256".equals(curveName)) { + log.debug("Invalid curve name for ecdsa-sha2-nistp256: " + curveName); + os.write(SSH_AGENT_FAILURE); + return; + } + + ECParameterSpec nistp256 = ECDSASHA2Verify.EllipticCurves.nistp256; + ECPoint group = ECDSASHA2Verify.decodeECPoint(groupBytes, nistp256.getCurve()); + + if (group == null) { + log.debug("No groupfor ecdsa-sha2-nistp256: "); + os.write(SSH_AGENT_FAILURE); + return; + } + + pubSpec = new ECPublicKeySpec(group, nistp256); + privSpec = new ECPrivateKeySpec(exponent, nistp256); + } + else { + log.debug("Unknown key type: " + type); + os.write(SSH_AGENT_FAILURE); + return; + } + + PublicKey pubKey; + PrivateKey privKey; + + try { + KeyFactory kf = KeyFactory.getInstance(keyType); + pubKey = kf.generatePublic(pubSpec); + privKey = kf.generatePrivate(privSpec); + } + catch (NoSuchAlgorithmException ex) { + // TODO: log error + os.write(SSH_AGENT_FAILURE); + return; + } + catch (InvalidKeySpecException ex) { + // TODO: log error + os.write(SSH_AGENT_FAILURE); + return; + } + + KeyPair pair = new KeyPair(pubKey, privKey); + boolean confirmUse = false; + int lifetime = 0; + + if (checkConstraints) { + while (tr.remain() > 0) { + int constraint = tr.readByte(); + + if (constraint == SSH_AGENT_CONSTRAIN_CONFIRM) + confirmUse = true; + else if (constraint == SSH_AGENT_CONSTRAIN_LIFETIME) + lifetime = tr.readUINT32(); + else { + // Unknown constraint. Bail. + os.write(SSH_AGENT_FAILURE); + return; + } + } + } + + if (authAgent.addIdentity(pair, comment, confirmUse, lifetime)) + os.write(SSH_AGENT_SUCCESS); + else + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e) { + try { + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e1) { + } + } + } + + /** + * @param tr + */ + private void removeIdentity(TypesReader tr) { + try { + if (failWhenLocked()) + return; + + byte[] publicKey = tr.readByteString(); + + if (authAgent.removeIdentity(publicKey)) + os.write(SSH_AGENT_SUCCESS); + else + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e) { + try { + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e1) { + } + } + } + + /** + * @param tr + */ + private void removeAllIdentities(TypesReader tr) { + try { + if (failWhenLocked()) + return; + + if (authAgent.removeAllIdentities()) + os.write(SSH_AGENT_SUCCESS); + else + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e) { + try { + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e1) { + } + } + } + + private void processSignRequest(TypesReader tr) { + try { + if (failWhenLocked()) + return; + + byte[] publicKeyBytes = tr.readByteString(); + byte[] challenge = tr.readByteString(); + int flags = tr.readUINT32(); + + if (flags != 0) { + // We don't understand any flags; abort! + os.write(SSH_AGENT_FAILURE); + return; + } + + KeyPair pair = authAgent.getKeyPair(publicKeyBytes); + + if (pair == null) { + os.write(SSH_AGENT_FAILURE); + return; + } + + byte[] response; + PrivateKey privKey = pair.getPrivate(); + + if (privKey instanceof RSAPrivateKey) { + byte[] signature = RSASHA1Verify.generateSignature(challenge, + (RSAPrivateKey) privKey); + response = RSASHA1Verify.encodeSSHRSASignature(signature); + } + else if (privKey instanceof DSAPrivateKey) { + byte[] signature = DSASHA1Verify.generateSignature(challenge, + (DSAPrivateKey) privKey, new SecureRandomFix()); + response = DSASHA1Verify.encodeSSHDSASignature(signature); + } + else if (privKey instanceof ECPrivateKey) { + ECPrivateKey pk = (ECPrivateKey) privKey; + byte[] signature = ECDSASHA2Verify.generateSignature(challenge, pk); + response = ECDSASHA2Verify.encodeSSHECDSASignature(signature, pk.getParams()); + } + else { + os.write(SSH_AGENT_FAILURE); + return; + } + + TypesWriter tw = new TypesWriter(); + tw.writeByte(SSH2_AGENT_SIGN_RESPONSE); + tw.writeString(response, 0, response.length); + sendPacket(tw.getBytes()); + } + catch (IOException e) { + try { + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e1) { + } + } + } + + /** + * @param tr + */ + private void processLockRequest(TypesReader tr) { + try { + if (failWhenLocked()) + return; + + String lockPassphrase = tr.readString(); + + if (!authAgent.setAgentLock(lockPassphrase)) { + os.write(SSH_AGENT_FAILURE); + return; + } + else + os.write(SSH_AGENT_SUCCESS); + } + catch (IOException e) { + try { + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e1) { + } + } + } + + /** + * @param tr + */ + private void processUnlockRequest(TypesReader tr) { + try { + String unlockPassphrase = tr.readString(); + + if (authAgent.requestAgentUnlock(unlockPassphrase)) + os.write(SSH_AGENT_SUCCESS); + else + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e) { + try { + os.write(SSH_AGENT_FAILURE); + } + catch (IOException e1) { + } + } + } + + /** + * @param tw + * @throws IOException + */ + private void sendPacket(byte[] message) throws IOException { + TypesWriter packet = new TypesWriter(); + packet.writeUINT32(message.length); + packet.writeBytes(message); + os.write(packet.getBytes()); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/channel/Channel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/channel/Channel.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.channel; + +import java.io.IOException; + +import ch.ethz.ssh2.transport.TransportManager; + +/** + * Channel. + * + * @author Christian Plattner + * @version $Id: Channel.java 123 2014-04-12 21:11:47Z dkocher@sudo.ch $ + */ +public class Channel { + /* + * OK. Here is an important part of the JVM Specification: + * (http://java.sun.com/docs/books/vmspec/2nd-edition/html/Threads.doc.html#22214) + * + * Any association between locks and variables is purely conventional. + * Locking any lock conceptually flushes all variables from a thread's + * working memory, and unlocking any lock forces the writing out to main + * memory of all variables that the thread has assigned. That a lock may be + * associated with a particular object or a class is purely a convention. + * (...) + * + * If a thread uses a particular shared variable only after locking a + * particular lock and before the corresponding unlocking of that same lock, + * then the thread will read the shared value of that variable from main + * memory after the lock operation, if necessary, and will copy back to main + * memory the value most recently assigned to that variable before the + * unlock operation. + * + * This, in conjunction with the mutual exclusion rules for locks, suffices + * to guarantee that values are correctly transmitted from one thread to + * another through shared variables. + * + * ====> Always keep that in mind when modifying the Channel/ChannelManger + * code. + * + */ + + public static final int STATE_OPENING = 1; + public static final int STATE_OPEN = 2; + public static final int STATE_CLOSED = 4; + + static final int CHANNEL_BUFFER_SIZE = 32 * 1024 * 3 * 2; + + /* + * To achieve correctness, the following rules have to be respected when + * accessing this object: + */ + + // These fields can always be read + final ChannelManager cm; + final ChannelOutputStream stdinStream; + final ChannelInputStream stdoutStream; + final ChannelInputStream stderrStream; + + // In case this channel belongs to a server-side session. + ServerSessionImpl ss; + + // These two fields will only be written while the Channel is in state + // STATE_OPENING. + // The code makes sure that the two fields are written out when the state is + // changing to STATE_OPEN. + // Therefore, if you know that the Channel is in state STATE_OPEN, then you + // can read these two fields without synchronizing on the Channel. However, make + // sure that you get the latest values (e.g., flush caches by synchronizing on any + // object). However, to be on the safe side, you can lock the channel. + + int localID = -1; + int remoteID = -1; + + /* + * Make sure that we never send a data/EOF/WindowChange msg after a CLOSE + * msg. + * + * This is a little bit complicated, but we have to do it in that way, since + * we cannot keep a lock on the Channel during the send operation (this + * would block sometimes the receiver thread, and, in extreme cases, can + * lead to a deadlock on both sides of the connection (senders are blocked + * since the receive buffers on the other side are full, and receiver + * threads wait for the senders to finish). It all depends on the + * implementation on the other side. But we cannot make any assumptions, we + * have to assume the worst case. Confused? Just believe me. + */ + + /* + * If you send a message on a channel, then you have to aquire the + * "channelSendLock" and check the "closeMessageSent" flag (this variable + * may only be accessed while holding the "channelSendLock" !!! + * + * BTW: NEVER EVER SEND MESSAGES FROM THE RECEIVE THREAD - see explanation + * above. + */ + + final Object channelSendLock = new Object(); + boolean closeMessageSent = false; + + /* + * Stop memory fragmentation by allocating this often used buffer. + * May only be used while holding the channelSendLock + */ + + final byte[] msgWindowAdjust = new byte[9]; + + // If you access (read or write) any of the following fields, then you have + // to synchronize on the channel. + + int state = STATE_OPENING; + + boolean closeMessageRecv = false; + + /* This is a stupid implementation. At the moment we can only wait + * for one pending request per channel. + */ + int successCounter = 0; + int failedCounter = 0; + + int localWindow = 0; /* locally, we use a small window, < 2^31 */ + long remoteWindow = 0; /* long for readable 2^32 - 1 window support */ + + int localMaxPacketSize = -1; + int remoteMaxPacketSize = -1; + + final byte[] stdoutBuffer = new byte[CHANNEL_BUFFER_SIZE]; + final byte[] stderrBuffer = new byte[CHANNEL_BUFFER_SIZE]; + + int stdoutReadpos = 0; + int stdoutWritepos = 0; + int stderrReadpos = 0; + int stderrWritepos = 0; + + boolean EOF = false; + + Integer exit_status; + + String exit_signal; + + // we keep the x11 cookie so that this channel can be closed when this + // specific x11 forwarding gets stopped + + String hexX11FakeCookie; + + // reasonClosed is special, since we sometimes need to access it + // while holding the channelSendLock. + // We protect it with a private short term lock. + + private final Object reasonClosedLock = new Object(); + private IOException reasonClosed = null; + + public Channel(ChannelManager cm) { + this.cm = cm; + this.localWindow = CHANNEL_BUFFER_SIZE; + this.localMaxPacketSize = TransportManager.MAX_PACKET_SIZE; + this.stdinStream = new ChannelOutputStream(this); + this.stdoutStream = new ChannelInputStream(this, false); + this.stderrStream = new ChannelInputStream(this, true); + } + + /* Methods to allow access from classes outside of this package */ + + public ChannelInputStream getStderrStream() { + return stderrStream; + } + + public ChannelOutputStream getStdinStream() { + return stdinStream; + } + + public ChannelInputStream getStdoutStream() { + return stdoutStream; + } + + public String getExitSignal() { + synchronized (this) { + return exit_signal; + } + } + + public Integer getExitStatus() { + synchronized (this) { + return exit_status; + } + } + + public IOException getReasonClosed() { + synchronized (reasonClosedLock) { + return reasonClosed; + } + } + + public void setReasonClosed(IOException e) { + synchronized (reasonClosedLock) { + this.reasonClosed = e; + } + } + + public int getState() { + return this.state; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/channel/ChannelClosedException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/channel/ChannelClosedException.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2011 David Kocher. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2.channel; + +import java.io.IOException; + +/** + * @version $Id: ChannelClosedException.java 3183 2007-07-30 19:22:34Z dkocher $ + */ +public class ChannelClosedException extends IOException { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public ChannelClosedException(String s) { + super(s); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/channel/ChannelInputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/channel/ChannelInputStream.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; + +/** + * ChannelInputStream. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public final class ChannelInputStream extends InputStream { + Channel c; + + boolean isClosed = false; + boolean isEOF = false; + boolean extendedFlag = false; + + ChannelInputStream(Channel c, boolean isExtended) { + this.c = c; + this.extendedFlag = isExtended; + } + + @Override + public int available() throws IOException { + if (isEOF) + return 0; + + int avail = c.cm.getAvailable(c, extendedFlag); + /* We must not return -1 on EOF */ + return (avail > 0) ? avail : 0; + } + + @Override + public void close() throws IOException { + isClosed = true; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (b == null) + throw new NullPointerException(); + + if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) + throw new IndexOutOfBoundsException(); + + if (len == 0) + return 0; + + if (isEOF) + return -1; + + int ret = c.cm.getChannelData(c, extendedFlag, b, off, len); + + if (ret == -1) { + isEOF = true; + } + + return ret; + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read() throws IOException { + /* Yes, this stream is pure and unbuffered, a single byte read() is slow */ + final byte b[] = new byte[1]; + int ret = read(b, 0, 1); + + if (ret != 1) + return -1; + + return b[0] & 0xff; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/channel/ChannelManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/channel/ChannelManager.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,1653 @@ +/* + + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2.channel; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import ch.ethz.ssh2.AuthAgentCallback; +import ch.ethz.ssh2.ChannelCondition; +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; +import ch.ethz.ssh2.PtySettings; +import ch.ethz.ssh2.ServerConnectionCallback; +import ch.ethz.ssh2.ServerSessionCallback; +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.PacketChannelAuthAgentReq; +import ch.ethz.ssh2.packets.PacketChannelFailure; +import ch.ethz.ssh2.packets.PacketChannelOpenConfirmation; +import ch.ethz.ssh2.packets.PacketChannelOpenFailure; +import ch.ethz.ssh2.packets.PacketChannelSuccess; +import ch.ethz.ssh2.packets.PacketGlobalCancelForwardRequest; +import ch.ethz.ssh2.packets.PacketGlobalForwardRequest; +import ch.ethz.ssh2.packets.PacketOpenDirectTCPIPChannel; +import ch.ethz.ssh2.packets.PacketOpenSessionChannel; +import ch.ethz.ssh2.packets.PacketSessionExecCommand; +import ch.ethz.ssh2.packets.PacketSessionPtyRequest; +import ch.ethz.ssh2.packets.PacketSessionStartShell; +import ch.ethz.ssh2.packets.PacketSessionSubsystemRequest; +import ch.ethz.ssh2.packets.PacketSessionX11Request; +import ch.ethz.ssh2.packets.PacketWindowChange; +import ch.ethz.ssh2.packets.Packets; +import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.server.ServerConnectionState; +import ch.ethz.ssh2.transport.MessageHandler; +import ch.ethz.ssh2.transport.TransportManager; + +/** + * ChannelManager. Please read the comments in Channel.java. + *

+ * Besides the crypto part, this is the core of the library. + * + * @author Christian Plattner + * @version $Id: ChannelManager.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public class ChannelManager implements MessageHandler { + private static final Logger log = Logger.getLogger(ChannelManager.class); + + private final ServerConnectionState server_state; + private final TransportManager tm; + + private final Map x11_magic_cookies = new HashMap(); + + private final List channels = new ArrayList(); + private int nextLocalChannel = 100; + private boolean shutdown = false; + private int globalSuccessCounter = 0; + private int globalFailedCounter = 0; + + private final HashMap remoteForwardings = new HashMap(); + + private AuthAgentCallback authAgent; + + private final List listenerThreads = new ArrayList(); + + private boolean listenerThreadsAllowed = true; + + /** + * Constructor for client-mode. + * + * @param tm + */ + public ChannelManager(TransportManager tm) { + this.server_state = null; + this.tm = tm; + tm.registerMessageHandler(this, 80, 100); + } + + /** + * Constructor for server-mode. + * + * @param state + */ + public ChannelManager(ServerConnectionState state) { + this.server_state = state; + this.tm = state.tm; + tm.registerMessageHandler(this, 80, 100); + } + + private Channel getChannel(int id) { + synchronized (channels) { + for (Channel c : channels) { + if (c.localID == id) { + return c; + } + } + } + + return null; + } + + private void removeChannel(int id) { + synchronized (channels) { + for (Channel c : channels) { + if (c.localID == id) { + channels.remove(c); + break; + } + } + } + } + + private int addChannel(Channel c) { + synchronized (channels) { + channels.add(c); + return nextLocalChannel++; + } + } + + private void waitUntilChannelOpen(Channel c) throws IOException { + synchronized (c) { + while (c.state == Channel.STATE_OPENING) { + try { + c.wait(); + } + catch (InterruptedException e) { + throw new InterruptedIOException(e.getMessage()); + } + } + + if (c.state != Channel.STATE_OPEN) { + removeChannel(c.localID); + throw c.getReasonClosed(); + } + } + } + + private void waitForGlobalSuccessOrFailure() throws IOException { + synchronized (channels) { + while ((globalSuccessCounter == 0) && (globalFailedCounter == 0)) { + if (shutdown) { + throw new IOException("The connection is being shutdown"); + } + + try { + channels.wait(); + } + catch (InterruptedException e) { + throw new InterruptedIOException(e.getMessage()); + } + } + + if ((globalFailedCounter == 0) && (globalSuccessCounter == 1)) { + return; + } + + if ((globalFailedCounter == 1) && (globalSuccessCounter == 0)) { + throw new IOException("The server denied the request (did you enable port forwarding?)"); + } + + throw new IOException("Illegal state. The server sent " + globalSuccessCounter + + " SSH_MSG_REQUEST_SUCCESS and " + globalFailedCounter + " SSH_MSG_REQUEST_FAILURE messages."); + } + } + + private void waitForChannelSuccessOrFailure(Channel c) throws IOException { + synchronized (c) { + while ((c.successCounter == 0) && (c.failedCounter == 0)) { + if (c.state != Channel.STATE_OPEN) { + throw c.getReasonClosed(); + } + + try { + c.wait(); + } + catch (InterruptedException ignore) { + throw new InterruptedIOException(); + } + } + + if ((c.failedCounter == 0) && (c.successCounter == 1)) { + return; + } + + if ((c.failedCounter == 1) && (c.successCounter == 0)) { + throw new IOException("The server denied the request."); + } + + throw new IOException("Illegal state. The server sent " + c.successCounter + + " SSH_MSG_CHANNEL_SUCCESS and " + c.failedCounter + " SSH_MSG_CHANNEL_FAILURE messages."); + } + } + + public void registerX11Cookie(String hexFakeCookie, X11ServerData data) { + synchronized (x11_magic_cookies) { + x11_magic_cookies.put(hexFakeCookie, data); + } + } + + public void unRegisterX11Cookie(String hexFakeCookie, boolean killChannels) { + if (hexFakeCookie == null) { + throw new IllegalStateException("hexFakeCookie may not be null"); + } + + synchronized (x11_magic_cookies) { + x11_magic_cookies.remove(hexFakeCookie); + } + + if (killChannels == false) { + return; + } + + log.debug("Closing all X11 channels for the given fake cookie"); + List channel_copy = new ArrayList(); + + synchronized (channels) { + channel_copy.addAll(channels); + } + + for (Channel c : channel_copy) { + synchronized (c) { + if (hexFakeCookie.equals(c.hexX11FakeCookie) == false) { + continue; + } + } + + try { + closeChannel(c, "Closing X11 channel since the corresponding session is closing", true); + } + catch (IOException ignored) { + } + } + } + + public X11ServerData checkX11Cookie(String hexFakeCookie) { + synchronized (x11_magic_cookies) { + if (hexFakeCookie != null) { + return x11_magic_cookies.get(hexFakeCookie); + } + } + + return null; + } + + public void closeAllChannels() { + log.debug("Closing all channels"); + List channel_copy = new ArrayList(); + + synchronized (channels) { + channel_copy.addAll(channels); + } + + for (Channel c : channel_copy) { + try { + closeChannel(c, "Closing all channels", true); + } + catch (IOException ignored) { + } + } + } + + public void closeChannel(Channel c, String reason, boolean force) throws IOException { + this.closeChannel(c, new ChannelClosedException(reason), force); + } + + public void closeChannel(Channel c, IOException reason, boolean force) throws IOException { + byte msg[] = new byte[5]; + + synchronized (c) { + if (force) { + c.state = Channel.STATE_CLOSED; + c.EOF = true; + } + + c.setReasonClosed(reason); + msg[0] = Packets.SSH_MSG_CHANNEL_CLOSE; + msg[1] = (byte)(c.remoteID >> 24); + msg[2] = (byte)(c.remoteID >> 16); + msg[3] = (byte)(c.remoteID >> 8); + msg[4] = (byte)(c.remoteID); + c.notifyAll(); + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent) { + return; + } + + tm.sendMessage(msg); + c.closeMessageSent = true; + } + + log.debug("Sent SSH_MSG_CHANNEL_CLOSE (channel " + c.localID + ")"); + } + + public void sendEOF(Channel c) throws IOException { + byte[] msg = new byte[5]; + + synchronized (c) { + if (c.state != Channel.STATE_OPEN) { + return; + } + + msg[0] = Packets.SSH_MSG_CHANNEL_EOF; + msg[1] = (byte)(c.remoteID >> 24); + msg[2] = (byte)(c.remoteID >> 16); + msg[3] = (byte)(c.remoteID >> 8); + msg[4] = (byte)(c.remoteID); + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent == true) { + return; + } + + tm.sendMessage(msg); + } + + log.debug("Sent EOF (Channel " + c.localID + "/" + c.remoteID + ")"); + } + + public void sendOpenConfirmation(Channel c) throws IOException { + PacketChannelOpenConfirmation pcoc = null; + + synchronized (c) { + if (c.state != Channel.STATE_OPENING) { + return; + } + + c.state = Channel.STATE_OPEN; + pcoc = new PacketChannelOpenConfirmation(c.remoteID, c.localID, c.localWindow, c.localMaxPacketSize); + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent == true) { + return; + } + + tm.sendMessage(pcoc.getPayload()); + } + } + + public void sendData(Channel c, byte[] buffer, int pos, int len) throws IOException { + while (len > 0) { + int thislen = 0; + byte[] msg; + + synchronized (c) { + while (true) { + if (c.state == Channel.STATE_CLOSED) { + throw c.getReasonClosed(); + } + + if (c.state != Channel.STATE_OPEN) { + throw new ChannelClosedException("SSH channel in strange state. (" + c.state + ")"); + } + + if (c.remoteWindow != 0) { + break; + } + + try { + c.wait(); + } + catch (InterruptedException e) { + throw new InterruptedIOException(e.getMessage()); + } + } + + /* len > 0, no sign extension can happen when comparing */ + thislen = (c.remoteWindow >= len) ? len : (int) c.remoteWindow; + int estimatedMaxDataLen = c.remoteMaxPacketSize - (tm.getPacketOverheadEstimate() + 9); + + /* The worst case scenario =) a true bottleneck */ + + if (estimatedMaxDataLen <= 0) { + estimatedMaxDataLen = 1; + } + + if (thislen > estimatedMaxDataLen) { + thislen = estimatedMaxDataLen; + } + + c.remoteWindow -= thislen; + msg = new byte[1 + 8 + thislen]; + msg[0] = Packets.SSH_MSG_CHANNEL_DATA; + msg[1] = (byte)(c.remoteID >> 24); + msg[2] = (byte)(c.remoteID >> 16); + msg[3] = (byte)(c.remoteID >> 8); + msg[4] = (byte)(c.remoteID); + msg[5] = (byte)(thislen >> 24); + msg[6] = (byte)(thislen >> 16); + msg[7] = (byte)(thislen >> 8); + msg[8] = (byte)(thislen); + System.arraycopy(buffer, pos, msg, 9, thislen); + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent) { + throw c.getReasonClosed(); + } + + tm.sendMessage(msg); + } + + pos += thislen; + len -= thislen; + } + } + + public int requestGlobalForward(String bindAddress, int bindPort, String targetAddress, int targetPort) + throws IOException { + RemoteForwardingData rfd = new RemoteForwardingData(); + rfd.bindAddress = bindAddress; + rfd.bindPort = bindPort; + rfd.targetAddress = targetAddress; + rfd.targetPort = targetPort; + + synchronized (remoteForwardings) { + if (remoteForwardings.get(bindPort) != null) { + throw new IOException("There is already a forwarding for remote port " + bindPort); + } + + remoteForwardings.put(bindPort, rfd); + } + + synchronized (channels) { + globalSuccessCounter = globalFailedCounter = 0; + } + + PacketGlobalForwardRequest pgf = new PacketGlobalForwardRequest(true, bindAddress, bindPort); + tm.sendMessage(pgf.getPayload()); + log.debug("Requesting a remote forwarding ('" + bindAddress + "', " + bindPort + ")"); + + try { + waitForGlobalSuccessOrFailure(); + } + catch (IOException e) { + synchronized (remoteForwardings) { + remoteForwardings.remove(bindPort); + } + + throw e; + } + + return bindPort; + } + + public void requestCancelGlobalForward(int bindPort) throws IOException { + RemoteForwardingData rfd; + + synchronized (remoteForwardings) { + rfd = remoteForwardings.get(bindPort); + + if (rfd == null) { + throw new IOException("Sorry, there is no known remote forwarding for remote port " + bindPort); + } + } + + synchronized (channels) { + globalSuccessCounter = globalFailedCounter = 0; + } + + PacketGlobalCancelForwardRequest pgcf = new PacketGlobalCancelForwardRequest(true, rfd.bindAddress, + rfd.bindPort); + tm.sendMessage(pgcf.getPayload()); + log.debug("Requesting cancelation of remote forward ('" + rfd.bindAddress + "', " + rfd.bindPort + ")"); + waitForGlobalSuccessOrFailure(); + + /* Only now we are sure that no more forwarded connections will arrive */ + + synchronized (remoteForwardings) { + remoteForwardings.remove(bindPort); + } + } + + /** + * @param agent + * @throws IOException + */ + public void requestChannelAgentForwarding(Channel c, AuthAgentCallback authAgent) throws IOException { + synchronized (this) { + if (this.authAgent != null) + throw new IllegalStateException("Auth agent already exists"); + + this.authAgent = authAgent; + } + + synchronized (channels) { + globalSuccessCounter = globalFailedCounter = 0; + } + + log.debug("Requesting agent forwarding"); + PacketChannelAuthAgentReq aar = new PacketChannelAuthAgentReq(c.remoteID); + + synchronized (c.channelSendLock) { + if (c.closeMessageSent) { + throw c.getReasonClosed(); + } + + tm.sendMessage(aar.getPayload()); + } + + try { + waitForChannelSuccessOrFailure(c); + } + catch (IOException e) { + authAgent = null; + throw e; + } + } + + public void registerThread(IChannelWorkerThread thr) throws IOException { + synchronized (listenerThreads) { + if (listenerThreadsAllowed == false) { + throw new IOException("Too late, this connection is closed."); + } + + listenerThreads.add(thr); + } + } + + public Channel openDirectTCPIPChannel(String host_to_connect, int port_to_connect, String originator_IP_address, + int originator_port) throws IOException { + Channel c = new Channel(this); + + synchronized (c) { + c.localID = addChannel(c); + // end of synchronized block forces writing out to main memory + } + + PacketOpenDirectTCPIPChannel dtc = new PacketOpenDirectTCPIPChannel(c.localID, c.localWindow, + c.localMaxPacketSize, host_to_connect, port_to_connect, originator_IP_address, originator_port); + tm.sendMessage(dtc.getPayload()); + waitUntilChannelOpen(c); + return c; + } + + public Channel openSessionChannel() throws IOException { + Channel c = new Channel(this); + + synchronized (c) { + c.localID = addChannel(c); + // end of synchronized block forces the writing out to main memory + } + + log.debug("Sending SSH_MSG_CHANNEL_OPEN (Channel " + c.localID + ")"); + PacketOpenSessionChannel smo = new PacketOpenSessionChannel(c.localID, c.localWindow, c.localMaxPacketSize); + tm.sendMessage(smo.getPayload()); + waitUntilChannelOpen(c); + return c; + } + + public void requestPTY(Channel c, String term, int term_width_characters, int term_height_characters, + int term_width_pixels, int term_height_pixels, byte[] terminal_modes) throws IOException { + PacketSessionPtyRequest spr; + + synchronized (c) { + if (c.state != Channel.STATE_OPEN) { + throw c.getReasonClosed(); + } + + spr = new PacketSessionPtyRequest(c.remoteID, true, term, term_width_characters, term_height_characters, + term_width_pixels, term_height_pixels, terminal_modes); + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent) { + throw c.getReasonClosed(); + } + + tm.sendMessage(spr.getPayload()); + } + + try { + waitForChannelSuccessOrFailure(c); + } + catch (IOException e) { + throw new IOException("PTY request failed", e); + } + } + + public void requestWindowChange(Channel c, int term_width_characters, int term_height_characters, + int term_width_pixels, int term_height_pixels) throws IOException { + PacketWindowChange pwc; + + synchronized (c) { + if (c.state != Channel.STATE_OPEN) { + throw c.getReasonClosed(); + } + + pwc = new PacketWindowChange(c.remoteID, term_width_characters, term_height_characters, + term_width_pixels, term_height_pixels); + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent) { + throw c.getReasonClosed(); + } + + tm.sendMessage(pwc.getPayload()); + } + } + + public void requestX11(Channel c, boolean singleConnection, String x11AuthenticationProtocol, + String x11AuthenticationCookie, int x11ScreenNumber) throws IOException { + PacketSessionX11Request psr; + + synchronized (c) { + if (c.state != Channel.STATE_OPEN) { + throw c.getReasonClosed(); + } + + psr = new PacketSessionX11Request(c.remoteID, true, singleConnection, x11AuthenticationProtocol, + x11AuthenticationCookie, x11ScreenNumber); + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent) { + throw c.getReasonClosed(); + } + + tm.sendMessage(psr.getPayload()); + } + + log.debug("Requesting X11 forwarding (Channel " + c.localID + "/" + c.remoteID + ")"); + + try { + waitForChannelSuccessOrFailure(c); + } + catch (IOException e) { + throw new IOException("The X11 request failed.", e); + } + } + + public void requestSubSystem(Channel c, String subSystemName) throws IOException { + PacketSessionSubsystemRequest ssr; + + synchronized (c) { + if (c.state != Channel.STATE_OPEN) { + throw c.getReasonClosed(); + } + + ssr = new PacketSessionSubsystemRequest(c.remoteID, true, subSystemName); + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent) { + throw c.getReasonClosed(); + } + + tm.sendMessage(ssr.getPayload()); + } + + try { + waitForChannelSuccessOrFailure(c); + } + catch (IOException e) { + throw new IOException("The subsystem request failed.", e); + } + } + + public void requestExecCommand(Channel c, String cmd) throws IOException { + this.requestExecCommand(c, cmd, null); + } + + /** + * @param charsetName The charset used to convert between Java Unicode Strings and byte encodings + */ + public void requestExecCommand(Channel c, String cmd, String charsetName) throws IOException { + PacketSessionExecCommand sm; + + synchronized (c) { + if (c.state != Channel.STATE_OPEN) { + throw c.getReasonClosed(); + } + + sm = new PacketSessionExecCommand(c.remoteID, true, cmd, charsetName); + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent) { + throw c.getReasonClosed(); + } + + tm.sendMessage(sm.getPayload()); + } + + log.debug("Executing command (channel " + c.localID + ", '" + cmd + "')"); + + try { + waitForChannelSuccessOrFailure(c); + } + catch (IOException e) { + throw new IOException("The execute request failed.", e); + } + } + + public void requestShell(Channel c) throws IOException { + PacketSessionStartShell sm; + + synchronized (c) { + if (c.state != Channel.STATE_OPEN) { + throw c.getReasonClosed(); + } + + sm = new PacketSessionStartShell(c.remoteID, true); + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent) { + throw c.getReasonClosed(); + } + + tm.sendMessage(sm.getPayload()); + } + + try { + waitForChannelSuccessOrFailure(c); + } + catch (IOException e) { + throw new IOException("The shell request failed.", e); + } + } + + public void msgChannelExtendedData(byte[] msg) throws IOException { + if (msg.length <= 13) { + throw new PacketFormatException(String.format("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong size (%d)", msg.length)); + } + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + int dataType = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); + int len = ((msg[9] & 0xff) << 24) | ((msg[10] & 0xff) << 16) | ((msg[11] & 0xff) << 8) | (msg[12] & 0xff); + Channel c = getChannel(id); + + if (c == null) { + throw new PacketTypeException("Unexpected SSH_MSG_CHANNEL_EXTENDED_DATA message for non-existent channel " + id); + } + + if (dataType != Packets.SSH_EXTENDED_DATA_STDERR) { + throw new PacketFormatException("SSH_MSG_CHANNEL_EXTENDED_DATA message has unknown type (" + dataType + ")"); + } + + if (len != (msg.length - 13)) { + throw new PacketFormatException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong len (calculated " + (msg.length - 13) + + ", got " + len + ")"); + } + + log.debug("Got SSH_MSG_CHANNEL_EXTENDED_DATA (channel " + id + ", " + len + ")"); + + synchronized (c) { + if (c.state == Channel.STATE_CLOSED) { + return; // ignore + } + + if (c.state != Channel.STATE_OPEN) { + throw new PacketTypeException("Got SSH_MSG_CHANNEL_EXTENDED_DATA, but channel is not in correct state (" + + c.state + ")"); + } + + if (c.localWindow < len) { + throw new PacketFormatException("Remote sent too much data, does not fit into window."); + } + + c.localWindow -= len; + System.arraycopy(msg, 13, c.stderrBuffer, c.stderrWritepos, len); + c.stderrWritepos += len; + c.notifyAll(); + } + } + + /** + * Wait until for a condition. + * + * @param c Channel + * @param timeout in ms, 0 means no timeout. + * @param condition_mask minimum event mask (at least one of the conditions must be fulfilled) + * @return all current events + */ + public int waitForCondition(Channel c, long timeout, int condition_mask) throws IOException { + long end_time = 0; + boolean end_time_set = false; + + synchronized (c) { + while (true) { + int current_cond = 0; + int stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; + int stderrAvail = c.stderrWritepos - c.stderrReadpos; + + if (stdoutAvail > 0) { + current_cond = current_cond | ChannelCondition.STDOUT_DATA; + } + + if (stderrAvail > 0) { + current_cond = current_cond | ChannelCondition.STDERR_DATA; + } + + if (c.EOF) { + current_cond = current_cond | ChannelCondition.EOF; + } + + if (c.getExitStatus() != null) { + current_cond = current_cond | ChannelCondition.EXIT_STATUS; + } + + if (c.getExitSignal() != null) { + current_cond = current_cond | ChannelCondition.EXIT_SIGNAL; + } + + if (c.state == Channel.STATE_CLOSED) { + return current_cond | ChannelCondition.CLOSED | ChannelCondition.EOF; + } + + if ((current_cond & condition_mask) != 0) { + return current_cond; + } + + if (timeout > 0) { + if (!end_time_set) { + end_time = System.currentTimeMillis() + timeout; + end_time_set = true; + } + else { + timeout = end_time - System.currentTimeMillis(); + + if (timeout <= 0) { + return current_cond | ChannelCondition.TIMEOUT; + } + } + } + + try { + if (timeout > 0) { + c.wait(timeout); + } + else { + c.wait(); + } + } + catch (InterruptedException e) { + throw new InterruptedIOException(e.getMessage()); + } + } + } + } + + public int getAvailable(Channel c, boolean extended) throws IOException { + synchronized (c) { + int avail; + + if (extended) { + avail = c.stderrWritepos - c.stderrReadpos; + } + else { + avail = c.stdoutWritepos - c.stdoutReadpos; + } + + return ((avail > 0) ? avail : (c.EOF ? -1 : 0)); + } + } + + public int getChannelData(Channel c, boolean extended, byte[] target, int off, int len) throws IOException { + int copylen = 0; + int increment = 0; + int remoteID = 0; + int localID = 0; + + synchronized (c) { + int stdoutAvail = 0; + int stderrAvail = 0; + + while (true) { + /* + * Data available? We have to return remaining data even if the + * channel is already closed. + */ + stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; + stderrAvail = c.stderrWritepos - c.stderrReadpos; + + if ((!extended) && (stdoutAvail != 0)) { + break; + } + + if ((extended) && (stderrAvail != 0)) { + break; + } + + /* Do not wait if more data will never arrive (EOF or CLOSED) */ + + if ((c.EOF) || (c.state != Channel.STATE_OPEN)) { + return -1; + } + + try { + c.wait(); + } + catch (InterruptedException e) { + throw new InterruptedIOException(e.getMessage()); + } + } + + /* OK, there is some data. Return it. */ + + if (!extended) { + copylen = (stdoutAvail > len) ? len : stdoutAvail; + System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, target, off, copylen); + c.stdoutReadpos += copylen; + + if (c.stdoutReadpos != c.stdoutWritepos) { + System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, c.stdoutBuffer, 0, c.stdoutWritepos + - c.stdoutReadpos); + } + + c.stdoutWritepos -= c.stdoutReadpos; + c.stdoutReadpos = 0; + } + else { + copylen = (stderrAvail > len) ? len : stderrAvail; + System.arraycopy(c.stderrBuffer, c.stderrReadpos, target, off, copylen); + c.stderrReadpos += copylen; + + if (c.stderrReadpos != c.stderrWritepos) { + System.arraycopy(c.stderrBuffer, c.stderrReadpos, c.stderrBuffer, 0, c.stderrWritepos + - c.stderrReadpos); + } + + c.stderrWritepos -= c.stderrReadpos; + c.stderrReadpos = 0; + } + + if (c.state != Channel.STATE_OPEN) { + return copylen; + } + + if (c.localWindow < ((Channel.CHANNEL_BUFFER_SIZE + 1) / 2)) { + int minFreeSpace = Math.min(Channel.CHANNEL_BUFFER_SIZE - c.stdoutWritepos, + Channel.CHANNEL_BUFFER_SIZE - c.stderrWritepos); + increment = minFreeSpace - c.localWindow; + c.localWindow = minFreeSpace; + } + + remoteID = c.remoteID; /* read while holding the lock */ + localID = c.localID; /* read while holding the lock */ + } + + /* + * If a consumer reads stdout and stdin in parallel, we may end up with + * sending two msgWindowAdjust messages. Luckily, it + * does not matter in which order they arrive at the server. + */ + + if (increment > 0) { + log.debug("Sending SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + localID + ", " + increment + ")"); + + synchronized (c.channelSendLock) { + byte[] msg = c.msgWindowAdjust; + msg[0] = Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST; + msg[1] = (byte)(remoteID >> 24); + msg[2] = (byte)(remoteID >> 16); + msg[3] = (byte)(remoteID >> 8); + msg[4] = (byte)(remoteID); + msg[5] = (byte)(increment >> 24); + msg[6] = (byte)(increment >> 16); + msg[7] = (byte)(increment >> 8); + msg[8] = (byte)(increment); + + if (!c.closeMessageSent) { + tm.sendMessage(msg); + } + } + } + + return copylen; + } + + public void msgChannelData(byte[] msg) throws IOException { + if (msg.length <= 9) { + throw new PacketFormatException(String.format("SSH_MSG_CHANNEL_DATA message has wrong size (%d)", msg.length)); + } + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + int len = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); + Channel c = getChannel(id); + + if (c == null) { + throw new PacketTypeException("Unexpected SSH_MSG_CHANNEL_DATA message for non-existent channel " + id); + } + + if (len != (msg.length - 9)) { + throw new PacketFormatException("SSH_MSG_CHANNEL_DATA message has wrong len (calculated " + (msg.length - 9) + ", got " + + len + ")"); + } + + log.debug("Got SSH_MSG_CHANNEL_DATA (channel " + id + ", " + len + ")"); + + synchronized (c) { + if (c.state == Channel.STATE_CLOSED) { + return; // ignore + } + + if (c.state != Channel.STATE_OPEN) { + throw new PacketTypeException("Got SSH_MSG_CHANNEL_DATA, but channel is not in correct state (" + c.state + ")"); + } + + if (c.localWindow < len) { + throw new IOException("Remote sent too much data, does not fit into window."); + } + + c.localWindow -= len; + System.arraycopy(msg, 9, c.stdoutBuffer, c.stdoutWritepos, len); + c.stdoutWritepos += len; + c.notifyAll(); + } + } + + public void msgChannelWindowAdjust(byte[] msg) throws IOException { + if (msg.length != 9) { + throw new PacketFormatException(String.format("SSH_MSG_CHANNEL_WINDOW_ADJUST message has wrong size (%d)", msg.length)); + } + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + int windowChange = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); + Channel c = getChannel(id); + + if (c == null) { + throw new PacketTypeException("Unexpected SSH_MSG_CHANNEL_WINDOW_ADJUST message for non-existent channel " + id); + } + + synchronized (c) { + final long huge = 0xFFFFffffL; /* 2^32 - 1 */ + c.remoteWindow += (windowChange & huge); /* avoid sign extension */ + + /* TODO - is this a good heuristic? */ + + if ((c.remoteWindow > huge)) { + c.remoteWindow = huge; + } + + c.notifyAll(); + } + + log.debug("Got SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + id + ", " + windowChange + ")"); + } + + public void msgChannelOpen(byte[] msg) throws IOException { + TypesReader tr = new TypesReader(msg); + tr.readByte(); // skip packet type + String channelType = tr.readString(); + int remoteID = tr.readUINT32(); /* sender channel */ + int remoteWindow = tr.readUINT32(); /* initial window size */ + int remoteMaxPacketSize = tr.readUINT32(); /* maximum packet size */ + + if ("x11".equals(channelType)) { + synchronized (x11_magic_cookies) { + /* If we did not request X11 forwarding, then simply ignore this bogus request. */ + if (x11_magic_cookies.size() == 0) { + PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, + Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "X11 forwarding not activated", ""); + tm.sendAsynchronousMessage(pcof.getPayload()); + log.warning("Unexpected X11 request, denying it!"); + return; + } + } + + String remoteOriginatorAddress = tr.readString(); + int remoteOriginatorPort = tr.readUINT32(); + Channel c = new Channel(this); + + synchronized (c) { + c.remoteID = remoteID; + c.remoteWindow = remoteWindow & 0xFFFFffffL; /* properly convert UINT32 to long */ + c.remoteMaxPacketSize = remoteMaxPacketSize; + c.localID = addChannel(c); + } + + /* + * The open confirmation message will be sent from another thread + */ + RemoteX11AcceptThread rxat = new RemoteX11AcceptThread(c, remoteOriginatorAddress, remoteOriginatorPort); + rxat.setDaemon(true); + rxat.start(); + return; + } + + if ("forwarded-tcpip".equals(channelType)) { + String remoteConnectedAddress = tr.readString(); /* address that was connected */ + int remoteConnectedPort = tr.readUINT32(); /* port that was connected */ + String remoteOriginatorAddress = tr.readString(); /* originator IP address */ + int remoteOriginatorPort = tr.readUINT32(); /* originator port */ + RemoteForwardingData rfd; + + synchronized (remoteForwardings) { + rfd = remoteForwardings.get(remoteConnectedPort); + } + + if (rfd == null) { + PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, + Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, + "No thanks, unknown port in forwarded-tcpip request", ""); + /* Always try to be polite. */ + tm.sendAsynchronousMessage(pcof.getPayload()); + log.debug("Unexpected forwarded-tcpip request, denying it!"); + return; + } + + Channel c = new Channel(this); + + synchronized (c) { + c.remoteID = remoteID; + c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */ + c.remoteMaxPacketSize = remoteMaxPacketSize; + c.localID = addChannel(c); + } + + /* + * The open confirmation message will be sent from another thread. + */ + RemoteAcceptThread rat = new RemoteAcceptThread(c, remoteConnectedAddress, remoteConnectedPort, + remoteOriginatorAddress, remoteOriginatorPort, rfd.targetAddress, rfd.targetPort); + rat.setDaemon(true); + rat.start(); + return; + } + + if ((server_state != null) && ("session".equals(channelType))) { + ServerConnectionCallback cb; + + synchronized (server_state) { + cb = server_state.cb_conn; + } + + if (cb == null) { + tm.sendAsynchronousMessage(new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, + "Sessions are currently not enabled", "en").getPayload()); + return; + } + + final Channel c = new Channel(this); + + synchronized (c) { + c.remoteID = remoteID; + c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */ + c.remoteMaxPacketSize = remoteMaxPacketSize; + c.localID = addChannel(c); + c.state = Channel.STATE_OPEN; + c.ss = new ServerSessionImpl(c); + } + + PacketChannelOpenConfirmation pcoc = new PacketChannelOpenConfirmation(c.remoteID, c.localID, + c.localWindow, c.localMaxPacketSize); + tm.sendAsynchronousMessage(pcoc.getPayload()); + c.ss.sscb = cb.acceptSession(c.ss); + return; + } + + /* Tell the server that we have no idea what it is talking about */ + PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_UNKNOWN_CHANNEL_TYPE, + "Unknown channel type", ""); + tm.sendAsynchronousMessage(pcof.getPayload()); + log.warning("The peer tried to open an unsupported channel type (" + channelType + ")"); + } + + /* Starts the given runnable in a foreground (non-daemon) thread */ + private void runAsync(Runnable r) { + Thread t = new Thread(r); + t.start(); + } + + public void msgChannelRequest(byte[] msg) throws IOException { + TypesReader tr = new TypesReader(msg); + tr.readByte(); // skip packet type + int id = tr.readUINT32(); + Channel c = getChannel(id); + + if (c == null) { + throw new IOException("Unexpected SSH_MSG_CHANNEL_REQUEST message for non-existent channel " + id); + } + + ServerSessionImpl server_session = null; + + if (server_state != null) { + synchronized (c) { + server_session = c.ss; + } + } + + String type = tr.readString("US-ASCII"); + boolean wantReply = tr.readBoolean(); + log.debug("Got SSH_MSG_CHANNEL_REQUEST (channel " + id + ", '" + type + "')"); + + if (type.equals("exit-status")) { + if (wantReply) { + throw new IOException( + "Badly formatted SSH_MSG_CHANNEL_REQUEST exit-status message, 'want reply' is true"); + } + + int exit_status = tr.readUINT32(); + + if (tr.remain() != 0) { + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); + } + + synchronized (c) { + c.exit_status = exit_status; + c.notifyAll(); + } + + log.debug("Got EXIT STATUS (channel " + id + ", status " + exit_status + ")"); + return; + } + + if ((server_state == null) && (type.equals("exit-signal"))) { + if (wantReply) { + throw new IOException( + "Badly formatted SSH_MSG_CHANNEL_REQUEST exit-signal message, 'want reply' is true"); + } + + String signame = tr.readString("US-ASCII"); + tr.readBoolean(); + tr.readString(); + tr.readString(); + + if (tr.remain() != 0) { + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); + } + + synchronized (c) { + c.exit_signal = signame; + c.notifyAll(); + } + + log.debug("Got EXIT SIGNAL (channel " + id + ", signal " + signame + ")"); + return; + } + + if ((server_session != null) && (type.equals("pty-req"))) { + PtySettings pty = new PtySettings(); + pty.term = tr.readString(); + pty.term_width_characters = tr.readUINT32(); + pty.term_height_characters = tr.readUINT32(); + pty.term_width_pixels = tr.readUINT32(); + pty.term_height_pixels = tr.readUINT32(); + pty.terminal_modes = tr.readByteString(); + + if (tr.remain() != 0) { + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); + } + + Runnable run_after_sending_success = null; + ServerSessionCallback sscb = server_session.getServerSessionCallback(); + + if (sscb != null) { + run_after_sending_success = sscb.requestPtyReq(server_session, pty); + } + + if (wantReply) { + if (run_after_sending_success != null) { + tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); + } + else { + tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); + } + } + + if (run_after_sending_success != null) { + runAsync(run_after_sending_success); + } + + return; + } + + if ((server_session != null) && (type.equals("shell"))) { + if (tr.remain() != 0) { + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); + } + + Runnable run_after_sending_success = null; + ServerSessionCallback sscb = server_session.getServerSessionCallback(); + + if (sscb != null) { + run_after_sending_success = sscb.requestShell(server_session); + } + + if (wantReply) { + if (run_after_sending_success != null) { + tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); + } + else { + tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); + } + } + + if (run_after_sending_success != null) { + runAsync(run_after_sending_success); + } + + return; + } + + if ((server_session != null) && (type.equals("exec"))) { + String command = tr.readString(); + + if (tr.remain() != 0) { + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); + } + + Runnable run_after_sending_success = null; + ServerSessionCallback sscb = server_session.getServerSessionCallback(); + + if (sscb != null) { + run_after_sending_success = sscb.requestExec(server_session, command); + } + + if (wantReply) { + if (run_after_sending_success != null) { + tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); + } + else { + tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); + } + } + + if (run_after_sending_success != null) { + runAsync(run_after_sending_success); + } + + return; + } + + /* We simply ignore unknown channel requests, however, if the server wants a reply, + * then we signal that we have no idea what it is about. + */ + + if (wantReply) { + tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); + } + + log.debug("Channel request '" + type + "' is not known, ignoring it"); + } + + public void msgChannelEOF(byte[] msg) throws IOException { + if (msg.length != 5) { + throw new PacketFormatException(String.format("SSH_MSG_CHANNEL_EOF message has wrong size (%d)", msg.length)); + } + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + Channel c = getChannel(id); + + if (c == null) { + throw new PacketTypeException("Unexpected SSH_MSG_CHANNEL_EOF message for non-existent channel " + id); + } + + synchronized (c) { + c.EOF = true; + c.notifyAll(); + } + + log.debug("Got SSH_MSG_CHANNEL_EOF (channel " + id + ")"); + } + + public void msgChannelClose(byte[] msg) throws IOException { + if (msg.length != 5) { + throw new PacketFormatException("SSH_MSG_CHANNEL_CLOSE message has wrong size (" + msg.length + ")"); + } + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + Channel c = getChannel(id); + + if (c == null) { + throw new IOException("Unexpected SSH_MSG_CHANNEL_CLOSE message for non-existent channel " + id); + } + + synchronized (c) { + c.EOF = true; + c.state = Channel.STATE_CLOSED; + c.setReasonClosed(new ChannelClosedException("Close requested by remote")); + c.closeMessageRecv = true; + removeChannel(c.localID); + c.notifyAll(); + } + + log.debug("Got SSH_MSG_CHANNEL_CLOSE (channel " + id + ")"); + } + + public void msgChannelSuccess(byte[] msg) throws IOException { + if (msg.length != 5) { + throw new PacketFormatException("SSH_MSG_CHANNEL_SUCCESS message has wrong size (" + msg.length + ")"); + } + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + Channel c = getChannel(id); + + if (c == null) { + throw new PacketTypeException("Unexpected SSH_MSG_CHANNEL_SUCCESS message for non-existent channel " + id); + } + + synchronized (c) { + c.successCounter++; + c.notifyAll(); + } + + log.debug("Got SSH_MSG_CHANNEL_SUCCESS (channel " + id + ")"); + } + + public void msgChannelFailure(byte[] msg) throws IOException { + if (msg.length != 5) { + throw new PacketFormatException(String.format("SSH_MSG_CHANNEL_FAILURE message has wrong size (%d)", msg.length)); + } + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + Channel c = getChannel(id); + + if (c == null) { + throw new PacketTypeException("Unexpected SSH_MSG_CHANNEL_FAILURE message for non-existent channel " + id); + } + + synchronized (c) { + c.failedCounter++; + c.notifyAll(); + } + + log.debug("Got SSH_MSG_CHANNEL_FAILURE (channel " + id + ")"); + } + + public void msgChannelOpenConfirmation(byte[] msg) throws IOException { + PacketChannelOpenConfirmation sm = new PacketChannelOpenConfirmation(msg); + Channel c = getChannel(sm.getRecipientChannelID()); + + if (c == null) { + throw new PacketTypeException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for non-existent channel " + + sm.getRecipientChannelID()); + } + + synchronized (c) { + if (c.state != Channel.STATE_OPENING) { + throw new PacketTypeException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for channel " + + sm.getRecipientChannelID()); + } + + c.remoteID = sm.getSenderChannelID(); + c.remoteWindow = sm.getInitialWindowSize() & 0xFFFFffffL; /* convert UINT32 to long */ + c.remoteMaxPacketSize = sm.getMaxPacketSize(); + c.state = Channel.STATE_OPEN; + c.notifyAll(); + } + + log.debug("Got SSH_MSG_CHANNEL_OPEN_CONFIRMATION (channel " + sm.getRecipientChannelID() + " / remote: " + + sm.getSenderChannelID() + ")"); + } + + public void msgChannelOpenFailure(byte[] msg) throws IOException { + if (msg.length < 5) { + throw new PacketFormatException(String.format("SSH_MSG_CHANNEL_OPEN_FAILURE message has wrong size (%d)", msg.length)); + } + + TypesReader tr = new TypesReader(msg); + tr.readByte(); // skip packet type + int id = tr.readUINT32(); /* sender channel */ + Channel c = getChannel(id); + + if (c == null) { + throw new PacketTypeException("Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE message for non-existent channel " + id); + } + + int reasonCode = tr.readUINT32(); + String description = tr.readString("UTF-8"); + String reasonCodeSymbolicName; + + switch (reasonCode) { + case 1: + reasonCodeSymbolicName = "SSH_OPEN_ADMINISTRATIVELY_PROHIBITED"; + break; + + case 2: + reasonCodeSymbolicName = "SSH_OPEN_CONNECT_FAILED"; + break; + + case 3: + reasonCodeSymbolicName = "SSH_OPEN_UNKNOWN_CHANNEL_TYPE"; + break; + + case 4: + reasonCodeSymbolicName = "SSH_OPEN_RESOURCE_SHORTAGE"; + break; + + default: + reasonCodeSymbolicName = "UNKNOWN REASON CODE (" + reasonCode + ")"; + } + + StringBuilder descriptionBuffer = new StringBuilder(); + descriptionBuffer.append(description); + + for (int i = 0; i < descriptionBuffer.length(); i++) { + char cc = descriptionBuffer.charAt(i); + + if ((cc >= 32) && (cc <= 126)) { + continue; + } + + descriptionBuffer.setCharAt(i, '\uFFFD'); + } + + synchronized (c) { + c.EOF = true; + c.state = Channel.STATE_CLOSED; + c.setReasonClosed(new ChannelClosedException(String.format("The server refused to open the channel (%s, '%s')", + reasonCodeSymbolicName, descriptionBuffer.toString()))); + c.notifyAll(); + } + + log.debug("Got SSH_MSG_CHANNEL_OPEN_FAILURE (channel " + id + ")"); + } + + public void msgGlobalRequest(byte[] msg) throws IOException { + /* Currently we do not support any kind of global request */ + TypesReader tr = new TypesReader(msg); + tr.readByte(); // skip packet type + String requestName = tr.readString(); + boolean wantReply = tr.readBoolean(); + + if (wantReply) { + byte[] reply_failure = new byte[1]; + reply_failure[0] = Packets.SSH_MSG_REQUEST_FAILURE; + tm.sendAsynchronousMessage(reply_failure); + } + + /* We do not clean up the requestName String - that is OK for debug */ + log.debug("Got SSH_MSG_GLOBAL_REQUEST (" + requestName + ")"); + } + + public void msgGlobalSuccess() throws IOException { + synchronized (channels) { + globalSuccessCounter++; + channels.notifyAll(); + } + + log.debug("Got SSH_MSG_REQUEST_SUCCESS"); + } + + public void msgGlobalFailure() throws IOException { + synchronized (channels) { + globalFailedCounter++; + channels.notifyAll(); + } + + log.debug("Got SSH_MSG_REQUEST_FAILURE"); + } + + public void handleFailure(final IOException failure) { + log.debug("HandleMessage: got shutdown"); + + synchronized (listenerThreads) { + for (IChannelWorkerThread lat : listenerThreads) { + lat.stopWorking(); + } + + listenerThreadsAllowed = false; + } + + synchronized (channels) { + shutdown = true; + + for (Channel c : channels) { + synchronized (c) { + c.EOF = true; + c.state = Channel.STATE_CLOSED; + c.setReasonClosed(failure); + c.closeMessageRecv = true; + c.notifyAll(); + } + } + + channels.clear(); + channels.notifyAll(); /* Notify global response waiters */ + } + } + + public void handleMessage(byte[] msg) throws IOException { + switch (msg[0]) { + case Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION: + msgChannelOpenConfirmation(msg); + break; + + case Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST: + msgChannelWindowAdjust(msg); + break; + + case Packets.SSH_MSG_CHANNEL_DATA: + msgChannelData(msg); + break; + + case Packets.SSH_MSG_CHANNEL_EXTENDED_DATA: + msgChannelExtendedData(msg); + break; + + case Packets.SSH_MSG_CHANNEL_REQUEST: + msgChannelRequest(msg); + break; + + case Packets.SSH_MSG_CHANNEL_EOF: + msgChannelEOF(msg); + break; + + case Packets.SSH_MSG_CHANNEL_OPEN: + msgChannelOpen(msg); + break; + + case Packets.SSH_MSG_CHANNEL_CLOSE: + msgChannelClose(msg); + break; + + case Packets.SSH_MSG_CHANNEL_SUCCESS: + msgChannelSuccess(msg); + break; + + case Packets.SSH_MSG_CHANNEL_FAILURE: + msgChannelFailure(msg); + break; + + case Packets.SSH_MSG_CHANNEL_OPEN_FAILURE: + msgChannelOpenFailure(msg); + break; + + case Packets.SSH_MSG_GLOBAL_REQUEST: + msgGlobalRequest(msg); + break; + + case Packets.SSH_MSG_REQUEST_SUCCESS: + msgGlobalSuccess(); + break; + + case Packets.SSH_MSG_REQUEST_FAILURE: + msgGlobalFailure(); + break; + + default: + throw new PacketTypeException(msg[0]); + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/channel/ChannelOutputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/channel/ChannelOutputStream.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.channel; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * ChannelOutputStream. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public final class ChannelOutputStream extends OutputStream { + Channel c; + + boolean isClosed = false; + + ChannelOutputStream(Channel c) { + this.c = c; + } + + @Override + public void write(int b) throws IOException { + byte[] buff = new byte[1]; + buff[0] = (byte) b; + write(buff, 0, 1); + } + + @Override + public void close() throws IOException { + if (isClosed == false) { + isClosed = true; + c.cm.sendEOF(c); + } + } + + @Override + public void flush() throws IOException { + if (isClosed) + throw new IOException("This OutputStream is closed."); + + /* This is a no-op, since this stream is unbuffered */ + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (isClosed) + throw new IOException("This OutputStream is closed."); + + if (b == null) + throw new NullPointerException(); + + if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) + throw new IndexOutOfBoundsException(); + + if (len == 0) + return; + + c.cm.sendData(c, b, off, len); + } + + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/channel/DynamicAcceptThread.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/channel/DynamicAcceptThread.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,295 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.ethz.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.io.PushbackInputStream; +import java.net.ConnectException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.NoRouteToHostException; +import java.net.ServerSocket; +import java.net.Socket; + +import net.sourceforge.jsocks.CProxy; +import net.sourceforge.jsocks.ProxyMessage; +import net.sourceforge.jsocks.Socks4Message; +import net.sourceforge.jsocks.Socks5Message; +import net.sourceforge.jsocks.SocksException; +import net.sourceforge.jsocks.server.ServerAuthenticator; +import net.sourceforge.jsocks.server.ServerAuthenticatorNone; + +/** + * DynamicAcceptThread. + * + * @author Kenny Root + * @version $Id$ + */ +public class DynamicAcceptThread extends Thread implements IChannelWorkerThread { + private ChannelManager cm; + private ServerSocket ss; + + class DynamicAcceptRunnable implements Runnable { + private static final int idleTimeout = 180000; //3 minutes + + private ServerAuthenticator auth; + private Socket sock; + private InputStream in; + private OutputStream out; + private ProxyMessage msg; + + public DynamicAcceptRunnable(ServerAuthenticator auth, Socket sock) { + this.auth = auth; + this.sock = sock; + setName("DynamicAcceptRunnable"); + } + + public void run() { + try { + startSession(); + } + catch (IOException ioe) { + int error_code = CProxy.SOCKS_FAILURE; + + if (ioe instanceof SocksException) + error_code = ((SocksException) ioe).errCode; + else if (ioe instanceof NoRouteToHostException) + error_code = CProxy.SOCKS_HOST_UNREACHABLE; + else if (ioe instanceof ConnectException) + error_code = CProxy.SOCKS_CONNECTION_REFUSED; + else if (ioe instanceof InterruptedIOException) + error_code = CProxy.SOCKS_TTL_EXPIRE; + + if (error_code > CProxy.SOCKS_ADDR_NOT_SUPPORTED + || error_code < 0) { + error_code = CProxy.SOCKS_FAILURE; + } + + sendErrorMessage(error_code); + } + finally { + if (auth != null) + auth.endSession(); + } + } + + private ProxyMessage readMsg(InputStream in) throws IOException { + PushbackInputStream push_in; + + if (in instanceof PushbackInputStream) + push_in = (PushbackInputStream) in; + else + push_in = new PushbackInputStream(in); + + int version = push_in.read(); + push_in.unread(version); + ProxyMessage msg; + + if (version == 5) { + msg = new Socks5Message(push_in, false); + } + else if (version == 4) { + msg = new Socks4Message(push_in, false); + } + else { + throw new SocksException(CProxy.SOCKS_FAILURE); + } + + return msg; + } + + private void sendErrorMessage(int error_code) { + ProxyMessage err_msg; + + if (msg instanceof Socks4Message) + err_msg = new Socks4Message(Socks4Message.REPLY_REJECTED); + else + err_msg = new Socks5Message(error_code); + + try { + err_msg.write(out); + } + catch (IOException ioe) { + } + } + + private void handleRequest(ProxyMessage msg) throws IOException { + if (!auth.checkRequest(msg)) + throw new SocksException(CProxy.SOCKS_FAILURE); + + switch (msg.command) { + case CProxy.SOCKS_CMD_CONNECT: + onConnect(msg); + break; + + default: + throw new SocksException(CProxy.SOCKS_CMD_NOT_SUPPORTED); + } + } + + private void startSession() throws IOException { + sock.setSoTimeout(idleTimeout); + + try { + auth = auth.startSession(sock); + } + catch (IOException ioe) { + System.out.println("Could not start SOCKS session"); + ioe.printStackTrace(); + auth = null; + return; + } + + if (auth == null) { // Authentication failed + System.out.println("SOCKS auth failed"); + return; + } + + in = auth.getInputStream(); + out = auth.getOutputStream(); + msg = readMsg(in); + handleRequest(msg); + } + + private void onConnect(ProxyMessage msg) throws IOException { + ProxyMessage response = null; + Channel cn = null; + StreamForwarder r2l = null; + StreamForwarder l2r = null; + + if (msg instanceof Socks5Message) { + response = new Socks5Message(CProxy.SOCKS_SUCCESS, (InetAddress)null, 0); + } + else { + response = new Socks4Message(Socks4Message.REPLY_OK, (InetAddress)null, 0); + } + + response.write(out); + String destHost = msg.host; + + if (msg.ip != null) + destHost = msg.ip.getHostAddress(); + + try { + /* + * This may fail, e.g., if the remote port is closed (in + * optimistic terms: not open yet) + */ + cn = cm.openDirectTCPIPChannel(destHost, msg.port, + "127.0.0.1", 0); + } + catch (IOException e) { + /* + * Simply close the local socket and wait for the next incoming + * connection + */ + try { + sock.close(); + } + catch (IOException ignore) { + } + + return; + } + + try { + r2l = new StreamForwarder(cn, null, sock, cn.stdoutStream, out, "RemoteToLocal"); + l2r = new StreamForwarder(cn, r2l, sock, in, cn.stdinStream, "LocalToRemote"); + } + catch (IOException e) { + try { + /* + * This message is only visible during debugging, since we + * discard the channel immediatelly + */ + cn.cm.closeChannel(cn, + "Weird error during creation of StreamForwarder (" + + e.getMessage() + ")", true); + } + catch (IOException ignore) { + } + + return; + } + + r2l.setDaemon(true); + l2r.setDaemon(true); + r2l.start(); + l2r.start(); + } + } + + public DynamicAcceptThread(ChannelManager cm, int local_port) + throws IOException { + this.cm = cm; + setName("DynamicAcceptThread"); + ss = new ServerSocket(local_port); + } + + public DynamicAcceptThread(ChannelManager cm, InetSocketAddress localAddress) + throws IOException { + this.cm = cm; + ss = new ServerSocket(); + ss.bind(localAddress); + } + + @Override + public void run() { + try { + cm.registerThread(this); + } + catch (IOException e) { + stopWorking(); + return; + } + + while (true) { + Socket sock = null; + + try { + sock = ss.accept(); + } + catch (IOException e) { + stopWorking(); + return; + } + + DynamicAcceptRunnable dar = new DynamicAcceptRunnable(new ServerAuthenticatorNone(), sock); + Thread t = new Thread(dar); + t.setDaemon(true); + t.start(); + } + } + + /* + * (non-Javadoc) + * + * @see ch.ethz.ssh2.channel.IChannelWorkerThread#stopWorking() + */ + public void stopWorking() { + try { + /* This will lead to an IOException in the ss.accept() call */ + ss.close(); + } + catch (IOException e) { + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/channel/IChannelWorkerThread.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/channel/IChannelWorkerThread.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.channel; + +/** + * IChannelWorkerThread. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +interface IChannelWorkerThread { + public void stopWorking(); +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/channel/LocalAcceptThread.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/channel/LocalAcceptThread.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.channel; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; + +/** + * LocalAcceptThread. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class LocalAcceptThread extends Thread implements IChannelWorkerThread { + ChannelManager cm; + String host_to_connect; + int port_to_connect; + + final ServerSocket ss; + + public LocalAcceptThread(ChannelManager cm, int local_port, String host_to_connect, int port_to_connect) + throws IOException { + this.cm = cm; + this.host_to_connect = host_to_connect; + this.port_to_connect = port_to_connect; + ss = new ServerSocket(local_port); + } + + public LocalAcceptThread(ChannelManager cm, InetSocketAddress localAddress, String host_to_connect, + int port_to_connect) throws IOException { + this.cm = cm; + this.host_to_connect = host_to_connect; + this.port_to_connect = port_to_connect; + ss = new ServerSocket(); + ss.bind(localAddress); + } + + public ServerSocket getServerSocket() { + return ss; + } + + @Override + public void run() { + try { + cm.registerThread(this); + } + catch (IOException e) { + stopWorking(); + return; + } + + while (true) { + Socket s = null; + + try { + s = ss.accept(); + } + catch (IOException e) { + stopWorking(); + return; + } + + Channel cn = null; + StreamForwarder r2l = null; + StreamForwarder l2r = null; + + try { + /* This may fail, e.g., if the remote port is closed (in optimistic terms: not open yet) */ + cn = cm.openDirectTCPIPChannel(host_to_connect, port_to_connect, s.getInetAddress().getHostAddress(), s + .getPort()); + } + catch (IOException e) { + /* Simply close the local socket and wait for the next incoming connection */ + try { + s.close(); + } + catch (IOException ignore) { + } + + continue; + } + + try { + r2l = new StreamForwarder(cn, null, null, cn.stdoutStream, s.getOutputStream(), "RemoteToLocal"); + l2r = new StreamForwarder(cn, r2l, s, s.getInputStream(), cn.stdinStream, "LocalToRemote"); + } + catch (IOException e) { + try { + /* This message is only visible during debugging, since we discard the channel immediatelly */ + cn.cm.closeChannel(cn, e, true); + } + catch (IOException ignore) { + } + + continue; + } + + r2l.setDaemon(true); + l2r.setDaemon(true); + r2l.start(); + l2r.start(); + } + } + + public void stopWorking() { + try { + /* This will lead to an IOException in the ss.accept() call */ + ss.close(); + } + catch (IOException ignored) { + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/channel/RemoteAcceptThread.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/channel/RemoteAcceptThread.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.channel; + +import java.io.IOException; +import java.net.Socket; + +import ch.ethz.ssh2.log.Logger; + +/** + * RemoteAcceptThread. + * + * @author Christian Plattner + * @version $Id: RemoteAcceptThread.java 119 2014-04-12 20:30:58Z dkocher@sudo.ch $ + */ +public class RemoteAcceptThread extends Thread { + private static final Logger log = Logger.getLogger(RemoteAcceptThread.class); + + Channel c; + + String remoteConnectedAddress; + int remoteConnectedPort; + String remoteOriginatorAddress; + int remoteOriginatorPort; + String targetAddress; + int targetPort; + + Socket s; + + public RemoteAcceptThread(Channel c, String remoteConnectedAddress, int remoteConnectedPort, + String remoteOriginatorAddress, int remoteOriginatorPort, String targetAddress, int targetPort) { + this.c = c; + this.remoteConnectedAddress = remoteConnectedAddress; + this.remoteConnectedPort = remoteConnectedPort; + this.remoteOriginatorAddress = remoteOriginatorAddress; + this.remoteOriginatorPort = remoteOriginatorPort; + this.targetAddress = targetAddress; + this.targetPort = targetPort; + log.debug("RemoteAcceptThread: " + remoteConnectedAddress + "/" + remoteConnectedPort + ", R: " + + remoteOriginatorAddress + "/" + remoteOriginatorPort); + } + + @Override + public void run() { + try { + c.cm.sendOpenConfirmation(c); + s = new Socket(targetAddress, targetPort); + StreamForwarder r2l = new StreamForwarder(c, null, null, c.getStdoutStream(), s.getOutputStream(), + "RemoteToLocal"); + StreamForwarder l2r = new StreamForwarder(c, null, null, s.getInputStream(), c.getStdinStream(), + "LocalToRemote"); + /* No need to start two threads, one can be executed in the current thread */ + r2l.setDaemon(true); + r2l.start(); + l2r.run(); + + while (r2l.isAlive()) { + try { + r2l.join(); + } + catch (InterruptedException ignored) { + } + } + + /* If the channel is already closed, then this is a no-op */ + c.cm.closeChannel(c, "EOF on both streams reached.", true); + s.close(); + } + catch (IOException e) { + log.warning("IOException in proxy code: " + e.getMessage()); + + try { + c.cm.closeChannel(c, e, true); + } + catch (IOException ignored) { + } + + try { + if (s != null) + s.close(); + } + catch (IOException ignored) { + } + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/channel/RemoteForwardingData.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/channel/RemoteForwardingData.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.channel; + +/** + * RemoteForwardingData. Data about a requested remote forwarding. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class RemoteForwardingData { + public String bindAddress; + public int bindPort; + + String targetAddress; + int targetPort; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/channel/RemoteX11AcceptThread.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/channel/RemoteX11AcceptThread.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.util.StringEncoder; + +/** + * RemoteX11AcceptThread. + * + * @author Christian Plattner + * @version $Id: RemoteX11AcceptThread.java 119 2014-04-12 20:30:58Z dkocher@sudo.ch $ + */ +public class RemoteX11AcceptThread extends Thread { + private static final Logger log = Logger.getLogger(RemoteX11AcceptThread.class); + + Channel c; + + String remoteOriginatorAddress; + int remoteOriginatorPort; + + Socket s; + + public RemoteX11AcceptThread(Channel c, String remoteOriginatorAddress, int remoteOriginatorPort) { + this.c = c; + this.remoteOriginatorAddress = remoteOriginatorAddress; + this.remoteOriginatorPort = remoteOriginatorPort; + } + + @Override + public void run() { + try { + /* Send Open Confirmation */ + c.cm.sendOpenConfirmation(c); + /* Read startup packet from client */ + OutputStream remote_os = c.getStdinStream(); + InputStream remote_is = c.getStdoutStream(); + /* The following code is based on the protocol description given in: + * Scheifler/Gettys, + * X Windows System: Core and Extension Protocols: + * X Version 11, Releases 6 and 6.1 ISBN 1-55558-148-X + * (from the ETH library - after being here for almost ten + * years one of the few books I borrowed... sad but true =) + */ + /* + * Client startup: + * + * 1 0X42 MSB first/0x6c lSB first - byteorder + * 1 - unused + * 2 card16 - protocol-major-version + * 2 card16 - protocol-minor-version + * 2 n - lenght of authorization-protocol-name + * 2 d - lenght of authorization-protocol-data + * 2 - unused + * string8 - authorization-protocol-name + * p - unused, p=pad(n) + * string8 - authorization-protocol-data + * q - unused, q=pad(d) + * + * pad(X) = (4 - (X mod 4)) mod 4 + * + * Server response: + * + * 1 (0 failed, 2 authenticate, 1 success) + * ... + * + */ + /* Later on we will simply forward the first 6 header bytes to the "real" X11 server */ + byte[] header = new byte[6]; + + if (remote_is.read(header) != 6) + throw new IOException("Unexpected EOF on X11 startup!"); + + if ((header[0] != 0x42) && (header[0] != 0x6c)) // 0x42 MSB first, 0x6C LSB first + throw new IOException("Unknown endian format in X11 message!"); + + /* Yes, I came up with this myself - shall I file an application for a patent? =) */ + int idxMSB = (header[0] == 0x42) ? 0 : 1; + /* Read authorization data header */ + byte[] auth_buff = new byte[6]; + + if (remote_is.read(auth_buff) != 6) + throw new IOException("Unexpected EOF on X11 startup!"); + + int authProtocolNameLength = ((auth_buff[idxMSB] & 0xff) << 8) | (auth_buff[1 - idxMSB] & 0xff); + int authProtocolDataLength = ((auth_buff[2 + idxMSB] & 0xff) << 8) | (auth_buff[3 - idxMSB] & 0xff); + + if ((authProtocolNameLength > 256) || (authProtocolDataLength > 256)) + throw new IOException("Buggy X11 authorization data"); + + int authProtocolNamePadding = ((4 - (authProtocolNameLength % 4)) % 4); + int authProtocolDataPadding = ((4 - (authProtocolDataLength % 4)) % 4); + byte[] authProtocolName = new byte[authProtocolNameLength]; + byte[] authProtocolData = new byte[authProtocolDataLength]; + byte[] paddingBuffer = new byte[4]; + + if (remote_is.read(authProtocolName) != authProtocolNameLength) + throw new IOException("Unexpected EOF on X11 startup! (authProtocolName)"); + + if (remote_is.read(paddingBuffer, 0, authProtocolNamePadding) != authProtocolNamePadding) + throw new IOException("Unexpected EOF on X11 startup! (authProtocolNamePadding)"); + + if (remote_is.read(authProtocolData) != authProtocolDataLength) + throw new IOException("Unexpected EOF on X11 startup! (authProtocolData)"); + + if (remote_is.read(paddingBuffer, 0, authProtocolDataPadding) != authProtocolDataPadding) + throw new IOException("Unexpected EOF on X11 startup! (authProtocolDataPadding)"); + + if ("MIT-MAGIC-COOKIE-1".equals(StringEncoder.GetString(authProtocolName)) == false) + throw new IOException("Unknown X11 authorization protocol!"); + + if (authProtocolDataLength != 16) + throw new IOException("Wrong data length for X11 authorization data!"); + + StringBuilder tmp = new StringBuilder(32); + + for (int i = 0; i < authProtocolData.length; i++) { + String digit2 = Integer.toHexString(authProtocolData[i] & 0xff); + tmp.append((digit2.length() == 2) ? digit2 : "0" + digit2); + } + + String hexEncodedFakeCookie = tmp.toString(); + + /* Order is very important here - it may be that a certain x11 forwarding + * gets disabled right in the moment when we check and register our connection + * */ + + synchronized (c) { + /* Please read the comment in Channel.java */ + c.hexX11FakeCookie = hexEncodedFakeCookie; + } + + /* Now check our fake cookie directory to see if we produced this cookie */ + X11ServerData sd = c.cm.checkX11Cookie(hexEncodedFakeCookie); + + if (sd == null) + throw new IOException("Invalid X11 cookie received."); + + /* If the session which corresponds to this cookie is closed then we will + * detect this: the session's close code will close all channels + * with the session's assigned x11 fake cookie. + */ + s = new Socket(sd.hostname, sd.port); + OutputStream x11_os = s.getOutputStream(); + InputStream x11_is = s.getInputStream(); + /* Now we are sending the startup packet to the real X11 server */ + x11_os.write(header); + + if (sd.x11_magic_cookie == null) { + byte[] emptyAuthData = new byte[6]; + /* empty auth data, hopefully you are connecting to localhost =) */ + x11_os.write(emptyAuthData); + } + else { + if (sd.x11_magic_cookie.length != 16) + throw new IOException("The real X11 cookie has an invalid length!"); + + /* send X11 cookie specified by client */ + x11_os.write(auth_buff); + x11_os.write(authProtocolName); /* re-use */ + x11_os.write(paddingBuffer, 0, authProtocolNamePadding); + x11_os.write(sd.x11_magic_cookie); + x11_os.write(paddingBuffer, 0, authProtocolDataPadding); + } + + x11_os.flush(); + /* Start forwarding traffic */ + StreamForwarder r2l = new StreamForwarder(c, null, null, remote_is, x11_os, "RemoteToX11"); + StreamForwarder l2r = new StreamForwarder(c, null, null, x11_is, remote_os, "X11ToRemote"); + /* No need to start two threads, one can be executed in the current thread */ + r2l.setDaemon(true); + r2l.start(); + l2r.run(); + + while (r2l.isAlive()) { + try { + r2l.join(); + } + catch (InterruptedException ignored) { + } + } + + /* If the channel is already closed, then this is a no-op */ + c.cm.closeChannel(c, "EOF on both X11 streams reached.", true); + s.close(); + } + catch (IOException e) { + log.warning("IOException in X11 proxy code: " + e.getMessage()); + + try { + c.cm.closeChannel(c, e, true); + } + catch (IOException ignored) { + } + + try { + if (s != null) + s.close(); + } + catch (IOException ignored) { + } + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/channel/ServerSessionImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/channel/ServerSessionImpl.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,50 @@ + +package ch.ethz.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import ch.ethz.ssh2.ServerSession; +import ch.ethz.ssh2.ServerSessionCallback; + +public class ServerSessionImpl implements ServerSession { + Channel c; + public ServerSessionCallback sscb; + + public ServerSessionImpl(Channel c) { + this.c = c; + } + + public int getState() { + return c.getState(); + } + + public InputStream getStdout() { + return c.getStdoutStream(); + } + + public InputStream getStderr() { + return c.getStderrStream(); + } + + public OutputStream getStdin() { + return c.getStdinStream(); + } + + public void close() { + try { + c.cm.closeChannel(c, "Closed due to server request", true); + } + catch (IOException ignored) { + } + } + + public synchronized ServerSessionCallback getServerSessionCallback() { + return sscb; + } + + public synchronized void setServerSessionCallback(ServerSessionCallback sscb) { + this.sscb = sscb; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/channel/StreamForwarder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/channel/StreamForwarder.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +/** + * A StreamForwarder forwards data between two given streams. + * If two StreamForwarder threads are used (one for each direction) + * then one can be configured to shutdown the underlying channel/socket + * if both threads have finished forwarding (EOF). + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class StreamForwarder extends Thread { + OutputStream os; + InputStream is; + byte[] buffer = new byte[Channel.CHANNEL_BUFFER_SIZE]; + Channel c; + StreamForwarder sibling; + Socket s; + String mode; + + StreamForwarder(Channel c, StreamForwarder sibling, Socket s, InputStream is, OutputStream os, String mode) + throws IOException { + this.is = is; + this.os = os; + this.mode = mode; + this.c = c; + this.sibling = sibling; + this.s = s; + } + + @Override + public void run() { + try { + while (true) { + int len = is.read(buffer); + + if (len <= 0) + break; + + os.write(buffer, 0, len); + os.flush(); + } + } + catch (IOException e) { + try { + c.cm.closeChannel(c, e, true); + } + catch (IOException ignored) { + } + } + finally { + try { + os.close(); + } + catch (IOException ignored) { + } + + try { + is.close(); + } + catch (IOException ignored) { + } + + if (sibling != null) { + while (sibling.isAlive()) { + try { + sibling.join(); + } + catch (InterruptedException ignored) { + } + } + + try { + c.cm.closeChannel(c, "StreamForwarder (" + mode + ") is cleaning up the connection", true); + } + catch (IOException ignored) { + } + + try { + if (s != null) + s.close(); + } + catch (IOException ignored) { + } + } + } + } +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/channel/X11ServerData.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/channel/X11ServerData.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.channel; + +/** + * X11ServerData. Data regarding an x11 forwarding target. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + * + */ +public class X11ServerData { + public String hostname; + public int port; + public byte[] x11_magic_cookie; /* not the remote (fake) one, the local (real) one */ +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/compression/CompressionFactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/compression/CompressionFactory.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,88 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.ethz.ssh2.compression; + +import java.util.Vector; + +/** + * @author Kenny Root + * + */ +public class CompressionFactory { + static class CompressorEntry { + String type; + String compressorClass; + + public CompressorEntry(String type, String compressorClass) { + this.type = type; + this.compressorClass = compressorClass; + } + } + + static Vector compressors = new Vector(); + + static { + /* Higher Priority First */ + compressors.addElement(new CompressorEntry("zlib", "ch.ethz.ssh2.compression.Zlib")); + compressors.addElement(new CompressorEntry("zlib@openssh.com", "ch.ethz.ssh2.compression.ZlibOpenSSH")); + compressors.addElement(new CompressorEntry("none", "")); + } + + public static String[] getDefaultCompressorList() { + String list[] = new String[compressors.size()]; + + for (int i = 0; i < compressors.size(); i++) { + CompressorEntry ce = compressors.elementAt(i); + list[i] = new String(ce.type); + } + + return list; + } + + public static void checkCompressorList(String[] compressorCandidates) { + for (int i = 0; i < compressorCandidates.length; i++) + getEntry(compressorCandidates[i]); + } + + public static Compressor createCompressor(String type) { + try { + CompressorEntry ce = getEntry(type); + + if ("".equals(ce.compressorClass)) + return null; + + Class cc = Class.forName(ce.compressorClass); + Compressor cmp = (Compressor) cc.newInstance(); + return cmp; + } + catch (Exception e) { + throw new IllegalArgumentException("Cannot instantiate " + type); + } + } + + private static CompressorEntry getEntry(String type) { + for (int i = 0; i < compressors.size(); i++) { + CompressorEntry ce = compressors.elementAt(i); + + if (ce.type.equals(type)) + return ce; + } + + throw new IllegalArgumentException("Unkown algorithm " + type); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/compression/Compressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/compression/Compressor.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,32 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.ethz.ssh2.compression; + +/** + * @author Kenny Root + * + */ +public interface Compressor { + int getBufferSize(); + + int compress(byte[] buf, int start, int len, byte[] output); + + byte[] uncompress(byte[] buf, int start, int[] len); + + boolean canCompressPreauth(); +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/compression/Zlib.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/compression/Zlib.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,131 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.ethz.ssh2.compression; + +import com.jcraft.jzlib.JZlib; +import com.jcraft.jzlib.ZStream; + +/** + * @author Kenny Root + * + */ +public class Zlib implements Compressor { + static private final int DEFAULT_BUF_SIZE = 4096; + static private final int LEVEL = 5; + + private ZStream deflate; + private byte[] deflate_tmpbuf; + + private ZStream inflate; + private byte[] inflate_tmpbuf; + private byte[] inflated_buf; + + public Zlib() { + deflate = new ZStream(); + inflate = new ZStream(); + deflate.deflateInit(LEVEL); + inflate.inflateInit(); + deflate_tmpbuf = new byte[DEFAULT_BUF_SIZE]; + inflate_tmpbuf = new byte[DEFAULT_BUF_SIZE]; + inflated_buf = new byte[DEFAULT_BUF_SIZE]; + } + + public boolean canCompressPreauth() { + return true; + } + + public int getBufferSize() { + return DEFAULT_BUF_SIZE; + } + + public int compress(byte[] buf, int start, int len, byte[] output) { + deflate.next_in = buf; + deflate.next_in_index = start; + deflate.avail_in = len - start; + + if ((buf.length + 1024) > deflate_tmpbuf.length) { + deflate_tmpbuf = new byte[buf.length + 1024]; + } + + deflate.next_out = deflate_tmpbuf; + deflate.next_out_index = 0; + deflate.avail_out = output.length; + + if (deflate.deflate(JZlib.Z_PARTIAL_FLUSH) != JZlib.Z_OK) { + System.err.println("compress: compression failure"); + } + + if (deflate.avail_in > 0) { + System.err.println("compress: deflated data too large"); + } + + int outputlen = output.length - deflate.avail_out; + System.arraycopy(deflate_tmpbuf, 0, output, 0, outputlen); + return outputlen; + } + + public byte[] uncompress(byte[] buffer, int start, int[] length) { + int inflated_end = 0; + inflate.next_in = buffer; + inflate.next_in_index = start; + inflate.avail_in = length[0]; + + while (true) { + inflate.next_out = inflate_tmpbuf; + inflate.next_out_index = 0; + inflate.avail_out = DEFAULT_BUF_SIZE; + int status = inflate.inflate(JZlib.Z_PARTIAL_FLUSH); + + switch (status) { + case JZlib.Z_OK: + if (inflated_buf.length < inflated_end + DEFAULT_BUF_SIZE + - inflate.avail_out) { + byte[] foo = new byte[inflated_end + DEFAULT_BUF_SIZE + - inflate.avail_out]; + System.arraycopy(inflated_buf, 0, foo, 0, inflated_end); + inflated_buf = foo; + } + + System.arraycopy(inflate_tmpbuf, 0, inflated_buf, inflated_end, + DEFAULT_BUF_SIZE - inflate.avail_out); + inflated_end += (DEFAULT_BUF_SIZE - inflate.avail_out); + length[0] = inflated_end; + break; + + case JZlib.Z_BUF_ERROR: + if (inflated_end > buffer.length - start) { + byte[] foo = new byte[inflated_end + start]; + System.arraycopy(buffer, 0, foo, 0, start); + System.arraycopy(inflated_buf, 0, foo, start, inflated_end); + buffer = foo; + } + else { + System.arraycopy(inflated_buf, 0, buffer, start, + inflated_end); + } + + length[0] = inflated_end; + return buffer; + + default: + System.err.println("uncompress: inflate returnd " + status); + return null; + } + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/compression/ZlibOpenSSH.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/compression/ZlibOpenSSH.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,35 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.ethz.ssh2.compression; + +/** + * Defines how zlib@openssh.org compression works. + * See + * http://www.openssh.org/txt/draft-miller-secsh-compression-delayed-00.txt + * compression is disabled until userauth has occurred. + * + * @author Matt Johnston + * + */ +public class ZlibOpenSSH extends Zlib { + + public boolean canCompressPreauth() { + return false; + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/Base64.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/Base64.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto; + +import java.io.CharArrayWriter; +import java.io.IOException; + +/** + * Basic Base64 Support. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class Base64 { + static final char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); + + public static char[] encode(byte[] content) { + CharArrayWriter cw = new CharArrayWriter((4 * content.length) / 3); + int idx = 0; + int x = 0; + + for (int i = 0; i < content.length; i++) { + if (idx == 0) + x = (content[i] & 0xff) << 16; + else if (idx == 1) + x = x | ((content[i] & 0xff) << 8); + else + x = x | (content[i] & 0xff); + + idx++; + + if (idx == 3) { + cw.write(alphabet[x >> 18]); + cw.write(alphabet[(x >> 12) & 0x3f]); + cw.write(alphabet[(x >> 6) & 0x3f]); + cw.write(alphabet[x & 0x3f]); + idx = 0; + } + } + + if (idx == 1) { + cw.write(alphabet[x >> 18]); + cw.write(alphabet[(x >> 12) & 0x3f]); + cw.write('='); + cw.write('='); + } + + if (idx == 2) { + cw.write(alphabet[x >> 18]); + cw.write(alphabet[(x >> 12) & 0x3f]); + cw.write(alphabet[(x >> 6) & 0x3f]); + cw.write('='); + } + + return cw.toCharArray(); + } + + public static byte[] decode(char[] message) throws IOException { + byte buff[] = new byte[4]; + byte dest[] = new byte[message.length]; + int bpos = 0; + int destpos = 0; + + for (int i = 0; i < message.length; i++) { + int c = message[i]; + + if ((c == '\n') || (c == '\r') || (c == ' ') || (c == '\t')) + continue; + + if ((c >= 'A') && (c <= 'Z')) { + buff[bpos++] = (byte)(c - 'A'); + } + else if ((c >= 'a') && (c <= 'z')) { + buff[bpos++] = (byte)((c - 'a') + 26); + } + else if ((c >= '0') && (c <= '9')) { + buff[bpos++] = (byte)((c - '0') + 52); + } + else if (c == '+') { + buff[bpos++] = 62; + } + else if (c == '/') { + buff[bpos++] = 63; + } + else if (c == '=') { + buff[bpos++] = 64; + } + else { + throw new IOException("Illegal char in base64 code."); + } + + if (bpos == 4) { + bpos = 0; + + if (buff[0] == 64) + break; + + if (buff[1] == 64) + throw new IOException("Unexpected '=' in base64 code."); + + if (buff[2] == 64) { + int v = (((buff[0] & 0x3f) << 6) | ((buff[1] & 0x3f))); + dest[destpos++] = (byte)(v >> 4); + break; + } + else if (buff[3] == 64) { + int v = (((buff[0] & 0x3f) << 12) | ((buff[1] & 0x3f) << 6) | ((buff[2] & 0x3f))); + dest[destpos++] = (byte)(v >> 10); + dest[destpos++] = (byte)(v >> 2); + break; + } + else { + int v = (((buff[0] & 0x3f) << 18) | ((buff[1] & 0x3f) << 12) | ((buff[2] & 0x3f) << 6) | ((buff[3] & 0x3f))); + dest[destpos++] = (byte)(v >> 16); + dest[destpos++] = (byte)(v >> 8); + dest[destpos++] = (byte)(v); + } + } + } + + byte[] res = new byte[destpos]; + System.arraycopy(dest, 0, res, 0, destpos); + return res; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/CryptoWishList.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/CryptoWishList.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto; + +import ch.ethz.ssh2.compression.CompressionFactory; +import ch.ethz.ssh2.crypto.cipher.BlockCipherFactory; +import ch.ethz.ssh2.crypto.digest.MAC; +import ch.ethz.ssh2.transport.KexManager; + +/** + * CryptoWishList. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class CryptoWishList { + public String[] kexAlgorithms = KexManager.getDefaultClientKexAlgorithmList(); + public String[] serverHostKeyAlgorithms = KexManager.getDefaultServerHostkeyAlgorithmList(); + public String[] c2s_enc_algos = BlockCipherFactory.getDefaultCipherList(); + public String[] s2c_enc_algos = BlockCipherFactory.getDefaultCipherList(); + public String[] c2s_mac_algos = MAC.getMacList(); + public String[] s2c_mac_algos = MAC.getMacList(); + public String[] c2s_comp_algos = CompressionFactory.getDefaultCompressorList(); + public String[] s2c_comp_algos = CompressionFactory.getDefaultCompressorList(); + + public static CryptoWishList forServer() { + CryptoWishList cwl = new CryptoWishList(); + cwl.kexAlgorithms = KexManager.getDefaultServerKexAlgorithmList(); + return cwl; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/KeyMaterial.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/KeyMaterial.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto; + +import java.io.IOException; +import java.math.BigInteger; + +import ch.ethz.ssh2.crypto.digest.HashForSSH2Types; + +/** + * Establishes key material for iv/key/mac (both directions). + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class KeyMaterial { + public byte[] initial_iv_client_to_server; + public byte[] initial_iv_server_to_client; + public byte[] enc_key_client_to_server; + public byte[] enc_key_server_to_client; + public byte[] integrity_key_client_to_server; + public byte[] integrity_key_server_to_client; + + private static byte[] calculateKey(HashForSSH2Types sh, BigInteger K, byte[] H, byte type, byte[] SessionID, + int keyLength) throws IOException { + byte[] res = new byte[keyLength]; + int dglen = sh.getDigestLength(); + int numRounds = (keyLength + dglen - 1) / dglen; + byte[][] tmp = new byte[numRounds][]; + sh.reset(); + sh.updateBigInt(K); + sh.updateBytes(H); + sh.updateByte(type); + sh.updateBytes(SessionID); + tmp[0] = sh.getDigest(); + int off = 0; + int produced = Math.min(dglen, keyLength); + System.arraycopy(tmp[0], 0, res, off, produced); + keyLength -= produced; + off += produced; + + for (int i = 1; i < numRounds; i++) { + sh.updateBigInt(K); + sh.updateBytes(H); + + for (int j = 0; j < i; j++) { + sh.updateBytes(tmp[j]); + } + + tmp[i] = sh.getDigest(); + produced = Math.min(dglen, keyLength); + System.arraycopy(tmp[i], 0, res, off, produced); + keyLength -= produced; + off += produced; + } + + return res; + } + + public static KeyMaterial create(String hashType, byte[] H, BigInteger K, byte[] SessionID, int keyLengthCS, + int blockSizeCS, int macLengthCS, int keyLengthSC, int blockSizeSC, int macLengthSC) + throws IOException { + KeyMaterial km = new KeyMaterial(); + HashForSSH2Types sh = new HashForSSH2Types(hashType); + km.initial_iv_client_to_server = calculateKey(sh, K, H, (byte) 'A', SessionID, blockSizeCS); + km.initial_iv_server_to_client = calculateKey(sh, K, H, (byte) 'B', SessionID, blockSizeSC); + km.enc_key_client_to_server = calculateKey(sh, K, H, (byte) 'C', SessionID, keyLengthCS); + km.enc_key_server_to_client = calculateKey(sh, K, H, (byte) 'D', SessionID, keyLengthSC); + km.integrity_key_client_to_server = calculateKey(sh, K, H, (byte) 'E', SessionID, macLengthCS); + km.integrity_key_server_to_client = calculateKey(sh, K, H, (byte) 'F', SessionID, macLengthSC); + return km; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/PEMDecoder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/PEMDecoder.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,444 @@ + +package ch.ethz.ssh2.crypto; + +import java.io.BufferedReader; +import java.io.CharArrayReader; +import java.io.IOException; +import java.math.BigInteger; +import java.security.DigestException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.DSAPrivateKeySpec; +import java.security.spec.DSAPublicKeySpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.security.spec.RSAPrivateKeySpec; +import java.security.spec.RSAPublicKeySpec; + +import ch.ethz.ssh2.crypto.cipher.AES; +import ch.ethz.ssh2.crypto.cipher.BlockCipher; +import ch.ethz.ssh2.crypto.cipher.CBCMode; +import ch.ethz.ssh2.crypto.cipher.DES; +import ch.ethz.ssh2.crypto.cipher.DESede; +import ch.ethz.ssh2.signature.ECDSASHA2Verify; + +/** + * PEM Support. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: PEMDecoder.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ + */ +public class PEMDecoder { + public static final int PEM_RSA_PRIVATE_KEY = 1; + public static final int PEM_DSA_PRIVATE_KEY = 2; + public static final int PEM_EC_PRIVATE_KEY = 3; + + private static final int hexToInt(char c) { + if ((c >= 'a') && (c <= 'f')) { + return (c - 'a') + 10; + } + + if ((c >= 'A') && (c <= 'F')) { + return (c - 'A') + 10; + } + + if ((c >= '0') && (c <= '9')) { + return (c - '0'); + } + + throw new IllegalArgumentException("Need hex char"); + } + + private static byte[] hexToByteArray(String hex) { + if (hex == null) + throw new IllegalArgumentException("null argument"); + + if ((hex.length() % 2) != 0) + throw new IllegalArgumentException("Uneven string length in hex encoding."); + + byte decoded[] = new byte[hex.length() / 2]; + + for (int i = 0; i < decoded.length; i++) { + int hi = hexToInt(hex.charAt(i * 2)); + int lo = hexToInt(hex.charAt((i * 2) + 1)); + decoded[i] = (byte)(hi * 16 + lo); + } + + return decoded; + } + + private static byte[] generateKeyFromPasswordSaltWithMD5(byte[] password, byte[] salt, int keyLen) + throws IOException { + if (salt.length < 8) + throw new IllegalArgumentException("Salt needs to be at least 8 bytes for key generation."); + + MessageDigest md5; + + try { + md5 = MessageDigest.getInstance("MD5"); + } + catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException("VM does not support MD5", e); + } + + byte[] key = new byte[keyLen]; + byte[] tmp = new byte[md5.getDigestLength()]; + + while (true) { + md5.update(password, 0, password.length); + md5.update(salt, 0, 8); // ARGH we only use the first 8 bytes of the + // salt in this step. + // This took me two hours until I got AES-xxx running. + int copy = (keyLen < tmp.length) ? keyLen : tmp.length; + + try { + md5.digest(tmp, 0, tmp.length); + } + catch (DigestException e) { + IOException ex = new IOException("could not digest password"); + ex.initCause(e); + throw ex; + } + + System.arraycopy(tmp, 0, key, key.length - keyLen, copy); + keyLen -= copy; + + if (keyLen == 0) + return key; + + md5.update(tmp, 0, tmp.length); + } + } + + private static byte[] removePadding(byte[] buff, int blockSize) throws IOException { + /* Removes RFC 1423/PKCS #7 padding */ + int rfc_1423_padding = buff[buff.length - 1] & 0xff; + + if ((rfc_1423_padding < 1) || (rfc_1423_padding > blockSize)) + throw new IOException("Decrypted PEM has wrong padding, did you specify the correct password?"); + + for (int i = 2; i <= rfc_1423_padding; i++) { + if (buff[buff.length - i] != rfc_1423_padding) + throw new IOException("Decrypted PEM has wrong padding, did you specify the correct password?"); + } + + byte[] tmp = new byte[buff.length - rfc_1423_padding]; + System.arraycopy(buff, 0, tmp, 0, buff.length - rfc_1423_padding); + return tmp; + } + + public static final PEMStructure parsePEM(char[] pem) throws IOException { + PEMStructure ps = new PEMStructure(); + String line = null; + BufferedReader br = new BufferedReader(new CharArrayReader(pem)); + String endLine = null; + + while (true) { + line = br.readLine(); + + if (line == null) + throw new IOException("Invalid PEM structure, '-----BEGIN...' missing"); + + line = line.trim(); + + if (line.startsWith("-----BEGIN DSA PRIVATE KEY-----")) { + endLine = "-----END DSA PRIVATE KEY-----"; + ps.pemType = PEM_DSA_PRIVATE_KEY; + break; + } + + if (line.startsWith("-----BEGIN RSA PRIVATE KEY-----")) { + endLine = "-----END RSA PRIVATE KEY-----"; + ps.pemType = PEM_RSA_PRIVATE_KEY; + break; + } + + if (line.startsWith("-----BEGIN EC PRIVATE KEY-----")) { + endLine = "-----END EC PRIVATE KEY-----"; + ps.pemType = PEM_EC_PRIVATE_KEY; + break; + } + } + + while (true) { + line = br.readLine(); + + if (line == null) + throw new IOException("Invalid PEM structure, " + endLine + " missing"); + + line = line.trim(); + int sem_idx = line.indexOf(':'); + + if (sem_idx == -1) + break; + + String name = line.substring(0, sem_idx + 1); + String value = line.substring(sem_idx + 1); + String values[] = value.split(","); + + for (int i = 0; i < values.length; i++) + values[i] = values[i].trim(); + + // Proc-Type: 4,ENCRYPTED + // DEK-Info: DES-EDE3-CBC,579B6BE3E5C60483 + + if ("Proc-Type:".equals(name)) { + ps.procType = values; + continue; + } + + if ("DEK-Info:".equals(name)) { + ps.dekInfo = values; + continue; + } + + /* Ignore line */ + } + + StringBuffer keyData = new StringBuffer(); + + while (true) { + if (line == null) + throw new IOException("Invalid PEM structure, " + endLine + " missing"); + + line = line.trim(); + + if (line.startsWith(endLine)) + break; + + keyData.append(line); + line = br.readLine(); + } + + char[] pem_chars = new char[keyData.length()]; + keyData.getChars(0, pem_chars.length, pem_chars, 0); + ps.data = Base64.decode(pem_chars); + + if (ps.data.length == 0) + throw new IOException("Invalid PEM structure, no data available"); + + return ps; + } + + private static final void decryptPEM(PEMStructure ps, byte[] pw) throws IOException { + if (ps.dekInfo == null) + throw new IOException("Broken PEM, no mode and salt given, but encryption enabled"); + + if (ps.dekInfo.length != 2) + throw new IOException("Broken PEM, DEK-Info is incomplete!"); + + String algo = ps.dekInfo[0]; + byte[] salt = hexToByteArray(ps.dekInfo[1]); + BlockCipher bc = null; + + if (algo.equals("DES-EDE3-CBC")) { + DESede des3 = new DESede(); + des3.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24)); + bc = new CBCMode(des3, salt, false); + } + else if (algo.equals("DES-CBC")) { + DES des = new DES(); + des.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 8)); + bc = new CBCMode(des, salt, false); + } + else if (algo.equals("AES-128-CBC")) { + AES aes = new AES(); + aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 16)); + bc = new CBCMode(aes, salt, false); + } + else if (algo.equals("AES-192-CBC")) { + AES aes = new AES(); + aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24)); + bc = new CBCMode(aes, salt, false); + } + else if (algo.equals("AES-256-CBC")) { + AES aes = new AES(); + aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 32)); + bc = new CBCMode(aes, salt, false); + } + else { + throw new IOException("Cannot decrypt PEM structure, unknown cipher " + algo); + } + + if ((ps.data.length % bc.getBlockSize()) != 0) + throw new IOException("Invalid PEM structure, size of encrypted block is not a multiple of " + + bc.getBlockSize()); + + /* Now decrypt the content */ + byte[] dz = new byte[ps.data.length]; + + for (int i = 0; i < ps.data.length / bc.getBlockSize(); i++) { + bc.transformBlock(ps.data, i * bc.getBlockSize(), dz, i * bc.getBlockSize()); + } + + /* Now check and remove RFC 1423/PKCS #7 padding */ + dz = removePadding(dz, bc.getBlockSize()); + ps.data = dz; + ps.dekInfo = null; + ps.procType = null; + } + + public static final boolean isPEMEncrypted(PEMStructure ps) throws IOException { + if (ps.procType == null) + return false; + + if (ps.procType.length != 2) + throw new IOException("Unknown Proc-Type field."); + + if ("4".equals(ps.procType[0]) == false) + throw new IOException("Unknown Proc-Type field (" + ps.procType[0] + ")"); + + if ("ENCRYPTED".equals(ps.procType[1])) + return true; + + return false; + } + + public static KeyPair decode(char[] pem, String password) throws IOException { + PEMStructure ps = parsePEM(pem); + return decode(ps, password); + } + + public static KeyPair decode(PEMStructure ps, String password) throws IOException { + if (isPEMEncrypted(ps)) { + if (password == null) + throw new IOException("PEM is encrypted, but no password was specified"); + + decryptPEM(ps, password.getBytes("ISO-8859-1")); + } + + if (ps.pemType == PEM_DSA_PRIVATE_KEY) { + SimpleDERReader dr = new SimpleDERReader(ps.data); + byte[] seq = dr.readSequenceAsByteArray(); + + if (dr.available() != 0) + throw new IOException("Padding in DSA PRIVATE KEY DER stream."); + + dr.resetInput(seq); + BigInteger version = dr.readInt(); + + if (version.compareTo(BigInteger.ZERO) != 0) + throw new IOException("Wrong version (" + version + ") in DSA PRIVATE KEY DER stream."); + + BigInteger p = dr.readInt(); + BigInteger q = dr.readInt(); + BigInteger g = dr.readInt(); + BigInteger y = dr.readInt(); + BigInteger x = dr.readInt(); + + if (dr.available() != 0) + throw new IOException("Padding in DSA PRIVATE KEY DER stream."); + + DSAPrivateKeySpec privSpec = new DSAPrivateKeySpec(x, p, q, g); + DSAPublicKeySpec pubSpec = new DSAPublicKeySpec(y, p, q, g); + return generateKeyPair("DSA", privSpec, pubSpec); + } + + if (ps.pemType == PEM_RSA_PRIVATE_KEY) { + SimpleDERReader dr = new SimpleDERReader(ps.data); + byte[] seq = dr.readSequenceAsByteArray(); + + if (dr.available() != 0) + throw new IOException("Padding in RSA PRIVATE KEY DER stream."); + + dr.resetInput(seq); + BigInteger version = dr.readInt(); + + if ((version.compareTo(BigInteger.ZERO) != 0) && (version.compareTo(BigInteger.ONE) != 0)) + throw new IOException("Wrong version (" + version + ") in RSA PRIVATE KEY DER stream."); + + BigInteger n = dr.readInt(); + BigInteger e = dr.readInt(); + BigInteger d = dr.readInt(); + // TODO: is this right? + BigInteger primeP = dr.readInt(); + BigInteger primeQ = dr.readInt(); + BigInteger expP = dr.readInt(); + BigInteger expQ = dr.readInt(); + BigInteger coeff = dr.readInt(); + RSAPrivateKeySpec privSpec = new RSAPrivateCrtKeySpec(n, e, d, primeP, primeQ, expP, expQ, coeff); + RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(n, e); + return generateKeyPair("RSA", privSpec, pubSpec); + } + + if (ps.pemType == PEM_EC_PRIVATE_KEY) { + SimpleDERReader dr = new SimpleDERReader(ps.data); + byte[] seq = dr.readSequenceAsByteArray(); + + if (dr.available() != 0) + throw new IOException("Padding in EC PRIVATE KEY DER stream."); + + dr.resetInput(seq); + BigInteger version = dr.readInt(); + + if ((version.compareTo(BigInteger.ONE) != 0)) + throw new IOException("Wrong version (" + version + ") in EC PRIVATE KEY DER stream."); + + byte[] privateBytes = dr.readOctetString(); + String curveOid = null; + byte[] publicBytes = null; + + while (dr.available() > 0) { + int type = dr.readConstructedType(); + SimpleDERReader cr = dr.readConstructed(); + + switch (type) { + case 0: + curveOid = cr.readOid(); + break; + + case 1: + publicBytes = cr.readOctetString(); + break; + } + } + + ECParameterSpec params = ECDSASHA2Verify.getCurveForOID(curveOid); + + if (params == null) + throw new IOException("invalid OID"); + + BigInteger s = new BigInteger(privateBytes); + byte[] publicBytesSlice = new byte[publicBytes.length - 1]; + System.arraycopy(publicBytes, 1, publicBytesSlice, 0, publicBytesSlice.length); + ECPoint w = ECDSASHA2Verify.decodeECPoint(publicBytesSlice, params.getCurve()); + ECPrivateKeySpec privSpec = new ECPrivateKeySpec(s, params); + ECPublicKeySpec pubSpec = new ECPublicKeySpec(w, params); + return generateKeyPair("EC", privSpec, pubSpec); + } + + throw new IOException("PEM problem: it is of unknown type"); + } + + /** + * Generate a {@code KeyPair} given an {@code algorithm} and {@code KeySpec}. + */ + private static KeyPair generateKeyPair(String algorithm, KeySpec privSpec, KeySpec pubSpec) + throws IOException { + try { + final KeyFactory kf = KeyFactory.getInstance(algorithm); + final PublicKey pubKey = kf.generatePublic(pubSpec); + final PrivateKey privKey = kf.generatePrivate(privSpec); + return new KeyPair(pubKey, privKey); + } + catch (NoSuchAlgorithmException ex) { + IOException ioex = new IOException(); + ioex.initCause(ex); + throw ioex; + } + catch (InvalidKeySpecException ex) { + IOException ioex = new IOException("invalid keyspec"); + ioex.initCause(ex); + throw ioex; + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/PEMDecryptException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/PEMDecryptException.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2006-2014 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto; + +import java.io.IOException; + +/** + * @version $Id: PEMDecryptException.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class PEMDecryptException extends IOException { + /** + * + */ + private static final long serialVersionUID = 1L; + + public PEMDecryptException(String message) { + super(message); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/PEMStructure.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/PEMStructure.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto; + +/** + * Parsed PEM structure. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ + +public class PEMStructure { + public int pemType; + String dekInfo[]; + String procType[]; + byte[] data; +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/SecureRandomFix.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/SecureRandomFix.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,82 @@ +// +// Copyright (C) 2014 by 510 Software Group +// licensed under the GPLv3 or later + +package ch.ethz.ssh2.crypto; + +import android.os.Build; +import android.os.Process; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.SecureRandom; + + +public class SecureRandomFix extends SecureRandom { + + // http://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html + + private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18; + private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = + getBuildFingerprintAndDeviceSerial(); + + private static byte[] generateReasonableSeed() { + try { + ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); + DataOutputStream seedBufferOut = + new DataOutputStream(seedBuffer); + seedBufferOut.writeLong(System.currentTimeMillis()); + seedBufferOut.writeLong(System.nanoTime()); + seedBufferOut.writeInt(Process.myPid()); + seedBufferOut.writeInt(Process.myUid()); + seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL); + seedBufferOut.close(); + return seedBuffer.toByteArray(); + } catch (IOException e) { + throw new SecurityException("Failed to generate seed", e); + } + } + + /** + * Gets the hardware serial number of this device. + * + * @return serial number or {@code null} if not available. + */ + private static String getDeviceSerialNumber() { + // We're using the Reflection API because Build.SERIAL is only available + // since API Level 9 (Gingerbread, Android 2.3). + try { + return (String) Build.class.getField("SERIAL").get(null); + } catch (Exception ignored) { + return null; + } + } + + private static byte[] getBuildFingerprintAndDeviceSerial() { + StringBuilder result = new StringBuilder(); + String fingerprint = Build.FINGERPRINT; + if (fingerprint != null) { + result.append(fingerprint); + } + String serial = getDeviceSerialNumber(); + if (serial != null) { + result.append(serial); + } + try { + return result.toString().getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8 encoding not supported"); + } + } + + public SecureRandomFix() { + super(); + if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) { + // No need to apply the fix + return; + } + setSeed(generateReasonableSeed()); + } +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/SimpleDERReader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/SimpleDERReader.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,205 @@ +package ch.ethz.ssh2.crypto; + +import java.io.IOException; + +import java.math.BigInteger; + +/** + * SimpleDERReader. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: SimpleDERReader.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class SimpleDERReader { + private static final int CONSTRUCTED = 0x20; + + byte[] buffer; + int pos; + int count; + + public SimpleDERReader(byte[] b) { + resetInput(b); + } + + public SimpleDERReader(byte[] b, int off, int len) { + resetInput(b, off, len); + } + + public void resetInput(byte[] b) { + resetInput(b, 0, b.length); + } + + public void resetInput(byte[] b, int off, int len) { + buffer = b; + pos = off; + count = len; + } + + private byte readByte() throws IOException { + if (count <= 0) + throw new IOException("DER byte array: out of data"); + + count--; + return buffer[pos++]; + } + + private byte[] readBytes(int len) throws IOException { + if (len > count) + throw new IOException("DER byte array: out of data"); + + byte[] b = new byte[len]; + System.arraycopy(buffer, pos, b, 0, len); + pos += len; + count -= len; + return b; + } + + public int available() { + return count; + } + + private int readLength() throws IOException { + int len = readByte() & 0xff; + + if ((len & 0x80) == 0) + return len; + + int remain = len & 0x7F; + + if (remain == 0) + return -1; + + len = 0; + + while (remain > 0) { + len = len << 8; + len = len | (readByte() & 0xff); + remain--; + } + + return len; + } + + public int ignoreNextObject() throws IOException { + int type = readByte() & 0xff; + int len = readLength(); + + if ((len < 0) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + readBytes(len); + return type; + } + + public BigInteger readInt() throws IOException { + int type = readByte() & 0xff; + + if (type != 0x02) + throw new IOException("Expected DER Integer, but found type " + type); + + int len = readLength(); + + if ((len < 0) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + byte[] b = readBytes(len); + BigInteger bi = new BigInteger(b); + return bi; + } + + public int readConstructedType() throws IOException { + int type = readByte() & 0xff; + + if ((type & CONSTRUCTED) != CONSTRUCTED) + throw new IOException("Expected constructed type, but was " + type); + + return type & 0x1f; + } + + public SimpleDERReader readConstructed() throws IOException { + int len = readLength(); + + if ((len < 0) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + SimpleDERReader cr = new SimpleDERReader(buffer, pos, len); + pos += len; + count -= len; + return cr; + } + + public byte[] readSequenceAsByteArray() throws IOException { + int type = readByte() & 0xff; + + if (type != 0x30) + throw new IOException("Expected DER Sequence, but found type " + type); + + int len = readLength(); + + if ((len < 0) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + byte[] b = readBytes(len); + return b; + } + + public String readOid() throws IOException { + int type = readByte() & 0xff; + + if (type != 0x06) + throw new IOException("Expected DER OID, but found type " + type); + + int len = readLength(); + + if ((len < 1) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + byte[] b = readBytes(len); + long value = 0; + StringBuilder sb = new StringBuilder(64); + + switch (b[0] / 40) { + case 0: + sb.append('0'); + break; + + case 1: + sb.append('1'); + b[0] -= 40; + break; + + default: + sb.append('2'); + b[0] -= 80; + break; + } + + for (int i = 0; i < len; i++) { + value = (value << 7) + (b[i] & 0x7F); + + if ((b[i] & 0x80) == 0) { + sb.append('.'); + sb.append(value); + value = 0; + } + } + + return sb.toString(); + } + + public byte[] readOctetString() throws IOException { + int type = readByte() & 0xff; + + if (type != 0x04 && type != 0x03) + throw new IOException("Expected DER Octetstring, but found type " + type); + + int len = readLength(); + + if ((len < 0) || len > available()) + throw new IOException("Illegal len in DER object (" + len + ")"); + + byte[] b = readBytes(len); + return b; + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/cipher/AES.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/cipher/AES.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,661 @@ +package ch.ethz.ssh2.crypto.cipher; + +/* + This file was shamelessly taken from the Bouncy Castle Crypto package. + Their licence file states the following: + + Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle + (http://www.bouncycastle.org) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +/** + * An implementation of the AES (Rijndael), from FIPS-197. + *

+ * For further details see: http://csrc.nist.gov/encryption/aes/ + * . + * + * This implementation is based on optimizations from Dr. Brian Gladman's paper + * and C code at http://fp.gladman.plus.com/cryptography_technology/rijndael/ + * + * + * There are three levels of tradeoff of speed vs memory Because java has no + * preprocessor, they are written as three separate classes from which to choose + * + * The fastest uses 8Kbytes of static tables to precompute round calculations, 4 + * 256 word tables for encryption and 4 for decryption. + * + * The middle performance version uses only one 256 word table for each, for a + * total of 2Kbytes, adding 12 rotate operations per round to compute the values + * contained in the other tables from the contents of the first + * + * The slowest version uses no static tables at all and computes the values in + * each round + *

+ * This file contains the fast version with 8Kbytes of static tables for round + * precomputation + * + * @author See comments in the source file + * @version 2.50, 03/15/10 + */ +public class AES implements BlockCipher { + // The S box + private static final byte[] S = { (byte) 99, (byte) 124, (byte) 119, (byte) 123, (byte) 242, (byte) 107, + (byte) 111, (byte) 197, (byte) 48, (byte) 1, (byte) 103, (byte) 43, (byte) 254, (byte) 215, (byte) 171, + (byte) 118, (byte) 202, (byte) 130, (byte) 201, (byte) 125, (byte) 250, (byte) 89, (byte) 71, (byte) 240, + (byte) 173, (byte) 212, (byte) 162, (byte) 175, (byte) 156, (byte) 164, (byte) 114, (byte) 192, (byte) 183, + (byte) 253, (byte) 147, (byte) 38, (byte) 54, (byte) 63, (byte) 247, (byte) 204, (byte) 52, (byte) 165, + (byte) 229, (byte) 241, (byte) 113, (byte) 216, (byte) 49, (byte) 21, (byte) 4, (byte) 199, (byte) 35, + (byte) 195, (byte) 24, (byte) 150, (byte) 5, (byte) 154, (byte) 7, (byte) 18, (byte) 128, (byte) 226, + (byte) 235, (byte) 39, (byte) 178, (byte) 117, (byte) 9, (byte) 131, (byte) 44, (byte) 26, (byte) 27, + (byte) 110, (byte) 90, (byte) 160, (byte) 82, (byte) 59, (byte) 214, (byte) 179, (byte) 41, (byte) 227, + (byte) 47, (byte) 132, (byte) 83, (byte) 209, (byte) 0, (byte) 237, (byte) 32, (byte) 252, (byte) 177, + (byte) 91, (byte) 106, (byte) 203, (byte) 190, (byte) 57, (byte) 74, (byte) 76, (byte) 88, (byte) 207, + (byte) 208, (byte) 239, (byte) 170, (byte) 251, (byte) 67, (byte) 77, (byte) 51, (byte) 133, (byte) 69, + (byte) 249, (byte) 2, (byte) 127, (byte) 80, (byte) 60, (byte) 159, (byte) 168, (byte) 81, (byte) 163, + (byte) 64, (byte) 143, (byte) 146, (byte) 157, (byte) 56, (byte) 245, (byte) 188, (byte) 182, (byte) 218, + (byte) 33, (byte) 16, (byte) 255, (byte) 243, (byte) 210, (byte) 205, (byte) 12, (byte) 19, (byte) 236, + (byte) 95, (byte) 151, (byte) 68, (byte) 23, (byte) 196, (byte) 167, (byte) 126, (byte) 61, (byte) 100, + (byte) 93, (byte) 25, (byte) 115, (byte) 96, (byte) 129, (byte) 79, (byte) 220, (byte) 34, (byte) 42, + (byte) 144, (byte) 136, (byte) 70, (byte) 238, (byte) 184, (byte) 20, (byte) 222, (byte) 94, (byte) 11, + (byte) 219, (byte) 224, (byte) 50, (byte) 58, (byte) 10, (byte) 73, (byte) 6, (byte) 36, (byte) 92, + (byte) 194, (byte) 211, (byte) 172, (byte) 98, (byte) 145, (byte) 149, (byte) 228, (byte) 121, (byte) 231, + (byte) 200, (byte) 55, (byte) 109, (byte) 141, (byte) 213, (byte) 78, (byte) 169, (byte) 108, (byte) 86, + (byte) 244, (byte) 234, (byte) 101, (byte) 122, (byte) 174, (byte) 8, (byte) 186, (byte) 120, (byte) 37, + (byte) 46, (byte) 28, (byte) 166, (byte) 180, (byte) 198, (byte) 232, (byte) 221, (byte) 116, (byte) 31, + (byte) 75, (byte) 189, (byte) 139, (byte) 138, (byte) 112, (byte) 62, (byte) 181, (byte) 102, (byte) 72, + (byte) 3, (byte) 246, (byte) 14, (byte) 97, (byte) 53, (byte) 87, (byte) 185, (byte) 134, (byte) 193, + (byte) 29, (byte) 158, (byte) 225, (byte) 248, (byte) 152, (byte) 17, (byte) 105, (byte) 217, (byte) 142, + (byte) 148, (byte) 155, (byte) 30, (byte) 135, (byte) 233, (byte) 206, (byte) 85, (byte) 40, (byte) 223, + (byte) 140, (byte) 161, (byte) 137, (byte) 13, (byte) 191, (byte) 230, (byte) 66, (byte) 104, (byte) 65, + (byte) 153, (byte) 45, (byte) 15, (byte) 176, (byte) 84, (byte) 187, (byte) 22, + }; + + // The inverse S-box + private static final byte[] Si = { (byte) 82, (byte) 9, (byte) 106, (byte) 213, (byte) 48, (byte) 54, (byte) 165, + (byte) 56, (byte) 191, (byte) 64, (byte) 163, (byte) 158, (byte) 129, (byte) 243, (byte) 215, (byte) 251, + (byte) 124, (byte) 227, (byte) 57, (byte) 130, (byte) 155, (byte) 47, (byte) 255, (byte) 135, (byte) 52, + (byte) 142, (byte) 67, (byte) 68, (byte) 196, (byte) 222, (byte) 233, (byte) 203, (byte) 84, (byte) 123, + (byte) 148, (byte) 50, (byte) 166, (byte) 194, (byte) 35, (byte) 61, (byte) 238, (byte) 76, (byte) 149, + (byte) 11, (byte) 66, (byte) 250, (byte) 195, (byte) 78, (byte) 8, (byte) 46, (byte) 161, (byte) 102, + (byte) 40, (byte) 217, (byte) 36, (byte) 178, (byte) 118, (byte) 91, (byte) 162, (byte) 73, (byte) 109, + (byte) 139, (byte) 209, (byte) 37, (byte) 114, (byte) 248, (byte) 246, (byte) 100, (byte) 134, (byte) 104, + (byte) 152, (byte) 22, (byte) 212, (byte) 164, (byte) 92, (byte) 204, (byte) 93, (byte) 101, (byte) 182, + (byte) 146, (byte) 108, (byte) 112, (byte) 72, (byte) 80, (byte) 253, (byte) 237, (byte) 185, (byte) 218, + (byte) 94, (byte) 21, (byte) 70, (byte) 87, (byte) 167, (byte) 141, (byte) 157, (byte) 132, (byte) 144, + (byte) 216, (byte) 171, (byte) 0, (byte) 140, (byte) 188, (byte) 211, (byte) 10, (byte) 247, (byte) 228, + (byte) 88, (byte) 5, (byte) 184, (byte) 179, (byte) 69, (byte) 6, (byte) 208, (byte) 44, (byte) 30, + (byte) 143, (byte) 202, (byte) 63, (byte) 15, (byte) 2, (byte) 193, (byte) 175, (byte) 189, (byte) 3, + (byte) 1, (byte) 19, (byte) 138, (byte) 107, (byte) 58, (byte) 145, (byte) 17, (byte) 65, (byte) 79, + (byte) 103, (byte) 220, (byte) 234, (byte) 151, (byte) 242, (byte) 207, (byte) 206, (byte) 240, (byte) 180, + (byte) 230, (byte) 115, (byte) 150, (byte) 172, (byte) 116, (byte) 34, (byte) 231, (byte) 173, (byte) 53, + (byte) 133, (byte) 226, (byte) 249, (byte) 55, (byte) 232, (byte) 28, (byte) 117, (byte) 223, (byte) 110, + (byte) 71, (byte) 241, (byte) 26, (byte) 113, (byte) 29, (byte) 41, (byte) 197, (byte) 137, (byte) 111, + (byte) 183, (byte) 98, (byte) 14, (byte) 170, (byte) 24, (byte) 190, (byte) 27, (byte) 252, (byte) 86, + (byte) 62, (byte) 75, (byte) 198, (byte) 210, (byte) 121, (byte) 32, (byte) 154, (byte) 219, (byte) 192, + (byte) 254, (byte) 120, (byte) 205, (byte) 90, (byte) 244, (byte) 31, (byte) 221, (byte) 168, (byte) 51, + (byte) 136, (byte) 7, (byte) 199, (byte) 49, (byte) 177, (byte) 18, (byte) 16, (byte) 89, (byte) 39, + (byte) 128, (byte) 236, (byte) 95, (byte) 96, (byte) 81, (byte) 127, (byte) 169, (byte) 25, (byte) 181, + (byte) 74, (byte) 13, (byte) 45, (byte) 229, (byte) 122, (byte) 159, (byte) 147, (byte) 201, (byte) 156, + (byte) 239, (byte) 160, (byte) 224, (byte) 59, (byte) 77, (byte) 174, (byte) 42, (byte) 245, (byte) 176, + (byte) 200, (byte) 235, (byte) 187, (byte) 60, (byte) 131, (byte) 83, (byte) 153, (byte) 97, (byte) 23, + (byte) 43, (byte) 4, (byte) 126, (byte) 186, (byte) 119, (byte) 214, (byte) 38, (byte) 225, (byte) 105, + (byte) 20, (byte) 99, (byte) 85, (byte) 33, (byte) 12, (byte) 125, + }; + + // vector used in calculating key schedule (powers of x in GF(256)) + private static final int[] rcon = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, + 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 + }; + + // precomputation tables of calculations for rounds + private static final int[] T0 = { 0xa56363c6, 0x847c7cf8, 0x997777ee, 0x8d7b7bf6, 0x0df2f2ff, 0xbd6b6bd6, + 0xb16f6fde, 0x54c5c591, 0x50303060, 0x03010102, 0xa96767ce, 0x7d2b2b56, 0x19fefee7, 0x62d7d7b5, 0xe6abab4d, + 0x9a7676ec, 0x45caca8f, 0x9d82821f, 0x40c9c989, 0x877d7dfa, 0x15fafaef, 0xeb5959b2, 0xc947478e, 0x0bf0f0fb, + 0xecadad41, 0x67d4d4b3, 0xfda2a25f, 0xeaafaf45, 0xbf9c9c23, 0xf7a4a453, 0x967272e4, 0x5bc0c09b, 0xc2b7b775, + 0x1cfdfde1, 0xae93933d, 0x6a26264c, 0x5a36366c, 0x413f3f7e, 0x02f7f7f5, 0x4fcccc83, 0x5c343468, 0xf4a5a551, + 0x34e5e5d1, 0x08f1f1f9, 0x937171e2, 0x73d8d8ab, 0x53313162, 0x3f15152a, 0x0c040408, 0x52c7c795, 0x65232346, + 0x5ec3c39d, 0x28181830, 0xa1969637, 0x0f05050a, 0xb59a9a2f, 0x0907070e, 0x36121224, 0x9b80801b, 0x3de2e2df, + 0x26ebebcd, 0x6927274e, 0xcdb2b27f, 0x9f7575ea, 0x1b090912, 0x9e83831d, 0x742c2c58, 0x2e1a1a34, 0x2d1b1b36, + 0xb26e6edc, 0xee5a5ab4, 0xfba0a05b, 0xf65252a4, 0x4d3b3b76, 0x61d6d6b7, 0xceb3b37d, 0x7b292952, 0x3ee3e3dd, + 0x712f2f5e, 0x97848413, 0xf55353a6, 0x68d1d1b9, 0x00000000, 0x2cededc1, 0x60202040, 0x1ffcfce3, 0xc8b1b179, + 0xed5b5bb6, 0xbe6a6ad4, 0x46cbcb8d, 0xd9bebe67, 0x4b393972, 0xde4a4a94, 0xd44c4c98, 0xe85858b0, 0x4acfcf85, + 0x6bd0d0bb, 0x2aefefc5, 0xe5aaaa4f, 0x16fbfbed, 0xc5434386, 0xd74d4d9a, 0x55333366, 0x94858511, 0xcf45458a, + 0x10f9f9e9, 0x06020204, 0x817f7ffe, 0xf05050a0, 0x443c3c78, 0xba9f9f25, 0xe3a8a84b, 0xf35151a2, 0xfea3a35d, + 0xc0404080, 0x8a8f8f05, 0xad92923f, 0xbc9d9d21, 0x48383870, 0x04f5f5f1, 0xdfbcbc63, 0xc1b6b677, 0x75dadaaf, + 0x63212142, 0x30101020, 0x1affffe5, 0x0ef3f3fd, 0x6dd2d2bf, 0x4ccdcd81, 0x140c0c18, 0x35131326, 0x2fececc3, + 0xe15f5fbe, 0xa2979735, 0xcc444488, 0x3917172e, 0x57c4c493, 0xf2a7a755, 0x827e7efc, 0x473d3d7a, 0xac6464c8, + 0xe75d5dba, 0x2b191932, 0x957373e6, 0xa06060c0, 0x98818119, 0xd14f4f9e, 0x7fdcdca3, 0x66222244, 0x7e2a2a54, + 0xab90903b, 0x8388880b, 0xca46468c, 0x29eeeec7, 0xd3b8b86b, 0x3c141428, 0x79dedea7, 0xe25e5ebc, 0x1d0b0b16, + 0x76dbdbad, 0x3be0e0db, 0x56323264, 0x4e3a3a74, 0x1e0a0a14, 0xdb494992, 0x0a06060c, 0x6c242448, 0xe45c5cb8, + 0x5dc2c29f, 0x6ed3d3bd, 0xefacac43, 0xa66262c4, 0xa8919139, 0xa4959531, 0x37e4e4d3, 0x8b7979f2, 0x32e7e7d5, + 0x43c8c88b, 0x5937376e, 0xb76d6dda, 0x8c8d8d01, 0x64d5d5b1, 0xd24e4e9c, 0xe0a9a949, 0xb46c6cd8, 0xfa5656ac, + 0x07f4f4f3, 0x25eaeacf, 0xaf6565ca, 0x8e7a7af4, 0xe9aeae47, 0x18080810, 0xd5baba6f, 0x887878f0, 0x6f25254a, + 0x722e2e5c, 0x241c1c38, 0xf1a6a657, 0xc7b4b473, 0x51c6c697, 0x23e8e8cb, 0x7cdddda1, 0x9c7474e8, 0x211f1f3e, + 0xdd4b4b96, 0xdcbdbd61, 0x868b8b0d, 0x858a8a0f, 0x907070e0, 0x423e3e7c, 0xc4b5b571, 0xaa6666cc, 0xd8484890, + 0x05030306, 0x01f6f6f7, 0x120e0e1c, 0xa36161c2, 0x5f35356a, 0xf95757ae, 0xd0b9b969, 0x91868617, 0x58c1c199, + 0x271d1d3a, 0xb99e9e27, 0x38e1e1d9, 0x13f8f8eb, 0xb398982b, 0x33111122, 0xbb6969d2, 0x70d9d9a9, 0x898e8e07, + 0xa7949433, 0xb69b9b2d, 0x221e1e3c, 0x92878715, 0x20e9e9c9, 0x49cece87, 0xff5555aa, 0x78282850, 0x7adfdfa5, + 0x8f8c8c03, 0xf8a1a159, 0x80898909, 0x170d0d1a, 0xdabfbf65, 0x31e6e6d7, 0xc6424284, 0xb86868d0, 0xc3414182, + 0xb0999929, 0x772d2d5a, 0x110f0f1e, 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d, 0x3a16162c + }; + + private static final int[] T1 = { 0x6363c6a5, 0x7c7cf884, 0x7777ee99, 0x7b7bf68d, 0xf2f2ff0d, 0x6b6bd6bd, + 0x6f6fdeb1, 0xc5c59154, 0x30306050, 0x01010203, 0x6767cea9, 0x2b2b567d, 0xfefee719, 0xd7d7b562, 0xabab4de6, + 0x7676ec9a, 0xcaca8f45, 0x82821f9d, 0xc9c98940, 0x7d7dfa87, 0xfafaef15, 0x5959b2eb, 0x47478ec9, 0xf0f0fb0b, + 0xadad41ec, 0xd4d4b367, 0xa2a25ffd, 0xafaf45ea, 0x9c9c23bf, 0xa4a453f7, 0x7272e496, 0xc0c09b5b, 0xb7b775c2, + 0xfdfde11c, 0x93933dae, 0x26264c6a, 0x36366c5a, 0x3f3f7e41, 0xf7f7f502, 0xcccc834f, 0x3434685c, 0xa5a551f4, + 0xe5e5d134, 0xf1f1f908, 0x7171e293, 0xd8d8ab73, 0x31316253, 0x15152a3f, 0x0404080c, 0xc7c79552, 0x23234665, + 0xc3c39d5e, 0x18183028, 0x969637a1, 0x05050a0f, 0x9a9a2fb5, 0x07070e09, 0x12122436, 0x80801b9b, 0xe2e2df3d, + 0xebebcd26, 0x27274e69, 0xb2b27fcd, 0x7575ea9f, 0x0909121b, 0x83831d9e, 0x2c2c5874, 0x1a1a342e, 0x1b1b362d, + 0x6e6edcb2, 0x5a5ab4ee, 0xa0a05bfb, 0x5252a4f6, 0x3b3b764d, 0xd6d6b761, 0xb3b37dce, 0x2929527b, 0xe3e3dd3e, + 0x2f2f5e71, 0x84841397, 0x5353a6f5, 0xd1d1b968, 0x00000000, 0xededc12c, 0x20204060, 0xfcfce31f, 0xb1b179c8, + 0x5b5bb6ed, 0x6a6ad4be, 0xcbcb8d46, 0xbebe67d9, 0x3939724b, 0x4a4a94de, 0x4c4c98d4, 0x5858b0e8, 0xcfcf854a, + 0xd0d0bb6b, 0xefefc52a, 0xaaaa4fe5, 0xfbfbed16, 0x434386c5, 0x4d4d9ad7, 0x33336655, 0x85851194, 0x45458acf, + 0xf9f9e910, 0x02020406, 0x7f7ffe81, 0x5050a0f0, 0x3c3c7844, 0x9f9f25ba, 0xa8a84be3, 0x5151a2f3, 0xa3a35dfe, + 0x404080c0, 0x8f8f058a, 0x92923fad, 0x9d9d21bc, 0x38387048, 0xf5f5f104, 0xbcbc63df, 0xb6b677c1, 0xdadaaf75, + 0x21214263, 0x10102030, 0xffffe51a, 0xf3f3fd0e, 0xd2d2bf6d, 0xcdcd814c, 0x0c0c1814, 0x13132635, 0xececc32f, + 0x5f5fbee1, 0x979735a2, 0x444488cc, 0x17172e39, 0xc4c49357, 0xa7a755f2, 0x7e7efc82, 0x3d3d7a47, 0x6464c8ac, + 0x5d5dbae7, 0x1919322b, 0x7373e695, 0x6060c0a0, 0x81811998, 0x4f4f9ed1, 0xdcdca37f, 0x22224466, 0x2a2a547e, + 0x90903bab, 0x88880b83, 0x46468cca, 0xeeeec729, 0xb8b86bd3, 0x1414283c, 0xdedea779, 0x5e5ebce2, 0x0b0b161d, + 0xdbdbad76, 0xe0e0db3b, 0x32326456, 0x3a3a744e, 0x0a0a141e, 0x494992db, 0x06060c0a, 0x2424486c, 0x5c5cb8e4, + 0xc2c29f5d, 0xd3d3bd6e, 0xacac43ef, 0x6262c4a6, 0x919139a8, 0x959531a4, 0xe4e4d337, 0x7979f28b, 0xe7e7d532, + 0xc8c88b43, 0x37376e59, 0x6d6ddab7, 0x8d8d018c, 0xd5d5b164, 0x4e4e9cd2, 0xa9a949e0, 0x6c6cd8b4, 0x5656acfa, + 0xf4f4f307, 0xeaeacf25, 0x6565caaf, 0x7a7af48e, 0xaeae47e9, 0x08081018, 0xbaba6fd5, 0x7878f088, 0x25254a6f, + 0x2e2e5c72, 0x1c1c3824, 0xa6a657f1, 0xb4b473c7, 0xc6c69751, 0xe8e8cb23, 0xdddda17c, 0x7474e89c, 0x1f1f3e21, + 0x4b4b96dd, 0xbdbd61dc, 0x8b8b0d86, 0x8a8a0f85, 0x7070e090, 0x3e3e7c42, 0xb5b571c4, 0x6666ccaa, 0x484890d8, + 0x03030605, 0xf6f6f701, 0x0e0e1c12, 0x6161c2a3, 0x35356a5f, 0x5757aef9, 0xb9b969d0, 0x86861791, 0xc1c19958, + 0x1d1d3a27, 0x9e9e27b9, 0xe1e1d938, 0xf8f8eb13, 0x98982bb3, 0x11112233, 0x6969d2bb, 0xd9d9a970, 0x8e8e0789, + 0x949433a7, 0x9b9b2db6, 0x1e1e3c22, 0x87871592, 0xe9e9c920, 0xcece8749, 0x5555aaff, 0x28285078, 0xdfdfa57a, + 0x8c8c038f, 0xa1a159f8, 0x89890980, 0x0d0d1a17, 0xbfbf65da, 0xe6e6d731, 0x424284c6, 0x6868d0b8, 0x414182c3, + 0x999929b0, 0x2d2d5a77, 0x0f0f1e11, 0xb0b07bcb, 0x5454a8fc, 0xbbbb6dd6, 0x16162c3a + }; + + private static final int[] T2 = { 0x63c6a563, 0x7cf8847c, 0x77ee9977, 0x7bf68d7b, 0xf2ff0df2, 0x6bd6bd6b, + 0x6fdeb16f, 0xc59154c5, 0x30605030, 0x01020301, 0x67cea967, 0x2b567d2b, 0xfee719fe, 0xd7b562d7, 0xab4de6ab, + 0x76ec9a76, 0xca8f45ca, 0x821f9d82, 0xc98940c9, 0x7dfa877d, 0xfaef15fa, 0x59b2eb59, 0x478ec947, 0xf0fb0bf0, + 0xad41ecad, 0xd4b367d4, 0xa25ffda2, 0xaf45eaaf, 0x9c23bf9c, 0xa453f7a4, 0x72e49672, 0xc09b5bc0, 0xb775c2b7, + 0xfde11cfd, 0x933dae93, 0x264c6a26, 0x366c5a36, 0x3f7e413f, 0xf7f502f7, 0xcc834fcc, 0x34685c34, 0xa551f4a5, + 0xe5d134e5, 0xf1f908f1, 0x71e29371, 0xd8ab73d8, 0x31625331, 0x152a3f15, 0x04080c04, 0xc79552c7, 0x23466523, + 0xc39d5ec3, 0x18302818, 0x9637a196, 0x050a0f05, 0x9a2fb59a, 0x070e0907, 0x12243612, 0x801b9b80, 0xe2df3de2, + 0xebcd26eb, 0x274e6927, 0xb27fcdb2, 0x75ea9f75, 0x09121b09, 0x831d9e83, 0x2c58742c, 0x1a342e1a, 0x1b362d1b, + 0x6edcb26e, 0x5ab4ee5a, 0xa05bfba0, 0x52a4f652, 0x3b764d3b, 0xd6b761d6, 0xb37dceb3, 0x29527b29, 0xe3dd3ee3, + 0x2f5e712f, 0x84139784, 0x53a6f553, 0xd1b968d1, 0x00000000, 0xedc12ced, 0x20406020, 0xfce31ffc, 0xb179c8b1, + 0x5bb6ed5b, 0x6ad4be6a, 0xcb8d46cb, 0xbe67d9be, 0x39724b39, 0x4a94de4a, 0x4c98d44c, 0x58b0e858, 0xcf854acf, + 0xd0bb6bd0, 0xefc52aef, 0xaa4fe5aa, 0xfbed16fb, 0x4386c543, 0x4d9ad74d, 0x33665533, 0x85119485, 0x458acf45, + 0xf9e910f9, 0x02040602, 0x7ffe817f, 0x50a0f050, 0x3c78443c, 0x9f25ba9f, 0xa84be3a8, 0x51a2f351, 0xa35dfea3, + 0x4080c040, 0x8f058a8f, 0x923fad92, 0x9d21bc9d, 0x38704838, 0xf5f104f5, 0xbc63dfbc, 0xb677c1b6, 0xdaaf75da, + 0x21426321, 0x10203010, 0xffe51aff, 0xf3fd0ef3, 0xd2bf6dd2, 0xcd814ccd, 0x0c18140c, 0x13263513, 0xecc32fec, + 0x5fbee15f, 0x9735a297, 0x4488cc44, 0x172e3917, 0xc49357c4, 0xa755f2a7, 0x7efc827e, 0x3d7a473d, 0x64c8ac64, + 0x5dbae75d, 0x19322b19, 0x73e69573, 0x60c0a060, 0x81199881, 0x4f9ed14f, 0xdca37fdc, 0x22446622, 0x2a547e2a, + 0x903bab90, 0x880b8388, 0x468cca46, 0xeec729ee, 0xb86bd3b8, 0x14283c14, 0xdea779de, 0x5ebce25e, 0x0b161d0b, + 0xdbad76db, 0xe0db3be0, 0x32645632, 0x3a744e3a, 0x0a141e0a, 0x4992db49, 0x060c0a06, 0x24486c24, 0x5cb8e45c, + 0xc29f5dc2, 0xd3bd6ed3, 0xac43efac, 0x62c4a662, 0x9139a891, 0x9531a495, 0xe4d337e4, 0x79f28b79, 0xe7d532e7, + 0xc88b43c8, 0x376e5937, 0x6ddab76d, 0x8d018c8d, 0xd5b164d5, 0x4e9cd24e, 0xa949e0a9, 0x6cd8b46c, 0x56acfa56, + 0xf4f307f4, 0xeacf25ea, 0x65caaf65, 0x7af48e7a, 0xae47e9ae, 0x08101808, 0xba6fd5ba, 0x78f08878, 0x254a6f25, + 0x2e5c722e, 0x1c38241c, 0xa657f1a6, 0xb473c7b4, 0xc69751c6, 0xe8cb23e8, 0xdda17cdd, 0x74e89c74, 0x1f3e211f, + 0x4b96dd4b, 0xbd61dcbd, 0x8b0d868b, 0x8a0f858a, 0x70e09070, 0x3e7c423e, 0xb571c4b5, 0x66ccaa66, 0x4890d848, + 0x03060503, 0xf6f701f6, 0x0e1c120e, 0x61c2a361, 0x356a5f35, 0x57aef957, 0xb969d0b9, 0x86179186, 0xc19958c1, + 0x1d3a271d, 0x9e27b99e, 0xe1d938e1, 0xf8eb13f8, 0x982bb398, 0x11223311, 0x69d2bb69, 0xd9a970d9, 0x8e07898e, + 0x9433a794, 0x9b2db69b, 0x1e3c221e, 0x87159287, 0xe9c920e9, 0xce8749ce, 0x55aaff55, 0x28507828, 0xdfa57adf, + 0x8c038f8c, 0xa159f8a1, 0x89098089, 0x0d1a170d, 0xbf65dabf, 0xe6d731e6, 0x4284c642, 0x68d0b868, 0x4182c341, + 0x9929b099, 0x2d5a772d, 0x0f1e110f, 0xb07bcbb0, 0x54a8fc54, 0xbb6dd6bb, 0x162c3a16 + }; + + private static final int[] T3 = { 0xc6a56363, 0xf8847c7c, 0xee997777, 0xf68d7b7b, 0xff0df2f2, 0xd6bd6b6b, + 0xdeb16f6f, 0x9154c5c5, 0x60503030, 0x02030101, 0xcea96767, 0x567d2b2b, 0xe719fefe, 0xb562d7d7, 0x4de6abab, + 0xec9a7676, 0x8f45caca, 0x1f9d8282, 0x8940c9c9, 0xfa877d7d, 0xef15fafa, 0xb2eb5959, 0x8ec94747, 0xfb0bf0f0, + 0x41ecadad, 0xb367d4d4, 0x5ffda2a2, 0x45eaafaf, 0x23bf9c9c, 0x53f7a4a4, 0xe4967272, 0x9b5bc0c0, 0x75c2b7b7, + 0xe11cfdfd, 0x3dae9393, 0x4c6a2626, 0x6c5a3636, 0x7e413f3f, 0xf502f7f7, 0x834fcccc, 0x685c3434, 0x51f4a5a5, + 0xd134e5e5, 0xf908f1f1, 0xe2937171, 0xab73d8d8, 0x62533131, 0x2a3f1515, 0x080c0404, 0x9552c7c7, 0x46652323, + 0x9d5ec3c3, 0x30281818, 0x37a19696, 0x0a0f0505, 0x2fb59a9a, 0x0e090707, 0x24361212, 0x1b9b8080, 0xdf3de2e2, + 0xcd26ebeb, 0x4e692727, 0x7fcdb2b2, 0xea9f7575, 0x121b0909, 0x1d9e8383, 0x58742c2c, 0x342e1a1a, 0x362d1b1b, + 0xdcb26e6e, 0xb4ee5a5a, 0x5bfba0a0, 0xa4f65252, 0x764d3b3b, 0xb761d6d6, 0x7dceb3b3, 0x527b2929, 0xdd3ee3e3, + 0x5e712f2f, 0x13978484, 0xa6f55353, 0xb968d1d1, 0x00000000, 0xc12ceded, 0x40602020, 0xe31ffcfc, 0x79c8b1b1, + 0xb6ed5b5b, 0xd4be6a6a, 0x8d46cbcb, 0x67d9bebe, 0x724b3939, 0x94de4a4a, 0x98d44c4c, 0xb0e85858, 0x854acfcf, + 0xbb6bd0d0, 0xc52aefef, 0x4fe5aaaa, 0xed16fbfb, 0x86c54343, 0x9ad74d4d, 0x66553333, 0x11948585, 0x8acf4545, + 0xe910f9f9, 0x04060202, 0xfe817f7f, 0xa0f05050, 0x78443c3c, 0x25ba9f9f, 0x4be3a8a8, 0xa2f35151, 0x5dfea3a3, + 0x80c04040, 0x058a8f8f, 0x3fad9292, 0x21bc9d9d, 0x70483838, 0xf104f5f5, 0x63dfbcbc, 0x77c1b6b6, 0xaf75dada, + 0x42632121, 0x20301010, 0xe51affff, 0xfd0ef3f3, 0xbf6dd2d2, 0x814ccdcd, 0x18140c0c, 0x26351313, 0xc32fecec, + 0xbee15f5f, 0x35a29797, 0x88cc4444, 0x2e391717, 0x9357c4c4, 0x55f2a7a7, 0xfc827e7e, 0x7a473d3d, 0xc8ac6464, + 0xbae75d5d, 0x322b1919, 0xe6957373, 0xc0a06060, 0x19988181, 0x9ed14f4f, 0xa37fdcdc, 0x44662222, 0x547e2a2a, + 0x3bab9090, 0x0b838888, 0x8cca4646, 0xc729eeee, 0x6bd3b8b8, 0x283c1414, 0xa779dede, 0xbce25e5e, 0x161d0b0b, + 0xad76dbdb, 0xdb3be0e0, 0x64563232, 0x744e3a3a, 0x141e0a0a, 0x92db4949, 0x0c0a0606, 0x486c2424, 0xb8e45c5c, + 0x9f5dc2c2, 0xbd6ed3d3, 0x43efacac, 0xc4a66262, 0x39a89191, 0x31a49595, 0xd337e4e4, 0xf28b7979, 0xd532e7e7, + 0x8b43c8c8, 0x6e593737, 0xdab76d6d, 0x018c8d8d, 0xb164d5d5, 0x9cd24e4e, 0x49e0a9a9, 0xd8b46c6c, 0xacfa5656, + 0xf307f4f4, 0xcf25eaea, 0xcaaf6565, 0xf48e7a7a, 0x47e9aeae, 0x10180808, 0x6fd5baba, 0xf0887878, 0x4a6f2525, + 0x5c722e2e, 0x38241c1c, 0x57f1a6a6, 0x73c7b4b4, 0x9751c6c6, 0xcb23e8e8, 0xa17cdddd, 0xe89c7474, 0x3e211f1f, + 0x96dd4b4b, 0x61dcbdbd, 0x0d868b8b, 0x0f858a8a, 0xe0907070, 0x7c423e3e, 0x71c4b5b5, 0xccaa6666, 0x90d84848, + 0x06050303, 0xf701f6f6, 0x1c120e0e, 0xc2a36161, 0x6a5f3535, 0xaef95757, 0x69d0b9b9, 0x17918686, 0x9958c1c1, + 0x3a271d1d, 0x27b99e9e, 0xd938e1e1, 0xeb13f8f8, 0x2bb39898, 0x22331111, 0xd2bb6969, 0xa970d9d9, 0x07898e8e, + 0x33a79494, 0x2db69b9b, 0x3c221e1e, 0x15928787, 0xc920e9e9, 0x8749cece, 0xaaff5555, 0x50782828, 0xa57adfdf, + 0x038f8c8c, 0x59f8a1a1, 0x09808989, 0x1a170d0d, 0x65dabfbf, 0xd731e6e6, 0x84c64242, 0xd0b86868, 0x82c34141, + 0x29b09999, 0x5a772d2d, 0x1e110f0f, 0x7bcbb0b0, 0xa8fc5454, 0x6dd6bbbb, 0x2c3a1616 + }; + + private static final int[] Tinv0 = { 0x50a7f451, 0x5365417e, 0xc3a4171a, 0x965e273a, 0xcb6bab3b, 0xf1459d1f, + 0xab58faac, 0x9303e34b, 0x55fa3020, 0xf66d76ad, 0x9176cc88, 0x254c02f5, 0xfcd7e54f, 0xd7cb2ac5, 0x80443526, + 0x8fa362b5, 0x495ab1de, 0x671bba25, 0x980eea45, 0xe1c0fe5d, 0x02752fc3, 0x12f04c81, 0xa397468d, 0xc6f9d36b, + 0xe75f8f03, 0x959c9215, 0xeb7a6dbf, 0xda595295, 0x2d83bed4, 0xd3217458, 0x2969e049, 0x44c8c98e, 0x6a89c275, + 0x78798ef4, 0x6b3e5899, 0xdd71b927, 0xb64fe1be, 0x17ad88f0, 0x66ac20c9, 0xb43ace7d, 0x184adf63, 0x82311ae5, + 0x60335197, 0x457f5362, 0xe07764b1, 0x84ae6bbb, 0x1ca081fe, 0x942b08f9, 0x58684870, 0x19fd458f, 0x876cde94, + 0xb7f87b52, 0x23d373ab, 0xe2024b72, 0x578f1fe3, 0x2aab5566, 0x0728ebb2, 0x03c2b52f, 0x9a7bc586, 0xa50837d3, + 0xf2872830, 0xb2a5bf23, 0xba6a0302, 0x5c8216ed, 0x2b1ccf8a, 0x92b479a7, 0xf0f207f3, 0xa1e2694e, 0xcdf4da65, + 0xd5be0506, 0x1f6234d1, 0x8afea6c4, 0x9d532e34, 0xa055f3a2, 0x32e18a05, 0x75ebf6a4, 0x39ec830b, 0xaaef6040, + 0x069f715e, 0x51106ebd, 0xf98a213e, 0x3d06dd96, 0xae053edd, 0x46bde64d, 0xb58d5491, 0x055dc471, 0x6fd40604, + 0xff155060, 0x24fb9819, 0x97e9bdd6, 0xcc434089, 0x779ed967, 0xbd42e8b0, 0x888b8907, 0x385b19e7, 0xdbeec879, + 0x470a7ca1, 0xe90f427c, 0xc91e84f8, 0x00000000, 0x83868009, 0x48ed2b32, 0xac70111e, 0x4e725a6c, 0xfbff0efd, + 0x5638850f, 0x1ed5ae3d, 0x27392d36, 0x64d90f0a, 0x21a65c68, 0xd1545b9b, 0x3a2e3624, 0xb1670a0c, 0x0fe75793, + 0xd296eeb4, 0x9e919b1b, 0x4fc5c080, 0xa220dc61, 0x694b775a, 0x161a121c, 0x0aba93e2, 0xe52aa0c0, 0x43e0223c, + 0x1d171b12, 0x0b0d090e, 0xadc78bf2, 0xb9a8b62d, 0xc8a91e14, 0x8519f157, 0x4c0775af, 0xbbdd99ee, 0xfd607fa3, + 0x9f2601f7, 0xbcf5725c, 0xc53b6644, 0x347efb5b, 0x7629438b, 0xdcc623cb, 0x68fcedb6, 0x63f1e4b8, 0xcadc31d7, + 0x10856342, 0x40229713, 0x2011c684, 0x7d244a85, 0xf83dbbd2, 0x1132f9ae, 0x6da129c7, 0x4b2f9e1d, 0xf330b2dc, + 0xec52860d, 0xd0e3c177, 0x6c16b32b, 0x99b970a9, 0xfa489411, 0x2264e947, 0xc48cfca8, 0x1a3ff0a0, 0xd82c7d56, + 0xef903322, 0xc74e4987, 0xc1d138d9, 0xfea2ca8c, 0x360bd498, 0xcf81f5a6, 0x28de7aa5, 0x268eb7da, 0xa4bfad3f, + 0xe49d3a2c, 0x0d927850, 0x9bcc5f6a, 0x62467e54, 0xc2138df6, 0xe8b8d890, 0x5ef7392e, 0xf5afc382, 0xbe805d9f, + 0x7c93d069, 0xa92dd56f, 0xb31225cf, 0x3b99acc8, 0xa77d1810, 0x6e639ce8, 0x7bbb3bdb, 0x097826cd, 0xf418596e, + 0x01b79aec, 0xa89a4f83, 0x656e95e6, 0x7ee6ffaa, 0x08cfbc21, 0xe6e815ef, 0xd99be7ba, 0xce366f4a, 0xd4099fea, + 0xd67cb029, 0xafb2a431, 0x31233f2a, 0x3094a5c6, 0xc066a235, 0x37bc4e74, 0xa6ca82fc, 0xb0d090e0, 0x15d8a733, + 0x4a9804f1, 0xf7daec41, 0x0e50cd7f, 0x2ff69117, 0x8dd64d76, 0x4db0ef43, 0x544daacc, 0xdf0496e4, 0xe3b5d19e, + 0x1b886a4c, 0xb81f2cc1, 0x7f516546, 0x04ea5e9d, 0x5d358c01, 0x737487fa, 0x2e410bfb, 0x5a1d67b3, 0x52d2db92, + 0x335610e9, 0x1347d66d, 0x8c61d79a, 0x7a0ca137, 0x8e14f859, 0x893c13eb, 0xee27a9ce, 0x35c961b7, 0xede51ce1, + 0x3cb1477a, 0x59dfd29c, 0x3f73f255, 0x79ce1418, 0xbf37c773, 0xeacdf753, 0x5baafd5f, 0x146f3ddf, 0x86db4478, + 0x81f3afca, 0x3ec468b9, 0x2c342438, 0x5f40a3c2, 0x72c31d16, 0x0c25e2bc, 0x8b493c28, 0x41950dff, 0x7101a839, + 0xdeb30c08, 0x9ce4b4d8, 0x90c15664, 0x6184cb7b, 0x70b632d5, 0x745c6c48, 0x4257b8d0 + }; + + private static final int[] Tinv1 = { 0xa7f45150, 0x65417e53, 0xa4171ac3, 0x5e273a96, 0x6bab3bcb, 0x459d1ff1, + 0x58faacab, 0x03e34b93, 0xfa302055, 0x6d76adf6, 0x76cc8891, 0x4c02f525, 0xd7e54ffc, 0xcb2ac5d7, 0x44352680, + 0xa362b58f, 0x5ab1de49, 0x1bba2567, 0x0eea4598, 0xc0fe5de1, 0x752fc302, 0xf04c8112, 0x97468da3, 0xf9d36bc6, + 0x5f8f03e7, 0x9c921595, 0x7a6dbfeb, 0x595295da, 0x83bed42d, 0x217458d3, 0x69e04929, 0xc8c98e44, 0x89c2756a, + 0x798ef478, 0x3e58996b, 0x71b927dd, 0x4fe1beb6, 0xad88f017, 0xac20c966, 0x3ace7db4, 0x4adf6318, 0x311ae582, + 0x33519760, 0x7f536245, 0x7764b1e0, 0xae6bbb84, 0xa081fe1c, 0x2b08f994, 0x68487058, 0xfd458f19, 0x6cde9487, + 0xf87b52b7, 0xd373ab23, 0x024b72e2, 0x8f1fe357, 0xab55662a, 0x28ebb207, 0xc2b52f03, 0x7bc5869a, 0x0837d3a5, + 0x872830f2, 0xa5bf23b2, 0x6a0302ba, 0x8216ed5c, 0x1ccf8a2b, 0xb479a792, 0xf207f3f0, 0xe2694ea1, 0xf4da65cd, + 0xbe0506d5, 0x6234d11f, 0xfea6c48a, 0x532e349d, 0x55f3a2a0, 0xe18a0532, 0xebf6a475, 0xec830b39, 0xef6040aa, + 0x9f715e06, 0x106ebd51, 0x8a213ef9, 0x06dd963d, 0x053eddae, 0xbde64d46, 0x8d5491b5, 0x5dc47105, 0xd406046f, + 0x155060ff, 0xfb981924, 0xe9bdd697, 0x434089cc, 0x9ed96777, 0x42e8b0bd, 0x8b890788, 0x5b19e738, 0xeec879db, + 0x0a7ca147, 0x0f427ce9, 0x1e84f8c9, 0x00000000, 0x86800983, 0xed2b3248, 0x70111eac, 0x725a6c4e, 0xff0efdfb, + 0x38850f56, 0xd5ae3d1e, 0x392d3627, 0xd90f0a64, 0xa65c6821, 0x545b9bd1, 0x2e36243a, 0x670a0cb1, 0xe757930f, + 0x96eeb4d2, 0x919b1b9e, 0xc5c0804f, 0x20dc61a2, 0x4b775a69, 0x1a121c16, 0xba93e20a, 0x2aa0c0e5, 0xe0223c43, + 0x171b121d, 0x0d090e0b, 0xc78bf2ad, 0xa8b62db9, 0xa91e14c8, 0x19f15785, 0x0775af4c, 0xdd99eebb, 0x607fa3fd, + 0x2601f79f, 0xf5725cbc, 0x3b6644c5, 0x7efb5b34, 0x29438b76, 0xc623cbdc, 0xfcedb668, 0xf1e4b863, 0xdc31d7ca, + 0x85634210, 0x22971340, 0x11c68420, 0x244a857d, 0x3dbbd2f8, 0x32f9ae11, 0xa129c76d, 0x2f9e1d4b, 0x30b2dcf3, + 0x52860dec, 0xe3c177d0, 0x16b32b6c, 0xb970a999, 0x489411fa, 0x64e94722, 0x8cfca8c4, 0x3ff0a01a, 0x2c7d56d8, + 0x903322ef, 0x4e4987c7, 0xd138d9c1, 0xa2ca8cfe, 0x0bd49836, 0x81f5a6cf, 0xde7aa528, 0x8eb7da26, 0xbfad3fa4, + 0x9d3a2ce4, 0x9278500d, 0xcc5f6a9b, 0x467e5462, 0x138df6c2, 0xb8d890e8, 0xf7392e5e, 0xafc382f5, 0x805d9fbe, + 0x93d0697c, 0x2dd56fa9, 0x1225cfb3, 0x99acc83b, 0x7d1810a7, 0x639ce86e, 0xbb3bdb7b, 0x7826cd09, 0x18596ef4, + 0xb79aec01, 0x9a4f83a8, 0x6e95e665, 0xe6ffaa7e, 0xcfbc2108, 0xe815efe6, 0x9be7bad9, 0x366f4ace, 0x099fead4, + 0x7cb029d6, 0xb2a431af, 0x233f2a31, 0x94a5c630, 0x66a235c0, 0xbc4e7437, 0xca82fca6, 0xd090e0b0, 0xd8a73315, + 0x9804f14a, 0xdaec41f7, 0x50cd7f0e, 0xf691172f, 0xd64d768d, 0xb0ef434d, 0x4daacc54, 0x0496e4df, 0xb5d19ee3, + 0x886a4c1b, 0x1f2cc1b8, 0x5165467f, 0xea5e9d04, 0x358c015d, 0x7487fa73, 0x410bfb2e, 0x1d67b35a, 0xd2db9252, + 0x5610e933, 0x47d66d13, 0x61d79a8c, 0x0ca1377a, 0x14f8598e, 0x3c13eb89, 0x27a9ceee, 0xc961b735, 0xe51ce1ed, + 0xb1477a3c, 0xdfd29c59, 0x73f2553f, 0xce141879, 0x37c773bf, 0xcdf753ea, 0xaafd5f5b, 0x6f3ddf14, 0xdb447886, + 0xf3afca81, 0xc468b93e, 0x3424382c, 0x40a3c25f, 0xc31d1672, 0x25e2bc0c, 0x493c288b, 0x950dff41, 0x01a83971, + 0xb30c08de, 0xe4b4d89c, 0xc1566490, 0x84cb7b61, 0xb632d570, 0x5c6c4874, 0x57b8d042 + }; + + private static final int[] Tinv2 = { 0xf45150a7, 0x417e5365, 0x171ac3a4, 0x273a965e, 0xab3bcb6b, 0x9d1ff145, + 0xfaacab58, 0xe34b9303, 0x302055fa, 0x76adf66d, 0xcc889176, 0x02f5254c, 0xe54ffcd7, 0x2ac5d7cb, 0x35268044, + 0x62b58fa3, 0xb1de495a, 0xba25671b, 0xea45980e, 0xfe5de1c0, 0x2fc30275, 0x4c8112f0, 0x468da397, 0xd36bc6f9, + 0x8f03e75f, 0x9215959c, 0x6dbfeb7a, 0x5295da59, 0xbed42d83, 0x7458d321, 0xe0492969, 0xc98e44c8, 0xc2756a89, + 0x8ef47879, 0x58996b3e, 0xb927dd71, 0xe1beb64f, 0x88f017ad, 0x20c966ac, 0xce7db43a, 0xdf63184a, 0x1ae58231, + 0x51976033, 0x5362457f, 0x64b1e077, 0x6bbb84ae, 0x81fe1ca0, 0x08f9942b, 0x48705868, 0x458f19fd, 0xde94876c, + 0x7b52b7f8, 0x73ab23d3, 0x4b72e202, 0x1fe3578f, 0x55662aab, 0xebb20728, 0xb52f03c2, 0xc5869a7b, 0x37d3a508, + 0x2830f287, 0xbf23b2a5, 0x0302ba6a, 0x16ed5c82, 0xcf8a2b1c, 0x79a792b4, 0x07f3f0f2, 0x694ea1e2, 0xda65cdf4, + 0x0506d5be, 0x34d11f62, 0xa6c48afe, 0x2e349d53, 0xf3a2a055, 0x8a0532e1, 0xf6a475eb, 0x830b39ec, 0x6040aaef, + 0x715e069f, 0x6ebd5110, 0x213ef98a, 0xdd963d06, 0x3eddae05, 0xe64d46bd, 0x5491b58d, 0xc471055d, 0x06046fd4, + 0x5060ff15, 0x981924fb, 0xbdd697e9, 0x4089cc43, 0xd967779e, 0xe8b0bd42, 0x8907888b, 0x19e7385b, 0xc879dbee, + 0x7ca1470a, 0x427ce90f, 0x84f8c91e, 0x00000000, 0x80098386, 0x2b3248ed, 0x111eac70, 0x5a6c4e72, 0x0efdfbff, + 0x850f5638, 0xae3d1ed5, 0x2d362739, 0x0f0a64d9, 0x5c6821a6, 0x5b9bd154, 0x36243a2e, 0x0a0cb167, 0x57930fe7, + 0xeeb4d296, 0x9b1b9e91, 0xc0804fc5, 0xdc61a220, 0x775a694b, 0x121c161a, 0x93e20aba, 0xa0c0e52a, 0x223c43e0, + 0x1b121d17, 0x090e0b0d, 0x8bf2adc7, 0xb62db9a8, 0x1e14c8a9, 0xf1578519, 0x75af4c07, 0x99eebbdd, 0x7fa3fd60, + 0x01f79f26, 0x725cbcf5, 0x6644c53b, 0xfb5b347e, 0x438b7629, 0x23cbdcc6, 0xedb668fc, 0xe4b863f1, 0x31d7cadc, + 0x63421085, 0x97134022, 0xc6842011, 0x4a857d24, 0xbbd2f83d, 0xf9ae1132, 0x29c76da1, 0x9e1d4b2f, 0xb2dcf330, + 0x860dec52, 0xc177d0e3, 0xb32b6c16, 0x70a999b9, 0x9411fa48, 0xe9472264, 0xfca8c48c, 0xf0a01a3f, 0x7d56d82c, + 0x3322ef90, 0x4987c74e, 0x38d9c1d1, 0xca8cfea2, 0xd498360b, 0xf5a6cf81, 0x7aa528de, 0xb7da268e, 0xad3fa4bf, + 0x3a2ce49d, 0x78500d92, 0x5f6a9bcc, 0x7e546246, 0x8df6c213, 0xd890e8b8, 0x392e5ef7, 0xc382f5af, 0x5d9fbe80, + 0xd0697c93, 0xd56fa92d, 0x25cfb312, 0xacc83b99, 0x1810a77d, 0x9ce86e63, 0x3bdb7bbb, 0x26cd0978, 0x596ef418, + 0x9aec01b7, 0x4f83a89a, 0x95e6656e, 0xffaa7ee6, 0xbc2108cf, 0x15efe6e8, 0xe7bad99b, 0x6f4ace36, 0x9fead409, + 0xb029d67c, 0xa431afb2, 0x3f2a3123, 0xa5c63094, 0xa235c066, 0x4e7437bc, 0x82fca6ca, 0x90e0b0d0, 0xa73315d8, + 0x04f14a98, 0xec41f7da, 0xcd7f0e50, 0x91172ff6, 0x4d768dd6, 0xef434db0, 0xaacc544d, 0x96e4df04, 0xd19ee3b5, + 0x6a4c1b88, 0x2cc1b81f, 0x65467f51, 0x5e9d04ea, 0x8c015d35, 0x87fa7374, 0x0bfb2e41, 0x67b35a1d, 0xdb9252d2, + 0x10e93356, 0xd66d1347, 0xd79a8c61, 0xa1377a0c, 0xf8598e14, 0x13eb893c, 0xa9ceee27, 0x61b735c9, 0x1ce1ede5, + 0x477a3cb1, 0xd29c59df, 0xf2553f73, 0x141879ce, 0xc773bf37, 0xf753eacd, 0xfd5f5baa, 0x3ddf146f, 0x447886db, + 0xafca81f3, 0x68b93ec4, 0x24382c34, 0xa3c25f40, 0x1d1672c3, 0xe2bc0c25, 0x3c288b49, 0x0dff4195, 0xa8397101, + 0x0c08deb3, 0xb4d89ce4, 0x566490c1, 0xcb7b6184, 0x32d570b6, 0x6c48745c, 0xb8d04257 + }; + + private static final int[] Tinv3 = { 0x5150a7f4, 0x7e536541, 0x1ac3a417, 0x3a965e27, 0x3bcb6bab, 0x1ff1459d, + 0xacab58fa, 0x4b9303e3, 0x2055fa30, 0xadf66d76, 0x889176cc, 0xf5254c02, 0x4ffcd7e5, 0xc5d7cb2a, 0x26804435, + 0xb58fa362, 0xde495ab1, 0x25671bba, 0x45980eea, 0x5de1c0fe, 0xc302752f, 0x8112f04c, 0x8da39746, 0x6bc6f9d3, + 0x03e75f8f, 0x15959c92, 0xbfeb7a6d, 0x95da5952, 0xd42d83be, 0x58d32174, 0x492969e0, 0x8e44c8c9, 0x756a89c2, + 0xf478798e, 0x996b3e58, 0x27dd71b9, 0xbeb64fe1, 0xf017ad88, 0xc966ac20, 0x7db43ace, 0x63184adf, 0xe582311a, + 0x97603351, 0x62457f53, 0xb1e07764, 0xbb84ae6b, 0xfe1ca081, 0xf9942b08, 0x70586848, 0x8f19fd45, 0x94876cde, + 0x52b7f87b, 0xab23d373, 0x72e2024b, 0xe3578f1f, 0x662aab55, 0xb20728eb, 0x2f03c2b5, 0x869a7bc5, 0xd3a50837, + 0x30f28728, 0x23b2a5bf, 0x02ba6a03, 0xed5c8216, 0x8a2b1ccf, 0xa792b479, 0xf3f0f207, 0x4ea1e269, 0x65cdf4da, + 0x06d5be05, 0xd11f6234, 0xc48afea6, 0x349d532e, 0xa2a055f3, 0x0532e18a, 0xa475ebf6, 0x0b39ec83, 0x40aaef60, + 0x5e069f71, 0xbd51106e, 0x3ef98a21, 0x963d06dd, 0xddae053e, 0x4d46bde6, 0x91b58d54, 0x71055dc4, 0x046fd406, + 0x60ff1550, 0x1924fb98, 0xd697e9bd, 0x89cc4340, 0x67779ed9, 0xb0bd42e8, 0x07888b89, 0xe7385b19, 0x79dbeec8, + 0xa1470a7c, 0x7ce90f42, 0xf8c91e84, 0x00000000, 0x09838680, 0x3248ed2b, 0x1eac7011, 0x6c4e725a, 0xfdfbff0e, + 0x0f563885, 0x3d1ed5ae, 0x3627392d, 0x0a64d90f, 0x6821a65c, 0x9bd1545b, 0x243a2e36, 0x0cb1670a, 0x930fe757, + 0xb4d296ee, 0x1b9e919b, 0x804fc5c0, 0x61a220dc, 0x5a694b77, 0x1c161a12, 0xe20aba93, 0xc0e52aa0, 0x3c43e022, + 0x121d171b, 0x0e0b0d09, 0xf2adc78b, 0x2db9a8b6, 0x14c8a91e, 0x578519f1, 0xaf4c0775, 0xeebbdd99, 0xa3fd607f, + 0xf79f2601, 0x5cbcf572, 0x44c53b66, 0x5b347efb, 0x8b762943, 0xcbdcc623, 0xb668fced, 0xb863f1e4, 0xd7cadc31, + 0x42108563, 0x13402297, 0x842011c6, 0x857d244a, 0xd2f83dbb, 0xae1132f9, 0xc76da129, 0x1d4b2f9e, 0xdcf330b2, + 0x0dec5286, 0x77d0e3c1, 0x2b6c16b3, 0xa999b970, 0x11fa4894, 0x472264e9, 0xa8c48cfc, 0xa01a3ff0, 0x56d82c7d, + 0x22ef9033, 0x87c74e49, 0xd9c1d138, 0x8cfea2ca, 0x98360bd4, 0xa6cf81f5, 0xa528de7a, 0xda268eb7, 0x3fa4bfad, + 0x2ce49d3a, 0x500d9278, 0x6a9bcc5f, 0x5462467e, 0xf6c2138d, 0x90e8b8d8, 0x2e5ef739, 0x82f5afc3, 0x9fbe805d, + 0x697c93d0, 0x6fa92dd5, 0xcfb31225, 0xc83b99ac, 0x10a77d18, 0xe86e639c, 0xdb7bbb3b, 0xcd097826, 0x6ef41859, + 0xec01b79a, 0x83a89a4f, 0xe6656e95, 0xaa7ee6ff, 0x2108cfbc, 0xefe6e815, 0xbad99be7, 0x4ace366f, 0xead4099f, + 0x29d67cb0, 0x31afb2a4, 0x2a31233f, 0xc63094a5, 0x35c066a2, 0x7437bc4e, 0xfca6ca82, 0xe0b0d090, 0x3315d8a7, + 0xf14a9804, 0x41f7daec, 0x7f0e50cd, 0x172ff691, 0x768dd64d, 0x434db0ef, 0xcc544daa, 0xe4df0496, 0x9ee3b5d1, + 0x4c1b886a, 0xc1b81f2c, 0x467f5165, 0x9d04ea5e, 0x015d358c, 0xfa737487, 0xfb2e410b, 0xb35a1d67, 0x9252d2db, + 0xe9335610, 0x6d1347d6, 0x9a8c61d7, 0x377a0ca1, 0x598e14f8, 0xeb893c13, 0xceee27a9, 0xb735c961, 0xe1ede51c, + 0x7a3cb147, 0x9c59dfd2, 0x553f73f2, 0x1879ce14, 0x73bf37c7, 0x53eacdf7, 0x5f5baafd, 0xdf146f3d, 0x7886db44, + 0xca81f3af, 0xb93ec468, 0x382c3424, 0xc25f40a3, 0x1672c31d, 0xbc0c25e2, 0x288b493c, 0xff41950d, 0x397101a8, + 0x08deb30c, 0xd89ce4b4, 0x6490c156, 0x7b6184cb, 0xd570b632, 0x48745c6c, 0xd04257b8 + }; + + private int shift(int r, int shift) { + return (((r >>> shift) | (r << (32 - shift)))); + } + + /* multiply four bytes in GF(2^8) by 'x' {02} in parallel */ + + private static final int m1 = 0x80808080; + private static final int m2 = 0x7f7f7f7f; + private static final int m3 = 0x0000001b; + + private int FFmulX(int x) { + return (((x & m2) << 1) ^ (((x & m1) >>> 7) * m3)); + } + + /* + * The following defines provide alternative definitions of FFmulX that + * might give improved performance if a fast 32-bit multiply is not + * available. + * + * private int FFmulX(int x) { int u = x & m1; u |= (u >> 1); return ((x & + * m2) < < 1) ^ ((u >>> 3) | (u >>> 6)); } private static final int m4 = + * 0x1b1b1b1b; private int FFmulX(int x) { int u = x & m1; return ((x & m2) < < + * 1) ^ ((u - (u >>> 7)) & m4); } + * + */ + + private int inv_mcol(int x) { + int f2 = FFmulX(x); + int f4 = FFmulX(f2); + int f8 = FFmulX(f4); + int f9 = x ^ f8; + return f2 ^ f4 ^ f8 ^ shift(f2 ^ f9, 8) ^ shift(f4 ^ f9, 16) ^ shift(f9, 24); + } + + private int subWord(int x) { + return (S[x & 255] & 255 | ((S[(x >> 8) & 255] & 255) << 8) | ((S[(x >> 16) & 255] & 255) << 16) | S[(x >> 24) & 255] << 24); + } + + /** + * Calculate the necessary round keys The number of calculations depends on + * key size and block size AES specified a fixed block size of 128 bits and + * key sizes 128/192/256 bits This code is written assuming those are the + * only possible values + */ + private int[][] generateWorkingKey(byte[] key, boolean forEncryption) { + int KC = key.length / 4; // key length in words + int t; + + if (((KC != 4) && (KC != 6) && (KC != 8)) || ((KC * 4) != key.length)) { + throw new IllegalArgumentException("Key length not 128/192/256 bits."); + } + + ROUNDS = KC + 6; // This is not always true for the generalized + // Rijndael that allows larger block sizes + int[][] W = new int[ROUNDS + 1][4]; // 4 words in a block + // + // copy the key into the round key array + // + t = 0; + + for (int i = 0; i < key.length; t++) { + W[t >> 2][t & 3] = (key[i] & 0xff) | ((key[i + 1] & 0xff) << 8) | ((key[i + 2] & 0xff) << 16) + | (key[i + 3] << 24); + i += 4; + } + + // + // while not enough round key material calculated + // calculate new values + // + int k = (ROUNDS + 1) << 2; + + for (int i = KC; (i < k); i++) { + int temp = W[(i - 1) >> 2][(i - 1) & 3]; + + if ((i % KC) == 0) { + temp = subWord(shift(temp, 8)) ^ rcon[(i / KC) - 1]; + } + else if ((KC > 6) && ((i % KC) == 4)) { + temp = subWord(temp); + } + + W[i >> 2][i & 3] = W[(i - KC) >> 2][(i - KC) & 3] ^ temp; + } + + if (!forEncryption) { + for (int j = 1; j < ROUNDS; j++) { + for (int i = 0; i < 4; i++) { + W[j][i] = inv_mcol(W[j][i]); + } + } + } + + return W; + } + + private int ROUNDS; + private int[][] WorkingKey = null; + private int C0, C1, C2, C3; + private boolean doEncrypt; + + private static final int BLOCK_SIZE = 16; + + /** + * default constructor - 128 bit block size. + */ + public AES() { + } + + /** + * initialise an AES cipher. + * + * @param forEncryption + * whether or not we are for encryption. + * @param key + * the key required to set up the cipher. + * @exception IllegalArgumentException + * if the params argument is inappropriate. + */ + + public final void init(boolean forEncryption, byte[] key) { + WorkingKey = generateWorkingKey(key, forEncryption); + this.doEncrypt = forEncryption; + } + + public final String getAlgorithmName() { + return "AES"; + } + + public final int getBlockSize() { + return BLOCK_SIZE; + } + + public final int processBlock(byte[] in, int inOff, byte[] out, int outOff) { + if (WorkingKey == null) { + throw new IllegalStateException("AES engine not initialised"); + } + + if ((inOff + (32 / 2)) > in.length) { + throw new IllegalArgumentException("input buffer too short"); + } + + if ((outOff + (32 / 2)) > out.length) { + throw new IllegalArgumentException("output buffer too short"); + } + + if (doEncrypt) { + unpackBlock(in, inOff); + encryptBlock(WorkingKey); + packBlock(out, outOff); + } + else { + unpackBlock(in, inOff); + decryptBlock(WorkingKey); + packBlock(out, outOff); + } + + return BLOCK_SIZE; + } + + public final void reset() { + } + + private void unpackBlock(byte[] bytes, int off) { + int index = off; + C0 = (bytes[index++] & 0xff); + C0 |= (bytes[index++] & 0xff) << 8; + C0 |= (bytes[index++] & 0xff) << 16; + C0 |= bytes[index++] << 24; + C1 = (bytes[index++] & 0xff); + C1 |= (bytes[index++] & 0xff) << 8; + C1 |= (bytes[index++] & 0xff) << 16; + C1 |= bytes[index++] << 24; + C2 = (bytes[index++] & 0xff); + C2 |= (bytes[index++] & 0xff) << 8; + C2 |= (bytes[index++] & 0xff) << 16; + C2 |= bytes[index++] << 24; + C3 = (bytes[index++] & 0xff); + C3 |= (bytes[index++] & 0xff) << 8; + C3 |= (bytes[index++] & 0xff) << 16; + C3 |= bytes[index++] << 24; + } + + private void packBlock(byte[] bytes, int off) { + int index = off; + bytes[index++] = (byte) C0; + bytes[index++] = (byte)(C0 >> 8); + bytes[index++] = (byte)(C0 >> 16); + bytes[index++] = (byte)(C0 >> 24); + bytes[index++] = (byte) C1; + bytes[index++] = (byte)(C1 >> 8); + bytes[index++] = (byte)(C1 >> 16); + bytes[index++] = (byte)(C1 >> 24); + bytes[index++] = (byte) C2; + bytes[index++] = (byte)(C2 >> 8); + bytes[index++] = (byte)(C2 >> 16); + bytes[index++] = (byte)(C2 >> 24); + bytes[index++] = (byte) C3; + bytes[index++] = (byte)(C3 >> 8); + bytes[index++] = (byte)(C3 >> 16); + bytes[index++] = (byte)(C3 >> 24); + } + + private void encryptBlock(int[][] KW) { + int r, r0, r1, r2, r3; + C0 ^= KW[0][0]; + C1 ^= KW[0][1]; + C2 ^= KW[0][2]; + C3 ^= KW[0][3]; + + for (r = 1; r < ROUNDS - 1;) { + r0 = T0[C0 & 255] ^ T1[(C1 >> 8) & 255] ^ T2[(C2 >> 16) & 255] ^ T3[(C3 >> 24) & 255] ^ KW[r][0]; + r1 = T0[C1 & 255] ^ T1[(C2 >> 8) & 255] ^ T2[(C3 >> 16) & 255] ^ T3[(C0 >> 24) & 255] ^ KW[r][1]; + r2 = T0[C2 & 255] ^ T1[(C3 >> 8) & 255] ^ T2[(C0 >> 16) & 255] ^ T3[(C1 >> 24) & 255] ^ KW[r][2]; + r3 = T0[C3 & 255] ^ T1[(C0 >> 8) & 255] ^ T2[(C1 >> 16) & 255] ^ T3[(C2 >> 24) & 255] ^ KW[r++][3]; + C0 = T0[r0 & 255] ^ T1[(r1 >> 8) & 255] ^ T2[(r2 >> 16) & 255] ^ T3[(r3 >> 24) & 255] ^ KW[r][0]; + C1 = T0[r1 & 255] ^ T1[(r2 >> 8) & 255] ^ T2[(r3 >> 16) & 255] ^ T3[(r0 >> 24) & 255] ^ KW[r][1]; + C2 = T0[r2 & 255] ^ T1[(r3 >> 8) & 255] ^ T2[(r0 >> 16) & 255] ^ T3[(r1 >> 24) & 255] ^ KW[r][2]; + C3 = T0[r3 & 255] ^ T1[(r0 >> 8) & 255] ^ T2[(r1 >> 16) & 255] ^ T3[(r2 >> 24) & 255] ^ KW[r++][3]; + } + + r0 = T0[C0 & 255] ^ T1[(C1 >> 8) & 255] ^ T2[(C2 >> 16) & 255] ^ T3[(C3 >> 24) & 255] ^ KW[r][0]; + r1 = T0[C1 & 255] ^ T1[(C2 >> 8) & 255] ^ T2[(C3 >> 16) & 255] ^ T3[(C0 >> 24) & 255] ^ KW[r][1]; + r2 = T0[C2 & 255] ^ T1[(C3 >> 8) & 255] ^ T2[(C0 >> 16) & 255] ^ T3[(C1 >> 24) & 255] ^ KW[r][2]; + r3 = T0[C3 & 255] ^ T1[(C0 >> 8) & 255] ^ T2[(C1 >> 16) & 255] ^ T3[(C2 >> 24) & 255] ^ KW[r++][3]; + // the final round's table is a simple function of S so we don't use a + // whole other four tables for it + C0 = (S[r0 & 255] & 255) ^ ((S[(r1 >> 8) & 255] & 255) << 8) ^ ((S[(r2 >> 16) & 255] & 255) << 16) + ^ (S[(r3 >> 24) & 255] << 24) ^ KW[r][0]; + C1 = (S[r1 & 255] & 255) ^ ((S[(r2 >> 8) & 255] & 255) << 8) ^ ((S[(r3 >> 16) & 255] & 255) << 16) + ^ (S[(r0 >> 24) & 255] << 24) ^ KW[r][1]; + C2 = (S[r2 & 255] & 255) ^ ((S[(r3 >> 8) & 255] & 255) << 8) ^ ((S[(r0 >> 16) & 255] & 255) << 16) + ^ (S[(r1 >> 24) & 255] << 24) ^ KW[r][2]; + C3 = (S[r3 & 255] & 255) ^ ((S[(r0 >> 8) & 255] & 255) << 8) ^ ((S[(r1 >> 16) & 255] & 255) << 16) + ^ (S[(r2 >> 24) & 255] << 24) ^ KW[r][3]; + } + + private void decryptBlock(int[][] KW) { + int r, r0, r1, r2, r3; + C0 ^= KW[ROUNDS][0]; + C1 ^= KW[ROUNDS][1]; + C2 ^= KW[ROUNDS][2]; + C3 ^= KW[ROUNDS][3]; + + for (r = ROUNDS - 1; r > 1;) { + r0 = Tinv0[C0 & 255] ^ Tinv1[(C3 >> 8) & 255] ^ Tinv2[(C2 >> 16) & 255] ^ Tinv3[(C1 >> 24) & 255] + ^ KW[r][0]; + r1 = Tinv0[C1 & 255] ^ Tinv1[(C0 >> 8) & 255] ^ Tinv2[(C3 >> 16) & 255] ^ Tinv3[(C2 >> 24) & 255] + ^ KW[r][1]; + r2 = Tinv0[C2 & 255] ^ Tinv1[(C1 >> 8) & 255] ^ Tinv2[(C0 >> 16) & 255] ^ Tinv3[(C3 >> 24) & 255] + ^ KW[r][2]; + r3 = Tinv0[C3 & 255] ^ Tinv1[(C2 >> 8) & 255] ^ Tinv2[(C1 >> 16) & 255] ^ Tinv3[(C0 >> 24) & 255] + ^ KW[r--][3]; + C0 = Tinv0[r0 & 255] ^ Tinv1[(r3 >> 8) & 255] ^ Tinv2[(r2 >> 16) & 255] ^ Tinv3[(r1 >> 24) & 255] + ^ KW[r][0]; + C1 = Tinv0[r1 & 255] ^ Tinv1[(r0 >> 8) & 255] ^ Tinv2[(r3 >> 16) & 255] ^ Tinv3[(r2 >> 24) & 255] + ^ KW[r][1]; + C2 = Tinv0[r2 & 255] ^ Tinv1[(r1 >> 8) & 255] ^ Tinv2[(r0 >> 16) & 255] ^ Tinv3[(r3 >> 24) & 255] + ^ KW[r][2]; + C3 = Tinv0[r3 & 255] ^ Tinv1[(r2 >> 8) & 255] ^ Tinv2[(r1 >> 16) & 255] ^ Tinv3[(r0 >> 24) & 255] + ^ KW[r--][3]; + } + + r0 = Tinv0[C0 & 255] ^ Tinv1[(C3 >> 8) & 255] ^ Tinv2[(C2 >> 16) & 255] ^ Tinv3[(C1 >> 24) & 255] ^ KW[r][0]; + r1 = Tinv0[C1 & 255] ^ Tinv1[(C0 >> 8) & 255] ^ Tinv2[(C3 >> 16) & 255] ^ Tinv3[(C2 >> 24) & 255] ^ KW[r][1]; + r2 = Tinv0[C2 & 255] ^ Tinv1[(C1 >> 8) & 255] ^ Tinv2[(C0 >> 16) & 255] ^ Tinv3[(C3 >> 24) & 255] ^ KW[r][2]; + r3 = Tinv0[C3 & 255] ^ Tinv1[(C2 >> 8) & 255] ^ Tinv2[(C1 >> 16) & 255] ^ Tinv3[(C0 >> 24) & 255] ^ KW[r--][3]; + // the final round's table is a simple function of Si so we don't use a + // whole other four tables for it + C0 = (Si[r0 & 255] & 255) ^ ((Si[(r3 >> 8) & 255] & 255) << 8) ^ ((Si[(r2 >> 16) & 255] & 255) << 16) + ^ (Si[(r1 >> 24) & 255] << 24) ^ KW[0][0]; + C1 = (Si[r1 & 255] & 255) ^ ((Si[(r0 >> 8) & 255] & 255) << 8) ^ ((Si[(r3 >> 16) & 255] & 255) << 16) + ^ (Si[(r2 >> 24) & 255] << 24) ^ KW[0][1]; + C2 = (Si[r2 & 255] & 255) ^ ((Si[(r1 >> 8) & 255] & 255) << 8) ^ ((Si[(r0 >> 16) & 255] & 255) << 16) + ^ (Si[(r3 >> 24) & 255] << 24) ^ KW[0][2]; + C3 = (Si[r3 & 255] & 255) ^ ((Si[(r2 >> 8) & 255] & 255) << 8) ^ ((Si[(r1 >> 16) & 255] & 255) << 16) + ^ (Si[(r0 >> 24) & 255] << 24) ^ KW[0][3]; + } + + public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { + processBlock(src, srcoff, dst, dstoff); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/cipher/BlockCipher.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/cipher/BlockCipher.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.cipher; + +/** + * BlockCipher. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public interface BlockCipher { + public void init(boolean forEncryption, byte[] key); + + public int getBlockSize(); + + public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff); +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/cipher/BlockCipherFactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/cipher/BlockCipherFactory.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.cipher; + +import java.util.ArrayList; +import java.util.List; +import java.util.Vector; + +/** + * BlockCipherFactory. + * + * @author Christian Plattner + * @version $Id: BlockCipherFactory.java 86 2014-04-07 14:15:18Z dkocher@sudo.ch $ + */ +public class BlockCipherFactory { + private static final class CipherEntry { + String type; + int blocksize; + int keysize; + String cipherClass; + + public CipherEntry(String type, int blockSize, int keySize, String cipherClass) { + this.type = type; + this.blocksize = blockSize; + this.keysize = keySize; + this.cipherClass = cipherClass; + } + } + + private static final List ciphers = new ArrayList(); + + static { + // Higher priority (stronger) first + ciphers.add(new CipherEntry("aes256-ctr", 16, 32, "ch.ethz.ssh2.crypto.cipher.AES")); + ciphers.add(new CipherEntry("aes192-ctr", 16, 24, "ch.ethz.ssh2.crypto.cipher.AES")); + ciphers.add(new CipherEntry("aes128-ctr", 16, 16, "ch.ethz.ssh2.crypto.cipher.AES")); + ciphers.add(new CipherEntry("blowfish-ctr", 8, 16, "ch.ethz.ssh2.crypto.cipher.BlowFish")); + ciphers.add(new CipherEntry("aes256-cbc", 16, 32, "ch.ethz.ssh2.crypto.cipher.AES")); + ciphers.add(new CipherEntry("aes192-cbc", 16, 24, "ch.ethz.ssh2.crypto.cipher.AES")); + ciphers.add(new CipherEntry("aes128-cbc", 16, 16, "ch.ethz.ssh2.crypto.cipher.AES")); + ciphers.add(new CipherEntry("blowfish-cbc", 8, 16, "ch.ethz.ssh2.crypto.cipher.BlowFish")); + ciphers.add(new CipherEntry("3des-ctr", 8, 24, "ch.ethz.ssh2.crypto.cipher.DESede")); + ciphers.add(new CipherEntry("3des-cbc", 8, 24, "ch.ethz.ssh2.crypto.cipher.DESede")); + } + + public static String[] getDefaultCipherList() { + List list = new ArrayList(ciphers.size()); + + for (CipherEntry ce : ciphers) { + list.add(ce.type); + } + + return list.toArray(new String[ciphers.size()]); + } + + public static void checkCipherList(String[] cipherCandidates) { + for (String cipherCandidate : cipherCandidates) { + getEntry(cipherCandidate); + } + } + + // @SuppressWarnings("rawtypes") + public static BlockCipher createCipher(String type, boolean encrypt, byte[] key, byte[] iv) { + try { + CipherEntry ce = getEntry(type); + Class cc = Class.forName(ce.cipherClass); + BlockCipher bc = (BlockCipher) cc.newInstance(); + + if (type.endsWith("-cbc")) { + bc.init(encrypt, key); + return new CBCMode(bc, iv, encrypt); + } + else if (type.endsWith("-ctr")) { + bc.init(true, key); + return new CTRMode(bc, iv, encrypt); + } + + throw new IllegalArgumentException("Cannot instantiate " + type); + } + catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Cannot instantiate " + type, e); + } + catch (InstantiationException e) { + throw new IllegalArgumentException("Cannot instantiate " + type, e); + } + catch (IllegalAccessException e) { + throw new IllegalArgumentException("Cannot instantiate " + type, e); + } + } + + private static CipherEntry getEntry(String type) { + for (CipherEntry ce : ciphers) { + if (ce.type.equals(type)) { + return ce; + } + } + + throw new IllegalArgumentException("Unkown algorithm " + type); + } + + public static int getBlockSize(String type) { + CipherEntry ce = getEntry(type); + return ce.blocksize; + } + + public static int getKeySize(String type) { + CipherEntry ce = getEntry(type); + return ce.keysize; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/cipher/BlowFish.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/cipher/BlowFish.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,376 @@ +package ch.ethz.ssh2.crypto.cipher; + +/* + This file was shamelessly taken from the Bouncy Castle Crypto package. + Their licence file states the following: + + Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle + (http://www.bouncycastle.org) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +/** + * A class that provides Blowfish key encryption operations, such as encoding + * data and generating keys. All the algorithms herein are from Applied + * Cryptography and implement a simplified cryptography interface. + * + * @author See comments in the source file + * @version 2.50, 03/15/10 + */ +public class BlowFish implements BlockCipher { + + private final static int[] KP = { 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0, + 0x082EFA98, 0xEC4E6C89, 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, + 0xB5470917, 0x9216D5D9, 0x8979FB1B + }, + + KS0 = { 0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, 0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99, 0x24A19947, + 0xB3916CF7, 0x0801F2E2, 0x858EFC16, 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, 0x0D95748F, 0x728EB658, + 0x718BCD58, 0x82154AEE, 0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013, 0xC5D1B023, 0x286085F0, 0xCA417918, + 0xB8DB38EF, 0x8E79DCB0, 0x603A180E, 0x6C9E0E8B, 0xB01E8A3E, 0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60, + 0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440, 0x55CA396A, 0x2AAB10B6, 0xB4CC5C34, 0x1141E8CE, 0xA15486AF, + 0x7C72E993, 0xB3EE1411, 0x636FBC2A, 0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E, 0xAFD6BA33, 0x6C24CF5C, + 0x7A325381, 0x28958677, 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193, 0x61D809CC, 0xFB21A991, 0x487CAC60, + 0x5DEC8032, 0xEF845D5D, 0xE98575B1, 0xDC262302, 0xEB651B88, 0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239, + 0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E, 0x21C66842, 0xF6E96C9A, 0x670C9C61, 0xABD388F0, 0x6A51A0D2, + 0xD8542F68, 0x960FA728, 0xAB5133A3, 0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98, 0xA1F1651D, 0x39AF0176, + 0x66CA593E, 0x82430E88, 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE, 0xE06F75D8, 0x85C12073, 0x401A449F, + 0x56C16AA6, 0x4ED3AA62, 0x363F7706, 0x1BFEDF72, 0x429B023D, 0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B, + 0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7, 0xE3FE501A, 0xB6794C3B, 0x976CE0BD, 0x04C006BA, 0xC1A94FB6, + 0x409F60C4, 0x5E5C9EC2, 0x196A2463, 0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F, 0x6DFC511F, 0x9B30952C, + 0xCC814544, 0xAF5EBD09, 0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3, 0xC0CBA857, 0x45C8740F, 0xD20B5F39, + 0xB9D3FBDB, 0x5579C0BD, 0x1A60320A, 0xD6A100C6, 0x402C7279, 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8, + 0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB, 0x323DB5FA, 0xFD238760, 0x53317B48, 0x3E00DF82, 0x9E5C57BB, + 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB, 0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573, 0x695B27B0, 0xBBCA58C8, + 0xE1FFA35D, 0xB8F011A0, 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B, 0x9A53E479, 0xB6F84565, 0xD28E49BC, + 0x4BFB9790, 0xE1DDF2DA, 0xA4CB7E33, 0x62FB1341, 0xCEE4C6E8, 0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4, + 0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0, 0xD08ED1D0, 0xAFC725E0, 0x8E3C5B2F, 0x8E7594B7, 0x8FF6E2FB, + 0xF2122B64, 0x8888B812, 0x900DF01C, 0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD, 0x2F2F2218, 0xBE0E1777, + 0xEA752DFE, 0x8B021FA1, 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299, 0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, + 0xD2ADA8D9, 0x165FA266, 0x80957705, 0x93CC7314, 0x211A1477, 0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF, + 0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49, 0x00250E2D, 0x2071B35E, 0x226800BB, 0x57B8E0AF, 0x2464369B, + 0xF009B91E, 0x5563911D, 0x59DFA6AA, 0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5, 0x83260376, 0x6295CFA9, + 0x11C81968, 0x4E734A41, 0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915, 0xD60F573F, 0xBC9BC6E4, 0x2B60A476, + 0x81E67400, 0x08BA6FB5, 0x571BE91F, 0xF296EC6B, 0x2A0DD915, 0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664, + 0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A + }, + + KS1 = { 0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, 0xAD6EA6B0, 0x49A7DF7D, 0x9CEE60B8, 0x8FEDB266, 0xECAA8C71, + 0x699A17FF, 0x5664526C, 0xC2B19EE1, 0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, 0x3F54989A, 0x5B429D65, + 0x6B8FE4D6, 0x99F73FD6, 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1, 0x4CDD2086, 0x8470EB26, 0x6382E9C6, + 0x021ECC5E, 0x09686B3F, 0x3EBAEFC9, 0x3C971814, 0x6B6A70A1, 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737, + 0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8, 0xB03ADA37, 0xF0500C0D, 0xF01C1F04, 0x0200B3FF, 0xAE0CF51A, + 0x3CB574B2, 0x25837A58, 0xDC0921BD, 0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701, 0x3AE5E581, 0x37C2DADC, + 0xC8B57634, 0x9AF3DDA7, 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41, 0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, + 0x183EB331, 0x4E548B38, 0x4F6DB908, 0x6F420D03, 0xF60A04BF, 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF, + 0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E, 0x5512721F, 0x2E6B7124, 0x501ADDE6, 0x9F84CD87, 0x7A584718, + 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C, 0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2, 0xEF1C1847, 0x3215D908, + 0xDD433B37, 0x24C2BA16, 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD, 0x71DFF89E, 0x10314E55, 0x81AC77D6, + 0x5F11199B, 0x043556F1, 0xD7A3C76B, 0x3C11183B, 0x5924A509, 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E, + 0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3, 0x771FE71C, 0x4E3D06FA, 0x2965DCB9, 0x99E71D0F, 0x803E89D6, + 0x5266C825, 0x2E4CC978, 0x9C10B36A, 0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4, 0xF2F74EA7, 0x361D2B3D, + 0x1939260F, 0x19C27960, 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66, 0xE3BC4595, 0xA67BC883, 0xB17F37D1, + 0x018CFF28, 0xC332DDEF, 0xBE6C5AA5, 0x65582185, 0x68AB9802, 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84, + 0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510, 0x13CCA830, 0xEB61BD96, 0x0334FE1E, 0xAA0363CF, 0xB5735C90, + 0x4C70A239, 0xD59E9E0B, 0xCBAADE14, 0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E, 0x648B1EAF, 0x19BDF0CA, + 0xA02369B9, 0x655ABB50, 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7, 0x9B540B19, 0x875FA099, 0x95F7997E, + 0x623D7DA8, 0xF837889A, 0x97E32D77, 0x11ED935F, 0x16681281, 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99, + 0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696, 0xCDB30AEB, 0x532E3054, 0x8FD948E4, 0x6DBC3128, 0x58EBF2EF, + 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73, 0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0, 0x45EEE2B6, 0xA3AAABEA, + 0xDB6C4F15, 0xFACB4FD0, 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105, 0xD81E799E, 0x86854DC7, 0xE44B476A, + 0x3D816250, 0xCF62A1F2, 0x5B8D2646, 0xFC8883A0, 0xC1C7B6A3, 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285, + 0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00, 0x58428D2A, 0x0C55F5EA, 0x1DADF43E, 0x233F7061, 0x3372F092, + 0x8D937E41, 0xD65FECF1, 0x6C223BDB, 0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E, 0xA6078084, 0x19F8509E, + 0xE8EFD855, 0x61D99735, 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC, 0x9E447A2E, 0xC3453484, 0xFDD56705, + 0x0E1E9EC9, 0xDB73DBD3, 0x105588CD, 0x675FDA79, 0xE3674340, 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20, + 0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7 + }, + + KS2 = { 0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, 0x411520F7, 0x7602D4F7, 0xBCF46B2E, 0xD4A20068, 0xD4082471, + 0x3320F46A, 0x43B7D4B7, 0x500061AF, 0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, 0x4D95FC1D, 0x96B591AF, + 0x70F4DDD3, 0x66A02F45, 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504, 0x96EB27B3, 0x55FD3941, 0xDA2547E6, + 0xABCA0A9A, 0x28507825, 0x530429F4, 0x0A2C86DA, 0xE9B66DFB, 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE, + 0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6, 0xAACE1E7C, 0xD3375FEC, 0xCE78A399, 0x406B2A42, 0x20FE9E35, + 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B, 0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2, 0x3A6EFA74, 0xDD5B4332, + 0x6841E7F7, 0xCA7820FB, 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527, 0x55533A3A, 0x20838D87, 0xFE6BA9B7, + 0xD096954B, 0x55A867BC, 0xA1159A58, 0xCCA92963, 0x99E1DB33, 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C, + 0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3, 0x95C11548, 0xE4C66D22, 0x48C1133F, 0xC70F86DC, 0x07F9C9EE, + 0x41041F0F, 0x404779A4, 0x5D886E17, 0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564, 0x257B7834, 0x602A9C60, + 0xDFF8E8A3, 0x1F636C1B, 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115, 0x6B2395E0, 0x333E92E1, 0x3B240B62, + 0xEEBEB922, 0x85B2A20E, 0xE6BA0D99, 0xDE720C8C, 0x2DA2F728, 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0, + 0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E, 0x0A476341, 0x992EFF74, 0x3A6F6EAB, 0xF4F8FD37, 0xA812DC60, + 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D, 0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804, 0xF1290DC7, 0xCC00FFA3, + 0xB5390F92, 0x690FED0B, 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3, 0xBB132F88, 0x515BAD24, 0x7B9479BF, + 0x763BD6EB, 0x37392EB3, 0xCC115979, 0x8026E297, 0xF42E312D, 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C, + 0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350, 0x1A6B1018, 0x11CAEDFA, 0x3D25BDD8, 0xE2E1C3C9, 0x44421659, + 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A, 0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE, 0x9DBC8057, 0xF0F7C086, + 0x60787BF8, 0x6003604D, 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC, 0x83426B33, 0xF01EAB71, 0xB0804187, + 0x3C005E5F, 0x77A057BE, 0xBDE8AE24, 0x55464299, 0xBF582E61, 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2, + 0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9, 0x7AEB2661, 0x8B1DDF84, 0x846A0E79, 0x915F95E2, 0x466E598E, + 0x20B45770, 0x8CD55591, 0xC902DE4C, 0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E, 0xB77F19B6, 0xE0A9DC09, + 0x662D09A1, 0xC4324633, 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10, 0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, + 0x2868F169, 0xDCB7DA83, 0x573906FE, 0xA1E2CE9B, 0x4FCD7F52, 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027, + 0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5, 0xF0177A28, 0xC0F586E0, 0x006058AA, 0x30DC7D62, 0x11E69ED7, + 0x2338EA63, 0x53C2DD94, 0xC2C21634, 0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76, 0x6F05E409, 0x4B7C0188, + 0x39720A3D, 0x7C927C24, 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC, 0xED545578, 0x08FCA5B5, 0xD83D7CD3, + 0x4DAD0FC4, 0x1E50EF5E, 0xB161E6F8, 0xA28514D9, 0x6C51133C, 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837, + 0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0 + }, + + KS3 = { 0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, 0x5CB0679E, 0x4FA33742, 0xD3822740, 0x99BC9BBE, 0xD5118E9D, + 0xBF0F7315, 0xD62D1C7E, 0xC700C47B, 0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, 0x5748AB2F, 0xBC946E79, + 0xC6A376D2, 0x6549C2C8, 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6, 0x2939BBDB, 0xA9BA4650, 0xAC9526E8, + 0xBE5EE304, 0xA1FAD5F0, 0x6A2D519A, 0x63EF8CE2, 0x9A86EE22, 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4, + 0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6, 0x2826A2F9, 0xA73A3AE1, 0x4BA99586, 0xEF5562E9, 0xC72FEFD3, + 0xF752F7DA, 0x3F046F69, 0x77FA0A59, 0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593, 0xE990FD5A, 0x9E34D797, + 0x2CF0B7D9, 0x022B8B51, 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28, 0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, + 0x5A88F54C, 0xE029AC71, 0xE019A5E6, 0x47B0ACFD, 0xED93FA9B, 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28, + 0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C, 0x15056DD4, 0x88F46DBA, 0x03A16125, 0x0564F0BD, 0xC3EB9E15, + 0x3C9057A2, 0x97271AEC, 0xA93A072A, 0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319, 0x7533D928, 0xB155FDF5, + 0x03563482, 0x8ABA3CBB, 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F, 0x4DE81751, 0x3830DC8E, 0x379D5862, + 0x9320F991, 0xEA7A90C2, 0xFB3E7BCE, 0x5121CE64, 0x774FBE32, 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680, + 0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166, 0xB39A460A, 0x6445C0DD, 0x586CDECF, 0x1C20C8AE, 0x5BBEF7DD, + 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB, 0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5, 0x72EACEA8, 0xFA6484BB, + 0x8D6612AE, 0xBF3C6F47, 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370, 0x740E0D8D, 0xE75B1357, 0xF8721671, + 0xAF537D5D, 0x4040CB08, 0x4EB4E2CC, 0x34D2466A, 0x0115AF84, 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048, + 0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8, 0x611560B1, 0xE7933FDC, 0xBB3A792B, 0x344525BD, 0xA08839E1, + 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9, 0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7, 0x1A908749, 0xD44FBD9A, + 0xD0DADECB, 0xD50ADA38, 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F, 0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, + 0x27D9459C, 0xBF97222C, 0x15E6FC2A, 0x0F91FC71, 0x9B941525, 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1, + 0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442, 0xE0EC6E0E, 0x1698DB3B, 0x4C98A0BE, 0x3278E964, 0x9F1F9532, + 0xE0D392DF, 0xD3A0342B, 0x8971F21E, 0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8, 0xDF359F8D, 0x9B992F2E, + 0xE60B6F47, 0x0FE3F11D, 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F, 0x1618B166, 0xFD2C1D05, 0x848FD2C5, + 0xF6FB2299, 0xF523F357, 0xA6327623, 0x93A83531, 0x56CCCD02, 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC, + 0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614, 0xE6C6C7BD, 0x327A140A, 0x45E1D006, 0xC3F27B9A, 0xC9AA53FD, + 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6, 0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B, 0x53113EC0, 0x1640E3D3, + 0x38ABBD60, 0x2547ADF0, 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060, 0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, + 0x4CF9AA7E, 0x1948C25C, 0x02FB8A8C, 0x01C36AE4, 0xD6EBE1F9, 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F, + 0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6 + }; + + // ==================================== + // Useful constants + // ==================================== + + private static final int ROUNDS = 16; + private static final int BLOCK_SIZE = 8; // bytes = 64 bits + private static final int SBOX_SK = 256; + private static final int P_SZ = ROUNDS + 2; + + private final int[] S0, S1, S2, S3; // the s-boxes + private final int[] P; // the p-array + + private boolean doEncrypt = false; + + private byte[] workingKey = null; + + public BlowFish() { + S0 = new int[SBOX_SK]; + S1 = new int[SBOX_SK]; + S2 = new int[SBOX_SK]; + S3 = new int[SBOX_SK]; + P = new int[P_SZ]; + } + + /** + * initialise a Blowfish cipher. + * + * @param encrypting + * whether or not we are for encryption. + * @param key + * the key required to set up the cipher. + * @exception IllegalArgumentException + * if the params argument is inappropriate. + */ + public void init(boolean encrypting, byte[] key) { + this.doEncrypt = encrypting; + this.workingKey = key; + setKey(this.workingKey); + } + + public String getAlgorithmName() { + return "Blowfish"; + } + + public final void transformBlock(byte[] in, int inOff, byte[] out, int outOff) { + if (workingKey == null) { + throw new IllegalStateException("Blowfish not initialised"); + } + + if (doEncrypt) { + encryptBlock(in, inOff, out, outOff); + } + else { + decryptBlock(in, inOff, out, outOff); + } + } + + public void reset() { + } + + public int getBlockSize() { + return BLOCK_SIZE; + } + + // ================================== + // Private Implementation + // ================================== + + private int F(int x) { + return (((S0[(x >>> 24)] + S1[(x >>> 16) & 0xff]) ^ S2[(x >>> 8) & 0xff]) + S3[x & 0xff]); + } + + /** + * apply the encryption cycle to each value pair in the table. + */ + private void processTable(int xl, int xr, int[] table) { + int size = table.length; + + for (int s = 0; s < size; s += 2) { + xl ^= P[0]; + + for (int i = 1; i < ROUNDS; i += 2) { + xr ^= F(xl) ^ P[i]; + xl ^= F(xr) ^ P[i + 1]; + } + + xr ^= P[ROUNDS + 1]; + table[s] = xr; + table[s + 1] = xl; + xr = xl; // end of cycle swap + xl = table[s]; + } + } + + private void setKey(byte[] key) { + /* + * - comments are from _Applied Crypto_, Schneier, p338 please be + * careful comparing the two, AC numbers the arrays from 1, the enclosed + * code from 0. + * + * (1) Initialise the S-boxes and the P-array, with a fixed string This + * string contains the hexadecimal digits of pi (3.141...) + */ + System.arraycopy(KS0, 0, S0, 0, SBOX_SK); + System.arraycopy(KS1, 0, S1, 0, SBOX_SK); + System.arraycopy(KS2, 0, S2, 0, SBOX_SK); + System.arraycopy(KS3, 0, S3, 0, SBOX_SK); + System.arraycopy(KP, 0, P, 0, P_SZ); + /* + * (2) Now, XOR P[0] with the first 32 bits of the key, XOR P[1] with + * the second 32-bits of the key, and so on for all bits of the key (up + * to P[17]). Repeatedly cycle through the key bits until the entire + * P-array has been XOR-ed with the key bits + */ + int keyLength = key.length; + int keyIndex = 0; + + for (int i = 0; i < P_SZ; i++) { + // get the 32 bits of the key, in 4 * 8 bit chunks + int data = 0x0000000; + + for (int j = 0; j < 4; j++) { + // create a 32 bit block + data = (data << 8) | (key[keyIndex++] & 0xff); + + // wrap when we get to the end of the key + if (keyIndex >= keyLength) { + keyIndex = 0; + } + } + + // XOR the newly created 32 bit chunk onto the P-array + P[i] ^= data; + } + + /* + * (3) Encrypt the all-zero string with the Blowfish algorithm, using + * the subkeys described in (1) and (2) + * + * (4) Replace P1 and P2 with the output of step (3) + * + * (5) Encrypt the output of step(3) using the Blowfish algorithm, with + * the modified subkeys. + * + * (6) Replace P3 and P4 with the output of step (5) + * + * (7) Continue the process, replacing all elements of the P-array and + * then all four S-boxes in order, with the output of the continuously + * changing Blowfish algorithm + */ + processTable(0, 0, P); + processTable(P[P_SZ - 2], P[P_SZ - 1], S0); + processTable(S0[SBOX_SK - 2], S0[SBOX_SK - 1], S1); + processTable(S1[SBOX_SK - 2], S1[SBOX_SK - 1], S2); + processTable(S2[SBOX_SK - 2], S2[SBOX_SK - 1], S3); + } + + /** + * Encrypt the given input starting at the given offset and place the result + * in the provided buffer starting at the given offset. The input will be an + * exact multiple of our blocksize. + */ + private void encryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex) { + int xl = BytesTo32bits(src, srcIndex); + int xr = BytesTo32bits(src, srcIndex + 4); + xl ^= P[0]; + + for (int i = 1; i < ROUNDS; i += 2) { + xr ^= F(xl) ^ P[i]; + xl ^= F(xr) ^ P[i + 1]; + } + + xr ^= P[ROUNDS + 1]; + Bits32ToBytes(xr, dst, dstIndex); + Bits32ToBytes(xl, dst, dstIndex + 4); + } + + /** + * Decrypt the given input starting at the given offset and place the result + * in the provided buffer starting at the given offset. The input will be an + * exact multiple of our blocksize. + */ + private void decryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex) { + int xl = BytesTo32bits(src, srcIndex); + int xr = BytesTo32bits(src, srcIndex + 4); + xl ^= P[ROUNDS + 1]; + + for (int i = ROUNDS; i > 0; i -= 2) { + xr ^= F(xl) ^ P[i]; + xl ^= F(xr) ^ P[i - 1]; + } + + xr ^= P[0]; + Bits32ToBytes(xr, dst, dstIndex); + Bits32ToBytes(xl, dst, dstIndex + 4); + } + + private int BytesTo32bits(byte[] b, int i) { + return ((b[i] & 0xff) << 24) | ((b[i + 1] & 0xff) << 16) | ((b[i + 2] & 0xff) << 8) | ((b[i + 3] & 0xff)); + } + + private void Bits32ToBytes(int in, byte[] b, int offset) { + b[offset + 3] = (byte) in; + b[offset + 2] = (byte)(in >> 8); + b[offset + 1] = (byte)(in >> 16); + b[offset] = (byte)(in >> 24); + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/cipher/CBCMode.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/cipher/CBCMode.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.cipher; + +/** + * CBCMode. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class CBCMode implements BlockCipher { + BlockCipher tc; + int blockSize; + boolean doEncrypt; + + byte[] cbc_vector; + byte[] tmp_vector; + + public void init(boolean forEncryption, byte[] key) { + } + + public CBCMode(BlockCipher tc, byte[] iv, boolean doEncrypt) + throws IllegalArgumentException { + this.tc = tc; + this.blockSize = tc.getBlockSize(); + this.doEncrypt = doEncrypt; + + if (this.blockSize != iv.length) + throw new IllegalArgumentException("IV must be " + blockSize + + " bytes long! (currently " + iv.length + ")"); + + this.cbc_vector = new byte[blockSize]; + this.tmp_vector = new byte[blockSize]; + System.arraycopy(iv, 0, cbc_vector, 0, blockSize); + } + + public int getBlockSize() { + return blockSize; + } + + private void encryptBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { + for (int i = 0; i < blockSize; i++) + cbc_vector[i] ^= src[srcoff + i]; + + tc.transformBlock(cbc_vector, 0, dst, dstoff); + System.arraycopy(dst, dstoff, cbc_vector, 0, blockSize); + } + + private void decryptBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { + /* Assume the worst, src and dst are overlapping... */ + System.arraycopy(src, srcoff, tmp_vector, 0, blockSize); + tc.transformBlock(src, srcoff, dst, dstoff); + + for (int i = 0; i < blockSize; i++) + dst[dstoff + i] ^= cbc_vector[i]; + + /* ...that is why we need a tmp buffer. */ + byte[] swap = cbc_vector; + cbc_vector = tmp_vector; + tmp_vector = swap; + } + + public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { + if (doEncrypt) + encryptBlock(src, srcoff, dst, dstoff); + else + decryptBlock(src, srcoff, dst, dstoff); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/cipher/CTRMode.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/cipher/CTRMode.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.cipher; + +/** + * This is CTR mode as described in draft-ietf-secsh-newmodes-XY.txt + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class CTRMode implements BlockCipher { + byte[] X; + byte[] Xenc; + + BlockCipher bc; + int blockSize; + boolean doEncrypt; + + int count = 0; + + public void init(boolean forEncryption, byte[] key) { + } + + public CTRMode(BlockCipher tc, byte[] iv, boolean doEnc) throws IllegalArgumentException { + bc = tc; + blockSize = bc.getBlockSize(); + doEncrypt = doEnc; + + if (blockSize != iv.length) + throw new IllegalArgumentException("IV must be " + blockSize + " bytes long! (currently " + iv.length + ")"); + + X = new byte[blockSize]; + Xenc = new byte[blockSize]; + System.arraycopy(iv, 0, X, 0, blockSize); + } + + public final int getBlockSize() { + return blockSize; + } + + public final void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { + bc.transformBlock(X, 0, Xenc, 0); + + for (int i = 0; i < blockSize; i++) { + dst[dstoff + i] = (byte)(src[srcoff + i] ^ Xenc[i]); + } + + for (int i = (blockSize - 1); i >= 0; i--) { + X[i]++; + + if (X[i] != 0) + break; + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/cipher/CipherInputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/cipher/CipherInputStream.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.cipher; + +import java.io.IOException; +import java.io.InputStream; + +/** + * CipherInputStream. + * + * @author Christian Plattner + * @version $Id: CipherInputStream.java 11 2011-05-27 14:14:06Z dkocher@sudo.ch $ + */ +public class CipherInputStream { + BlockCipher currentCipher; + InputStream bi; + byte[] buffer; + byte[] enc; + int blockSize; + int pos; + + /* + * We cannot use java.io.BufferedInputStream, since that is not available in + * J2ME. Everything could be improved alot here. + */ + + private static final int BUFF_SIZE = 8192; + byte[] input_buffer = new byte[BUFF_SIZE]; + int input_buffer_pos = 0; + int input_buffer_size = 0; + + public CipherInputStream(BlockCipher tc, InputStream bi) { + this.bi = bi; + changeCipher(tc); + } + + private int fill_buffer() throws IOException { + input_buffer_pos = 0; + input_buffer_size = 0; + input_buffer_size = bi.read(input_buffer, 0, BUFF_SIZE); + return input_buffer_size; + } + + private int internal_read(byte[] b, int off, int len) throws IOException { + if (input_buffer_size < 0) { + return -1; + } + + if (input_buffer_pos >= input_buffer_size) { + if (fill_buffer() <= 0) { + return -1; + } + } + + int avail = input_buffer_size - input_buffer_pos; + int thiscopy = (len > avail) ? avail : len; + System.arraycopy(input_buffer, input_buffer_pos, b, off, thiscopy); + input_buffer_pos += thiscopy; + return thiscopy; + } + + public void changeCipher(BlockCipher bc) { + this.currentCipher = bc; + blockSize = bc.getBlockSize(); + buffer = new byte[blockSize]; + enc = new byte[blockSize]; + pos = blockSize; + } + + private void getBlock() throws IOException { + int n = 0; + + while (n < blockSize) { + int len = internal_read(enc, n, blockSize - n); + + if (len < 0) { + throw new IOException("Cannot read full block, EOF reached."); + } + + n += len; + } + + try { + currentCipher.transformBlock(enc, 0, buffer, 0); + } + catch (Exception e) { + throw new IOException("Error while decrypting block."); + } + + pos = 0; + } + + public int read(byte[] dst) throws IOException { + return read(dst, 0, dst.length); + } + + public int read(byte[] dst, int off, int len) throws IOException { + int count = 0; + + while (len > 0) { + if (pos >= blockSize) { + getBlock(); + } + + int avail = blockSize - pos; + int copy = Math.min(avail, len); + System.arraycopy(buffer, pos, dst, off, copy); + pos += copy; + off += copy; + len -= copy; + count += copy; + } + + return count; + } + + public int read() throws IOException { + if (pos >= blockSize) { + getBlock(); + } + + return buffer[pos++] & 0xff; + } + + public int readPlain(byte[] b, int off, int len) throws IOException { + if (pos != blockSize) { + throw new IOException("Cannot read plain since crypto buffer is not aligned."); + } + + int n = 0; + + while (n < len) { + int cnt = internal_read(b, off + n, len - n); + + if (cnt < 0) { + throw new IOException("Cannot fill buffer, EOF reached."); + } + + n += cnt; + } + + return n; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/cipher/CipherOutputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/cipher/CipherOutputStream.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.cipher; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * CipherOutputStream. + * + * @author Christian Plattner + * @version $Id: CipherOutputStream.java 85 2014-04-07 14:05:09Z dkocher@sudo.ch $ + */ +public class CipherOutputStream { + BlockCipher currentCipher; + OutputStream bo; + byte[] buffer; + byte[] enc; + int blockSize; + int pos; + + /* + * We cannot use java.io.BufferedOutputStream, since that is not available + * in J2ME. Everything could be improved here alot. + */ + + private static final int BUFF_SIZE = 8192; + byte[] out_buffer = new byte[BUFF_SIZE]; + int out_buffer_pos = 0; + + public CipherOutputStream(BlockCipher tc, OutputStream bo) { + this.bo = bo; + changeCipher(tc); + } + + private void internal_write(byte[] src, int off, int len) throws IOException { + while (len > 0) { + int space = BUFF_SIZE - out_buffer_pos; + int copy = (len > space) ? space : len; + System.arraycopy(src, off, out_buffer, out_buffer_pos, copy); + off += copy; + out_buffer_pos += copy; + len -= copy; + + if (out_buffer_pos >= BUFF_SIZE) { + bo.write(out_buffer, 0, BUFF_SIZE); + out_buffer_pos = 0; + } + } + } + + private void internal_write(int b) throws IOException { + out_buffer[out_buffer_pos++] = (byte) b; + + if (out_buffer_pos >= BUFF_SIZE) { + bo.write(out_buffer, 0, BUFF_SIZE); + out_buffer_pos = 0; + } + } + + public void flush() throws IOException { + if (pos != 0) { + throw new IOException("FATAL: cannot flush since crypto buffer is not aligned."); + } + + if (out_buffer_pos > 0) { + bo.write(out_buffer, 0, out_buffer_pos); + out_buffer_pos = 0; + } + + bo.flush(); + } + + public void changeCipher(BlockCipher bc) { + this.currentCipher = bc; + blockSize = bc.getBlockSize(); + buffer = new byte[blockSize]; + enc = new byte[blockSize]; + pos = 0; + } + + private void writeBlock() throws IOException { + try { + currentCipher.transformBlock(buffer, 0, enc, 0); + } + catch (Exception e) { + throw new IOException("Error while decrypting block.", e); + } + + internal_write(enc, 0, blockSize); + pos = 0; + } + + public void write(byte[] src, int off, int len) throws IOException { + while (len > 0) { + int avail = blockSize - pos; + int copy = Math.min(avail, len); + System.arraycopy(src, off, buffer, pos, copy); + pos += copy; + off += copy; + len -= copy; + + if (pos >= blockSize) { + writeBlock(); + } + } + } + + public void write(int b) throws IOException { + buffer[pos++] = (byte) b; + + if (pos >= blockSize) { + writeBlock(); + } + } + + public void writePlain(int b) throws IOException { + if (pos != 0) { + throw new IOException("Cannot write plain since crypto buffer is not aligned."); + } + + internal_write(b); + } + + public void writePlain(byte[] b, int off, int len) throws IOException { + if (pos != 0) { + throw new IOException("Cannot write plain since crypto buffer is not aligned."); + } + + internal_write(b, off, len); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/cipher/DES.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/cipher/DES.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,352 @@ +package ch.ethz.ssh2.crypto.cipher; + +/* + This file is based on the 3DES implementation from the Bouncy Castle Crypto package. + Their licence file states the following: + + Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle + (http://www.bouncycastle.org) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +/** + * DES. + * + * @author See comments in the source file + * @version 2.50, 03/15/10 + * + */ +public class DES implements BlockCipher { + private int[] workingKey = null; + + /** + * standard constructor. + */ + public DES() { + } + + /** + * initialise a DES cipher. + * + * @param encrypting + * whether or not we are for encryption. + * @param key + * the parameters required to set up the cipher. + * @exception IllegalArgumentException + * if the params argument is inappropriate. + */ + public void init(boolean encrypting, byte[] key) { + this.workingKey = generateWorkingKey(encrypting, key, 0); + } + + public String getAlgorithmName() { + return "DES"; + } + + public int getBlockSize() { + return 8; + } + + public void transformBlock(byte[] in, int inOff, byte[] out, int outOff) { + if (workingKey == null) { + throw new IllegalStateException("DES engine not initialised!"); + } + + desFunc(workingKey, in, inOff, out, outOff); + } + + public void reset() { + } + + /** + * what follows is mainly taken from "Applied Cryptography", by Bruce + * Schneier, however it also bears great resemblance to Richard + * Outerbridge's D3DES... + */ + + static short[] Df_Key = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, + 0x10, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67 + }; + + static short[] bytebit = { 0200, 0100, 040, 020, 010, 04, 02, 01 }; + + static int[] bigbyte = { 0x800000, 0x400000, 0x200000, 0x100000, 0x80000, 0x40000, 0x20000, 0x10000, 0x8000, + 0x4000, 0x2000, 0x1000, 0x800, 0x400, 0x200, 0x100, 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1 + }; + + /* + * Use the key schedule specified in the Standard (ANSI X3.92-1981). + */ + + static byte[] pc1 = { 56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, + 59, 51, 43, 35, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 60, 52, 44, 36, 28, 20, 12, + 4, 27, 19, 11, 3 + }; + + static byte[] totrot = { 1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28 }; + + static byte[] pc2 = { 13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, 40, + 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47, 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31 + }; + + static int[] SP1 = { 0x01010400, 0x00000000, 0x00010000, 0x01010404, 0x01010004, 0x00010404, 0x00000004, + 0x00010000, 0x00000400, 0x01010400, 0x01010404, 0x00000400, 0x01000404, 0x01010004, 0x01000000, 0x00000004, + 0x00000404, 0x01000400, 0x01000400, 0x00010400, 0x00010400, 0x01010000, 0x01010000, 0x01000404, 0x00010004, + 0x01000004, 0x01000004, 0x00010004, 0x00000000, 0x00000404, 0x00010404, 0x01000000, 0x00010000, 0x01010404, + 0x00000004, 0x01010000, 0x01010400, 0x01000000, 0x01000000, 0x00000400, 0x01010004, 0x00010000, 0x00010400, + 0x01000004, 0x00000400, 0x00000004, 0x01000404, 0x00010404, 0x01010404, 0x00010004, 0x01010000, 0x01000404, + 0x01000004, 0x00000404, 0x00010404, 0x01010400, 0x00000404, 0x01000400, 0x01000400, 0x00000000, 0x00010004, + 0x00010400, 0x00000000, 0x01010004 + }; + + static int[] SP2 = { 0x80108020, 0x80008000, 0x00008000, 0x00108020, 0x00100000, 0x00000020, 0x80100020, + 0x80008020, 0x80000020, 0x80108020, 0x80108000, 0x80000000, 0x80008000, 0x00100000, 0x00000020, 0x80100020, + 0x00108000, 0x00100020, 0x80008020, 0x00000000, 0x80000000, 0x00008000, 0x00108020, 0x80100000, 0x00100020, + 0x80000020, 0x00000000, 0x00108000, 0x00008020, 0x80108000, 0x80100000, 0x00008020, 0x00000000, 0x00108020, + 0x80100020, 0x00100000, 0x80008020, 0x80100000, 0x80108000, 0x00008000, 0x80100000, 0x80008000, 0x00000020, + 0x80108020, 0x00108020, 0x00000020, 0x00008000, 0x80000000, 0x00008020, 0x80108000, 0x00100000, 0x80000020, + 0x00100020, 0x80008020, 0x80000020, 0x00100020, 0x00108000, 0x00000000, 0x80008000, 0x00008020, 0x80000000, + 0x80100020, 0x80108020, 0x00108000 + }; + + static int[] SP3 = { 0x00000208, 0x08020200, 0x00000000, 0x08020008, 0x08000200, 0x00000000, 0x00020208, + 0x08000200, 0x00020008, 0x08000008, 0x08000008, 0x00020000, 0x08020208, 0x00020008, 0x08020000, 0x00000208, + 0x08000000, 0x00000008, 0x08020200, 0x00000200, 0x00020200, 0x08020000, 0x08020008, 0x00020208, 0x08000208, + 0x00020200, 0x00020000, 0x08000208, 0x00000008, 0x08020208, 0x00000200, 0x08000000, 0x08020200, 0x08000000, + 0x00020008, 0x00000208, 0x00020000, 0x08020200, 0x08000200, 0x00000000, 0x00000200, 0x00020008, 0x08020208, + 0x08000200, 0x08000008, 0x00000200, 0x00000000, 0x08020008, 0x08000208, 0x00020000, 0x08000000, 0x08020208, + 0x00000008, 0x00020208, 0x00020200, 0x08000008, 0x08020000, 0x08000208, 0x00000208, 0x08020000, 0x00020208, + 0x00000008, 0x08020008, 0x00020200 + }; + + static int[] SP4 = { 0x00802001, 0x00002081, 0x00002081, 0x00000080, 0x00802080, 0x00800081, 0x00800001, + 0x00002001, 0x00000000, 0x00802000, 0x00802000, 0x00802081, 0x00000081, 0x00000000, 0x00800080, 0x00800001, + 0x00000001, 0x00002000, 0x00800000, 0x00802001, 0x00000080, 0x00800000, 0x00002001, 0x00002080, 0x00800081, + 0x00000001, 0x00002080, 0x00800080, 0x00002000, 0x00802080, 0x00802081, 0x00000081, 0x00800080, 0x00800001, + 0x00802000, 0x00802081, 0x00000081, 0x00000000, 0x00000000, 0x00802000, 0x00002080, 0x00800080, 0x00800081, + 0x00000001, 0x00802001, 0x00002081, 0x00002081, 0x00000080, 0x00802081, 0x00000081, 0x00000001, 0x00002000, + 0x00800001, 0x00002001, 0x00802080, 0x00800081, 0x00002001, 0x00002080, 0x00800000, 0x00802001, 0x00000080, + 0x00800000, 0x00002000, 0x00802080 + }; + + static int[] SP5 = { 0x00000100, 0x02080100, 0x02080000, 0x42000100, 0x00080000, 0x00000100, 0x40000000, + 0x02080000, 0x40080100, 0x00080000, 0x02000100, 0x40080100, 0x42000100, 0x42080000, 0x00080100, 0x40000000, + 0x02000000, 0x40080000, 0x40080000, 0x00000000, 0x40000100, 0x42080100, 0x42080100, 0x02000100, 0x42080000, + 0x40000100, 0x00000000, 0x42000000, 0x02080100, 0x02000000, 0x42000000, 0x00080100, 0x00080000, 0x42000100, + 0x00000100, 0x02000000, 0x40000000, 0x02080000, 0x42000100, 0x40080100, 0x02000100, 0x40000000, 0x42080000, + 0x02080100, 0x40080100, 0x00000100, 0x02000000, 0x42080000, 0x42080100, 0x00080100, 0x42000000, 0x42080100, + 0x02080000, 0x00000000, 0x40080000, 0x42000000, 0x00080100, 0x02000100, 0x40000100, 0x00080000, 0x00000000, + 0x40080000, 0x02080100, 0x40000100 + }; + + static int[] SP6 = { 0x20000010, 0x20400000, 0x00004000, 0x20404010, 0x20400000, 0x00000010, 0x20404010, + 0x00400000, 0x20004000, 0x00404010, 0x00400000, 0x20000010, 0x00400010, 0x20004000, 0x20000000, 0x00004010, + 0x00000000, 0x00400010, 0x20004010, 0x00004000, 0x00404000, 0x20004010, 0x00000010, 0x20400010, 0x20400010, + 0x00000000, 0x00404010, 0x20404000, 0x00004010, 0x00404000, 0x20404000, 0x20000000, 0x20004000, 0x00000010, + 0x20400010, 0x00404000, 0x20404010, 0x00400000, 0x00004010, 0x20000010, 0x00400000, 0x20004000, 0x20000000, + 0x00004010, 0x20000010, 0x20404010, 0x00404000, 0x20400000, 0x00404010, 0x20404000, 0x00000000, 0x20400010, + 0x00000010, 0x00004000, 0x20400000, 0x00404010, 0x00004000, 0x00400010, 0x20004010, 0x00000000, 0x20404000, + 0x20000000, 0x00400010, 0x20004010 + }; + + static int[] SP7 = { 0x00200000, 0x04200002, 0x04000802, 0x00000000, 0x00000800, 0x04000802, 0x00200802, + 0x04200800, 0x04200802, 0x00200000, 0x00000000, 0x04000002, 0x00000002, 0x04000000, 0x04200002, 0x00000802, + 0x04000800, 0x00200802, 0x00200002, 0x04000800, 0x04000002, 0x04200000, 0x04200800, 0x00200002, 0x04200000, + 0x00000800, 0x00000802, 0x04200802, 0x00200800, 0x00000002, 0x04000000, 0x00200800, 0x04000000, 0x00200800, + 0x00200000, 0x04000802, 0x04000802, 0x04200002, 0x04200002, 0x00000002, 0x00200002, 0x04000000, 0x04000800, + 0x00200000, 0x04200800, 0x00000802, 0x00200802, 0x04200800, 0x00000802, 0x04000002, 0x04200802, 0x04200000, + 0x00200800, 0x00000000, 0x00000002, 0x04200802, 0x00000000, 0x00200802, 0x04200000, 0x00000800, 0x04000002, + 0x04000800, 0x00000800, 0x00200002 + }; + + static int[] SP8 = { 0x10001040, 0x00001000, 0x00040000, 0x10041040, 0x10000000, 0x10001040, 0x00000040, + 0x10000000, 0x00040040, 0x10040000, 0x10041040, 0x00041000, 0x10041000, 0x00041040, 0x00001000, 0x00000040, + 0x10040000, 0x10000040, 0x10001000, 0x00001040, 0x00041000, 0x00040040, 0x10040040, 0x10041000, 0x00001040, + 0x00000000, 0x00000000, 0x10040040, 0x10000040, 0x10001000, 0x00041040, 0x00040000, 0x00041040, 0x00040000, + 0x10041000, 0x00001000, 0x00000040, 0x10040040, 0x00001000, 0x00041040, 0x10001000, 0x00000040, 0x10000040, + 0x10040000, 0x10040040, 0x10000000, 0x00040000, 0x10001040, 0x00000000, 0x10041040, 0x00040040, 0x10000040, + 0x10040000, 0x10001000, 0x10001040, 0x00000000, 0x10041040, 0x00041000, 0x00041000, 0x00001040, 0x00001040, + 0x00040040, 0x10000000, 0x10041000 + }; + + /** + * generate an integer based working key based on our secret key and what we + * processing we are planning to do. + * + * Acknowledgements for this routine go to James Gillogly & Phil Karn. + * (whoever, and wherever they are!). + */ + protected int[] generateWorkingKey(boolean encrypting, byte[] key, int off) { + int[] newKey = new int[32]; + boolean[] pc1m = new boolean[56], pcr = new boolean[56]; + + for (int j = 0; j < 56; j++) { + int l = pc1[j]; + pc1m[j] = ((key[off + (l >>> 3)] & bytebit[l & 07]) != 0); + } + + for (int i = 0; i < 16; i++) { + int l, m, n; + + if (encrypting) { + m = i << 1; + } + else { + m = (15 - i) << 1; + } + + n = m + 1; + newKey[m] = newKey[n] = 0; + + for (int j = 0; j < 28; j++) { + l = j + totrot[i]; + + if (l < 28) { + pcr[j] = pc1m[l]; + } + else { + pcr[j] = pc1m[l - 28]; + } + } + + for (int j = 28; j < 56; j++) { + l = j + totrot[i]; + + if (l < 56) { + pcr[j] = pc1m[l]; + } + else { + pcr[j] = pc1m[l - 28]; + } + } + + for (int j = 0; j < 24; j++) { + if (pcr[pc2[j]]) { + newKey[m] |= bigbyte[j]; + } + + if (pcr[pc2[j + 24]]) { + newKey[n] |= bigbyte[j]; + } + } + } + + // + // store the processed key + // + for (int i = 0; i != 32; i += 2) { + int i1, i2; + i1 = newKey[i]; + i2 = newKey[i + 1]; + newKey[i] = ((i1 & 0x00fc0000) << 6) | ((i1 & 0x00000fc0) << 10) | ((i2 & 0x00fc0000) >>> 10) + | ((i2 & 0x00000fc0) >>> 6); + newKey[i + 1] = ((i1 & 0x0003f000) << 12) | ((i1 & 0x0000003f) << 16) | ((i2 & 0x0003f000) >>> 4) + | (i2 & 0x0000003f); + } + + return newKey; + } + + /** + * the DES engine. + */ + protected void desFunc(int[] wKey, byte[] in, int inOff, byte[] out, int outOff) { + int work, right, left; + left = (in[inOff + 0] & 0xff) << 24; + left |= (in[inOff + 1] & 0xff) << 16; + left |= (in[inOff + 2] & 0xff) << 8; + left |= (in[inOff + 3] & 0xff); + right = (in[inOff + 4] & 0xff) << 24; + right |= (in[inOff + 5] & 0xff) << 16; + right |= (in[inOff + 6] & 0xff) << 8; + right |= (in[inOff + 7] & 0xff); + work = ((left >>> 4) ^ right) & 0x0f0f0f0f; + right ^= work; + left ^= (work << 4); + work = ((left >>> 16) ^ right) & 0x0000ffff; + right ^= work; + left ^= (work << 16); + work = ((right >>> 2) ^ left) & 0x33333333; + left ^= work; + right ^= (work << 2); + work = ((right >>> 8) ^ left) & 0x00ff00ff; + left ^= work; + right ^= (work << 8); + right = ((right << 1) | ((right >>> 31) & 1)) & 0xffffffff; + work = (left ^ right) & 0xaaaaaaaa; + left ^= work; + right ^= work; + left = ((left << 1) | ((left >>> 31) & 1)) & 0xffffffff; + + for (int round = 0; round < 8; round++) { + int fval; + work = (right << 28) | (right >>> 4); + work ^= wKey[round * 4 + 0]; + fval = SP7[work & 0x3f]; + fval |= SP5[(work >>> 8) & 0x3f]; + fval |= SP3[(work >>> 16) & 0x3f]; + fval |= SP1[(work >>> 24) & 0x3f]; + work = right ^ wKey[round * 4 + 1]; + fval |= SP8[work & 0x3f]; + fval |= SP6[(work >>> 8) & 0x3f]; + fval |= SP4[(work >>> 16) & 0x3f]; + fval |= SP2[(work >>> 24) & 0x3f]; + left ^= fval; + work = (left << 28) | (left >>> 4); + work ^= wKey[round * 4 + 2]; + fval = SP7[work & 0x3f]; + fval |= SP5[(work >>> 8) & 0x3f]; + fval |= SP3[(work >>> 16) & 0x3f]; + fval |= SP1[(work >>> 24) & 0x3f]; + work = left ^ wKey[round * 4 + 3]; + fval |= SP8[work & 0x3f]; + fval |= SP6[(work >>> 8) & 0x3f]; + fval |= SP4[(work >>> 16) & 0x3f]; + fval |= SP2[(work >>> 24) & 0x3f]; + right ^= fval; + } + + right = (right << 31) | (right >>> 1); + work = (left ^ right) & 0xaaaaaaaa; + left ^= work; + right ^= work; + left = (left << 31) | (left >>> 1); + work = ((left >>> 8) ^ right) & 0x00ff00ff; + right ^= work; + left ^= (work << 8); + work = ((left >>> 2) ^ right) & 0x33333333; + right ^= work; + left ^= (work << 2); + work = ((right >>> 16) ^ left) & 0x0000ffff; + left ^= work; + right ^= (work << 16); + work = ((right >>> 4) ^ left) & 0x0f0f0f0f; + left ^= work; + right ^= (work << 4); + out[outOff + 0] = (byte)((right >>> 24) & 0xff); + out[outOff + 1] = (byte)((right >>> 16) & 0xff); + out[outOff + 2] = (byte)((right >>> 8) & 0xff); + out[outOff + 3] = (byte)(right & 0xff); + out[outOff + 4] = (byte)((left >>> 24) & 0xff); + out[outOff + 5] = (byte)((left >>> 16) & 0xff); + out[outOff + 6] = (byte)((left >>> 8) & 0xff); + out[outOff + 7] = (byte)(left & 0xff); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/cipher/DESede.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/cipher/DESede.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,98 @@ +package ch.ethz.ssh2.crypto.cipher; + +/* + This file was shamelessly taken (and modified) from the Bouncy Castle Crypto package. + Their licence file states the following: + + Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle + (http://www.bouncycastle.org) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +/** + * DESede. + * + * @author See comments in the source file + * @version 2.50, 03/15/10 + * + */ +public class DESede extends DES { + private int[] key1 = null; + private int[] key2 = null; + private int[] key3 = null; + + private boolean encrypt; + + /** + * standard constructor. + */ + public DESede() { + } + + /** + * initialise a DES cipher. + * + * @param encrypting + * whether or not we are for encryption. + * @param key + * the parameters required to set up the cipher. + * @exception IllegalArgumentException + * if the params argument is inappropriate. + */ + @Override + public void init(boolean encrypting, byte[] key) { + key1 = generateWorkingKey(encrypting, key, 0); + key2 = generateWorkingKey(!encrypting, key, 8); + key3 = generateWorkingKey(encrypting, key, 16); + encrypt = encrypting; + } + + @Override + public String getAlgorithmName() { + return "DESede"; + } + + @Override + public int getBlockSize() { + return 8; + } + + @Override + public void transformBlock(byte[] in, int inOff, byte[] out, int outOff) { + if (key1 == null) { + throw new IllegalStateException("DESede engine not initialised!"); + } + + if (encrypt) { + desFunc(key1, in, inOff, out, outOff); + desFunc(key2, out, outOff, out, outOff); + desFunc(key3, out, outOff, out, outOff); + } + else { + desFunc(key3, in, inOff, out, outOff); + desFunc(key2, out, outOff, out, outOff); + desFunc(key1, out, outOff, out, outOff); + } + } + + @Override + public void reset() { + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/cipher/NullCipher.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/cipher/NullCipher.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.cipher; + +/** + * NullCipher. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class NullCipher implements BlockCipher { + private int blockSize = 8; + + public NullCipher() { + } + + public NullCipher(int blockSize) { + this.blockSize = blockSize; + } + + public void init(boolean forEncryption, byte[] key) { + } + + public int getBlockSize() { + return blockSize; + } + + public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) { + System.arraycopy(src, srcoff, dst, dstoff, blockSize); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/dh/DhExchange.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/dh/DhExchange.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,147 @@ +/** + * + */ +package ch.ethz.ssh2.crypto.dh; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + +import javax.crypto.KeyAgreement; +import javax.crypto.interfaces.DHPrivateKey; +import javax.crypto.interfaces.DHPublicKey; +import javax.crypto.spec.DHParameterSpec; +import javax.crypto.spec.DHPublicKeySpec; + +/** + * @author kenny + * + */ +public class DhExchange extends GenericDhExchange { + + /* Given by the standard */ + + private static final BigInteger P1 = new BigInteger( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381" + + "FFFFFFFFFFFFFFFF", 16); + + private static final BigInteger P14 = new BigInteger( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16); + + private static final BigInteger G = BigInteger.valueOf(2); + + /* Client public and private */ + + private DHPrivateKey clientPrivate; + private DHPublicKey clientPublic; + + /* Server public */ + + private DHPublicKey serverPublic; + private byte[] f; + + @Override + public void init(String name) throws IOException { + final DHParameterSpec spec; + + if ("diffie-hellman-group1-sha1".equals(name)) { + spec = new DHParameterSpec(P1, G); + } + else if ("diffie-hellman-group14-sha1".equals(name)) { + spec = new DHParameterSpec(P14, G); + } + else { + throw new IllegalArgumentException("Unknown DH group " + name); + } + + try { + KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH"); + kpg.initialize(spec); + KeyPair pair = kpg.generateKeyPair(); + clientPrivate = (DHPrivateKey) pair.getPrivate(); + clientPublic = (DHPublicKey) pair.getPublic(); + } + catch (NoSuchAlgorithmException e) { + throw(IOException) new IOException("No DH keypair generator").initCause(e); + } + catch (InvalidAlgorithmParameterException e) { + throw(IOException) new IOException("Invalid DH parameters").initCause(e); + } + } + + @Override + public byte[] getE() { + if (clientPublic == null) + throw new IllegalStateException("DhExchange not initialized!"); + + return clientPublic.getY().toByteArray(); + } + + @Override + protected byte[] getServerE() { + if (serverPublic == null) + throw new IllegalStateException("DhExchange not initialized!"); + + return serverPublic.getY().toByteArray(); + } + + @Override + public byte[] getF() { + return f; + } + + @Override + public void setF(byte[] f) throws IOException { + if (clientPublic == null) + throw new IllegalStateException("DhExchange not initialized!"); + + final KeyAgreement ka; + + try { + KeyFactory kf = KeyFactory.getInstance("DH"); + DHParameterSpec params = clientPublic.getParams(); + this.f = f; + this.serverPublic = (DHPublicKey) kf.generatePublic(new DHPublicKeySpec( + new BigInteger(f), params.getP(), params.getG())); + ka = KeyAgreement.getInstance("DH"); + ka.init(clientPrivate); + ka.doPhase(serverPublic, true); + } + catch (NoSuchAlgorithmException e) { + throw(IOException) new IOException("No DH key agreement method").initCause(e); + } + catch (InvalidKeyException e) { + throw(IOException) new IOException("Invalid DH key").initCause(e); + } + catch (InvalidKeySpecException e) { + throw(IOException) new IOException("Invalid DH key").initCause(e); + } + + sharedSecret = new BigInteger(ka.generateSecret()); + } + + @Override + public String getHashAlgo() { + return "SHA1"; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/dh/DhGroupExchange.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/dh/DhGroupExchange.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,108 @@ + +package ch.ethz.ssh2.crypto.dh; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.io.IOException; + +import ch.ethz.ssh2.DHGexParameters; +import ch.ethz.ssh2.crypto.digest.HashForSSH2Types; + + +/** + * DhGroupExchange. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: DhGroupExchange.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ + */ +public class DhGroupExchange { + /* Given by the standard */ + + private BigInteger p; + private BigInteger g; + + /* Client public and private */ + + private BigInteger e; + private BigInteger x; + + /* Server public */ + + private BigInteger f; + + /* Shared secret */ + + private BigInteger k; + + public DhGroupExchange(BigInteger p, BigInteger g) { + this.p = p; + this.g = g; + } + + public void init(SecureRandom rnd) { + k = null; + x = new BigInteger(p.bitLength() - 1, rnd); + e = g.modPow(x, p); + } + + /** + * @return Returns the e. + */ + public BigInteger getE() { + if (e == null) + throw new IllegalStateException("Not initialized!"); + + return e; + } + + /** + * @return Returns the shared secret k. + */ + public BigInteger getK() { + if (k == null) + throw new IllegalStateException("Shared secret not yet known, need f first!"); + + return k; + } + + /** + * Sets f and calculates the shared secret. + */ + public void setF(BigInteger f) { + if (e == null) + throw new IllegalStateException("Not initialized!"); + + BigInteger zero = BigInteger.valueOf(0); + + if (zero.compareTo(f) >= 0 || p.compareTo(f) <= 0) + throw new IllegalArgumentException("Invalid f specified!"); + + this.f = f; + this.k = f.modPow(x, p); + } + + public byte[] calculateH(String hashAlgo, byte[] clientversion, byte[] serverversion, + byte[] clientKexPayload, byte[] serverKexPayload, byte[] hostKey, DHGexParameters para) throws IOException { + HashForSSH2Types hash = new HashForSSH2Types(hashAlgo); + hash.updateByteString(clientversion); + hash.updateByteString(serverversion); + hash.updateByteString(clientKexPayload); + hash.updateByteString(serverKexPayload); + hash.updateByteString(hostKey); + + if (para.getMin_group_len() > 0) + hash.updateUINT32(para.getMin_group_len()); + + hash.updateUINT32(para.getPref_group_len()); + + if (para.getMax_group_len() > 0) + hash.updateUINT32(para.getMax_group_len()); + + hash.updateBigInt(p); + hash.updateBigInt(g); + hash.updateBigInt(e); + hash.updateBigInt(f); + hash.updateBigInt(k); + return hash.getDigest(); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/dh/EcDhExchange.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/dh/EcDhExchange.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,127 @@ +/** + * + */ +package ch.ethz.ssh2.crypto.dh; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; + +import javax.crypto.KeyAgreement; + +import ch.ethz.ssh2.signature.ECDSASHA2Verify; + +/** + * @author kenny + * + */ +public class EcDhExchange extends GenericDhExchange { + + /* Client public and private */ + + private ECPrivateKey clientPrivate; + private ECPublicKey clientPublic; + + /* Server public */ + + private ECPublicKey serverPublic; + private byte[] f; + + @Override + public void init(String name) throws IOException { + final ECParameterSpec spec; + + if ("ecdh-sha2-nistp256".equals(name)) { + spec = ECDSASHA2Verify.EllipticCurves.nistp256; + } + else if ("ecdh-sha2-nistp384".equals(name)) { + spec = ECDSASHA2Verify.EllipticCurves.nistp384; + } + else if ("ecdh-sha2-nistp521".equals(name)) { + spec = ECDSASHA2Verify.EllipticCurves.nistp521; + } + else { + throw new IllegalArgumentException("Unknown EC curve " + name); + } + + KeyPairGenerator kpg; + + try { + kpg = KeyPairGenerator.getInstance("EC"); + kpg.initialize(spec); + KeyPair pair = kpg.generateKeyPair(); + clientPrivate = (ECPrivateKey) pair.getPrivate(); + clientPublic = (ECPublicKey) pair.getPublic(); + } + catch (NoSuchAlgorithmException e) { + throw(IOException) new IOException("No DH keypair generator").initCause(e); + } + catch (InvalidAlgorithmParameterException e) { + throw(IOException) new IOException("Invalid DH parameters").initCause(e); + } + } + + @Override + public byte[] getE() { + return ECDSASHA2Verify.encodeECPoint(clientPublic.getW(), clientPublic.getParams() + .getCurve()); + } + + @Override + protected byte[] getServerE() { + return ECDSASHA2Verify.encodeECPoint(serverPublic.getW(), serverPublic.getParams() + .getCurve()); + } + + @Override + public byte[] getF() { + return f; + } + + @Override + public void setF(byte[] f) throws IOException { + if (clientPublic == null) + throw new IllegalStateException("DhDsaExchange not initialized!"); + + final KeyAgreement ka; + + try { + KeyFactory kf = KeyFactory.getInstance("EC"); + ECParameterSpec params = clientPublic.getParams(); + ECPoint serverPoint = ECDSASHA2Verify.decodeECPoint(f, params.getCurve()); + this.f = f; + this.serverPublic = (ECPublicKey) kf.generatePublic(new ECPublicKeySpec(serverPoint, + params)); + ka = KeyAgreement.getInstance("ECDH"); + ka.init(clientPrivate); + ka.doPhase(serverPublic, true); + } + catch (NoSuchAlgorithmException e) { + throw(IOException) new IOException("No ECDH key agreement method").initCause(e); + } + catch (InvalidKeyException e) { + throw(IOException) new IOException("Invalid ECDH key").initCause(e); + } + catch (InvalidKeySpecException e) { + throw(IOException) new IOException("Invalid ECDH key").initCause(e); + } + + sharedSecret = new BigInteger(ka.generateSecret()); + } + + @Override + public String getHashAlgo() { + return ECDSASHA2Verify.getDigestAlgorithmForParams(clientPublic.getParams()); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/dh/GenericDhExchange.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/dh/GenericDhExchange.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,94 @@ + +package ch.ethz.ssh2.crypto.dh; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; + +import ch.ethz.ssh2.crypto.digest.HashForSSH2Types; +import ch.ethz.ssh2.log.Logger; + + +/** + * DhExchange. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: DhExchange.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ + */ +public abstract class GenericDhExchange { + private static final Logger log = Logger.getLogger(GenericDhExchange.class); + + /* Shared secret */ + + BigInteger sharedSecret; + + protected GenericDhExchange() { + } + + public static GenericDhExchange getInstance(String algo) { + if (algo.startsWith("ecdh-sha2-")) { + return new EcDhExchange(); + } + else { + return new DhExchange(); + } + } + + public abstract void init(String name) throws IOException; + + /** + * @return Returns the e (public value) + * @throws IllegalStateException + */ + public abstract byte[] getE(); + + public void setE(BigInteger e) throws IOException { + throw new IOException(); + } + + /** + * @return Returns the server's e (public value) + * @throws IllegalStateException + */ + protected abstract byte[] getServerE(); + + /** + * @return Returns the shared secret k. + * @throws IllegalStateException + */ + public BigInteger getK() { + if (sharedSecret == null) + throw new IllegalStateException("Shared secret not yet known, need f first!"); + + return sharedSecret; + } + + /** + * @param f + */ + public void setF(BigInteger f) throws IOException { + setF(f.toByteArray()); + } + + public abstract byte[] getF(); + + public abstract void setF(byte[] f) throws IOException; + + public byte[] calculateH(byte[] clientversion, byte[] serverversion, byte[] clientKexPayload, + byte[] serverKexPayload, byte[] hostKey) throws UnsupportedEncodingException, IOException { + HashForSSH2Types hash = new HashForSSH2Types(getHashAlgo()); + log.debug("Client: '" + new String(clientversion, "ISO-8859-1") + "'"); + log.debug("Server: '" + new String(serverversion, "ISO-8859-1") + "'"); + hash.updateByteString(clientversion); + hash.updateByteString(serverversion); + hash.updateByteString(clientKexPayload); + hash.updateByteString(serverKexPayload); + hash.updateByteString(hostKey); + hash.updateByteString(getE()); + hash.updateByteString(getServerE()); + hash.updateBigInt(sharedSecret); + return hash.getDigest(); + } + + public abstract String getHashAlgo(); +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/digest/Digest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/digest/Digest.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.digest; + +import java.security.DigestException; + +/** + * Digest. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public interface Digest { + public int getDigestLength(); + + public void update(byte b); + + public void update(byte[] b); + + public void update(byte b[], int off, int len); + + public void reset(); + + public void digest(byte[] out) throws DigestException; + + public void digest(byte[] out, int off) throws DigestException; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/digest/HMAC.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/digest/HMAC.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.digest; + +import java.security.DigestException; + +/** + * HMAC. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public final class HMAC implements Digest { + Digest md; + byte[] k_xor_ipad; + byte[] k_xor_opad; + + byte[] tmp; + + int size; + + public HMAC(Digest md, byte[] key, int size) throws DigestException { + this.md = md; + this.size = size; + tmp = new byte[md.getDigestLength()]; + final int BLOCKSIZE = 64; + k_xor_ipad = new byte[BLOCKSIZE]; + k_xor_opad = new byte[BLOCKSIZE]; + + if (key.length > BLOCKSIZE) { + md.reset(); + md.update(key); + md.digest(tmp); + key = tmp; + } + + System.arraycopy(key, 0, k_xor_ipad, 0, key.length); + System.arraycopy(key, 0, k_xor_opad, 0, key.length); + + for (int i = 0; i < BLOCKSIZE; i++) { + k_xor_ipad[i] ^= 0x36; + k_xor_opad[i] ^= 0x5C; + } + + md.update(k_xor_ipad); + } + + public final int getDigestLength() { + return size; + } + + public final void update(byte b) { + md.update(b); + } + + public final void update(byte[] b) { + md.update(b); + } + + public final void update(byte[] b, int off, int len) { + md.update(b, off, len); + } + + public final void reset() { + md.reset(); + md.update(k_xor_ipad); + } + + public final void digest(byte[] out) throws DigestException { + digest(out, 0); + } + + public final void digest(byte[] out, int off) throws DigestException { + md.digest(tmp); + md.update(k_xor_opad); + md.update(tmp); + md.digest(tmp); + System.arraycopy(tmp, 0, out, off, size); + md.update(k_xor_ipad); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/digest/HashForSSH2Types.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/digest/HashForSSH2Types.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.digest; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.DigestException; + +/** + * HashForSSH2Types. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class HashForSSH2Types { + Digest md; + + public HashForSSH2Types(Digest md) { + this.md = md; + } + + public HashForSSH2Types(String type) { + if (type.equals("SHA1")) { + md = new SHA1(); + } + else if (type.equals("SHA2")) { + md = new SHA256(); + } + else if (type.equals("MD5")) { + md = new MD5(); + } + else { + throw new IllegalArgumentException(String.format("Unknown algorithm %s", type)); + } + } + + public void updateByte(byte b) { + md.update(b); + } + + public void updateBytes(byte[] b) { + md.update(b); + } + + public void updateUINT32(int v) { + md.update((byte)(v >> 24)); + md.update((byte)(v >> 16)); + md.update((byte)(v >> 8)); + md.update((byte)(v)); + } + + public void updateByteString(byte[] b) { + updateUINT32(b.length); + updateBytes(b); + } + + public void updateBigInt(BigInteger b) { + updateByteString(b.toByteArray()); + } + + public void reset() { + md.reset(); + } + + public int getDigestLength() { + return md.getDigestLength(); + } + + public byte[] getDigest() throws IOException { + byte[] tmp = new byte[md.getDigestLength()]; + getDigest(tmp); + return tmp; + } + + public void getDigest(byte[] out) throws IOException { + try { + getDigest(out, 0); + } + catch (DigestException e) { + throw new IOException(e); + } + } + + public void getDigest(byte[] out, int off) throws DigestException { + md.digest(out, off); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/digest/MAC.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/digest/MAC.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.digest; + +import java.io.IOException; +import java.security.DigestException; + +/** + * MAC. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public final class MAC { + private Digest mac; + private int size; + + public static String[] getMacList() { + // Higher priority (stronger) first. Added SHA-2 algorithms as in RFC 6668 + return new String[] { + // "hmac-sha2-512", // fails interop w/ centos6 + "hmac-sha2-256", + "hmac-sha1", + "hmac-sha1-96", + "hmac-md5", + "hmac-md5-96" + }; + } + + public static void checkMacList(final String[] macs) { + for (String m : macs) { + getKeyLen(m); + } + } + + public static int getKeyLen(final String type) { + if (type.equals("hmac-sha1")) { + return 20; + } + + if (type.equals("hmac-sha1-96")) { + return 20; + } + + if (type.equals("hmac-md5")) { + return 16; + } + + if (type.equals("hmac-md5-96")) { + return 16; + } + + if (type.equals("hmac-sha2-256")) { + return 32; + } + + if (type.equals("hmac-sha2-512")) { + return 64; + } + + throw new IllegalArgumentException(String.format("Unknown algorithm %s", type)); + } + + public MAC(final String type, final byte[] key) throws DigestException { + if (type.equals("hmac-sha1")) { + mac = new HMAC(new SHA1(), key, 20); + } + else if (type.equals("hmac-sha1-96")) { + mac = new HMAC(new SHA1(), key, 12); + } + else if (type.equals("hmac-md5")) { + mac = new HMAC(new MD5(), key, 16); + } + else if (type.equals("hmac-md5-96")) { + mac = new HMAC(new MD5(), key, 12); + } + else if (type.equals("hmac-sha2-256")) { + mac = new HMAC(new SHA256(), key, 32); + } + else if (type.equals("hmac-sha2-512")) { + mac = new HMAC(new SHA512(), key, 64); + } + else { + throw new IllegalArgumentException(String.format("Unknown algorithm %s", type)); + } + + size = mac.getDigestLength(); + } + + public final void initMac(final int seq) { + mac.reset(); + mac.update((byte)(seq >> 24)); + mac.update((byte)(seq >> 16)); + mac.update((byte)(seq >> 8)); + mac.update((byte)(seq)); + } + + public final void update(byte[] packetdata, int off, int len) { + mac.update(packetdata, off, len); + } + + public final void getMac(byte[] out, int off) throws IOException { + try { + mac.digest(out, off); + } + catch (DigestException e) { + throw new IOException(e); + } + } + + public final int size() { + return size; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/digest/MD5.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/digest/MD5.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.digest; + +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * @version $Id: MD5.java 154 2014-04-28 11:45:02Z dkocher@sudo.ch $ + */ +public final class MD5 implements Digest { + + private MessageDigest md; + + public MD5() { + try { + md = MessageDigest.getInstance("MD5"); + } + catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } + } + + public final int getDigestLength() { + return md.getDigestLength(); + } + + public final void reset() { + md.reset(); + } + + public final void update(byte b[]) { + this.update(b, 0, b.length); + } + + public final void update(byte b[], int off, int len) { + md.update(b, off, len); + } + + public final void update(byte b) { + md.update(b); + } + + public final void digest(byte[] out) throws DigestException { + this.digest(out, 0); + } + + public final void digest(byte[] out, int off) throws DigestException { + md.digest(out, off, out.length); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/digest/SHA1.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/digest/SHA1.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.digest; + +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * @version $Id: SHA1.java 154 2014-04-28 11:45:02Z dkocher@sudo.ch $ + */ +public final class SHA1 implements Digest { + + private MessageDigest md; + + public SHA1() { + try { + md = MessageDigest.getInstance("SHA-1"); + } + catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } + } + + public final int getDigestLength() { + return md.getDigestLength(); + } + + public final void reset() { + md.reset(); + } + + public final void update(byte b[]) { + this.update(b, 0, b.length); + } + + public final void update(byte b[], int off, int len) { + md.update(b, off, len); + } + + public final void update(byte b) { + md.update(b); + } + + public final void digest(byte[] out) throws DigestException { + this.digest(out, 0); + } + + public final void digest(byte[] out, int off) throws DigestException { + md.digest(out, off, out.length); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/digest/SHA256.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/digest/SHA256.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.digest; + +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * @version $Id: SHA256.java 152 2014-04-28 11:02:23Z dkocher@sudo.ch $ + */ +public final class SHA256 implements Digest { + + private MessageDigest md; + + public SHA256() { + try { + md = MessageDigest.getInstance("SHA-256"); + } + catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } + } + + public final int getDigestLength() { + return md.getDigestLength(); + } + + public final void reset() { + md.reset(); + } + + public final void update(byte b[]) { + this.update(b, 0, b.length); + } + + public final void update(byte b[], int off, int len) { + md.update(b, off, len); + } + + public final void update(byte b) { + md.update(b); + } + + public final void digest(byte[] out) throws DigestException { + this.digest(out, 0); + } + + public final void digest(byte[] out, int off) throws DigestException { + md.digest(out, off, out.length); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/crypto/digest/SHA512.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/crypto/digest/SHA512.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.crypto.digest; + +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * @version $Id: SHA512.java 152 2014-04-28 11:02:23Z dkocher@sudo.ch $ + */ +public final class SHA512 implements Digest { + + private MessageDigest md; + + public SHA512() { + try { + md = MessageDigest.getInstance("SHA-512"); + } + catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } + } + + public final int getDigestLength() { + return md.getDigestLength(); + } + + public final void reset() { + md.reset(); + } + + public final void update(byte b[]) { + md.update(b); + } + + public final void update(byte b[], int off, int len) { + md.update(b, off, len); + } + + public final void update(byte b) { + md.update(b); + } + + public final void digest(byte[] out) { + md.digest(out); + } + + public final void digest(byte[] out, int off) throws DigestException { + md.digest(out, off, out.length); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/log/Logger.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/log/Logger.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2.log; + +import android.util.Log; + +public class Logger { + private static final String TAG = "ConnectBot.ssh"; + public static boolean enabled = false; + + public static Logger getLogger(Class x) { + return new Logger(); + } + + public Logger() { + } + + public final boolean isEnabled() { + return enabled; + } + + public boolean isDebugEnabled() { + return enabled; + } + + public void debug(String message) { + if (enabled) Log.d(TAG, message); + } + + public boolean isInfoEnabled() { + return enabled; + } + + public void info(String message) { + if (enabled) Log.i(TAG, message); + } + + public boolean isWarningEnabled() { + return enabled; + } + + public void warning(String message) { + if (enabled) Log.w(TAG, message); + } +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketChannelAuthAgentReq.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketChannelAuthAgentReq.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,30 @@ +package ch.ethz.ssh2.packets; + +/** + * PacketGlobalAuthAgent. + * + * @author Kenny Root, kenny@the-b.org + * @version $Id$ + */ +public class PacketChannelAuthAgentReq { + byte[] payload; + + public int recipientChannelID; + + public PacketChannelAuthAgentReq(int recipientChannelID) { + this.recipientChannelID = recipientChannelID; + } + + public byte[] getPayload() { + if (payload == null) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("auth-agent-req@openssh.com"); + tw.writeBoolean(true); // want reply + payload = tw.getBytes(); + } + + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketChannelFailure.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketChannelFailure.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketChannelFailure.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketChannelFailure { + private final byte[] payload; + + public PacketChannelFailure(int recipientChannelID) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_FAILURE); + tw.writeUINT32(recipientChannelID); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketChannelOpenConfirmation.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketChannelOpenConfirmation.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketChannelOpenConfirmation.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketChannelOpenConfirmation { + + private final byte[] payload; + + private final int recipientChannelID; + private final int senderChannelID; + private final int initialWindowSize; + private final int maxPacketSize; + + public PacketChannelOpenConfirmation(int recipientChannelID, int senderChannelID, int initialWindowSize, + int maxPacketSize) { + this.recipientChannelID = recipientChannelID; + this.senderChannelID = senderChannelID; + this.initialWindowSize = initialWindowSize; + this.maxPacketSize = maxPacketSize; + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION); + tw.writeUINT32(recipientChannelID); + tw.writeUINT32(senderChannelID); + tw.writeUINT32(initialWindowSize); + tw.writeUINT32(maxPacketSize); + payload = tw.getBytes(); + } + + public PacketChannelOpenConfirmation(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION) { + throw new PacketTypeException(packet_type); + } + + recipientChannelID = tr.readUINT32(); + senderChannelID = tr.readUINT32(); + initialWindowSize = tr.readUINT32(); + maxPacketSize = tr.readUINT32(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public int getRecipientChannelID() { + return recipientChannelID; + } + + public int getSenderChannelID() { + return senderChannelID; + } + + public int getInitialWindowSize() { + return initialWindowSize; + } + + public int getMaxPacketSize() { + return maxPacketSize; + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketChannelOpenFailure.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketChannelOpenFailure.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; + +/** + * @author Christian Plattner + * @version $Id: PacketChannelOpenFailure.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketChannelOpenFailure { + + private final byte[] payload; + + public PacketChannelOpenFailure(int recipientChannelID, int reasonCode, String description, + String languageTag) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN_FAILURE); + tw.writeUINT32(recipientChannelID); + tw.writeUINT32(reasonCode); + tw.writeString(description); + tw.writeString(languageTag); + payload = tw.getBytes(); + } + + public PacketChannelOpenFailure(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_CHANNEL_OPEN_FAILURE) { + throw new IOException( + "This is not a SSH_MSG_CHANNEL_OPEN_FAILURE! (" + + packet_type + ")" + ); + } + + int recipientChannelID = tr.readUINT32(); + int reasonCode = tr.readUINT32(); + String description = tr.readString(); + String languageTag = tr.readString(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketChannelSuccess.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketChannelSuccess.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketChannelSuccess.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketChannelSuccess { + + private final byte[] payload; + + public PacketChannelSuccess(int recipientChannelID) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_SUCCESS); + tw.writeUINT32(recipientChannelID); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketChannelWindowAdjust.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketChannelWindowAdjust.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; + +/** + * @author Christian Plattner + * @version $Id: PacketChannelWindowAdjust.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketChannelWindowAdjust { + + private final byte[] payload; + + public PacketChannelWindowAdjust(int recipientChannelID, int windowChange) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST); + tw.writeUINT32(recipientChannelID); + tw.writeUINT32(windowChange); + payload = tw.getBytes(); + } + + public PacketChannelWindowAdjust(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST) { + throw new IOException( + "This is not a SSH_MSG_CHANNEL_WINDOW_ADJUST! (" + + packet_type + ")" + ); + } + + int recipientChannelID = tr.readUINT32(); + int windowChange = tr.readUINT32(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketDisconnect.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketDisconnect.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketDisconnect.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketDisconnect { + + public enum Reason { + SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT, + SSH_DISCONNECT_PROTOCOL_ERROR, + SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + SSH_DISCONNECT_RESERVED, + SSH_DISCONNECT_MAC_ERROR, + SSH_DISCONNECT_COMPRESSION_ERROR, + SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, + SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, + SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE, + SSH_DISCONNECT_CONNECTION_LOST, + SSH_DISCONNECT_BY_APPLICATION, + SSH_DISCONNECT_TOO_MANY_CONNECTIONS, + SSH_DISCONNECT_AUTH_CANCELLED_BY_USER, + SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, + SSH_DISCONNECT_ILLEGAL_USER_NAME + } + + private final byte[] payload; + + private final Reason reason; + + private final String message; + + public PacketDisconnect(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_DISCONNECT) { + throw new PacketTypeException(packet_type); + } + + reason = PacketDisconnect.Reason.values()[tr.readUINT32()]; + message = tr.readString(); + String lang = tr.readString(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public PacketDisconnect(Reason reason, String desc) { + this.reason = reason; + this.message = desc; + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_DISCONNECT); + tw.writeUINT32(reason.ordinal()); + tw.writeString(desc); + tw.writeString(""); + payload = tw.getBytes(); + } + + public Reason getReason() { + return reason; + } + + public String getMessage() { + return message; + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketGlobalCancelForwardRequest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketGlobalCancelForwardRequest.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketGlobalCancelForwardRequest.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketGlobalCancelForwardRequest { + + private final byte[] payload; + + public PacketGlobalCancelForwardRequest(boolean wantReply, String bindAddress, int bindPort) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_GLOBAL_REQUEST); + tw.writeString("cancel-tcpip-forward"); + tw.writeBoolean(wantReply); + tw.writeString(bindAddress); + tw.writeUINT32(bindPort); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketGlobalForwardRequest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketGlobalForwardRequest.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketGlobalForwardRequest.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketGlobalForwardRequest { + + private final byte[] payload; + + public PacketGlobalForwardRequest(boolean wantReply, String bindAddress, int bindPort) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_GLOBAL_REQUEST); + tw.writeString("tcpip-forward"); + tw.writeBoolean(wantReply); + tw.writeString(bindAddress); + tw.writeUINT32(bindPort); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketIgnore.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketIgnore.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketIgnore.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketIgnore { + private final byte[] payload; + + public PacketIgnore(byte[] data) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_IGNORE); + + if (data != null) { + tw.writeString(data, 0, data.length); + } + else { + tw.writeString(""); + } + + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketKexDHInit.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketKexDHInit.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; +import java.math.BigInteger; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketKexDHInit.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketKexDHInit { + + private final byte[] payload; + + private final BigInteger e; + + public PacketKexDHInit(BigInteger e) { + this.e = e; + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEXDH_INIT); + tw.writeMPInt(e); + payload = tw.getBytes(); + } + + public PacketKexDHInit(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_KEXDH_INIT) { + throw new PacketTypeException(packet_type); + } + + e = tr.readMPINT(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public BigInteger getE() { + return e; + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketKexDHReply.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketKexDHReply.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; +import java.math.BigInteger; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketKexDHReply.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketKexDHReply { + + private final byte[] payload; + + private final byte[] hostKey; + private final BigInteger f; + private final byte[] signature; + + public PacketKexDHReply(byte[] hostKey, BigInteger f, byte[] signature) { + this.hostKey = hostKey; + this.f = f; + this.signature = signature; + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEXDH_REPLY); + tw.writeString(hostKey, 0, hostKey.length); + tw.writeMPInt(f); + tw.writeString(signature, 0, signature.length); + payload = tw.getBytes(); + } + + public PacketKexDHReply(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_KEXDH_REPLY) { + throw new PacketTypeException(packet_type); + } + + hostKey = tr.readByteString(); + f = tr.readMPINT(); + signature = tr.readByteString(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } + + public BigInteger getF() { + return f; + } + + public byte[] getHostKey() { + return hostKey; + } + + public byte[] getSignature() { + return signature; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketKexDhGexGroup.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketKexDhGexGroup.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; +import java.math.BigInteger; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketKexDhGexGroup.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketKexDhGexGroup { + + private final BigInteger p; + private final BigInteger g; + + public PacketKexDhGexGroup(byte payload[]) throws IOException { + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_KEX_DH_GEX_GROUP) { + throw new PacketTypeException(packet_type); + } + + p = tr.readMPINT(); + g = tr.readMPINT(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public BigInteger getG() { + return g; + } + + public BigInteger getP() { + return p; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketKexDhGexInit.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketKexDhGexInit.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.math.BigInteger; + +/** + * @author Christian Plattner + * @version $Id: PacketKexDhGexInit.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketKexDhGexInit { + + private final byte[] payload; + + public PacketKexDhGexInit(BigInteger e) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEX_DH_GEX_INIT); + tw.writeMPInt(e); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketKexDhGexReply.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketKexDhGexReply.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; +import java.math.BigInteger; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketKexDhGexReply.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketKexDhGexReply { + + private final byte[] hostKey; + private final BigInteger f; + private final byte[] signature; + + public PacketKexDhGexReply(byte payload[]) throws IOException { + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_KEX_DH_GEX_REPLY) { + throw new PacketTypeException(packet_type); + } + + hostKey = tr.readByteString(); + f = tr.readMPINT(); + signature = tr.readByteString(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public BigInteger getF() { + return f; + } + + public byte[] getHostKey() { + return hostKey; + } + + public byte[] getSignature() { + return signature; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketKexDhGexRequest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketKexDhGexRequest.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import ch.ethz.ssh2.DHGexParameters; + +/** + * @author Christian Plattner + * @version $Id: PacketKexDhGexRequest.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketKexDhGexRequest { + private final byte[] payload; + + public PacketKexDhGexRequest(DHGexParameters para) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEX_DH_GEX_REQUEST); + tw.writeUINT32(para.getMin_group_len()); + tw.writeUINT32(para.getPref_group_len()); + tw.writeUINT32(para.getMax_group_len()); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketKexDhGexRequestOld.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketKexDhGexRequestOld.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import ch.ethz.ssh2.DHGexParameters; + +/** + * @author Christian Plattner + * @version $Id: PacketKexDhGexRequestOld.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketKexDhGexRequestOld { + + private final byte[] payload; + + public PacketKexDhGexRequestOld(DHGexParameters para) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEX_DH_GEX_REQUEST_OLD); + tw.writeUINT32(para.getPref_group_len()); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketKexInit.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketKexInit.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; +import java.security.SecureRandom; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; +import ch.ethz.ssh2.crypto.CryptoWishList; +import ch.ethz.ssh2.transport.KexParameters; + +/** + * @author Christian Plattner + * @version $Id: PacketKexInit.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketKexInit { + private final byte[] payload; + + KexParameters kp = new KexParameters(); + + public PacketKexInit(CryptoWishList cwl, SecureRandom rnd) { + kp.cookie = new byte[16]; + rnd.nextBytes(kp.cookie); + kp.kex_algorithms = cwl.kexAlgorithms; + kp.server_host_key_algorithms = cwl.serverHostKeyAlgorithms; + kp.encryption_algorithms_client_to_server = cwl.c2s_enc_algos; + kp.encryption_algorithms_server_to_client = cwl.s2c_enc_algos; + kp.mac_algorithms_client_to_server = cwl.c2s_mac_algos; + kp.mac_algorithms_server_to_client = cwl.s2c_mac_algos; + kp.compression_algorithms_client_to_server = cwl.c2s_comp_algos; + kp.compression_algorithms_server_to_client = cwl.s2c_comp_algos; + kp.languages_client_to_server = new String[] {""}; + kp.languages_server_to_client = new String[] {""}; + kp.first_kex_packet_follows = false; + kp.reserved_field1 = 0; + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_KEXINIT); + tw.writeBytes(kp.cookie, 0, 16); + tw.writeNameList(kp.kex_algorithms); + tw.writeNameList(kp.server_host_key_algorithms); + tw.writeNameList(kp.encryption_algorithms_client_to_server); + tw.writeNameList(kp.encryption_algorithms_server_to_client); + tw.writeNameList(kp.mac_algorithms_client_to_server); + tw.writeNameList(kp.mac_algorithms_server_to_client); + tw.writeNameList(kp.compression_algorithms_client_to_server); + tw.writeNameList(kp.compression_algorithms_server_to_client); + tw.writeNameList(kp.languages_client_to_server); + tw.writeNameList(kp.languages_server_to_client); + tw.writeBoolean(kp.first_kex_packet_follows); + tw.writeUINT32(kp.reserved_field1); + payload = tw.getBytes(); + } + + public PacketKexInit(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_KEXINIT) { + throw new PacketTypeException(packet_type); + } + + kp.cookie = tr.readBytes(16); + kp.kex_algorithms = tr.readNameList(); + kp.server_host_key_algorithms = tr.readNameList(); + kp.encryption_algorithms_client_to_server = tr.readNameList(); + kp.encryption_algorithms_server_to_client = tr.readNameList(); + kp.mac_algorithms_client_to_server = tr.readNameList(); + kp.mac_algorithms_server_to_client = tr.readNameList(); + kp.compression_algorithms_client_to_server = tr.readNameList(); + kp.compression_algorithms_server_to_client = tr.readNameList(); + kp.languages_client_to_server = tr.readNameList(); + kp.languages_server_to_client = tr.readNameList(); + kp.first_kex_packet_follows = tr.readBoolean(); + kp.reserved_field1 = tr.readUINT32(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } + + public KexParameters getKexParameters() { + return kp; + } + + public String[] getCompression_algorithms_client_to_server() { + return kp.compression_algorithms_client_to_server; + } + + public String[] getCompression_algorithms_server_to_client() { + return kp.compression_algorithms_server_to_client; + } + + public byte[] getCookie() { + return kp.cookie; + } + + public String[] getEncryption_algorithms_client_to_server() { + return kp.encryption_algorithms_client_to_server; + } + + public String[] getEncryption_algorithms_server_to_client() { + return kp.encryption_algorithms_server_to_client; + } + + public boolean isFirst_kex_packet_follows() { + return kp.first_kex_packet_follows; + } + + public String[] getKex_algorithms() { + return kp.kex_algorithms; + } + + public String[] getLanguages_client_to_server() { + return kp.languages_client_to_server; + } + + public String[] getLanguages_server_to_client() { + return kp.languages_server_to_client; + } + + public String[] getMac_algorithms_client_to_server() { + return kp.mac_algorithms_client_to_server; + } + + public String[] getMac_algorithms_server_to_client() { + return kp.mac_algorithms_server_to_client; + } + + public int getReserved_field1() { + return kp.reserved_field1; + } + + public String[] getServer_host_key_algorithms() { + return kp.server_host_key_algorithms; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketNewKeys.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketNewKeys.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketNewKeys.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketNewKeys { + private final byte[] payload; + + public PacketNewKeys() { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_NEWKEYS); + payload = tw.getBytes(); + } + + public PacketNewKeys(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_NEWKEYS) { + throw new PacketTypeException(packet_type); + } + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketOpenDirectTCPIPChannel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketOpenDirectTCPIPChannel.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketOpenDirectTCPIPChannel.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketOpenDirectTCPIPChannel { + private final byte[] payload; + + public PacketOpenDirectTCPIPChannel(int channelID, int initialWindowSize, int maxPacketSize, + String host_to_connect, int port_to_connect, String originator_IP_address, + int originator_port) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN); + tw.writeString("direct-tcpip"); + tw.writeUINT32(channelID); + tw.writeUINT32(initialWindowSize); + tw.writeUINT32(maxPacketSize); + tw.writeString(host_to_connect); + tw.writeUINT32(port_to_connect); + tw.writeString(originator_IP_address); + tw.writeUINT32(originator_port); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketOpenSessionChannel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketOpenSessionChannel.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketOpenSessionChannel.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketOpenSessionChannel { + private final byte[] payload; + + public PacketOpenSessionChannel(int channelID, int initialWindowSize, int maxPacketSize) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_OPEN); + tw.writeString("session"); + tw.writeUINT32(channelID); + tw.writeUINT32(initialWindowSize); + tw.writeUINT32(maxPacketSize); + payload = tw.getBytes(); + } + + public PacketOpenSessionChannel(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_CHANNEL_OPEN) { + throw new PacketTypeException(packet_type); + } + + int channelID = tr.readUINT32(); + int initialWindowSize = tr.readUINT32(); + int maxPacketSize = tr.readUINT32(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketServiceAccept.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketServiceAccept.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketServiceAccept.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketServiceAccept { + private final byte[] payload; + + public PacketServiceAccept(String serviceName) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_SERVICE_ACCEPT); + tw.writeString(serviceName); + payload = tw.getBytes(); + } + + public PacketServiceAccept(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_SERVICE_ACCEPT) { + throw new PacketTypeException(packet_type); + } + + if (tr.remain() != 0) { + String serviceName = tr.readString(); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketServiceRequest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketServiceRequest.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketServiceRequest.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketServiceRequest { + private final byte[] payload; + + private final String serviceName; + + public PacketServiceRequest(String serviceName) { + this.serviceName = serviceName; + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_SERVICE_REQUEST); + tw.writeString(serviceName); + payload = tw.getBytes(); + } + + public String getServiceName() { + return serviceName; + } + + public PacketServiceRequest(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_SERVICE_REQUEST) { + throw new PacketTypeException(packet_type); + } + + serviceName = tr.readString(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketSessionExecCommand.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketSessionExecCommand.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +/** + * @author Christian Plattner + * @version $Id: PacketSessionExecCommand.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketSessionExecCommand { + private final byte[] payload; + + public PacketSessionExecCommand(int recipientChannelID, boolean wantReply, String command, String charsetName) throws UnsupportedEncodingException { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("exec"); + tw.writeBoolean(wantReply); + tw.writeString(command, charsetName); + payload = tw.getBytes(); + } + + public byte[] getPayload() throws IOException { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketSessionPtyRequest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketSessionPtyRequest.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketSessionPtyRequest.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketSessionPtyRequest { + private final byte[] payload; + + public PacketSessionPtyRequest(int recipientChannelID, boolean wantReply, String term, + int character_width, int character_height, int pixel_width, int pixel_height, + byte[] terminal_modes) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("pty-req"); + tw.writeBoolean(wantReply); + tw.writeString(term); + tw.writeUINT32(character_width); + tw.writeUINT32(character_height); + tw.writeUINT32(pixel_width); + tw.writeUINT32(pixel_height); + tw.writeString(terminal_modes, 0, terminal_modes.length); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketSessionStartShell.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketSessionStartShell.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketSessionStartShell.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketSessionStartShell { + private final byte[] payload; + + public PacketSessionStartShell(int recipientChannelID, boolean wantReply) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("shell"); + tw.writeBoolean(wantReply); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketSessionSubsystemRequest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketSessionSubsystemRequest.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketSessionSubsystemRequest.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketSessionSubsystemRequest { + private final byte[] payload; + + public PacketSessionSubsystemRequest(int recipientChannelID, boolean wantReply, String subsystem) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("subsystem"); + tw.writeBoolean(wantReply); + tw.writeString(subsystem); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketSessionX11Request.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketSessionX11Request.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketSessionX11Request.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketSessionX11Request { + private final byte[] payload; + + public PacketSessionX11Request(int recipientChannelID, boolean wantReply, boolean singleConnection, + String x11AuthenticationProtocol, String x11AuthenticationCookie, int x11ScreenNumber) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("x11-req"); + tw.writeBoolean(wantReply); + tw.writeBoolean(singleConnection); + tw.writeString(x11AuthenticationProtocol); + tw.writeString(x11AuthenticationCookie); + tw.writeUINT32(x11ScreenNumber); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketUserauthBanner.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketUserauthBanner.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketUserauthBanner.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketUserauthBanner { + private final byte[] payload; + + private final String message; + + public PacketUserauthBanner(String message) { + this.message = message; + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_BANNER); + tw.writeString(message); + tw.writeString(""); + payload = tw.getBytes(); + } + + public String getBanner() { + return message; + } + + public PacketUserauthBanner(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_BANNER) { + throw new PacketTypeException(packet_type); + } + + message = tr.readString("UTF-8"); + String language = tr.readString(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketUserauthFailure.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketUserauthFailure.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketUserauthFailure.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketUserauthFailure { + + private final byte[] payload; + + private Set authThatCanContinue; + + private boolean partialSuccess; + + public PacketUserauthFailure(Set authThatCanContinue, boolean partialSuccess) { + this.authThatCanContinue = authThatCanContinue; + this.partialSuccess = partialSuccess; + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_FAILURE); + tw.writeNameList(authThatCanContinue.toArray(new String[authThatCanContinue.size()])); + tw.writeBoolean(partialSuccess); + payload = tw.getBytes(); + } + + public PacketUserauthFailure(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_FAILURE) { + throw new PacketTypeException(packet_type); + } + + authThatCanContinue = new HashSet(Arrays.asList(tr.readNameList())); + partialSuccess = tr.readBoolean(); + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } + + public Set getAuthThatCanContinue() { + return authThatCanContinue; + } + + public boolean isPartialSuccess() { + return partialSuccess; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketUserauthInfoRequest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketUserauthInfoRequest.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketUserauthInfoRequest.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketUserauthInfoRequest { + + private final String name; + private final String instruction; + private final String languageTag; + private final int numPrompts; + + private final String prompt[]; + private final boolean echo[]; + + public PacketUserauthInfoRequest(byte payload[]) throws IOException { + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_INFO_REQUEST) { + throw new PacketTypeException(packet_type); + } + + name = tr.readString(); + instruction = tr.readString(); + languageTag = tr.readString(); + numPrompts = tr.readUINT32(); + prompt = new String[numPrompts]; + echo = new boolean[numPrompts]; + + for (int i = 0; i < numPrompts; i++) { + prompt[i] = tr.readString(); + echo[i] = tr.readBoolean(); + } + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public boolean[] getEcho() { + return echo; + } + + public String getInstruction() { + return instruction; + } + + public String getLanguageTag() { + return languageTag; + } + + public String getName() { + return name; + } + + public int getNumPrompts() { + return numPrompts; + } + + public String[] getPrompt() { + return prompt; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketUserauthInfoResponse.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketUserauthInfoResponse.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketUserauthInfoResponse.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketUserauthInfoResponse { + private final byte[] payload; + + public PacketUserauthInfoResponse(String[] responses) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_INFO_RESPONSE); + tw.writeUINT32(responses.length); + + for (String response : responses) { + tw.writeString(response); + } + + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketUserauthRequestInteractive.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketUserauthRequestInteractive.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +/** + * @author Christian Plattner + * @version $Id: PacketUserauthRequestInteractive.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketUserauthRequestInteractive { + + private final byte[] payload; + + public PacketUserauthRequestInteractive(String serviceName, String user, String[] submethods) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString(serviceName); + tw.writeString("keyboard-interactive"); + tw.writeString(""); // draft-ietf-secsh-newmodes-04.txt says that + // the language tag should be empty. + tw.writeNameList(null == submethods ? new String[] {} : submethods); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketUserauthRequestNone.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketUserauthRequestNone.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketUserauthRequestNone.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketUserauthRequestNone { + + private final byte[] payload; + + public PacketUserauthRequestNone(String serviceName, String user) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString(serviceName); + tw.writeString("none"); + payload = tw.getBytes(); + } + + public PacketUserauthRequestNone(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_REQUEST) { + throw new PacketTypeException(packet_type); + } + + String userName = tr.readString(); + String serviceName = tr.readString(); + String method = tr.readString(); + + if (!method.equals("none")) { + throw new IOException(String.format("Unexpected method %s", method)); + } + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketUserauthRequestPassword.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketUserauthRequestPassword.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketUserauthRequestPassword.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketUserauthRequestPassword { + + private final byte[] payload; + + public PacketUserauthRequestPassword(String serviceName, String user, String pass) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString(serviceName); + tw.writeString("password"); + tw.writeBoolean(false); + tw.writeString(pass); + payload = tw.getBytes(); + } + + public PacketUserauthRequestPassword(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_REQUEST) { + throw new PacketTypeException(packet_type); + } + + String userName = tr.readString(); + String serviceName = tr.readString(); + String method = tr.readString(); + + if (!method.equals("password")) { + throw new IOException(String.format("Unexpected method %s", method)); + } + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketUserauthRequestPublicKey.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketUserauthRequestPublicKey.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketUserauthRequestPublicKey.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketUserauthRequestPublicKey { + + private final byte[] payload; + + public PacketUserauthRequestPublicKey(String serviceName, String user, + String pkAlgorithmName, byte[] pk, byte[] sig) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString(serviceName); + tw.writeString("publickey"); + tw.writeBoolean(true); + tw.writeString(pkAlgorithmName); + tw.writeString(pk, 0, pk.length); + tw.writeString(sig, 0, sig.length); + payload = tw.getBytes(); + } + + public PacketUserauthRequestPublicKey(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_REQUEST) { + throw new PacketTypeException(packet_type); + } + + String userName = tr.readString(); + String serviceName = tr.readString(); + String method = tr.readString(); + + if (!method.equals("publickey")) { + throw new IOException(String.format("Unexpected method %s", method)); + } + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketUserauthSuccess.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketUserauthSuccess.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.PacketTypeException; + +/** + * @author Christian Plattner + * @version $Id: PacketUserauthSuccess.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class PacketUserauthSuccess { + + private final byte[] payload; + + public PacketUserauthSuccess() { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_USERAUTH_SUCCESS); + payload = tw.getBytes(); + } + + public PacketUserauthSuccess(byte payload[]) throws IOException { + this.payload = payload; + TypesReader tr = new TypesReader(payload); + int packet_type = tr.readByte(); + + if (packet_type != Packets.SSH_MSG_USERAUTH_SUCCESS) { + throw new PacketTypeException(packet_type); + } + + if (tr.remain() != 0) { + throw new PacketFormatException(String.format("Padding in %s", Packets.getMessageName(packet_type))); + } + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/PacketWindowChange.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/PacketWindowChange.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,30 @@ +package ch.ethz.ssh2.packets; + +/** + * Indicates that that size of the terminal (window) size has changed on the client side. + *

+ * See section 6.7 of RFC 4254. + * + * @author Kohsuke Kawaguchi + */ +public final class PacketWindowChange { + private final byte[] payload; + + public PacketWindowChange(int recipientChannelID, + int character_width, int character_height, int pixel_width, int pixel_height) { + TypesWriter tw = new TypesWriter(); + tw.writeByte(Packets.SSH_MSG_CHANNEL_REQUEST); + tw.writeUINT32(recipientChannelID); + tw.writeString("window-change"); + tw.writeBoolean(false); + tw.writeUINT32(character_width); + tw.writeUINT32(character_height); + tw.writeUINT32(pixel_width); + tw.writeUINT32(pixel_height); + payload = tw.getBytes(); + } + + public byte[] getPayload() { + return payload; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/Packets.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/Packets.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +/** + * Packets. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public final class Packets { + public static final int SSH_MSG_DISCONNECT = 1; + public static final int SSH_MSG_IGNORE = 2; + public static final int SSH_MSG_UNIMPLEMENTED = 3; + public static final int SSH_MSG_DEBUG = 4; + public static final int SSH_MSG_SERVICE_REQUEST = 5; + public static final int SSH_MSG_SERVICE_ACCEPT = 6; + + public static final int SSH_MSG_KEXINIT = 20; + public static final int SSH_MSG_NEWKEYS = 21; + + public static final int SSH_MSG_KEXDH_INIT = 30; + public static final int SSH_MSG_KEXDH_REPLY = 31; + + public static final int SSH_MSG_KEX_DH_GEX_REQUEST_OLD = 30; + public static final int SSH_MSG_KEX_DH_GEX_REQUEST = 34; + public static final int SSH_MSG_KEX_DH_GEX_GROUP = 31; + public static final int SSH_MSG_KEX_DH_GEX_INIT = 32; + public static final int SSH_MSG_KEX_DH_GEX_REPLY = 33; + + public static final int SSH_MSG_USERAUTH_REQUEST = 50; + public static final int SSH_MSG_USERAUTH_FAILURE = 51; + public static final int SSH_MSG_USERAUTH_SUCCESS = 52; + public static final int SSH_MSG_USERAUTH_BANNER = 53; + public static final int SSH_MSG_USERAUTH_INFO_REQUEST = 60; + public static final int SSH_MSG_USERAUTH_INFO_RESPONSE = 61; + + public static final int SSH_MSG_GLOBAL_REQUEST = 80; + public static final int SSH_MSG_REQUEST_SUCCESS = 81; + public static final int SSH_MSG_REQUEST_FAILURE = 82; + + public static final int SSH_MSG_CHANNEL_OPEN = 90; + public static final int SSH_MSG_CHANNEL_OPEN_CONFIRMATION = 91; + public static final int SSH_MSG_CHANNEL_OPEN_FAILURE = 92; + public static final int SSH_MSG_CHANNEL_WINDOW_ADJUST = 93; + public static final int SSH_MSG_CHANNEL_DATA = 94; + public static final int SSH_MSG_CHANNEL_EXTENDED_DATA = 95; + public static final int SSH_MSG_CHANNEL_EOF = 96; + public static final int SSH_MSG_CHANNEL_CLOSE = 97; + public static final int SSH_MSG_CHANNEL_REQUEST = 98; + public static final int SSH_MSG_CHANNEL_SUCCESS = 99; + public static final int SSH_MSG_CHANNEL_FAILURE = 100; + + public static final int SSH_EXTENDED_DATA_STDERR = 1; + + public static final int SSH_OPEN_ADMINISTRATIVELY_PROHIBITED = 1; + public static final int SSH_OPEN_CONNECT_FAILED = 2; + public static final int SSH_OPEN_UNKNOWN_CHANNEL_TYPE = 3; + public static final int SSH_OPEN_RESOURCE_SHORTAGE = 4; + + private static final String[] reverseNames = new String[101]; + + static { + reverseNames[1] = "SSH_MSG_DISCONNECT"; + reverseNames[2] = "SSH_MSG_IGNORE"; + reverseNames[3] = "SSH_MSG_UNIMPLEMENTED"; + reverseNames[4] = "SSH_MSG_DEBUG"; + reverseNames[5] = "SSH_MSG_SERVICE_REQUEST"; + reverseNames[6] = "SSH_MSG_SERVICE_ACCEPT"; + reverseNames[20] = "SSH_MSG_KEXINIT"; + reverseNames[21] = "SSH_MSG_NEWKEYS"; + reverseNames[30] = "SSH_MSG_KEXDH_INIT"; + reverseNames[31] = "SSH_MSG_KEXDH_REPLY/SSH_MSG_KEX_DH_GEX_GROUP"; + reverseNames[32] = "SSH_MSG_KEX_DH_GEX_INIT"; + reverseNames[33] = "SSH_MSG_KEX_DH_GEX_REPLY"; + reverseNames[34] = "SSH_MSG_KEX_DH_GEX_REQUEST"; + reverseNames[50] = "SSH_MSG_USERAUTH_REQUEST"; + reverseNames[51] = "SSH_MSG_USERAUTH_FAILURE"; + reverseNames[52] = "SSH_MSG_USERAUTH_SUCCESS"; + reverseNames[53] = "SSH_MSG_USERAUTH_BANNER"; + reverseNames[60] = "SSH_MSG_USERAUTH_INFO_REQUEST"; + reverseNames[61] = "SSH_MSG_USERAUTH_INFO_RESPONSE"; + reverseNames[80] = "SSH_MSG_GLOBAL_REQUEST"; + reverseNames[81] = "SSH_MSG_REQUEST_SUCCESS"; + reverseNames[82] = "SSH_MSG_REQUEST_FAILURE"; + reverseNames[90] = "SSH_MSG_CHANNEL_OPEN"; + reverseNames[91] = "SSH_MSG_CHANNEL_OPEN_CONFIRMATION"; + reverseNames[92] = "SSH_MSG_CHANNEL_OPEN_FAILURE"; + reverseNames[93] = "SSH_MSG_CHANNEL_WINDOW_ADJUST"; + reverseNames[94] = "SSH_MSG_CHANNEL_DATA"; + reverseNames[95] = "SSH_MSG_CHANNEL_EXTENDED_DATA"; + reverseNames[96] = "SSH_MSG_CHANNEL_EOF"; + reverseNames[97] = "SSH_MSG_CHANNEL_CLOSE"; + reverseNames[98] = "SSH_MSG_CHANNEL_REQUEST"; + reverseNames[99] = "SSH_MSG_CHANNEL_SUCCESS"; + reverseNames[100] = "SSH_MSG_CHANNEL_FAILURE"; + } + + public static final String getMessageName(int type) { + String res = null; + + if ((type >= 0) && (type < reverseNames.length)) { + res = reverseNames[type]; + } + + return (res == null) ? ("UNKNOWN MSG " + type) : res; + } + + // public static final void debug(String tag, byte[] msg) + // { + // System.err.println(tag + " Type: " + msg[0] + ", LEN: " + msg.length); + // + // for (int i = 0; i < msg.length; i++) + // { + // if (((msg[i] >= 'a') && (msg[i] <= 'z')) || ((msg[i] >= 'A') && (msg[i] <= 'Z')) + // || ((msg[i] >= '0') && (msg[i] <= '9')) || (msg[i] == ' ')) + // System.err.print((char) msg[i]); + // else + // System.err.print("."); + // } + // System.err.println(); + // System.err.flush(); + // } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/TypesReader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/TypesReader.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.IOException; +import java.math.BigInteger; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.util.StringEncoder; + +/** + * @author Christian Plattner + * @version $Id: TypesReader.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class TypesReader { + byte[] arr; + int pos = 0; + int max = 0; + + public TypesReader(byte[] arr) { + this.arr = arr; + pos = 0; + max = arr.length; + } + + public TypesReader(byte[] arr, int off) { + this.arr = arr; + this.pos = off; + this.max = arr.length; + + if ((pos < 0) || (pos > arr.length)) { + throw new IllegalArgumentException("Illegal offset."); + } + } + + public TypesReader(byte[] arr, int off, int len) { + this.arr = arr; + this.pos = off; + this.max = off + len; + + if ((pos < 0) || (pos > arr.length)) { + throw new IllegalArgumentException("Illegal offset."); + } + + if ((max < 0) || (max > arr.length)) { + throw new IllegalArgumentException("Illegal length."); + } + } + + public int readByte() throws IOException { + if (pos >= max) { + throw new PacketFormatException("Packet too short."); + } + + return (arr[pos++] & 0xff); + } + + public byte[] readBytes(int len) throws IOException { + if ((pos + len) > max) { + throw new PacketFormatException("Packet too short."); + } + + byte[] res = new byte[len]; + System.arraycopy(arr, pos, res, 0, len); + pos += len; + return res; + } + + public void readBytes(byte[] dst, int off, int len) throws IOException { + if ((pos + len) > max) { + throw new PacketFormatException("Packet too short."); + } + + System.arraycopy(arr, pos, dst, off, len); + pos += len; + } + + public boolean readBoolean() throws IOException { + if (pos >= max) { + throw new PacketFormatException("Packet too short."); + } + + return (arr[pos++] != 0); + } + + public int readUINT32() throws IOException { + if ((pos + 4) > max) { + throw new PacketFormatException("Packet too short."); + } + + return ((arr[pos++] & 0xff) << 24) | ((arr[pos++] & 0xff) << 16) | ((arr[pos++] & 0xff) << 8) + | (arr[pos++] & 0xff); + } + + public long readUINT64() throws IOException { + if ((pos + 8) > max) { + throw new PacketFormatException("Packet too short."); + } + + long high = ((arr[pos++] & 0xff) << 24) | ((arr[pos++] & 0xff) << 16) | ((arr[pos++] & 0xff) << 8) + | (arr[pos++] & 0xff); /* sign extension may take place - will be shifted away =) */ + long low = ((arr[pos++] & 0xff) << 24) | ((arr[pos++] & 0xff) << 16) | ((arr[pos++] & 0xff) << 8) + | (arr[pos++] & 0xff); /* sign extension may take place - handle below */ + return (high << 32) | (low & 0xffffffffl); /* see Java language spec (15.22.1, 5.6.2) */ + } + + public BigInteger readMPINT() throws IOException { + BigInteger b; + byte raw[] = readByteString(); + + if (raw.length == 0) { + b = BigInteger.ZERO; + } + else { + b = new BigInteger(raw); + } + + return b; + } + + public byte[] readByteString() throws IOException { + int len = readUINT32(); + + if ((len + pos) > max) { + throw new PacketFormatException("Malformed SSH byte string."); + } + + byte[] res = new byte[len]; + System.arraycopy(arr, pos, res, 0, len); + pos += len; + return res; + } + + public String readString(String charsetName) throws IOException { + int len = readUINT32(); + + if ((len + pos) > max) { + throw new PacketFormatException("Malformed SSH string."); + } + + String res = (charsetName == null) ? new String(arr, pos, len) : new String(arr, pos, len, charsetName); + pos += len; + return res; + } + + public String readString() throws IOException { + int len = readUINT32(); + + if ((len + pos) > max) { + throw new PacketFormatException("Malformed SSH string."); + } + + String res = StringEncoder.GetString(arr, pos, len); + pos += len; + return res; + } + + public String[] readNameList() throws IOException { + return readString().split(","); + } + + public int remain() { + return max - pos; + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/packets/TypesWriter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/packets/TypesWriter.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.packets; + +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; + +import ch.ethz.ssh2.util.StringEncoder; + +/** + * @author Christian Plattner + * @version $Id: TypesWriter.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public final class TypesWriter { + byte arr[]; + int pos; + + public TypesWriter() { + arr = new byte[256]; + pos = 0; + } + + private void resize(int len) { + byte new_arr[] = new byte[len]; + System.arraycopy(arr, 0, new_arr, 0, arr.length); + arr = new_arr; + } + + public int length() { + return pos; + } + + public byte[] getBytes() { + byte[] dst = new byte[pos]; + System.arraycopy(arr, 0, dst, 0, pos); + return dst; + } + + public void getBytes(byte dst[]) { + System.arraycopy(arr, 0, dst, 0, pos); + } + + public void writeUINT32(int val, int off) { + if ((off + 4) > arr.length) { + resize(off + 32); + } + + arr[off++] = (byte)(val >> 24); + arr[off++] = (byte)(val >> 16); + arr[off++] = (byte)(val >> 8); + arr[off++] = (byte) val; + } + + public void writeUINT32(int val) { + writeUINT32(val, pos); + pos += 4; + } + + public void writeUINT64(long val) { + if ((pos + 8) > arr.length) { + resize(arr.length + 32); + } + + arr[pos++] = (byte)(val >> 56); + arr[pos++] = (byte)(val >> 48); + arr[pos++] = (byte)(val >> 40); + arr[pos++] = (byte)(val >> 32); + arr[pos++] = (byte)(val >> 24); + arr[pos++] = (byte)(val >> 16); + arr[pos++] = (byte)(val >> 8); + arr[pos++] = (byte) val; + } + + public void writeBoolean(boolean v) { + if ((pos + 1) > arr.length) { + resize(arr.length + 32); + } + + arr[pos++] = v ? (byte) 1 : (byte) 0; + } + + public void writeByte(int v, int off) { + if ((off + 1) > arr.length) { + resize(off + 32); + } + + arr[off] = (byte) v; + } + + public void writeByte(int v) { + writeByte(v, pos); + pos++; + } + + public void writeMPInt(BigInteger b) { + byte raw[] = b.toByteArray(); + + if ((raw.length == 1) && (raw[0] == 0)) { + writeUINT32(0); /* String with zero bytes of data */ + } + else { + writeString(raw, 0, raw.length); + } + } + + public void writeBytes(byte[] buff) { + writeBytes(buff, 0, buff.length); + } + + public void writeBytes(byte[] buff, int off, int len) { + if ((pos + len) > arr.length) { + resize(arr.length + len + 32); + } + + System.arraycopy(buff, off, arr, pos, len); + pos += len; + } + + public void writeString(byte[] buff, int off, int len) { + writeUINT32(len); + writeBytes(buff, off, len); + } + + public void writeString(String v) { + byte[] b = StringEncoder.GetBytes(v); + writeUINT32(b.length); + writeBytes(b, 0, b.length); + } + + public void writeString(String v, String charsetName) throws UnsupportedEncodingException { + byte[] b = (charsetName == null) ? StringEncoder.GetBytes(v) : v.getBytes(charsetName); + writeUINT32(b.length); + writeBytes(b, 0, b.length); + } + + public void writeNameList(String v[]) { + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < v.length; i++) { + if (i > 0) { + sb.append(','); + } + + sb.append(v[i]); + } + + writeString(sb.toString()); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/server/ServerConnectionState.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/server/ServerConnectionState.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.server; + +import java.net.Socket; + +import ch.ethz.ssh2.ServerAuthenticationCallback; +import ch.ethz.ssh2.ServerConnection; +import ch.ethz.ssh2.ServerConnectionCallback; +import ch.ethz.ssh2.auth.ServerAuthenticationManager; +import ch.ethz.ssh2.channel.ChannelManager; +import ch.ethz.ssh2.crypto.CryptoWishList; +import ch.ethz.ssh2.crypto.SecureRandomFix; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.RSAPrivateKey; +import ch.ethz.ssh2.transport.ClientServerHello; +import ch.ethz.ssh2.transport.ServerTransportManager; + +public class ServerConnectionState { + public ServerConnection conn; + + public SecureRandomFix generator = new SecureRandomFix(); + + public String softwareversion; + + //public String auth_banner = null; + public ServerConnectionCallback cb_conn; + public ServerAuthenticationCallback cb_auth; + + /* Settings for the next key exchange */ + public CryptoWishList next_cryptoWishList = CryptoWishList.forServer(); + public KeyPair next_dsa_key; + public KeyPair next_ec_key; + public KeyPair next_rsa_key; + + public Socket s; + + public ClientServerHello csh; + public ServerTransportManager tm; + public ServerAuthenticationManager am; + public ChannelManager cm; + + public boolean flag_auth_serviceRequested = false; + public boolean flag_auth_completed = false; + + public ServerConnectionState(ServerConnection conn) { + this.conn = conn; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/sftp/AceFlags.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/sftp/AceFlags.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,17 @@ +package ch.ethz.ssh2.sftp; + +/** + * @version $Id: AceFlags.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public final class AceFlags { + private AceFlags() { + } + + public static final int ACE4_FILE_INHERIT_ACE = 0x00000001; + public static final int ACE4_DIRECTORY_INHERIT_ACE = 0x00000002; + public static final int ACE4_NO_PROPAGATE_INHERIT_ACE = 0x00000004; + public static final int ACE4_INHERIT_ONLY_ACE = 0x00000008; + public static final int ACE4_SUCCESSFUL_ACCESS_ACE_FLAG = 0x00000010; + public static final int ACE4_FAILED_ACCESS_ACE_FLAG = 0x00000020; + public static final int ACE4_IDENTIFIER_GROUP = 0x00000040; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/sftp/AceMask.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/sftp/AceMask.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,28 @@ +package ch.ethz.ssh2.sftp; + +/** + * @version $Id: AceMask.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public final class AceMask { + private AceMask() { + } + + public static final int ACE4_READ_DATA = 0x00000001; + public static final int ACE4_LIST_DIRECTORY = 0x00000001; + public static final int ACE4_WRITE_DATA = 0x00000002; + public static final int ACE4_ADD_FILE = 0x00000002; + public static final int ACE4_APPEND_DATA = 0x00000004; + public static final int ACE4_ADD_SUBDIRECTORY = 0x00000004; + public static final int ACE4_READ_NAMED_ATTRS = 0x00000008; + public static final int ACE4_WRITE_NAMED_ATTRS = 0x00000010; + public static final int ACE4_EXECUTE = 0x00000020; + public static final int ACE4_DELETE_CHILD = 0x00000040; + public static final int ACE4_READ_ATTRIBUTES = 0x00000080; + public static final int ACE4_WRITE_ATTRIBUTES = 0x00000100; + public static final int ACE4_DELETE = 0x00010000; + public static final int ACE4_READ_ACL = 0x00020000; + public static final int ACE4_WRITE_ACL = 0x00040000; + public static final int ACE4_WRITE_OWNER = 0x00080000; + public static final int ACE4_SYNCHRONIZE = 0x00100000; + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/sftp/AceType.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/sftp/AceType.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,16 @@ +package ch.ethz.ssh2.sftp; + +/** + * @version $Id: AceType.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public final class AceType { + + private AceType() { + } + + private static final int ACE4_ACCESS_ALLOWED_ACE_TYPE = 0x00000000; + private static final int ACE4_ACCESS_DENIED_ACE_TYPE = 0x00000001; + private static final int ACE4_SYSTEM_AUDIT_ACE_TYPE = 0x00000002; + private static final int ACE4_SYSTEM_ALARM_ACE_TYPE = 0x00000003; + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/sftp/AclFlags.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/sftp/AclFlags.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,45 @@ +package ch.ethz.ssh2.sftp; + +/** + * @version $Id: AclFlags.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public final class AclFlags { + + /** + * If INCLUDED is set during a setstat operation, then the client + * intends to modify the ALLOWED/DENIED entries of the ACL. + * Otherwise, the client intends for these entries to be + * preserved. + */ + public static final int SFX_ACL_CONTROL_INCLUDED = 0x00000001; + /** + * If the PRESENT bit is not set, then the client wishes to remove + * control entries. If the server doesn't support separate + * control and audit information, the client MUST not clear this + * bit without also clearing the AUDIT_ALARM_PRESENT bit. + */ + public static final int SFX_ACL_CONTROL_PRESENT = 0x00000002; + /** + * If INHERITED is set, then ALLOW/DENY ACEs MAY be inherited from + * the parent directory. If it is off, then they MUST not be + * INHERITED. If the server does not support controlling + * inheritance, then the client MUST clear this bit; in this case + * the inheritance properties of the server are undefined. + */ + public static final int SFX_ACL_CONTROL_INHERITED = 0x00000004; + /** + * If INCLUDE is set during a setstat operation, then the client + * intends to modify the AUDIT/ALARM entries of the ACL. + * Otherwise, the client intends for these entries to be + * preserved. + */ + public static final int SFX_ACL_AUDIT_ALARM_INCLUDED = 0x00000010; + /** + * If INHERITED is set, then AUDIT/ALARM ACEs MAY be inherited + * from the parent directory. If it is off, then they MUST not be + * INHERITED. If the server does not support controlling + * inheritance, then the client MUST clear this bit; in this case + * the inheritance properties of the server are undefined. + */ + public static final int SFX_ACL_AUDIT_ALARM_INHERITED = 0x00000020; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/sftp/AttrTextHints.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/sftp/AttrTextHints.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.sftp; + +/** + * + * Values for the 'text-hint' field in the SFTP ATTRS data type. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + * + */ +public class AttrTextHints { + /** + * The server knows the file is a text file, and should be opened + * using the SSH_FXF_ACCESS_TEXT_MODE flag. + */ + public static final int SSH_FILEXFER_ATTR_KNOWN_TEXT = 0x00; + + /** + * The server has applied a heuristic or other mechanism and + * believes that the file should be opened with the + * SSH_FXF_ACCESS_TEXT_MODE flag. + */ + public static final int SSH_FILEXFER_ATTR_GUESSED_TEXT = 0x01; + + /** + * The server knows the file has binary content. + */ + public static final int SSH_FILEXFER_ATTR_KNOWN_BINARY = 0x02; + + /** + * The server has applied a heuristic or other mechanism and + * believes has binary content, and should not be opened with the + * SSH_FXF_ACCESS_TEXT_MODE flag. + */ + public static final int SSH_FILEXFER_ATTR_GUESSED_BINARY = 0x03; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/sftp/AttribBits.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/sftp/AttribBits.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.sftp; + +/** + * SFTP Attribute Bits for the "attrib-bits" and "attrib-bits-valid" fields + * of the SFTP ATTR data type. + *

+ * Yes, these are the "attrib-bits", even though they have "_FLAGS_" in + * their name. Don't ask - I did not invent it. + *

+ * "These fields, taken together, reflect various attributes of the file + * or directory, on the server. Bits not set in 'attrib-bits-valid' MUST be + * ignored in the 'attrib-bits' field. This allows both the server and the + * client to communicate only the bits it knows about without inadvertently + * twiddling bits they don't understand." + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public final class AttribBits { + + private AttribBits() { + } + + /** + * Advisory, read-only bit. This bit is not part of the access + * control information on the file, but is rather an advisory field + * indicating that the file should not be written. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_READONLY = 0x00000001; + + /** + * The file is part of the operating system. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_SYSTEM = 0x00000002; + + /** + * File SHOULD NOT be shown to user unless specifically requested. + * For example, most UNIX systems SHOULD set this bit if the filename + * begins with a 'period'. This bit may be read-only (see section 5.4 of + * the SFTP standard draft). Most UNIX systems will not allow this to be + * changed. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_HIDDEN = 0x00000004; + + /** + * This attribute applies only to directories. This attribute is + * always read-only, and cannot be modified. This attribute means + * that files and directory names in this directory should be compared + * without regard to case. + *

+ * It is recommended that where possible, the server's filesystem be + * allowed to do comparisons. For example, if a client wished to prompt + * a user before overwriting a file, it should not compare the new name + * with the previously retrieved list of names in the directory. Rather, + * it should first try to create the new file by specifying + * SSH_FXF_CREATE_NEW flag. Then, if this fails and returns + * SSH_FX_FILE_ALREADY_EXISTS, it should prompt the user and then retry + * the create specifying SSH_FXF_CREATE_TRUNCATE. + *

+ * Unless otherwise specified, filenames are assumed to be case sensitive. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE = 0x00000008; + + /** + * The file should be included in backup / archive operations. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_ARCHIVE = 0x00000010; + + /** + * The file is stored on disk using file-system level transparent + * encryption. This flag does not affect the file data on the wire + * (for either READ or WRITE requests.) + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_ENCRYPTED = 0x00000020; + + /** + * The file is stored on disk using file-system level transparent + * compression. This flag does not affect the file data on the wire. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_COMPRESSED = 0x00000040; + + /** + * The file is a sparse file; this means that file blocks that have + * not been explicitly written are not stored on disk. For example, if + * a client writes a buffer at 10 M from the beginning of the file, + * the blocks between the previous EOF marker and the 10 M offset would + * not consume physical disk space. + *

+ * Some servers may store all files as sparse files, in which case + * this bit will be unconditionally set. Other servers may not have + * a mechanism for determining if the file is sparse, and so the file + * MAY be stored sparse even if this flag is not set. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_SPARSE = 0x00000080; + + /** + * Opening the file without either the SSH_FXF_ACCESS_APPEND_DATA or + * the SSH_FXF_ACCESS_APPEND_DATA_ATOMIC flag (see section 8.1.1.3 + * of the SFTP standard draft) MUST result in an + * SSH_FX_INVALID_PARAMETER error. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_APPEND_ONLY = 0x00000100; + + /** + * The file cannot be deleted or renamed, no hard link can be created + * to this file, and no data can be written to the file. + *

+ * This bit implies a stronger level of protection than + * SSH_FILEXFER_ATTR_FLAGS_READONLY, the file permission mask or ACLs. + * Typically even the superuser cannot write to immutable files, and + * only the superuser can set or remove the bit. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE = 0x00000200; + + /** + * When the file is modified, the changes are written synchronously + * to the disk. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_SYNC = 0x00000400; + + /** + * The server MAY include this bit in a directory listing or realpath + * response. It indicates there was a failure in the translation to UTF-8. + * If this flag is included, the server SHOULD also include the + * UNTRANSLATED_NAME attribute. + */ + public static final int SSH_FILEXFER_ATTR_FLAGS_TRANSLATION_ERR = 0x00000800; + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/sftp/AttribFlags.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/sftp/AttribFlags.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.sftp; + +/** + * Attribute Flags. The 'valid-attribute-flags' field in + * the SFTP ATTRS data type specifies which of the fields are actually present. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public final class AttribFlags { + + private AttribFlags() { + } + + /** + * Indicates that the 'allocation-size' field is present. + */ + public static final int SSH_FILEXFER_ATTR_SIZE = 0x00000001; + + /** + * Protocol version 6: + * 0x00000002 was used in a previous version of this protocol. + * It is now a reserved value and MUST NOT appear in the mask. + * Some future version of this protocol may reuse this value. + */ + public static final int SSH_FILEXFER_ATTR_V3_UIDGID = 0x00000002; + + /** + * Indicates that the 'permissions' field is present. + */ + public static final int SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004; + + /** + * Indicates that the 'atime' and 'mtime' field are present + * (protocol v3). + */ + public static final int SSH_FILEXFER_ATTR_V3_ACMODTIME = 0x00000008; + + /** + * Indicates that the 'atime' field is present. + */ + public static final int SSH_FILEXFER_ATTR_ACCESSTIME = 0x00000008; + + /** + * Indicates that the 'createtime' field is present. + */ + public static final int SSH_FILEXFER_ATTR_CREATETIME = 0x00000010; + + /** + * Indicates that the 'mtime' field is present. + */ + public static final int SSH_FILEXFER_ATTR_MODIFYTIME = 0x00000020; + + /** + * Indicates that the 'acl' field is present. + */ + public static final int SSH_FILEXFER_ATTR_ACL = 0x00000040; + + /** + * Indicates that the 'owner' and 'group' fields are present. + */ + public static final int SSH_FILEXFER_ATTR_OWNERGROUP = 0x00000080; + + /** + * Indicates that additionally to the 'atime', 'createtime', + * 'mtime' and 'ctime' fields (if present), there is also + * 'atime-nseconds', 'createtime-nseconds', 'mtime-nseconds' + * and 'ctime-nseconds'. + */ + public static final int SSH_FILEXFER_ATTR_SUBSECOND_TIMES = 0x00000100; + + /** + * Indicates that the 'attrib-bits' and 'attrib-bits-valid' + * fields are present. + */ + public static final int SSH_FILEXFER_ATTR_BITS = 0x00000200; + + /** + * Indicates that the 'allocation-size' field is present. Field specifies the number of bytes that the + * file consumes on disk. + */ + public static final int SSH_FILEXFER_ATTR_ALLOCATION_SIZE = 0x00000400; + + /** + * Indicates that the 'text-hint' field is present. + */ + public static final int SSH_FILEXFER_ATTR_TEXT_HINT = 0x00000800; + + /** + * Indicates that the 'mime-type' field is present. + */ + public static final int SSH_FILEXFER_ATTR_MIME_TYPE = 0x00001000; + + /** + * Indicates that the 'link-count' field is present. + */ + public static final int SSH_FILEXFER_ATTR_LINK_COUNT = 0x00002000; + + /** + * Indicates that the 'untranslated-name' field is present. + */ + public static final int SSH_FILEXFER_ATTR_UNTRANSLATED_NAME = 0x00004000; + + /** + * Indicates that the 'ctime' field is present. + */ + public static final int SSH_FILEXFER_ATTR_CTIME = 0x00008000; + + /** + * Indicates that the 'extended-count' field (and probablby some + * 'extensions') is present. + */ + public static final int SSH_FILEXFER_ATTR_EXTENDED = 0x80000000; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/sftp/AttribPermissions.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/sftp/AttribPermissions.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.sftp; + +/** + * Permissions for the 'permissions' field in the SFTP ATTRS data type. + *

+ * "The 'permissions' field contains a bit mask specifying file permissions. + * These permissions correspond to the st_mode field of the stat structure + * defined by POSIX [IEEE.1003-1.1996]." + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class AttribPermissions { + private AttribPermissions() { + } + + /* Octal values! */ + + public static final int S_IRUSR = 0400; + public static final int S_IWUSR = 0200; + public static final int S_IXUSR = 0100; + public static final int S_IRGRP = 0040; + public static final int S_IWGRP = 0020; + public static final int S_IXGRP = 0010; + public static final int S_IROTH = 0004; + public static final int S_IWOTH = 0002; + public static final int S_IXOTH = 0001; + public static final int S_ISUID = 04000; + public static final int S_ISGID = 02000; + public static final int S_ISVTX = 01000; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/sftp/AttribTypes.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/sftp/AttribTypes.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.sftp; + +/** + * Types for the 'type' field in the SFTP ATTRS data type. + *

+ * "On a POSIX system, these values would be derived from the mode field + * of the stat structure. SPECIAL should be used for files that are of + * a known type which cannot be expressed in the protocol. UNKNOWN + * should be used if the type is not known." + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public final class AttribTypes { + private AttribTypes() { + } + + public static final int SSH_FILEXFER_TYPE_REGULAR = 1; + public static final int SSH_FILEXFER_TYPE_DIRECTORY = 2; + public static final int SSH_FILEXFER_TYPE_SYMLINK = 3; + public static final int SSH_FILEXFER_TYPE_SPECIAL = 4; + /** + * Should be used if the type is not known. + */ + public static final int SSH_FILEXFER_TYPE_UNKNOWN = 5; + public static final int SSH_FILEXFER_TYPE_SOCKET = 6; + public static final int SSH_FILEXFER_TYPE_CHAR_DEVICE = 7; + public static final int SSH_FILEXFER_TYPE_BLOCK_DEVICE = 8; + public static final int SSH_FILEXFER_TYPE_FIFO = 9; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/sftp/ErrorCodes.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/sftp/ErrorCodes.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.sftp; + +/** + * SFTP Error Codes + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public final class ErrorCodes { + private ErrorCodes() { + } + + public static final int SSH_FX_OK = 0; + public static final int SSH_FX_EOF = 1; + public static final int SSH_FX_NO_SUCH_FILE = 2; + public static final int SSH_FX_PERMISSION_DENIED = 3; + public static final int SSH_FX_FAILURE = 4; + public static final int SSH_FX_BAD_MESSAGE = 5; + public static final int SSH_FX_NO_CONNECTION = 6; + public static final int SSH_FX_CONNECTION_LOST = 7; + /** + * The server MUST respond with SSH_FXP_STATUS(SSH_FX_OP_UNSUPPORTED) if + * it receives a packet it does not recognize. + */ + public static final int SSH_FX_OP_UNSUPPORTED = 8; + public static final int SSH_FX_INVALID_HANDLE = 9; + public static final int SSH_FX_NO_SUCH_PATH = 10; + public static final int SSH_FX_FILE_ALREADY_EXISTS = 11; + public static final int SSH_FX_WRITE_PROTECT = 12; + public static final int SSH_FX_NO_MEDIA = 13; + public static final int SSH_FX_NO_SPACE_ON_FILESYSTEM = 14; + public static final int SSH_FX_QUOTA_EXCEEDED = 15; + public static final int SSH_FX_UNKNOWN_PRINCIPAL = 16; + public static final int SSH_FX_LOCK_CONFLICT = 17; + public static final int SSH_FX_DIR_NOT_EMPTY = 18; + public static final int SSH_FX_NOT_A_DIRECTORY = 19; + public static final int SSH_FX_INVALID_FILENAME = 20; + public static final int SSH_FX_LINK_LOOP = 21; + public static final int SSH_FX_CANNOT_DELETE = 22; + public static final int SSH_FX_INVALID_PARAMETER = 23; + public static final int SSH_FX_FILE_IS_A_DIRECTORY = 24; + public static final int SSH_FX_BYTE_RANGE_LOCK_CONFLICT = 25; + public static final int SSH_FX_BYTE_RANGE_LOCK_REFUSED = 26; + public static final int SSH_FX_DELETE_PENDING = 27; + public static final int SSH_FX_FILE_CORRUPT = 28; + public static final int SSH_FX_OWNER_INVALID = 29; + public static final int SSH_FX_GROUP_INVALID = 30; + public static final int SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK = 31; + + private static final String[][] messages = { + + {"SSH_FX_OK", "Indicates successful completion of the operation."}, + { + "SSH_FX_EOF", + "An attempt to read past the end-of-file was made; or, there are no more directory entries to return." + }, + {"SSH_FX_NO_SUCH_FILE", "A reference was made to a file which does not exist."}, + {"SSH_FX_PERMISSION_DENIED", "The user does not have sufficient permissions to perform the operation."}, + {"SSH_FX_FAILURE", "An error occurred, but no specific error code exists to describe the failure."}, + {"SSH_FX_BAD_MESSAGE", "A badly formatted packet or other SFTP protocol incompatibility was detected."}, + {"SSH_FX_NO_CONNECTION", "There is no connection to the server."}, + {"SSH_FX_CONNECTION_LOST", "The connection to the server was lost."}, + { + "SSH_FX_OP_UNSUPPORTED", + "An attempted operation could not be completed by the server because the server does not support the operation." + }, + {"SSH_FX_INVALID_HANDLE", "The handle value was invalid."}, + {"SSH_FX_NO_SUCH_PATH", "The file path does not exist or is invalid."}, + {"SSH_FX_FILE_ALREADY_EXISTS", "The file already exists."}, + {"SSH_FX_WRITE_PROTECT", "The file is on read-only media, or the media is write protected."}, + { + "SSH_FX_NO_MEDIA", + "The requested operation cannot be completed because there is no media available in the drive." + }, + { + "SSH_FX_NO_SPACE_ON_FILESYSTEM", + "The requested operation cannot be completed because there is insufficient free space on the filesystem." + }, + { + "SSH_FX_QUOTA_EXCEEDED", + "The operation cannot be completed because it would exceed the user's storage quota." + }, + { + "SSH_FX_UNKNOWN_PRINCIPAL", + "A principal referenced by the request (either the 'owner', 'group', or 'who' field of an ACL), was unknown. The error specific data contains the problematic names." + }, + {"SSH_FX_LOCK_CONFLICT", "The file could not be opened because it is locked by another process."}, + {"SSH_FX_DIR_NOT_EMPTY", "The directory is not empty."}, + {"SSH_FX_NOT_A_DIRECTORY", "The specified file is not a directory."}, + {"SSH_FX_INVALID_FILENAME", "The filename is not valid."}, + { + "SSH_FX_LINK_LOOP", + "Too many symbolic links encountered or, an SSH_FXF_NOFOLLOW open encountered a symbolic link as the final component." + }, + { + "SSH_FX_CANNOT_DELETE", + "The file cannot be deleted. One possible reason is that the advisory READONLY attribute-bit is set." + }, + { + "SSH_FX_INVALID_PARAMETER", + "One of the parameters was out of range, or the parameters specified cannot be used together." + }, + { + "SSH_FX_FILE_IS_A_DIRECTORY", + "The specified file was a directory in a context where a directory cannot be used." + }, + { + "SSH_FX_BYTE_RANGE_LOCK_CONFLICT", + " A read or write operation failed because another process's mandatory byte-range lock overlaps with the request." + }, + {"SSH_FX_BYTE_RANGE_LOCK_REFUSED", "A request for a byte range lock was refused."}, + {"SSH_FX_DELETE_PENDING", "An operation was attempted on a file for which a delete operation is pending."}, + {"SSH_FX_FILE_CORRUPT", "The file is corrupt; an filesystem integrity check should be run."}, + {"SSH_FX_OWNER_INVALID", "The principal specified can not be assigned as an owner of a file."}, + {"SSH_FX_GROUP_INVALID", "The principal specified can not be assigned as the primary group of a file."}, + { + "SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK", + "The requested operation could not be completed because the specifed byte range lock has not been granted." + }, + + }; + + public static final String[] getDescription(int errorCode) { + if ((errorCode < 0) || (errorCode >= messages.length)) { + return null; + } + + return messages[errorCode]; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/sftp/OpenFlags.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/sftp/OpenFlags.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.sftp; + +/** + * SFTP Open Flags. + *

+ * The following table is provided to assist in mapping POSIX semantics + * to equivalent SFTP file open parameters: + *

+ *

    + *
  • O_RDONLY + *
    • desired-access = READ_DATA | READ_ATTRIBUTES
    + *
  • + *
+ *
    + *
  • O_WRONLY + *
    • desired-access = WRITE_DATA | WRITE_ATTRIBUTES
    + *
  • + *
+ *
    + *
  • O_RDWR + *
    • desired-access = READ_DATA | READ_ATTRIBUTES | WRITE_DATA | WRITE_ATTRIBUTES
    + *
  • + *
+ *
    + *
  • O_APPEND + *
      + *
    • desired-access = WRITE_DATA | WRITE_ATTRIBUTES | APPEND_DATA
    • + *
    • flags = SSH_FXF_ACCESS_APPEND_DATA and or SSH_FXF_ACCESS_APPEND_DATA_ATOMIC
    • + *
    + *
  • + *
+ *
    + *
  • O_CREAT + *
      + *
    • flags = SSH_FXF_OPEN_OR_CREATE
    • + *
    + *
  • + *
+ *
    + *
  • O_TRUNC + *
      + *
    • flags = SSH_FXF_TRUNCATE_EXISTING
    • + *
    + *
  • + *
+ *
    + *
  • O_TRUNC|O_CREATE + *
      + *
    • flags = SSH_FXF_CREATE_TRUNCATE
    • + *
    + *
  • + *
+ * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public final class OpenFlags { + private OpenFlags() { + } + + /** + * Disposition is a 3 bit field that controls how the file is opened. + * The server MUST support these bits (possible enumaration values: + * SSH_FXF_CREATE_NEW, SSH_FXF_CREATE_TRUNCATE, SSH_FXF_OPEN_EXISTING, + * SSH_FXF_OPEN_OR_CREATE or SSH_FXF_TRUNCATE_EXISTING). + */ + public static final int SSH_FXF_ACCESS_DISPOSITION = 0x00000007; + + /** + * A new file is created; if the file already exists, the server + * MUST return status SSH_FX_FILE_ALREADY_EXISTS. + */ + public static final int SSH_FXF_CREATE_NEW = 0x00000000; + + /** + * A new file is created; if the file already exists, it is opened + * and truncated. + */ + public static final int SSH_FXF_CREATE_TRUNCATE = 0x00000001; + + /** + * An existing file is opened. If the file does not exist, the + * server MUST return SSH_FX_NO_SUCH_FILE. If a directory in the + * path does not exist, the server SHOULD return + * SSH_FX_NO_SUCH_PATH. It is also acceptable if the server + * returns SSH_FX_NO_SUCH_FILE in this case. + */ + public static final int SSH_FXF_OPEN_EXISTING = 0x00000002; + + /** + * If the file exists, it is opened. If the file does not exist, + * it is created. + */ + public static final int SSH_FXF_OPEN_OR_CREATE = 0x00000003; + + /** + * An existing file is opened and truncated. If the file does not + * exist, the server MUST return the same error codes as defined + * for SSH_FXF_OPEN_EXISTING. + */ + public static final int SSH_FXF_TRUNCATE_EXISTING = 0x00000004; + + /** + * Data is always written at the end of the file. The offset field + * of the SSH_FXP_WRITE requests are ignored. + *

+ * Data is not required to be appended atomically. This means that + * if multiple writers attempt to append data simultaneously, data + * from the first may be lost. However, data MAY be appended + * atomically. + */ + public static final int SSH_FXF_ACCESS_APPEND_DATA = 0x00000008; + + /** + * Data is always written at the end of the file. The offset field + * of the SSH_FXP_WRITE requests are ignored. + *

+ * Data MUST be written atomically so that there is no chance that + * multiple appenders can collide and result in data being lost. + *

+ * If both append flags are specified, the server SHOULD use atomic + * append if it is available, but SHOULD use non-atomic appends + * otherwise. The server SHOULD NOT fail the request in this case. + */ + public static final int SSH_FXF_ACCESS_APPEND_DATA_ATOMIC = 0x00000010; + + /** + * Indicates that the server should treat the file as text and + * convert it to the canonical newline convention in use. + * (See Determining Server Newline Convention in section 5.3 in the + * SFTP standard draft). + *

+ * When a file is opened with this flag, the offset field in the read + * and write functions is ignored. + *

+ * Servers MUST process multiple, parallel reads and writes correctly + * in this mode. Naturally, it is permissible for them to do this by + * serializing the requests. + *

+ * Clients SHOULD use the SSH_FXF_ACCESS_APPEND_DATA flag to append + * data to a text file rather then using write with a calculated offset. + */ + public static final int SSH_FXF_ACCESS_TEXT_MODE = 0x00000020; + + /** + * The server MUST guarantee that no other handle has been opened + * with ACE4_READ_DATA access, and that no other handle will be + * opened with ACE4_READ_DATA access until the client closes the + * handle. (This MUST apply both to other clients and to other + * processes on the server.) + *

+ * If there is a conflicting lock the server MUST return + * SSH_FX_LOCK_CONFLICT. If the server cannot make the locking + * guarantee, it MUST return SSH_FX_OP_UNSUPPORTED. + *

+ * Other handles MAY be opened for ACE4_WRITE_DATA or any other + * combination of accesses, as long as ACE4_READ_DATA is not included + * in the mask. + */ + public static final int SSH_FXF_ACCESS_BLOCK_READ = 0x00000040; + + /** + * The server MUST guarantee that no other handle has been opened + * with ACE4_WRITE_DATA or ACE4_APPEND_DATA access, and that no other + * handle will be opened with ACE4_WRITE_DATA or ACE4_APPEND_DATA + * access until the client closes the handle. (This MUST apply both + * to other clients and to other processes on the server.) + *

+ * If there is a conflicting lock the server MUST return + * SSH_FX_LOCK_CONFLICT. If the server cannot make the locking + * guarantee, it MUST return SSH_FX_OP_UNSUPPORTED. + *

+ * Other handles MAY be opened for ACE4_READ_DATA or any other + * combination of accesses, as long as neither ACE4_WRITE_DATA nor + * ACE4_APPEND_DATA are included in the mask. + */ + public static final int SSH_FXF_ACCESS_BLOCK_WRITE = 0x00000080; + + /** + * The server MUST guarantee that no other handle has been opened + * with ACE4_DELETE access, opened with the + * SSH_FXF_ACCESS_DELETE_ON_CLOSE flag set, and that no other handle + * will be opened with ACE4_DELETE access or with the + * SSH_FXF_ACCESS_DELETE_ON_CLOSE flag set, and that the file itself + * is not deleted in any other way until the client closes the handle. + *

+ * If there is a conflicting lock the server MUST return + * SSH_FX_LOCK_CONFLICT. If the server cannot make the locking + * guarantee, it MUST return SSH_FX_OP_UNSUPPORTED. + */ + public static final int SSH_FXF_ACCESS_BLOCK_DELETE = 0x00000100; + + /** + * If this bit is set, the above BLOCK modes are advisory. In advisory + * mode, only other accesses that specify a BLOCK mode need be + * considered when determining whether the BLOCK can be granted, + * and the server need not prevent I/O operations that violate the + * block mode. + *

+ * The server MAY perform mandatory locking even if the BLOCK_ADVISORY + * bit is set. + */ + public static final int SSH_FXF_ACCESS_BLOCK_ADVISORY = 0x00000200; + + /** + * If the final component of the path is a symlink, then the open + * MUST fail, and the error SSH_FX_LINK_LOOP MUST be returned. + */ + public static final int SSH_FXF_ACCESS_NOFOLLOW = 0x00000400; + + /** + * The file should be deleted when the last handle to it is closed. + * (The last handle may not be an sftp-handle.) This MAY be emulated + * by a server if the OS doesn't support it by deleting the file when + * this handle is closed. + *

+ * It is implementation specific whether the directory entry is + * removed immediately or when the handle is closed. + */ + public static final int SSH_FXF_ACCESS_DELETE_ON_CLOSE = 0x00000800; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/sftp/Packet.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/sftp/Packet.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.sftp; + +/** + * SFTP Paket Types + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class Packet { + public static final int SSH_FXP_INIT = 1; + public static final int SSH_FXP_VERSION = 2; + public static final int SSH_FXP_OPEN = 3; + public static final int SSH_FXP_CLOSE = 4; + public static final int SSH_FXP_READ = 5; + public static final int SSH_FXP_WRITE = 6; + public static final int SSH_FXP_LSTAT = 7; + public static final int SSH_FXP_FSTAT = 8; + public static final int SSH_FXP_SETSTAT = 9; + public static final int SSH_FXP_FSETSTAT = 10; + public static final int SSH_FXP_OPENDIR = 11; + public static final int SSH_FXP_READDIR = 12; + public static final int SSH_FXP_REMOVE = 13; + public static final int SSH_FXP_MKDIR = 14; + public static final int SSH_FXP_RMDIR = 15; + public static final int SSH_FXP_REALPATH = 16; + public static final int SSH_FXP_STAT = 17; + public static final int SSH_FXP_RENAME = 18; + public static final int SSH_FXP_READLINK = 19; + public static final int SSH_FXP_SYMLINK = 20; + public static final int SSH_FXP_LINK = 21; + + public static final int SSH_FXP_STATUS = 101; + public static final int SSH_FXP_HANDLE = 102; + public static final int SSH_FXP_DATA = 103; + public static final int SSH_FXP_NAME = 104; + public static final int SSH_FXP_ATTRS = 105; + + /** + * SSH_FXP_EXTENDED and SSH_FXP_EXTENDED_REPLY packets can be used to + * implement extensions, which can be vendor specific. + */ + public static final int SSH_FXP_EXTENDED = 200; + /** + * SSH_FXP_EXTENDED and SSH_FXP_EXTENDED_REPLY packets can be used to + * implement extensions, which can be vendor specific. + */ + public static final int SSH_FXP_EXTENDED_REPLY = 201; + + public static String forName(int type) { + switch (type) { + case SSH_FXP_INIT: + return "SSH_FXP_INIT"; + + case SSH_FXP_VERSION: + return "SSH_FXP_VERSION"; + + case SSH_FXP_OPEN: + return "SSH_FXP_OPEN"; + + case SSH_FXP_CLOSE: + return "SSH_FXP_CLOSE"; + + case SSH_FXP_READ: + return "SSH_FXP_READ"; + + case SSH_FXP_WRITE: + return "SSH_FXP_WRITE"; + + case SSH_FXP_LSTAT: + return "SSH_FXP_LSTAT"; + + case SSH_FXP_FSTAT: + return "SSH_FXP_FSTAT"; + + case SSH_FXP_SETSTAT: + return "SSH_FXP_SETSTAT"; + + case SSH_FXP_FSETSTAT: + return "SSH_FXP_FSETSTAT"; + + case SSH_FXP_OPENDIR: + return "SSH_FXP_OPENDIR"; + + case SSH_FXP_READDIR: + return "SSH_FXP_READDIR"; + + case SSH_FXP_REMOVE: + return "SSH_FXP_REMOVE"; + + case SSH_FXP_MKDIR: + return "SSH_FXP_MKDIR"; + + case SSH_FXP_RMDIR: + return "SSH_FXP_RMDIR"; + + case SSH_FXP_REALPATH: + return "SSH_FXP_REALPATH"; + + case SSH_FXP_STAT: + return "SSH_FXP_STAT"; + + case SSH_FXP_RENAME: + return "SSH_FXP_RENAME"; + + case SSH_FXP_READLINK: + return "SSH_FXP_READLINK"; + + case SSH_FXP_SYMLINK: + return "SSH_FXP_SYMLINK"; + + case SSH_FXP_STATUS: + return "SSH_FXP_STATUS"; + + case SSH_FXP_HANDLE: + return "SSH_FXP_HANDLE"; + + case SSH_FXP_DATA: + return "SSH_FXP_DATA"; + + case SSH_FXP_NAME: + return "SSH_FXP_NAME"; + + case SSH_FXP_ATTRS: + return "SSH_FXP_ATTRS"; + + case SSH_FXP_EXTENDED: + return "SSH_FXP_EXTENDED"; + + case SSH_FXP_EXTENDED_REPLY: + return "SSH_FXP_EXTENDED_REPLY"; + } + + return null; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/signature/DSASHA1Verify.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/signature/DSASHA1Verify.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,230 @@ + +package ch.ethz.ssh2.signature; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.Signature; +import java.security.SignatureException; +import java.security.interfaces.DSAParams; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; +import java.security.spec.DSAPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; + +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.packets.TypesWriter; + + +/** + * DSASHA1Verify. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: DSASHA1Verify.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ + */ +public class DSASHA1Verify { + private static final Logger log = Logger.getLogger(DSASHA1Verify.class); + + public static DSAPublicKey decodeSSHDSAPublicKey(byte[] key) throws IOException { + TypesReader tr = new TypesReader(key); + String key_format = tr.readString(); + + if (key_format.equals("ssh-dss") == false) + throw new IllegalArgumentException("This is not a ssh-dss public key!"); + + BigInteger p = tr.readMPINT(); + BigInteger q = tr.readMPINT(); + BigInteger g = tr.readMPINT(); + BigInteger y = tr.readMPINT(); + + if (tr.remain() != 0) + throw new IOException("Padding in DSA public key!"); + + try { + KeyFactory kf = KeyFactory.getInstance("DSA"); + KeySpec ks = new DSAPublicKeySpec(y, p, q, g); + return (DSAPublicKey) kf.generatePublic(ks); + } + catch (NoSuchAlgorithmException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + catch (InvalidKeySpecException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } + + public static byte[] encodeSSHDSAPublicKey(DSAPublicKey pk) throws IOException { + TypesWriter tw = new TypesWriter(); + tw.writeString("ssh-dss"); + DSAParams params = pk.getParams(); + tw.writeMPInt(params.getP()); + tw.writeMPInt(params.getQ()); + tw.writeMPInt(params.getG()); + tw.writeMPInt(pk.getY()); + return tw.getBytes(); + } + + /** + * Convert from Java's signature ASN.1 encoding to the SSH spec. + *

+ * Java ASN.1 encoding: + *

+     * SEQUENCE ::= {
+     *    r INTEGER,
+     *    s INTEGER
+     * }
+     * 
+ */ + public static byte[] encodeSSHDSASignature(byte[] ds) { + TypesWriter tw = new TypesWriter(); + tw.writeString("ssh-dss"); + int len, index; + index = 3; + len = ds[index++] & 0xff; + byte[] r = new byte[len]; + System.arraycopy(ds, index, r, 0, r.length); + index = index + len + 1; + len = ds[index++] & 0xff; + byte[] s = new byte[len]; + System.arraycopy(ds, index, s, 0, s.length); + byte[] a40 = new byte[40]; + /* Patch (unsigned) r and s into the target array. */ + int r_copylen = (r.length < 20) ? r.length : 20; + int s_copylen = (s.length < 20) ? s.length : 20; + System.arraycopy(r, r.length - r_copylen, a40, 20 - r_copylen, r_copylen); + System.arraycopy(s, s.length - s_copylen, a40, 40 - s_copylen, s_copylen); + tw.writeString(a40, 0, 40); + return tw.getBytes(); + } + + public static byte[] decodeSSHDSASignature(byte[] sig) throws IOException { + byte[] rsArray = null; + + if (sig.length == 40) { + /* OK, another broken SSH server. */ + rsArray = sig; + } + else { + /* Hopefully a server obeying the standard... */ + TypesReader tr = new TypesReader(sig); + String sig_format = tr.readString(); + + if (sig_format.equals("ssh-dss") == false) + throw new IOException("Peer sent wrong signature format"); + + rsArray = tr.readByteString(); + + if (rsArray.length != 40) + throw new IOException("Peer sent corrupt signature"); + + if (tr.remain() != 0) + throw new IOException("Padding in DSA signature!"); + } + + int i = 0; + int j = 0; + byte[] tmp; + + if (rsArray[0] == 0 && rsArray[1] == 0 && rsArray[2] == 0) { + j = ((rsArray[i++] << 24) & 0xff000000) | ((rsArray[i++] << 16) & 0x00ff0000) + | ((rsArray[i++] << 8) & 0x0000ff00) | ((rsArray[i++]) & 0x000000ff); + i += j; + j = ((rsArray[i++] << 24) & 0xff000000) | ((rsArray[i++] << 16) & 0x00ff0000) + | ((rsArray[i++] << 8) & 0x0000ff00) | ((rsArray[i++]) & 0x000000ff); + tmp = new byte[j]; + System.arraycopy(rsArray, i, tmp, 0, j); + rsArray = tmp; + } + + /* ASN.1 */ + int frst = ((rsArray[0] & 0x80) != 0 ? 1 : 0); + int scnd = ((rsArray[20] & 0x80) != 0 ? 1 : 0); + /* Calculate output length */ + int length = rsArray.length + 6 + frst + scnd; + tmp = new byte[length]; + /* DER-encoding to match Java */ + tmp[0] = (byte) 0x30; + + if (rsArray.length != 40) + throw new IOException("Peer sent corrupt signature"); + + /* Calculate length */ + tmp[1] = (byte) 0x2c; + tmp[1] += frst; + tmp[1] += scnd; + /* First item */ + tmp[2] = (byte) 0x02; + /* First item length */ + tmp[3] = (byte) 0x14; + tmp[3] += frst; + /* Copy in the data for first item */ + System.arraycopy(rsArray, 0, tmp, 4 + frst, 20); + /* Second item */ + tmp[4 + tmp[3]] = (byte) 0x02; + /* Second item length */ + tmp[5 + tmp[3]] = (byte) 0x14; + tmp[5 + tmp[3]] += scnd; + /* Copy in the data for the second item */ + System.arraycopy(rsArray, 20, tmp, 6 + tmp[3] + scnd, 20); + /* Swap buffers */ + rsArray = tmp; + return rsArray; + } + + public static boolean verifySignature(byte[] message, byte[] ds, DSAPublicKey dpk) throws IOException { + try { + Signature s = Signature.getInstance("SHA1withDSA"); + s.initVerify(dpk); + s.update(message); + return s.verify(ds); + } + catch (NoSuchAlgorithmException e) { + IOException ex = new IOException("No such algorithm"); + ex.initCause(e); + throw ex; + } + catch (InvalidKeyException e) { + IOException ex = new IOException("No such algorithm"); + ex.initCause(e); + throw ex; + } + catch (SignatureException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } + + public static byte[] generateSignature(byte[] message, DSAPrivateKey pk, SecureRandom rnd) throws IOException { + try { + Signature s = Signature.getInstance("SHA1withDSA"); + s.initSign(pk); + s.update(message); + return s.sign(); + } + catch (NoSuchAlgorithmException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + catch (InvalidKeyException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + catch (SignatureException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/signature/ECDSASHA2Verify.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/signature/ECDSASHA2Verify.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,488 @@ +/** + * + */ +package ch.ethz.ssh2.signature; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.Signature; +import java.security.SignatureException; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECFieldFp; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.EllipticCurve; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.Map; +import java.util.TreeMap; + +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.packets.TypesWriter; + +/** + * @author Kenny Root + * + */ +public class ECDSASHA2Verify { + private static final Logger log = Logger.getLogger(ECDSASHA2Verify.class); + + public static final String ECDSA_SHA2_PREFIX = "ecdsa-sha2-"; + + private static final String NISTP256 = "nistp256"; + private static final String NISTP256_OID = "1.2.840.10045.3.1.7"; + private static final String NISTP384 = "nistp384"; + private static final String NISTP384_OID = "1.3.132.0.34"; + private static final String NISTP521 = "nistp521"; + private static final String NISTP521_OID = "1.3.132.0.35"; + + private static final Map CURVES = new TreeMap(); + static { + CURVES.put(NISTP256, EllipticCurves.nistp256); + CURVES.put(NISTP384, EllipticCurves.nistp384); + CURVES.put(NISTP521, EllipticCurves.nistp521); + } + + private static final Map CURVE_SIZES = new TreeMap(); + static { + CURVE_SIZES.put(256, NISTP256); + CURVE_SIZES.put(384, NISTP384); + CURVE_SIZES.put(521, NISTP521); + } + + private static final Map CURVE_OIDS = new TreeMap(); + static { + CURVE_OIDS.put(NISTP256_OID, NISTP256); + CURVE_OIDS.put(NISTP384_OID, NISTP256); + CURVE_OIDS.put(NISTP521_OID, NISTP256); + } + + public static int[] getCurveSizes() { + int[] keys = new int[CURVE_SIZES.size()]; + int i = 0; + + for (Integer n : CURVE_SIZES.keySet().toArray(new Integer[keys.length])) { + keys[i++] = n; + } + + return keys; + } + + public static ECParameterSpec getCurveForSize(int size) { + final String name = CURVE_SIZES.get(size); + + if (name == null) { + return null; + } + + return CURVES.get(name); + } + + public static ECPublicKey decodeSSHECDSAPublicKey(byte[] key) throws IOException { + TypesReader tr = new TypesReader(key); + String key_format = tr.readString(); + + if (key_format.startsWith(ECDSA_SHA2_PREFIX) == false) + throw new IllegalArgumentException("This is not an ECDSA public key"); + + String curveName = tr.readString(); + byte[] groupBytes = tr.readByteString(); + + if (tr.remain() != 0) + throw new IOException("Padding in ECDSA public key!"); + + if (key_format.equals(ECDSA_SHA2_PREFIX + curveName) == false) { + throw new IOException("Key format is inconsistent with curve name: " + key_format + + " != " + curveName); + } + + ECParameterSpec params = CURVES.get(curveName); + + if (params == null) { + throw new IOException("Curve is not supported: " + curveName); + } + + ECPoint group = ECDSASHA2Verify.decodeECPoint(groupBytes, params.getCurve()); + + if (group == null) { + throw new IOException("Invalid ECDSA group"); + } + + KeySpec keySpec = new ECPublicKeySpec(group, params); + + try { + KeyFactory kf = KeyFactory.getInstance("EC"); + return (ECPublicKey) kf.generatePublic(keySpec); + } + catch (NoSuchAlgorithmException nsae) { + IOException ioe = new IOException("No EC KeyFactory available"); + ioe.initCause(nsae); + throw ioe; + } + catch (InvalidKeySpecException ikse) { + IOException ioe = new IOException("No EC KeyFactory available"); + ioe.initCause(ikse); + throw ioe; + } + } + + public static byte[] encodeSSHECDSAPublicKey(ECPublicKey key) throws IOException { + TypesWriter tw = new TypesWriter(); + String curveName = getCurveName(key.getParams()); + String keyFormat = ECDSA_SHA2_PREFIX + curveName; + tw.writeString(keyFormat); + tw.writeString(curveName); + byte[] encoded = encodeECPoint(key.getW(), key.getParams().getCurve()); + tw.writeString(encoded, 0, encoded.length); + return tw.getBytes(); + } + + public static String getCurveName(ECParameterSpec params) throws IOException { + int fieldSize = getCurveSize(params); + final String curveName = getCurveName(fieldSize); + + if (curveName == null) { + throw new IOException("invalid curve size " + fieldSize); + } + + return curveName; + } + + public static String getCurveName(int fieldSize) { + String curveName = CURVE_SIZES.get(fieldSize); + + if (curveName == null) { + return null; + } + + return curveName; + } + + public static int getCurveSize(ECParameterSpec params) { + return params.getCurve().getField().getFieldSize(); + } + + public static ECParameterSpec getCurveForOID(String oid) { + String name = CURVE_OIDS.get(oid); + + if (name == null) + return null; + + return CURVES.get(name); + } + + public static byte[] decodeSSHECDSASignature(byte[] sig) throws IOException { + byte[] rsArray = null; + TypesReader tr = new TypesReader(sig); + String sig_format = tr.readString(); + + if (sig_format.startsWith(ECDSA_SHA2_PREFIX) == false) + throw new IOException("Peer sent wrong signature format"); + + String curveName = sig_format.substring(ECDSA_SHA2_PREFIX.length()); + + if (CURVES.containsKey(curveName) == false) { + throw new IOException("Unsupported curve: " + curveName); + } + + rsArray = tr.readByteString(); + + if (tr.remain() != 0) + throw new IOException("Padding in ECDSA signature!"); + + byte[] rArray; + byte[] sArray; + { + TypesReader rsReader = new TypesReader(rsArray); + rArray = rsReader.readMPINT().toByteArray(); + sArray = rsReader.readMPINT().toByteArray(); + } + int first = rArray.length; + int second = sArray.length; + + /* We can't have the high bit set, so add an extra zero at the beginning if so. */ + if ((rArray[0] & 0x80) != 0) { + first++; + } + + if ((sArray[0] & 0x80) != 0) { + second++; + } + + /* Calculate total output length */ + ByteArrayOutputStream os = new ByteArrayOutputStream(6 + first + second); + /* ASN.1 SEQUENCE tag */ + os.write(0x30); + /* Size of SEQUENCE */ + writeLength(4 + first + second, os); + /* ASN.1 INTEGER tag */ + os.write(0x02); + /* "r" INTEGER length */ + writeLength(first, os); + + /* Copy in the "r" INTEGER */ + if (first != rArray.length) { + os.write(0x00); + } + + os.write(rArray); + /* ASN.1 INTEGER tag */ + os.write(0x02); + /* "s" INTEGER length */ + writeLength(second, os); + + /* Copy in the "s" INTEGER */ + if (second != sArray.length) { + os.write(0x00); + } + + os.write(sArray); + return os.toByteArray(); + } + + private static final void writeLength(int length, OutputStream os) throws IOException { + if (length <= 0x7F) { + os.write(length); + return; + } + + int numOctets = 0; + int lenCopy = length; + + while (lenCopy != 0) { + lenCopy >>>= 8; + numOctets++; + } + + os.write(0x80 | numOctets); + + for (int i = (numOctets - 1) * 8; i >= 0; i -= 8) { + os.write((byte)(length >> i)); + } + } + + public static byte[] encodeSSHECDSASignature(byte[] sig, ECParameterSpec params) throws IOException { + TypesWriter tw = new TypesWriter(); + String curveName = getCurveName(params); + tw.writeString(ECDSA_SHA2_PREFIX + curveName); + + if ((sig[0] != 0x30) || (sig[1] != sig.length - 2) || (sig[2] != 0x02)) { + throw new IOException("Invalid signature format"); + } + + int rLength = sig[3]; + + if ((rLength + 6 > sig.length) || (sig[4 + rLength] != 0x02)) { + throw new IOException("Invalid signature format"); + } + + int sLength = sig[5 + rLength]; + + if (6 + rLength + sLength > sig.length) { + throw new IOException("Invalid signature format"); + } + + byte[] rArray = new byte[rLength]; + byte[] sArray = new byte[sLength]; + System.arraycopy(sig, 4, rArray, 0, rLength); + System.arraycopy(sig, 6 + rLength, sArray, 0, sLength); + BigInteger r = new BigInteger(rArray); + BigInteger s = new BigInteger(sArray); + // Write the to its own types writer. + TypesWriter rsWriter = new TypesWriter(); + rsWriter.writeMPInt(r); + rsWriter.writeMPInt(s); + byte[] encoded = rsWriter.getBytes(); + tw.writeString(encoded, 0, encoded.length); + return tw.getBytes(); + } + + public static byte[] generateSignature(byte[] message, ECPrivateKey pk) throws IOException { + final String algo = getSignatureAlgorithmForParams(pk.getParams()); + + try { + Signature s = Signature.getInstance(algo); + s.initSign(pk); + s.update(message); + return s.sign(); + } + catch (NoSuchAlgorithmException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + catch (InvalidKeyException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + catch (SignatureException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } + + public static boolean verifySignature(byte[] message, byte[] ds, ECPublicKey dpk) throws IOException { + final String algo = getSignatureAlgorithmForParams(dpk.getParams()); + + try { + Signature s = Signature.getInstance(algo); + s.initVerify(dpk); + s.update(message); + return s.verify(ds); + } + catch (NoSuchAlgorithmException e) { + IOException ex = new IOException("No such algorithm"); + ex.initCause(e); + throw ex; + } + catch (InvalidKeyException e) { + IOException ex = new IOException("No such algorithm"); + ex.initCause(e); + throw ex; + } + catch (SignatureException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } + + private static String getSignatureAlgorithmForParams(ECParameterSpec params) { + int size = getCurveSize(params); + + if (size <= 256) { + return "SHA256withECDSA"; + } + else if (size <= 384) { + return "SHA384withECDSA"; + } + else { + return "SHA512withECDSA"; + } + } + + public static String getDigestAlgorithmForParams(ECParameterSpec params) { + int size = getCurveSize(params); + + if (size <= 256) { + return "SHA256"; + } + else if (size <= 384) { + return "SHA384"; + } + else { + return "SHA512"; + } + } + + /** + * Decode an OctetString to EllipticCurvePoint according to SECG 2.3.4 + */ + public static ECPoint decodeECPoint(byte[] M, EllipticCurve curve) { + if (M.length == 0) { + return null; + } + + // M has len 2 ceil(log_2(q)/8) + 1 ? + int elementSize = (curve.getField().getFieldSize() + 7) / 8; + + if (M.length != 2 * elementSize + 1) { + return null; + } + + // step 3.2 + if (M[0] != 0x04) { + return null; + } + + // Step 3.3 + byte[] xp = new byte[elementSize]; + System.arraycopy(M, 1, xp, 0, elementSize); + // Step 3.4 + byte[] yp = new byte[elementSize]; + System.arraycopy(M, 1 + elementSize, yp, 0, elementSize); + ECPoint P = new ECPoint(new BigInteger(1, xp), new BigInteger(1, yp)); + // TODO check point 3.5 + // Step 3.6 + return P; + } + + /** + * Encode EllipticCurvePoint to an OctetString + */ + public static byte[] encodeECPoint(ECPoint group, EllipticCurve curve) { + // M has len 2 ceil(log_2(q)/8) + 1 ? + int elementSize = (curve.getField().getFieldSize() + 7) / 8; + byte[] M = new byte[2 * elementSize + 1]; + // Uncompressed format + M[0] = 0x04; + { + byte[] affineX = removeLeadingZeroes(group.getAffineX().toByteArray()); + System.arraycopy(affineX, 0, M, 1 + elementSize - affineX.length, affineX.length); + } + { + byte[] affineY = removeLeadingZeroes(group.getAffineY().toByteArray()); + System.arraycopy(affineY, 0, M, 1 + elementSize + elementSize - affineY.length, + affineY.length); + } + return M; + } + + private static byte[] removeLeadingZeroes(byte[] input) { + if (input[0] != 0x00) { + return input; + } + + int pos = 1; + + while (pos < input.length - 1 && input[pos] == 0x00) { + pos++; + } + + byte[] output = new byte[input.length - pos]; + System.arraycopy(input, pos, output, 0, output.length); + return output; + } + + public static class EllipticCurves { + public static ECParameterSpec nistp256 = new ECParameterSpec( + new EllipticCurve( + new ECFieldFp(new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16)), + new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16), + new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16)), + new ECPoint(new BigInteger("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16), + new BigInteger("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16)), + new BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16), + 1); + + public static ECParameterSpec nistp384 = new ECParameterSpec( + new EllipticCurve( + new ECFieldFp(new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF", 16)), + new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC", 16), + new BigInteger("B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF", 16)), + new ECPoint(new BigInteger("AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7", 16), + new BigInteger("3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F", 16)), + new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973", 16), + 1); + + public static ECParameterSpec nistp521 = new ECParameterSpec( + new EllipticCurve( + new ECFieldFp(new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16)), + new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC", 16), + new BigInteger("0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00", 16)), + new ECPoint(new BigInteger("00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66", 16), + new BigInteger("011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650", 16)), + new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", 16), + 1); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/signature/RSASHA1Verify.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/signature/RSASHA1Verify.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,174 @@ + +package ch.ethz.ssh2.signature; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.Signature; +import java.security.SignatureException; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.RSAPublicKeySpec; + +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.TypesReader; +import ch.ethz.ssh2.packets.TypesWriter; + + +/** + * RSASHA1Verify. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: RSASHA1Verify.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $ + */ +public class RSASHA1Verify { + private static final Logger log = Logger.getLogger(RSASHA1Verify.class); + + public static RSAPublicKey decodeSSHRSAPublicKey(byte[] key) throws IOException { + TypesReader tr = new TypesReader(key); + String key_format = tr.readString(); + + if (key_format.equals("ssh-rsa") == false) + throw new IllegalArgumentException("This is not a ssh-rsa public key"); + + BigInteger e = tr.readMPINT(); + BigInteger n = tr.readMPINT(); + + if (tr.remain() != 0) + throw new IOException("Padding in RSA public key!"); + + KeySpec keySpec = new RSAPublicKeySpec(n, e); + + try { + KeyFactory kf = KeyFactory.getInstance("RSA"); + return (RSAPublicKey) kf.generatePublic(keySpec); + } + catch (NoSuchAlgorithmException nsae) { + IOException ioe = new IOException("No RSA KeyFactory available"); + ioe.initCause(nsae); + throw ioe; + } + catch (InvalidKeySpecException ikse) { + IOException ioe = new IOException("No RSA KeyFactory available"); + ioe.initCause(ikse); + throw ioe; + } + } + + public static byte[] encodeSSHRSAPublicKey(RSAPublicKey pk) throws IOException { + TypesWriter tw = new TypesWriter(); + tw.writeString("ssh-rsa"); + tw.writeMPInt(pk.getPublicExponent()); + tw.writeMPInt(pk.getModulus()); + return tw.getBytes(); + } + + public static byte[] decodeSSHRSASignature(byte[] sig) throws IOException { + TypesReader tr = new TypesReader(sig); + String sig_format = tr.readString(); + + if (sig_format.equals("ssh-rsa") == false) + throw new IOException("Peer sent wrong signature format"); + + /* S is NOT an MPINT. "The value for 'rsa_signature_blob' is encoded as a string + * containing s (which is an integer, without lengths or padding, unsigned and in + * network byte order)." See also below. + */ + byte[] s = tr.readByteString(); + + if (s.length == 0) + throw new IOException("Error in RSA signature, S is empty."); + + if (log.isEnabled()) { + log.info("Decoding ssh-rsa signature string (length: " + s.length + ")"); + } + + if (tr.remain() != 0) + throw new IOException("Padding in RSA signature!"); + + if (s[0] == 0 && s[1] == 0 && s[2] == 0) { + int i = 0; + int j = ((s[i++] << 24) & 0xff000000) | ((s[i++] << 16) & 0x00ff0000) + | ((s[i++] << 8) & 0x0000ff00) | ((s[i++]) & 0x000000ff); + i += j; + j = ((s[i++] << 24) & 0xff000000) | ((s[i++] << 16) & 0x00ff0000) + | ((s[i++] << 8) & 0x0000ff00) | ((s[i++]) & 0x000000ff); + byte[] tmp = new byte[j]; + System.arraycopy(s, i, tmp, 0, j); + sig = tmp; + } + + return s; + } + + public static byte[] encodeSSHRSASignature(byte[] s) throws IOException { + TypesWriter tw = new TypesWriter(); + tw.writeString("ssh-rsa"); + + /* S is NOT an MPINT. "The value for 'rsa_signature_blob' is encoded as a string + * containing s (which is an integer, without lengths or padding, unsigned and in + * network byte order)." + */ + + /* Remove first zero sign byte, if present */ + + if ((s.length > 1) && (s[0] == 0x00)) + tw.writeString(s, 1, s.length - 1); + else + tw.writeString(s, 0, s.length); + + return tw.getBytes(); + } + + public static byte[] generateSignature(byte[] message, RSAPrivateKey pk) throws IOException { + try { + Signature s = Signature.getInstance("SHA1withRSA"); + s.initSign(pk); + s.update(message); + return s.sign(); + } + catch (NoSuchAlgorithmException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + catch (InvalidKeyException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + catch (SignatureException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } + + public static boolean verifySignature(byte[] message, byte[] ds, RSAPublicKey dpk) throws IOException { + try { + Signature s = Signature.getInstance("SHA1withRSA"); + s.initVerify(dpk); + s.update(message); + return s.verify(ds); + } + catch (NoSuchAlgorithmException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + catch (InvalidKeyException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + catch (SignatureException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/transport/ClientKexManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/transport/ClientKexManager.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.transport; + +import java.io.IOException; +import java.security.DigestException; +import java.security.SecureRandom; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPublicKey; + +import ch.ethz.ssh2.ConnectionInfo; +import ch.ethz.ssh2.PacketTypeException; +import ch.ethz.ssh2.ServerHostKeyVerifier; +import ch.ethz.ssh2.compression.CompressionFactory; +import ch.ethz.ssh2.compression.Compressor; +import ch.ethz.ssh2.crypto.CryptoWishList; +import ch.ethz.ssh2.crypto.cipher.BlockCipher; +import ch.ethz.ssh2.crypto.cipher.BlockCipherFactory; +import ch.ethz.ssh2.crypto.dh.GenericDhExchange; +import ch.ethz.ssh2.crypto.dh.DhGroupExchange; +import ch.ethz.ssh2.crypto.digest.MAC; +import ch.ethz.ssh2.packets.PacketKexDHInit; +import ch.ethz.ssh2.packets.PacketKexDHReply; +import ch.ethz.ssh2.packets.PacketKexDhGexGroup; +import ch.ethz.ssh2.packets.PacketKexDhGexInit; +import ch.ethz.ssh2.packets.PacketKexDhGexReply; +import ch.ethz.ssh2.packets.PacketKexDhGexRequest; +import ch.ethz.ssh2.packets.PacketKexDhGexRequestOld; +import ch.ethz.ssh2.packets.PacketKexInit; +import ch.ethz.ssh2.packets.Packets; +import ch.ethz.ssh2.signature.DSASHA1Verify; +import ch.ethz.ssh2.signature.ECDSASHA2Verify; +import ch.ethz.ssh2.signature.RSASHA1Verify; + +/** + * @version $Id: ClientKexManager.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public class ClientKexManager extends KexManager { + + private final ServerHostKeyVerifier verifier; + + private final String hostname; + + private final int port; + + public ClientKexManager(TransportManager tm, ClientServerHello csh, CryptoWishList initialCwl, String hostname, int port, + ServerHostKeyVerifier keyVerifier, SecureRandom rnd) { + super(tm, csh, initialCwl, rnd); + this.hostname = hostname; + this.port = port; + this.verifier = keyVerifier; + } + + protected boolean verifySignature(byte[] sig, byte[] hostkey) throws IOException { + if (kxs.np.server_host_key_algo.startsWith("ecdsa-sha2-")) { + byte[] rs = ECDSASHA2Verify.decodeSSHECDSASignature(sig); + ECPublicKey epk = ECDSASHA2Verify.decodeSSHECDSAPublicKey(hostkey); + log.debug("Verifying ecdsa signature"); + return ECDSASHA2Verify.verifySignature(kxs.H, rs, epk); + } + + if (kxs.np.server_host_key_algo.equals("ssh-rsa")) { + byte[] rs = RSASHA1Verify.decodeSSHRSASignature(sig); + RSAPublicKey rpk = RSASHA1Verify.decodeSSHRSAPublicKey(hostkey); + log.debug("Verifying ssh-rsa signature"); + return RSASHA1Verify.verifySignature(kxs.H, rs, rpk); + } + + if (kxs.np.server_host_key_algo.equals("ssh-dss")) { + byte[] ds = DSASHA1Verify.decodeSSHDSASignature(sig); + DSAPublicKey dpk = DSASHA1Verify.decodeSSHDSAPublicKey(hostkey); + log.debug("Verifying ssh-dss signature"); + return DSASHA1Verify.verifySignature(kxs.H, ds, dpk); + } + + throw new IOException("Unknown server host key algorithm '" + kxs.np.server_host_key_algo + "'"); + } + + public void handleFailure(final IOException failure) { + synchronized (accessLock) { + connectionClosed = true; + accessLock.notifyAll(); + } + } + + public synchronized void handleMessage(byte[] msg) throws IOException { + PacketKexInit kip; + + if (msg == null) { + synchronized (accessLock) { + connectionClosed = true; + accessLock.notifyAll(); + return; + } + } + + log.debug(String.format("client kex manager, packet type %d", msg[0])); + + if ((kxs == null) && (msg[0] != Packets.SSH_MSG_KEXINIT)) { + throw new PacketTypeException(msg[0]); + } + + if (ignore_next_kex_packet) { + ignore_next_kex_packet = false; + return; + } + + if (msg[0] == Packets.SSH_MSG_KEXINIT) { + if ((kxs != null) && (kxs.state != 0)) { + throw new PacketTypeException(msg[0]); + } + + if (kxs == null) { + /* + * Ah, OK, peer wants to do KEX. Let's be nice and play + * together. + */ + kxs = new KexState(); + kxs.dhgexParameters = nextKEXdhgexParameters; + kip = new PacketKexInit(nextKEXcryptoWishList, rnd); + kxs.localKEX = kip; + tm.sendKexMessage(kip.getPayload()); + } + + kip = new PacketKexInit(msg); + kxs.remoteKEX = kip; + kxs.np = mergeKexParameters(kxs.localKEX.getKexParameters(), kxs.remoteKEX.getKexParameters()); + + if (kxs.np == null) + throw new IOException("Cannot negotiate, proposals do not match."); + + if (kxs.remoteKEX.isFirst_kex_packet_follows() && (kxs.np.guessOK == false)) { + // Guess was wrong, we need to ignore the next kex packet. + ignore_next_kex_packet = true; + } + + if (kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha1") || + kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha256")) { + if (kxs.dhgexParameters.getMin_group_len() == 0) { + PacketKexDhGexRequestOld dhgexreq = new PacketKexDhGexRequestOld(kxs.dhgexParameters); + tm.sendKexMessage(dhgexreq.getPayload()); + } + else { + PacketKexDhGexRequest dhgexreq = new PacketKexDhGexRequest(kxs.dhgexParameters); + tm.sendKexMessage(dhgexreq.getPayload()); + } + + if (kxs.np.kex_algo.endsWith("sha1")) { + kxs.hashAlgo = "SHA1"; + } + else { + kxs.hashAlgo = "SHA2"; + } + + kxs.state = 1; + return; + } + + if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1") || + kxs.np.kex_algo.equals("diffie-hellman-group14-sha1") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp256") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp384") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp521")) { + kxs.dhx = GenericDhExchange.getInstance(kxs.np.kex_algo); + kxs.dhx.init(kxs.np.kex_algo); + kxs.hashAlgo = kxs.dhx.getHashAlgo(); + PacketKexDHInit kp = new PacketKexDHInit(kxs.dhx.getE()); + tm.sendKexMessage(kp.getPayload()); + kxs.state = 1; + return; + } + + throw new IllegalStateException("Unkown KEX method!"); + } + + if (msg[0] == Packets.SSH_MSG_NEWKEYS) { + if (km == null) { + throw new IOException("Peer sent SSH_MSG_NEWKEYS, but I have no key material ready!"); + } + + BlockCipher cbc; + MAC mac; + Compressor comp; + + try { + cbc = BlockCipherFactory.createCipher(kxs.np.enc_algo_server_to_client, false, + km.enc_key_server_to_client, km.initial_iv_server_to_client); + + try { + mac = new MAC(kxs.np.mac_algo_server_to_client, km.integrity_key_server_to_client); + } + catch (DigestException e) { + throw new IOException(e); + } + + comp = CompressionFactory.createCompressor(kxs.np.comp_algo_server_to_client); + } + catch (IllegalArgumentException e) { + throw new IOException(e.getMessage()); + } + + tm.changeRecvCipher(cbc, mac); + tm.changeRecvCompression(comp); + ConnectionInfo sci = new ConnectionInfo(); + kexCount++; + sci.keyExchangeAlgorithm = kxs.np.kex_algo; + sci.keyExchangeCounter = kexCount; + sci.clientToServerCryptoAlgorithm = kxs.np.enc_algo_client_to_server; + sci.serverToClientCryptoAlgorithm = kxs.np.enc_algo_server_to_client; + sci.clientToServerMACAlgorithm = kxs.np.mac_algo_client_to_server; + sci.serverToClientMACAlgorithm = kxs.np.mac_algo_server_to_client; + sci.serverHostKeyAlgorithm = kxs.np.server_host_key_algo; + sci.serverHostKey = kxs.remote_hostkey; + + synchronized (accessLock) { + lastConnInfo = sci; + accessLock.notifyAll(); + } + + kxs = null; + return; + } + + if ((kxs == null) || (kxs.state == 0)) { + throw new IOException("Unexpected Kex submessage!"); + } + + if (kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha1") || + kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha256")) { + if (kxs.state == 1) { + PacketKexDhGexGroup dhgexgrp = new PacketKexDhGexGroup(msg); + kxs.dhgx = new DhGroupExchange(dhgexgrp.getP(), dhgexgrp.getG()); + kxs.dhgx.init(rnd); + PacketKexDhGexInit dhgexinit = new PacketKexDhGexInit(kxs.dhgx.getE()); + tm.sendKexMessage(dhgexinit.getPayload()); + kxs.state = 2; + return; + } + + if (kxs.state == 2) { + PacketKexDhGexReply dhgexrpl = new PacketKexDhGexReply(msg); + kxs.remote_hostkey = dhgexrpl.getHostKey(); + + if (verifier != null) { + try { + if (!verifier.verifyServerHostKey(hostname, port, kxs.np.server_host_key_algo, kxs.remote_hostkey)) { + throw new IOException("The server host key was not accepted by the verifier callback"); + } + } + catch (Exception e) { + throw new IOException( + "The server host key was not accepted by the verifier callback.", e); + } + } + + kxs.dhgx.setF(dhgexrpl.getF()); + + try { + kxs.H = kxs.dhgx.calculateH(kxs.hashAlgo, csh.getClientString(), csh.getServerString(), + kxs.localKEX.getPayload(), kxs.remoteKEX.getPayload(), dhgexrpl.getHostKey(), + kxs.dhgexParameters); + } + catch (IllegalArgumentException e) { + throw new IOException("KEX error.", e); + } + + if (!verifySignature(dhgexrpl.getSignature(), kxs.remote_hostkey)) { + throw new IOException("Invalid remote host key signature"); + } + + kxs.K = kxs.dhgx.getK(); + finishKex(true); + kxs.state = -1; + return; + } + + throw new IllegalStateException("Illegal State in KEX Exchange!"); + } + + if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1") || + kxs.np.kex_algo.equals("diffie-hellman-group14-sha1") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp256") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp384") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp521")) { + if (kxs.state == 1) { + PacketKexDHReply dhr = new PacketKexDHReply(msg); + kxs.remote_hostkey = dhr.getHostKey(); + + if (verifier != null) { + try { + if (!verifier.verifyServerHostKey(hostname, port, kxs.np.server_host_key_algo, kxs.remote_hostkey)) { + throw new IOException("The server host key was not accepted by the verifier callback"); + } + } + catch (Exception e) { + throw new IOException("The server host key was not accepted by the verifier callback", e); + } + } + + kxs.dhx.setF(dhr.getF().toByteArray()); + + try { + kxs.H = kxs.dhx.calculateH(csh.getClientString(), csh.getServerString(), kxs.localKEX.getPayload(), + kxs.remoteKEX.getPayload(), dhr.getHostKey()); + } + catch (IllegalArgumentException e) { + throw new IOException("KEX error.", e); + } + + if (!verifySignature(dhr.getSignature(), kxs.remote_hostkey)) { + throw new IOException("Invalid remote host key signature"); + } + + kxs.K = kxs.dhx.getK(); + finishKex(true); + kxs.state = -1; + return; + } + } + + throw new IllegalStateException(String.format("Unknown KEX method %s", kxs.np.kex_algo)); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/transport/ClientServerHello.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/transport/ClientServerHello.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2.transport; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.OutputStream; + +import ch.ethz.ssh2.util.StringEncoder; + +/** + * @author Christian Plattner + * @version $Id: ClientServerHello.java 155 2014-04-28 12:01:19Z dkocher@sudo.ch $ + */ +public class ClientServerHello { + private final String client_line; + private final String server_line; + + public final static int readLineRN(InputStream is, byte[] buffer) throws IOException { + int pos = 0; + boolean need10 = false; + int len = 0; + + while (true) { + int c = is.read(); + + if (c == -1) + throw new IOException("Premature connection close"); + + buffer[pos++] = (byte) c; + + if (c == 13) { + need10 = true; + continue; + } + + if (c == 10) + break; + + if (need10 == true) + throw new IOException("Malformed line sent by the server, the line does not end correctly."); + + len++; + + if (pos >= buffer.length) + throw new IOException("The server sent a too long line."); + } + + return len; + } + + private ClientServerHello(String client_line, String server_line) { + this.client_line = client_line; + this.server_line = server_line; + } + + public static ClientServerHello clientHello(String softwareversion, InputStream bi, OutputStream bo) + throws IOException { + return exchange(softwareversion, bi, bo, true); + } + + public static ClientServerHello serverHello(String softwareversion, InputStream bi, OutputStream bo) + throws IOException { + return exchange(softwareversion, bi, bo, false); + } + + private static ClientServerHello exchange(String softwareversion, InputStream bi, OutputStream bo, boolean clientMode) + throws IOException { + String localIdentifier = String.format("SSH-2.0-%s", softwareversion); + bo.write(StringEncoder.GetBytes(String.format("%s\r\n", localIdentifier))); + bo.flush(); + // Expect SSH-protoversion-softwareversion SP comments CR LF + byte[] serverVersion = new byte[512]; + String remoteIdentifier = null; + for (int i = 0; i < 50; i++) { + int len = readLineRN(bi, serverVersion); + remoteIdentifier = new String(serverVersion, 0, len, "ISO-8859-1"); + if (remoteIdentifier.startsWith("SSH-")) break; + } + + if (remoteIdentifier.equals("")) { + throw new IOException("Premature connection close"); + } + + if (!remoteIdentifier.startsWith("SSH-")) { + throw new IOException(String.format("Malformed SSH identification %s", remoteIdentifier)); + } + + if (!remoteIdentifier.startsWith("SSH-1.99-") + && !remoteIdentifier.startsWith("SSH-2.0-")) { + throw new IOException(String.format("Incompatible remote protocol version %s", remoteIdentifier)); + } + + if (clientMode) { + return new ClientServerHello(localIdentifier, remoteIdentifier); + } + else { + return new ClientServerHello(remoteIdentifier, localIdentifier); + } + } + + /** + * @return Returns the client_versioncomment. + */ + public byte[] getClientString() { + return StringEncoder.GetBytes(client_line); + } + + /** + * @return Returns the server_versioncomment. + */ + public byte[] getServerString() { + return StringEncoder.GetBytes(server_line); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/transport/ClientTransportManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/transport/ClientTransportManager.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,54 @@ +package ch.ethz.ssh2.transport; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.security.SecureRandom; + +import ch.ethz.ssh2.DHGexParameters; +import ch.ethz.ssh2.ServerHostKeyVerifier; +import ch.ethz.ssh2.crypto.CryptoWishList; + +/** + * @version $Id: ClientTransportManager.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class ClientTransportManager extends TransportManager { + + private final Socket sock; + + public ClientTransportManager(final Socket socket) { + super(socket); + this.sock = socket; + } + + public void setTcpNoDelay(boolean state) throws IOException { + sock.setTcpNoDelay(state); + } + + public void setSoTimeout(int timeout) throws IOException { + sock.setSoTimeout(timeout); + } + + public void connect(String hostname, int port, String softwareversion, CryptoWishList cwl, + ServerHostKeyVerifier verifier, DHGexParameters dhgex, int connectTimeout, SecureRandom rnd) + throws IOException { + // Establish the TCP connection to the SSH-2 server + this.connect(hostname, port, connectTimeout); + // Parse the server line and say hello - important: this information is later needed for the + // key exchange (to stop man-in-the-middle attacks) - that is why we wrap it into an object + // for later use. + ClientServerHello csh = ClientServerHello.clientHello(softwareversion, sock.getInputStream(), + sock.getOutputStream()); + TransportConnection tc = new TransportConnection(sock.getInputStream(), sock.getOutputStream(), rnd); + KexManager km = new ClientKexManager(this, csh, cwl, hostname, port, verifier, rnd); + super.init(tc, km); + km.initiateKEX(cwl, dhgex, null, null, null); + this.startReceiver(); + } + + protected void connect(String hostname, int port, int connectTimeout) throws IOException { + log.debug(String.format("client transport manager connecting to %s:%d", hostname, port)); + sock.connect(new InetSocketAddress(hostname, port), connectTimeout); + log.debug(String.format("client transport manager connected to %s:%d", hostname, port)); + } +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/transport/DisconnectException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/transport/DisconnectException.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,22 @@ +package ch.ethz.ssh2.transport; + +import java.io.IOException; + +import ch.ethz.ssh2.packets.PacketDisconnect; + +/** + * @version $Id: DisconnectException.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public class DisconnectException extends IOException { + + private PacketDisconnect.Reason reason; + + public DisconnectException(final PacketDisconnect.Reason reason, final String message) { + super(message); + this.reason = reason; + } + + public PacketDisconnect.Reason getReason() { + return reason; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/transport/HTTPProxyClientTransportManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/transport/HTTPProxyClientTransportManager.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,103 @@ +package ch.ethz.ssh2.transport; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; + +import ch.ethz.ssh2.HTTPProxyData; +import ch.ethz.ssh2.HTTPProxyException; +import ch.ethz.ssh2.crypto.Base64; +import ch.ethz.ssh2.util.StringEncoder; + +/** + * @version $Id: HTTPProxyClientTransportManager.java 155 2014-04-28 12:01:19Z dkocher@sudo.ch $ + */ +public class HTTPProxyClientTransportManager extends ClientTransportManager { + + /** + * Used to tell the library that the connection shall be established through a proxy server. + */ + + private HTTPProxyData pd; + + private final Socket sock; + + public HTTPProxyClientTransportManager(final Socket socket, final HTTPProxyData pd) { + super(socket); + this.sock = socket; + this.pd = pd; + } + + @Override + protected void connect(final String hostname, final int port, final int connectTimeout) throws IOException { + sock.connect(new InetSocketAddress(pd.proxyHost, pd.proxyPort), connectTimeout); + // Tell the proxy where we actually want to connect to + StringBuilder sb = new StringBuilder(); + sb.append("CONNECT "); + sb.append(hostname); + sb.append(':'); + sb.append(port); + sb.append(" HTTP/1.0\r\n"); + + if ((pd.proxyUser != null) && (pd.proxyPass != null)) { + String credentials = pd.proxyUser + ":" + pd.proxyPass; + char[] encoded = Base64.encode(StringEncoder.GetBytes(credentials)); + sb.append("Proxy-Authorization: Basic "); + sb.append(encoded); + sb.append("\r\n"); + } + + if (pd.requestHeaderLines != null) { + for (int i = 0; i < pd.requestHeaderLines.length; i++) { + if (pd.requestHeaderLines[i] != null) { + sb.append(pd.requestHeaderLines[i]); + sb.append("\r\n"); + } + } + } + + sb.append("\r\n"); + OutputStream out = sock.getOutputStream(); + out.write(StringEncoder.GetBytes(sb.toString())); + out.flush(); + // Parse the HTTP response + InputStream in = sock.getInputStream(); + final LineNumberReader reader = new LineNumberReader(new InputStreamReader(in)); + String httpReponse = reader.readLine(); + + if (!httpReponse.startsWith("HTTP/")) { + throw new IOException("The proxy did not send back a valid HTTP response."); + } + + // "HTTP/1.X XYZ X" => 14 characters minimum + + if ((httpReponse.length() < 14) || (httpReponse.charAt(8) != ' ') || (httpReponse.charAt(12) != ' ')) { + throw new IOException("The proxy did not send back a valid HTTP response."); + } + + int errorCode; + + try { + errorCode = Integer.parseInt(httpReponse.substring(9, 12)); + } + catch (NumberFormatException ignore) { + throw new IOException("The proxy did not send back a valid HTTP response."); + } + + if ((errorCode < 0) || (errorCode > 999)) { + throw new IOException("The proxy did not send back a valid HTTP response."); + } + + if (errorCode != 200) { + throw new HTTPProxyException(httpReponse.substring(13), errorCode); + } + + while (reader.readLine() != null) { + // Read until empty line + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/transport/KexManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/transport/KexManager.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.transport; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.security.DigestException; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.ECPrivateKey; +import java.util.Arrays; +import java.util.ArrayList; + +import ch.ethz.ssh2.ConnectionInfo; +import ch.ethz.ssh2.DHGexParameters; +import ch.ethz.ssh2.compression.CompressionFactory; +import ch.ethz.ssh2.compression.Compressor; +import ch.ethz.ssh2.crypto.CryptoWishList; +import ch.ethz.ssh2.crypto.KeyMaterial; +import ch.ethz.ssh2.crypto.cipher.BlockCipher; +import ch.ethz.ssh2.crypto.cipher.BlockCipherFactory; +import ch.ethz.ssh2.crypto.digest.MAC; +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.PacketKexInit; +import ch.ethz.ssh2.packets.PacketNewKeys; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.ECPrivateKey; + +/** + * @version $Id: KexManager.java 152 2014-04-28 11:02:23Z dkocher@sudo.ch $ + */ +public abstract class KexManager implements MessageHandler { + protected static final Logger log = Logger.getLogger(KexManager.class); + + private static final ArrayList HOSTKEY_ALGS = new ArrayList(); + static { + HOSTKEY_ALGS.add("ssh-rsa"); + HOSTKEY_ALGS.add("ssh-dss"); + HOSTKEY_ALGS.add("ecdsa-sha2-nistp256"); + HOSTKEY_ALGS.add("ecdsa-sha2-nistp384"); + HOSTKEY_ALGS.add("ecdsa-sha2-nistp521"); + } + + private static final ArrayList KEX_ALGS = new ArrayList(); + static { + KEX_ALGS.add("diffie-hellman-group-exchange-sha256"); + KEX_ALGS.add("diffie-hellman-group-exchange-sha1"); + KEX_ALGS.add("diffie-hellman-group14-sha1"); + KEX_ALGS.add("diffie-hellman-group1-sha1"); + KEX_ALGS.add("ecdh-sha2-nistp256"); + KEX_ALGS.add("ecdh-sha2-nistp384"); + KEX_ALGS.add("ecdh-sha2-nistp521"); + } + + KexState kxs; + int kexCount = 0; + KeyMaterial km; + byte[] sessionId; + ClientServerHello csh; + + final Object accessLock = new Object(); + ConnectionInfo lastConnInfo = null; + + boolean connectionClosed = false; + + boolean ignore_next_kex_packet = false; + + final TransportManager tm; + + CryptoWishList nextKEXcryptoWishList; + DHGexParameters nextKEXdhgexParameters; + KeyPair nextKEXdsakey; + KeyPair nextKEXrsakey; + KeyPair nextKEXeckey; + + final SecureRandom rnd; + + public KexManager(TransportManager tm, ClientServerHello csh, CryptoWishList initialCwl, SecureRandom rnd) { + this.tm = tm; + this.csh = csh; + this.nextKEXcryptoWishList = initialCwl; + this.nextKEXdhgexParameters = new DHGexParameters(); + this.rnd = rnd; + } + + public ConnectionInfo getOrWaitForConnectionInfo(int minKexCount) throws IOException { + synchronized (accessLock) { + while (true) { + if ((lastConnInfo != null) && (lastConnInfo.keyExchangeCounter >= minKexCount)) { + return lastConnInfo; + } + + if (connectionClosed) { + throw(IOException) new IOException("Key exchange was not finished, connection is closed.").initCause(tm.getReasonClosedCause()); + } + + try { + accessLock.wait(); + } + catch (InterruptedException e) { + throw new InterruptedIOException(e.getMessage()); + } + } + } + } + + private String getFirstMatch(String[] client, String[] server) throws NegotiateException { + if (client == null || server == null) { + throw new IllegalArgumentException(); + } + + for (String c : client) { + for (String s : server) { + if (c.equals(s)) { + return c; + } + } + } + + throw new NegotiateException(String.format("Negotiation failed for %s", Arrays.toString(server))); + } + + private boolean compareFirstOfNameList(String[] a, String[] b) { + if (a == null || b == null) { + throw new IllegalArgumentException(); + } + + if ((a.length == 0) && (b.length == 0)) { + return true; + } + + if ((a.length == 0) || (b.length == 0)) { + return false; + } + + return (a[0].equals(b[0])); + } + + private boolean isGuessOK(KexParameters cpar, KexParameters spar) { + if (cpar == null || spar == null) { + throw new IllegalArgumentException(); + } + + if (!compareFirstOfNameList(cpar.kex_algorithms, spar.kex_algorithms)) { + return false; + } + + if (!compareFirstOfNameList(cpar.server_host_key_algorithms, spar.server_host_key_algorithms)) { + return false; + } + + /* + * We do NOT check here if the other algorithms can be agreed on, this + * is just a check if kex_algorithms and server_host_key_algorithms were + * guessed right! + */ + return true; + } + + protected NegotiatedParameters mergeKexParameters(KexParameters client, KexParameters server) + throws NegotiateException { + NegotiatedParameters np = new NegotiatedParameters(); + np.kex_algo = getFirstMatch(client.kex_algorithms, server.kex_algorithms); + log.info("kex_algo=" + np.kex_algo); + np.server_host_key_algo = getFirstMatch(client.server_host_key_algorithms, + server.server_host_key_algorithms); + log.info("server_host_key_algo=" + np.server_host_key_algo); + np.enc_algo_client_to_server = getFirstMatch(client.encryption_algorithms_client_to_server, + server.encryption_algorithms_client_to_server); + np.enc_algo_server_to_client = getFirstMatch(client.encryption_algorithms_server_to_client, + server.encryption_algorithms_server_to_client); + log.info("enc_algo_client_to_server=" + np.enc_algo_client_to_server); + log.info("enc_algo_server_to_client=" + np.enc_algo_server_to_client); + np.mac_algo_client_to_server = getFirstMatch(client.mac_algorithms_client_to_server, + server.mac_algorithms_client_to_server); + np.mac_algo_server_to_client = getFirstMatch(client.mac_algorithms_server_to_client, + server.mac_algorithms_server_to_client); + log.info("mac_algo_client_to_server=" + np.mac_algo_client_to_server); + log.info("mac_algo_server_to_client=" + np.mac_algo_server_to_client); + np.comp_algo_client_to_server = getFirstMatch(client.compression_algorithms_client_to_server, + server.compression_algorithms_client_to_server); + np.comp_algo_server_to_client = getFirstMatch(client.compression_algorithms_server_to_client, + server.compression_algorithms_server_to_client); + log.info("comp_algo_client_to_server=" + np.comp_algo_client_to_server); + log.info("comp_algo_server_to_client=" + np.comp_algo_server_to_client); + np.lang_client_to_server = getFirstMatch(client.languages_client_to_server, + server.languages_client_to_server); + np.lang_server_to_client = getFirstMatch(client.languages_server_to_client, + server.languages_server_to_client); + + if (isGuessOK(client, server)) { + np.guessOK = true; + } + + return np; + } + + public synchronized void initiateKEX(CryptoWishList cwl, DHGexParameters dhgex, KeyPair dsa, KeyPair rsa, KeyPair ec) + throws IOException { + nextKEXcryptoWishList = cwl; + nextKEXdhgexParameters = dhgex; + nextKEXdsakey = dsa; + nextKEXrsakey = rsa; + nextKEXeckey = ec; + + if (kxs == null) { + kxs = new KexState(); + kxs.local_dsa_key = dsa; + kxs.local_rsa_key = rsa; + kxs.local_ec_key = ec; + kxs.dhgexParameters = nextKEXdhgexParameters; + kxs.localKEX = new PacketKexInit(nextKEXcryptoWishList, rnd); + tm.sendKexMessage(kxs.localKEX.getPayload()); + } + } + + private boolean establishKeyMaterial() throws IOException { + try { + int mac_cs_key_len = MAC.getKeyLen(kxs.np.mac_algo_client_to_server); + int enc_cs_key_len = BlockCipherFactory.getKeySize(kxs.np.enc_algo_client_to_server); + int enc_cs_block_len = BlockCipherFactory.getBlockSize(kxs.np.enc_algo_client_to_server); + int mac_sc_key_len = MAC.getKeyLen(kxs.np.mac_algo_server_to_client); + int enc_sc_key_len = BlockCipherFactory.getKeySize(kxs.np.enc_algo_server_to_client); + int enc_sc_block_len = BlockCipherFactory.getBlockSize(kxs.np.enc_algo_server_to_client); + km = KeyMaterial.create(kxs.hashAlgo, kxs.H, kxs.K, sessionId, enc_cs_key_len, enc_cs_block_len, mac_cs_key_len, + enc_sc_key_len, enc_sc_block_len, mac_sc_key_len); + } + catch (IllegalArgumentException e) { + return false; + } + + return true; + } + + protected void finishKex(boolean clientMode) throws IOException { + if (sessionId == null) { + sessionId = kxs.H; + } + + establishKeyMaterial(); + /* Tell the other side that we start using the new material */ + PacketNewKeys ign = new PacketNewKeys(); + tm.sendKexMessage(ign.getPayload()); + BlockCipher cbc; + MAC mac; + Compressor comp; + + try { + cbc = BlockCipherFactory.createCipher(clientMode ? kxs.np.enc_algo_client_to_server + : kxs.np.enc_algo_server_to_client, true, clientMode ? km.enc_key_client_to_server + : km.enc_key_server_to_client, clientMode ? km.initial_iv_client_to_server + : km.initial_iv_server_to_client); + + try { + mac = new MAC(clientMode ? kxs.np.mac_algo_client_to_server : kxs.np.mac_algo_server_to_client, clientMode + ? km.integrity_key_client_to_server : km.integrity_key_server_to_client); + } + catch (DigestException e) { + throw new IOException(e); + } + + comp = CompressionFactory.createCompressor(kxs.np.comp_algo_client_to_server); + } + catch (IllegalArgumentException f) { + throw new IOException(String.format("Fatal error initializing ciphers. %s", f.getMessage())); + } + + tm.changeSendCipher(cbc, mac); + tm.changeSendCompression(comp); + tm.kexFinished(); + } + + public static String[] getDefaultServerHostkeyAlgorithmList() { + return HOSTKEY_ALGS.toArray(new String[HOSTKEY_ALGS.size()]); + } + + public static void checkServerHostkeyAlgorithmsList(String[] algos) { + for (final String algo : algos) { + if (!HOSTKEY_ALGS.contains(algo)) + throw new IllegalArgumentException("Unknown server host key algorithm '" + algo + "'"); + } + } + + public static String[] getDefaultClientKexAlgorithmList() { + return KEX_ALGS.toArray(new String[KEX_ALGS.size()]); + } + + public static String[] getDefaultServerKexAlgorithmList() { + return KEX_ALGS.toArray(new String[KEX_ALGS.size()]); + } + + public static void checkKexAlgorithmList(String[] algos) { + for (final String algo : algos) { + if (!KEX_ALGS.contains(algo)) + throw new IllegalArgumentException("Unknown kex algorithm '" + algo + "'"); + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/transport/KexParameters.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/transport/KexParameters.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.transport; + +import java.util.Arrays; + +/** + * @author Christian Plattner + * @version $Id: KexParameters.java 159 2014-05-01 14:12:34Z dkocher@sudo.ch $ + */ +public class KexParameters { + public byte[] cookie; + public String[] kex_algorithms; + public String[] server_host_key_algorithms; + public String[] encryption_algorithms_client_to_server; + public String[] encryption_algorithms_server_to_client; + public String[] mac_algorithms_client_to_server; + public String[] mac_algorithms_server_to_client; + public String[] compression_algorithms_client_to_server; + public String[] compression_algorithms_server_to_client; + public String[] languages_client_to_server; + public String[] languages_server_to_client; + public boolean first_kex_packet_follows; + public int reserved_field1; + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("KexParameters{"); + sb.append("cookie=").append(Arrays.toString(cookie)); + sb.append(", kex_algorithms=").append(Arrays.toString(kex_algorithms)); + sb.append(", server_host_key_algorithms=").append(Arrays.toString(server_host_key_algorithms)); + sb.append(", encryption_algorithms_client_to_server=").append(Arrays.toString(encryption_algorithms_client_to_server)); + sb.append(", encryption_algorithms_server_to_client=").append(Arrays.toString(encryption_algorithms_server_to_client)); + sb.append(", mac_algorithms_client_to_server=").append(Arrays.toString(mac_algorithms_client_to_server)); + sb.append(", mac_algorithms_server_to_client=").append(Arrays.toString(mac_algorithms_server_to_client)); + sb.append(", compression_algorithms_client_to_server=").append(Arrays.toString(compression_algorithms_client_to_server)); + sb.append(", compression_algorithms_server_to_client=").append(Arrays.toString(compression_algorithms_server_to_client)); + sb.append(", languages_client_to_server=").append(Arrays.toString(languages_client_to_server)); + sb.append(", languages_server_to_client=").append(Arrays.toString(languages_server_to_client)); + sb.append(", first_kex_packet_follows=").append(first_kex_packet_follows); + sb.append(", reserved_field1=").append(reserved_field1); + sb.append('}'); + return sb.toString(); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/transport/KexState.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/transport/KexState.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.transport; + +import ch.ethz.ssh2.DHGexParameters; +import ch.ethz.ssh2.crypto.dh.GenericDhExchange; +import ch.ethz.ssh2.crypto.dh.DhGroupExchange; +import java.math.BigInteger; +import ch.ethz.ssh2.packets.PacketKexInit; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.ECPrivateKey; + +/** + * KexState. + * + * @author Christian Plattner + * @version 2.50, 03/15/10 + */ +public class KexState { + public PacketKexInit localKEX; + public PacketKexInit remoteKEX; + public NegotiatedParameters np; + public int state = 0; + + public BigInteger K; + public byte[] H; + + public byte[] remote_hostkey; + + public String hashAlgo; + public GenericDhExchange dhx; + public DhGroupExchange dhgx; + public DHGexParameters dhgexParameters; + + public KeyPair local_dsa_key; + public KeyPair local_rsa_key; + public KeyPair local_ec_key; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/transport/MessageHandler.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/transport/MessageHandler.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.transport; + +import java.io.IOException; + +/** + * @author Christian Plattner + * @version $Id: MessageHandler.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public interface MessageHandler { + public void handleMessage(byte[] msg) throws IOException; + + public void handleFailure(IOException failure); +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/transport/NegotiateException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/transport/NegotiateException.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.transport; + +import java.io.IOException; + +/** + * @version $Id: NegotiateException.java 149 2014-04-28 09:18:35Z dkocher@sudo.ch $ + */ +public class NegotiateException extends IOException { + private static final long serialVersionUID = 3689910669428143157L; + + public NegotiateException() { + // + } + + public NegotiateException(String message) { + super(message); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/transport/NegotiatedParameters.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/transport/NegotiatedParameters.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.transport; + +/** + * @author Christian Plattner + * @version $Id: NegotiatedParameters.java 159 2014-05-01 14:12:34Z dkocher@sudo.ch $ + */ +public class NegotiatedParameters { + public boolean guessOK; + public String kex_algo; + public String server_host_key_algo; + public String enc_algo_client_to_server; + public String enc_algo_server_to_client; + public String mac_algo_client_to_server; + public String mac_algo_server_to_client; + public String comp_algo_client_to_server; + public String comp_algo_server_to_client; + public String lang_client_to_server; + public String lang_server_to_client; + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("NegotiatedParameters{"); + sb.append("guessOK=").append(guessOK); + sb.append(", kex_algo='").append(kex_algo).append('\''); + sb.append(", server_host_key_algo='").append(server_host_key_algo).append('\''); + sb.append(", enc_algo_client_to_server='").append(enc_algo_client_to_server).append('\''); + sb.append(", enc_algo_server_to_client='").append(enc_algo_server_to_client).append('\''); + sb.append(", mac_algo_client_to_server='").append(mac_algo_client_to_server).append('\''); + sb.append(", mac_algo_server_to_client='").append(mac_algo_server_to_client).append('\''); + sb.append(", comp_algo_client_to_server='").append(comp_algo_client_to_server).append('\''); + sb.append(", comp_algo_server_to_client='").append(comp_algo_server_to_client).append('\''); + sb.append(", lang_client_to_server='").append(lang_client_to_server).append('\''); + sb.append(", lang_server_to_client='").append(lang_server_to_client).append('\''); + sb.append('}'); + return sb.toString(); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/transport/ServerKexManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/transport/ServerKexManager.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.transport; + +import java.io.IOException; +import java.security.DigestException; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.math.BigInteger; + +import ch.ethz.ssh2.ConnectionInfo; +import ch.ethz.ssh2.PacketTypeException; +import ch.ethz.ssh2.auth.ServerAuthenticationManager; +import ch.ethz.ssh2.crypto.cipher.BlockCipher; +import ch.ethz.ssh2.crypto.cipher.BlockCipherFactory; +import ch.ethz.ssh2.crypto.dh.GenericDhExchange; +import ch.ethz.ssh2.crypto.digest.MAC; +import ch.ethz.ssh2.packets.PacketKexDHInit; +import ch.ethz.ssh2.packets.PacketKexDHReply; +import ch.ethz.ssh2.packets.PacketKexInit; +import ch.ethz.ssh2.packets.Packets; +import ch.ethz.ssh2.server.ServerConnectionState; +import ch.ethz.ssh2.signature.DSASHA1Verify; +import ch.ethz.ssh2.signature.ECDSASHA2Verify; +import ch.ethz.ssh2.signature.RSASHA1Verify; + +/** + * @version $Id: ServerKexManager.java 160 2014-05-01 14:30:26Z dkocher@sudo.ch $ + */ +public class ServerKexManager extends KexManager { + + private final ServerConnectionState state; + + private boolean authenticationStarted = false; + + public ServerKexManager(ServerConnectionState state) { + super(state.tm, state.csh, state.next_cryptoWishList, state.generator); + this.state = state; + } + + public void handleFailure(final IOException failure) { + synchronized (accessLock) { + connectionClosed = true; + accessLock.notifyAll(); + } + } + + public void handleMessage(byte[] msg) throws IOException { + PacketKexInit kip; + + if (msg == null) { + synchronized (accessLock) { + connectionClosed = true; + accessLock.notifyAll(); + return; + } + } + + if ((kxs == null) && (msg[0] != Packets.SSH_MSG_KEXINIT)) { + throw new PacketTypeException(msg[0]); + } + + if (ignore_next_kex_packet) { + ignore_next_kex_packet = false; + return; + } + + if (msg[0] == Packets.SSH_MSG_KEXINIT) { + if ((kxs != null) && (kxs.state != 0)) { + throw new PacketTypeException(msg[0]); + } + + if (kxs == null) { + /* + * Ah, OK, peer wants to do KEX. Let's be nice and play + * together. + */ + kxs = new KexState(); + kxs.local_dsa_key = nextKEXdsakey; + kxs.local_rsa_key = nextKEXrsakey; + kxs.local_ec_key = nextKEXeckey; + kxs.dhgexParameters = nextKEXdhgexParameters; + kip = new PacketKexInit(nextKEXcryptoWishList, rnd); + kxs.localKEX = kip; + tm.sendKexMessage(kip.getPayload()); + } + + kip = new PacketKexInit(msg); + kxs.remoteKEX = kip; + kxs.np = mergeKexParameters(kxs.remoteKEX.getKexParameters(), kxs.localKEX.getKexParameters()); + + if (kxs.remoteKEX.isFirst_kex_packet_follows() && (kxs.np.guessOK == false)) { + // Guess was wrong, we need to ignore the next kex packet. + ignore_next_kex_packet = true; + } + + if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1") || + kxs.np.kex_algo.equals("diffie-hellman-group14-sha1") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp256") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp384") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp521")) { + kxs.dhx = GenericDhExchange.getInstance(kxs.np.kex_algo); + kxs.dhx.init(kxs.np.kex_algo); + kxs.state = 1; + return; + } + + throw new IllegalStateException("Unkown KEX method!"); + } + + if (msg[0] == Packets.SSH_MSG_NEWKEYS) { + if (km == null) { + throw new IOException("Peer sent SSH_MSG_NEWKEYS, but I have no key material ready!"); + } + + BlockCipher cbc; + MAC mac; + + try { + cbc = BlockCipherFactory.createCipher(kxs.np.enc_algo_client_to_server, false, + km.enc_key_client_to_server, km.initial_iv_client_to_server); + + try { + mac = new MAC(kxs.np.mac_algo_client_to_server, km.integrity_key_client_to_server); + } + catch (DigestException e) { + throw new IOException(e); + } + } + catch (IllegalArgumentException e) { + throw new IOException(e); + } + + tm.changeRecvCipher(cbc, mac); + ConnectionInfo sci = new ConnectionInfo(); + kexCount++; + sci.keyExchangeAlgorithm = kxs.np.kex_algo; + sci.keyExchangeCounter = kexCount; + sci.clientToServerCryptoAlgorithm = kxs.np.enc_algo_client_to_server; + sci.serverToClientCryptoAlgorithm = kxs.np.enc_algo_server_to_client; + sci.clientToServerMACAlgorithm = kxs.np.mac_algo_client_to_server; + sci.serverToClientMACAlgorithm = kxs.np.mac_algo_server_to_client; + sci.serverHostKeyAlgorithm = kxs.np.server_host_key_algo; + sci.serverHostKey = kxs.remote_hostkey; + + synchronized (accessLock) { + lastConnInfo = sci; + accessLock.notifyAll(); + } + + kxs = null; + return; + } + + if ((kxs == null) || (kxs.state == 0)) { + throw new IOException("Unexpected Kex submessage!"); + } + + if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1") || + kxs.np.kex_algo.equals("diffie-hellman-group14-sha1") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp256") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp384") || + kxs.np.kex_algo.equals("ecdh-sha2-nistp521")) { + if (kxs.state == 1) { + PacketKexDHInit dhi = new PacketKexDHInit(msg); + kxs.dhx.setE(dhi.getE()); + byte[] hostKey = null; + + if (kxs.np.server_host_key_algo.startsWith("ecdsa-sha2-")) { + hostKey = ECDSASHA2Verify.encodeSSHECDSAPublicKey((ECPublicKey)kxs.local_ec_key.getPublic()); + } + + if (kxs.np.server_host_key_algo.equals("ssh-rsa")) { + hostKey = RSASHA1Verify.encodeSSHRSAPublicKey((RSAPublicKey)kxs.local_rsa_key.getPublic()); + } + + if (kxs.np.server_host_key_algo.equals("ssh-dss")) { + hostKey = DSASHA1Verify.encodeSSHDSAPublicKey((DSAPublicKey)kxs.local_dsa_key.getPublic()); + } + + try { + kxs.H = kxs.dhx.calculateH(csh.getClientString(), csh.getServerString(), + kxs.remoteKEX.getPayload(), kxs.localKEX.getPayload(), hostKey); + } + catch (IllegalArgumentException e) { + throw new IOException("KEX error.", e); + } + + kxs.K = kxs.dhx.getK(); + byte[] signature = null; + + if (kxs.np.server_host_key_algo.startsWith("ecdsa-sha2-")) { + ECPrivateKey pk = (ECPrivateKey)kxs.local_ec_key.getPrivate(); + byte[] es = ECDSASHA2Verify.generateSignature(kxs.H, pk); + signature = ECDSASHA2Verify.encodeSSHECDSASignature(es, pk.getParams()); + } + + if (kxs.np.server_host_key_algo.equals("ssh-rsa")) { + byte[] rs = RSASHA1Verify.generateSignature(kxs.H, (RSAPrivateKey)kxs.local_rsa_key.getPrivate()); + signature = RSASHA1Verify.encodeSSHRSASignature(rs); + } + + if (kxs.np.server_host_key_algo.equals("ssh-dss")) { + byte[] ds = DSASHA1Verify.generateSignature(kxs.H, (DSAPrivateKey)kxs.local_dsa_key.getPrivate(), rnd); + signature = DSASHA1Verify.encodeSSHDSASignature(ds); + } + + PacketKexDHReply dhr = new PacketKexDHReply(hostKey, new BigInteger(kxs.dhx.getF()), signature); + tm.sendKexMessage(dhr.getPayload()); + finishKex(false); + kxs.state = -1; + + if (authenticationStarted == false) { + authenticationStarted = true; + state.am = new ServerAuthenticationManager(state); + } + + return; + } + } + + throw new IllegalStateException(String.format("Unknown KEX method %s", kxs.np.kex_algo)); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/transport/ServerTransportManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/transport/ServerTransportManager.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,34 @@ +package ch.ethz.ssh2.transport; + +import java.io.IOException; +import java.net.Socket; + +import ch.ethz.ssh2.server.ServerConnectionState; + +/** + * @version $Id: ServerTransportManager.java 151 2014-04-28 10:03:39Z dkocher@sudo.ch $ + */ +public class ServerTransportManager extends TransportManager { + + private final Socket sock; + + public ServerTransportManager(final Socket socket) { + super(socket); + // TCP connection is already established + this.sock = socket; + } + + public void connect(ServerConnectionState state) throws IOException { + /* Parse the client lin + e and say hello - important: this information is later needed for the + * key exchange (to stop man-in-the-middle attacks) - that is why we wrap it into an object + * for later use. + */ + state.csh = ClientServerHello.serverHello(state.softwareversion, sock.getInputStream(), sock.getOutputStream()); + TransportConnection tc = new TransportConnection(sock.getInputStream(), sock.getOutputStream(), state.generator); + KexManager km = new ServerKexManager(state); + super.init(tc, km); + km.initiateKEX(state.next_cryptoWishList, null, state.next_dsa_key, state.next_rsa_key, state.next_ec_key); + this.startReceiver(); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/transport/TransportConnection.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/transport/TransportConnection.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.transport; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SecureRandom; + +import ch.ethz.ssh2.PacketFormatException; +import ch.ethz.ssh2.compression.Compressor; +import ch.ethz.ssh2.crypto.cipher.BlockCipher; +import ch.ethz.ssh2.crypto.cipher.CipherInputStream; +import ch.ethz.ssh2.crypto.cipher.CipherOutputStream; +import ch.ethz.ssh2.crypto.cipher.NullCipher; +import ch.ethz.ssh2.crypto.digest.MAC; +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.Packets; + +/** + * TransportConnection. + * + * @author Christian Plattner + * @version $Id: TransportConnection.java 144 2014-04-25 12:48:25Z dkocher@sudo.ch $ + */ +public class TransportConnection { + private static final Logger log = Logger.getLogger(TransportConnection.class); + + int send_seq_number = 0; + + int recv_seq_number = 0; + + CipherInputStream cis; + + CipherOutputStream cos; + + boolean useRandomPadding; + + /* Depends on current MAC and CIPHER */ + + MAC send_mac; + + byte[] send_mac_buffer; + + int send_padd_blocksize = 8; + + MAC recv_mac; + + byte[] recv_mac_buffer; + + byte[] recv_mac_buffer_cmp; + + int recv_padd_blocksize = 8; + + Compressor recv_comp; + + Compressor send_comp; + + boolean can_compress; + + byte[] recv_comp_buffer; + + byte[] send_comp_buffer; + + /* won't change */ + + final byte[] send_padding_buffer = new byte[256]; + + final byte[] send_packet_header_buffer = new byte[5]; + + final byte[] recv_padding_buffer = new byte[256]; + + final byte[] recv_packet_header_buffer = new byte[5]; + + boolean recv_packet_header_present = false; + + ClientServerHello csh; + + final SecureRandom rnd; + + public TransportConnection(InputStream is, OutputStream os, SecureRandom rnd) { + this.cis = new CipherInputStream(new NullCipher(), is); + this.cos = new CipherOutputStream(new NullCipher(), os); + this.rnd = rnd; + } + + public void changeRecvCipher(BlockCipher bc, MAC mac) { + cis.changeCipher(bc); + recv_mac = mac; + recv_mac_buffer = (mac != null) ? new byte[mac.size()] : null; + recv_mac_buffer_cmp = (mac != null) ? new byte[mac.size()] : null; + recv_padd_blocksize = bc.getBlockSize(); + + if (recv_padd_blocksize < 8) { + recv_padd_blocksize = 8; + } + } + + public void changeSendCipher(BlockCipher bc, MAC mac) { + if ((bc instanceof NullCipher) == false) { + /* Only use zero byte padding for the first few packets */ + useRandomPadding = true; + /* Once we start encrypting, there is no way back */ + } + + cos.changeCipher(bc); + send_mac = mac; + send_mac_buffer = (mac != null) ? new byte[mac.size()] : null; + send_padd_blocksize = bc.getBlockSize(); + + if (send_padd_blocksize < 8) { + send_padd_blocksize = 8; + } + } + + public void changeRecvCompression(Compressor comp) { + recv_comp = comp; + + if (comp != null) { + recv_comp_buffer = new byte[comp.getBufferSize()]; + } + } + + public void changeSendCompression(Compressor comp) { + send_comp = comp; + + if (comp != null) { + send_comp_buffer = new byte[comp.getBufferSize()]; + } + } + + public void sendMessage(byte[] message) throws IOException { + sendMessage(message, 0, message.length, 0); + } + + public void sendMessage(byte[] message, int off, int len) throws IOException { + sendMessage(message, off, len, 0); + } + + public int getPacketOverheadEstimate() { + // return an estimate for the paket overhead (for send operations) + return 5 + 4 + (send_padd_blocksize - 1) + send_mac_buffer.length; + } + + public void sendMessage(byte[] message, int off, int len, int padd) throws IOException { + if (padd < 4) { + padd = 4; + } + else if (padd > 64) { + padd = 64; + } + + if (send_comp != null && can_compress) { + len = send_comp.compress(message, off, len, send_comp_buffer); + message = send_comp_buffer; + } + + int packet_len = 5 + len + padd; /* Minimum allowed padding is 4 */ + int slack = packet_len % send_padd_blocksize; + + if (slack != 0) { + packet_len += (send_padd_blocksize - slack); + } + + if (packet_len < 16) { + packet_len = 16; + } + + int padd_len = packet_len - (5 + len); + + if (useRandomPadding) { + for (int i = 0; i < padd_len; i = i + 4) { + /* + * don't waste calls to rnd.nextInt() (by using only 8bit of the + * output). just believe me: even though we may write here up to 3 + * bytes which won't be used, there is no "buffer overflow" (i.e., + * arrayindexoutofbounds). the padding buffer is big enough =) (256 + * bytes, and that is bigger than any current cipher block size + 64). + */ + int r = rnd.nextInt(); + send_padding_buffer[i] = (byte) r; + send_padding_buffer[i + 1] = (byte)(r >> 8); + send_padding_buffer[i + 2] = (byte)(r >> 16); + send_padding_buffer[i + 3] = (byte)(r >> 24); + } + } + else { + /* use zero padding for unencrypted traffic */ + for (int i = 0; i < padd_len; i++) { + send_padding_buffer[i] = 0; + } + + /* Actually this code is paranoid: we never filled any + * bytes into the padding buffer so far, therefore it should + * consist of zeros only. + */ + } + + send_packet_header_buffer[0] = (byte)((packet_len - 4) >> 24); + send_packet_header_buffer[1] = (byte)((packet_len - 4) >> 16); + send_packet_header_buffer[2] = (byte)((packet_len - 4) >> 8); + send_packet_header_buffer[3] = (byte)((packet_len - 4)); + send_packet_header_buffer[4] = (byte) padd_len; + cos.write(send_packet_header_buffer, 0, 5); + cos.write(message, off, len); + cos.write(send_padding_buffer, 0, padd_len); + + if (send_mac != null) { + send_mac.initMac(send_seq_number); + send_mac.update(send_packet_header_buffer, 0, 5); + send_mac.update(message, off, len); + send_mac.update(send_padding_buffer, 0, padd_len); + send_mac.getMac(send_mac_buffer, 0); + cos.writePlain(send_mac_buffer, 0, send_mac_buffer.length); + } + + cos.flush(); + + if (log.isDebugEnabled()) { + log.debug("Sent " + Packets.getMessageName(message[off] & 0xff) + " " + len + " bytes payload"); + } + + send_seq_number++; + } + + public int peekNextMessageLength() throws IOException { + if (recv_packet_header_present == false) { + cis.read(recv_packet_header_buffer, 0, 5); + recv_packet_header_present = true; + } + + int packet_length = ((recv_packet_header_buffer[0] & 0xff) << 24) | + ((recv_packet_header_buffer[1] & 0xff) << 16) | + ((recv_packet_header_buffer[2] & 0xff) << 8) | + ((recv_packet_header_buffer[3] & 0xff)); + int padding_length = recv_packet_header_buffer[4] & 0xff; + + if (packet_length > TransportManager.MAX_PACKET_SIZE || packet_length < 12) { + throw new PacketFormatException(String.format("Illegal packet size (%d)", packet_length)); + } + + int payload_length = packet_length - padding_length - 1; + + if (payload_length < 0) { + throw new PacketFormatException(String.format("Illegal padding_length in packet from remote (%d)", padding_length)); + } + + return payload_length; + } + + public int receiveMessage(byte buffer[], int off, int len) throws IOException { + if (recv_packet_header_present == false) { + cis.read(recv_packet_header_buffer, 0, 5); + } + else { + recv_packet_header_present = false; + } + + int packet_length = ((recv_packet_header_buffer[0] & 0xff) << 24) | + ((recv_packet_header_buffer[1] & 0xff) << 16) | + ((recv_packet_header_buffer[2] & 0xff) << 8) | + ((recv_packet_header_buffer[3] & 0xff)); + int padding_length = recv_packet_header_buffer[4] & 0xff; + + if (packet_length > TransportManager.MAX_PACKET_SIZE || packet_length < 12) { + throw new PacketFormatException(String.format("Illegal packet size (%d)", packet_length)); + } + + int payload_length = packet_length - padding_length - 1; + + if (payload_length < 0) { + throw new PacketFormatException(String.format("Illegal padding_length in packet from remote (%d)", padding_length)); + } + + if (payload_length >= len) { + throw new IOException("Receive buffer too small (" + len + ", need " + payload_length + ")"); + } + + cis.read(buffer, off, payload_length); + cis.read(recv_padding_buffer, 0, padding_length); + + if (recv_mac != null) { + cis.readPlain(recv_mac_buffer, 0, recv_mac_buffer.length); + recv_mac.initMac(recv_seq_number); + recv_mac.update(recv_packet_header_buffer, 0, 5); + recv_mac.update(buffer, off, payload_length); + recv_mac.update(recv_padding_buffer, 0, padding_length); + recv_mac.getMac(recv_mac_buffer_cmp, 0); + + for (int i = 0; i < recv_mac_buffer.length; i++) { + if (recv_mac_buffer[i] != recv_mac_buffer_cmp[i]) { + throw new IOException("Remote sent corrupt MAC."); + } + } + } + + recv_seq_number++; + + if (log.isDebugEnabled()) { + log.debug("Received " + Packets.getMessageName(buffer[off] & 0xff) + " " + payload_length + + " bytes payload"); + } + + if (recv_comp != null && can_compress) { + int[] uncomp_len = new int[] {payload_length}; + buffer = recv_comp.uncompress(buffer, off, uncomp_len); + return uncomp_len[0]; + } + else { + return payload_length; + } + } + + public void startCompression() { + can_compress = true; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/transport/TransportManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/transport/TransportManager.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,505 @@ +/* + * Copyright (c) 2006-2013 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2.transport; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.Socket; +import java.security.KeyPair; +import java.util.ArrayList; +import java.util.List; + +import ch.ethz.ssh2.ConnectionInfo; +import ch.ethz.ssh2.ConnectionMonitor; +import ch.ethz.ssh2.DHGexParameters; +import ch.ethz.ssh2.PacketTypeException; +import ch.ethz.ssh2.compression.Compressor; +import ch.ethz.ssh2.crypto.CryptoWishList; +import ch.ethz.ssh2.crypto.cipher.BlockCipher; +import ch.ethz.ssh2.crypto.digest.MAC; +import ch.ethz.ssh2.log.Logger; +import ch.ethz.ssh2.packets.PacketDisconnect; +import ch.ethz.ssh2.packets.Packets; +import ch.ethz.ssh2.packets.TypesReader; + +/** + * Yes, the "standard" is a big mess. On one side, the say that arbitrary channel + * packets are allowed during kex exchange, on the other side we need to blindly + * ignore the next _packet_ if the KEX guess was wrong. Where do we know from that + * the next packet is not a channel data packet? Yes, we could check if it is in + * the KEX range. But the standard says nothing about this. The OpenSSH guys + * block local "normal" traffic during KEX. That's fine - however, they assume + * that the other side is doing the same. During re-key, if they receive traffic + * other than KEX, they become horribly irritated and kill the connection. Since + * we are very likely going to communicate with OpenSSH servers, we have to play + * the same game - even though we could do better. + * + * @author Christian Plattner + * @version $Id: TransportManager.java 161 2014-05-01 18:01:55Z dkocher@sudo.ch $ + */ +public abstract class TransportManager { + protected static final Logger log = Logger.getLogger(TransportManager.class); + + private static final class HandlerEntry { + MessageHandler mh; + int low; + int high; + } + + /** + * Advertised maximum SSH packet size that the other side can send to us. + */ + public static final int MAX_PACKET_SIZE = 64 * 1024; + + private final List asynchronousQueue + = new ArrayList(); + + private Thread asynchronousThread = null; + private boolean asynchronousPending = false; + + private Socket socket; + + protected TransportManager(final Socket socket) { + this.socket = socket; + } + + private static final class AsynchronousEntry { + public byte[] message; + + public AsynchronousEntry(byte[] message) { + this.message = message; + } + } + + private final class AsynchronousWorker implements Runnable { + public void run() { + while (true) { + final AsynchronousEntry item; + + synchronized (asynchronousQueue) { + if (asynchronousQueue.size() == 0) { + // Only now we may reset the flag, since we are sure that all queued items + // have been sent (there is a slight delay between de-queuing and sending, + // this is why we need this flag! See code below. Sending takes place outside + // of this lock, this is why a test for size()==0 (from another thread) does not ensure + // that all messages have been sent. + asynchronousPending = false; + // Notify any senders that they can proceed, all async messages have been delivered + asynchronousQueue.notifyAll(); + + // After the queue is empty for about 2 seconds, stop this thread + try { + asynchronousQueue.wait(2000); + } + catch (InterruptedException ignore) { + // + } + + if (asynchronousQueue.size() == 0) { + asynchronousThread = null; + return; + } + } + + item = asynchronousQueue.remove(0); + } + + try { + sendMessageImmediate(item.message); + } + catch (IOException e) { + // There is no point in handling it - it simply means that the connection has a problem and we should stop + // sending asynchronously messages. We do not need to signal that we have exited (asynchronousThread = null): + // further messages in the queue cannot be sent by this or any other thread. + // Other threads will sooner or later (when receiving or sending the next message) get the + // same IOException and get to the same conclusion. + log.warning(e.getMessage()); + return; + } + } + } + } + + private final Object connectionSemaphore = new Object(); + + private boolean flagKexOngoing; + + private boolean connectionClosed; + private Throwable reasonClosedCause; + + private TransportConnection tc; + private KexManager km; + + private final List messageHandlers = new ArrayList(); + + private List connectionMonitors = new ArrayList(); + boolean monitorsWereInformed = false; + + protected void init(TransportConnection tc, KexManager km) { + this.tc = tc; + this.km = km; + } + + public int getPacketOverheadEstimate() { + return tc.getPacketOverheadEstimate(); + } + + public ConnectionInfo getConnectionInfo(int kexNumber) throws IOException { + return km.getOrWaitForConnectionInfo(kexNumber); + } + + public Throwable getReasonClosedCause() { + synchronized (connectionSemaphore) { + return reasonClosedCause; + } + } + + public byte[] getSessionIdentifier() { + return km.sessionId; + } + + public void close(Throwable cause, boolean useDisconnectPacket) { + if (useDisconnectPacket == false) { + // OK, hard shutdown - do not acquire the semaphore, + // perhaps somebody is inside (and waits until + // the remote side is ready to accept new data). + try { + socket.close(); + } + catch (IOException ignore) { + } + + // OK, whoever tried to send data, should now agree that + // there is no point in further waiting =) + // It is safe now to acquire the semaphore. + } + + synchronized (connectionSemaphore) { + if (!connectionClosed) { + if (useDisconnectPacket == true) { + try { + if (tc != null) + tc.sendMessage(new PacketDisconnect(PacketDisconnect.Reason.SSH_DISCONNECT_BY_APPLICATION, "").getPayload()); + } + catch (IOException ignore) { + } + + try { + socket.close(); + } + catch (IOException ignore) { + } + } + + connectionClosed = true; + reasonClosedCause = cause; + } + + connectionSemaphore.notifyAll(); + } + + // check if we need to inform the monitors + List monitors = null; + + synchronized (this) { + // Short term lock to protect "connectionMonitors" + // and "monitorsWereInformed" + // (they may be modified concurrently) + if (monitorsWereInformed == false) { + monitorsWereInformed = true; + monitors = new ArrayList(connectionMonitors); + } + } + + if (monitors != null) { + for (ConnectionMonitor cmon : monitors) { + try { + cmon.connectionLost(reasonClosedCause); + } + catch (Exception ignore) { + } + } + } + } + + protected void startReceiver() throws IOException { + final Thread receiveThread = new Thread(new Runnable() { + public void run() { + try { + receiveLoop(); + // Can only exit with exception + } + catch (IOException e) { + close(e, false); + log.warning(e.getMessage()); + + // Tell all handlers that it is time to say goodbye + if (km != null) { + km.handleFailure(e); + } + + for (HandlerEntry he : messageHandlers) { + he.mh.handleFailure(e); + } + } + + if (log.isDebugEnabled()) { + log.debug("Receive thread: back from receiveLoop"); + } + } + }); + receiveThread.setName("Transport Manager"); + receiveThread.setDaemon(true); + receiveThread.start(); + } + + public void registerMessageHandler(MessageHandler mh, int low, int high) { + HandlerEntry he = new HandlerEntry(); + he.mh = mh; + he.low = low; + he.high = high; + + synchronized (messageHandlers) { + messageHandlers.add(he); + } + } + + public void removeMessageHandler(MessageHandler handler) { + synchronized (messageHandlers) { + for (int i = 0; i < messageHandlers.size(); i++) { + HandlerEntry he = messageHandlers.get(i); + + if (he.mh == handler) { + messageHandlers.remove(i); + break; + } + } + } + } + + public void sendKexMessage(byte[] msg) throws IOException { + synchronized (connectionSemaphore) { + if (connectionClosed) { + throw(IOException) new IOException("Sorry, this connection is closed.").initCause(reasonClosedCause); + } + + flagKexOngoing = true; + + try { + tc.sendMessage(msg); + } + catch (IOException e) { + close(e, false); + throw e; + } + } + } + + public void kexFinished() throws IOException { + synchronized (connectionSemaphore) { + flagKexOngoing = false; + connectionSemaphore.notifyAll(); + } + } + + /** + * @param cwl Crypto wishlist + * @param dhgex Diffie-hellman group exchange + * @param dsa may be null if this is a client connection + * @param rsa may be null if this is a client connection + * @throws IOException + */ + public void forceKeyExchange(CryptoWishList cwl, DHGexParameters dhgex, KeyPair dsa, KeyPair rsa, KeyPair ec) + throws IOException { + synchronized (connectionSemaphore) { + if (connectionClosed) { + throw(IOException) new IOException("Sorry, this connection is closed.").initCause(reasonClosedCause); + } + } + + km.initiateKEX(cwl, dhgex, dsa, rsa, ec); + } + + public void changeRecvCipher(BlockCipher bc, MAC mac) { + tc.changeRecvCipher(bc, mac); + } + + public void changeSendCipher(BlockCipher bc, MAC mac) { + tc.changeSendCipher(bc, mac); + } + + public void changeRecvCompression(Compressor comp) { + tc.changeRecvCompression(comp); + } + + public void changeSendCompression(Compressor comp) { + tc.changeSendCompression(comp); + } + + public void sendAsynchronousMessage(byte[] msg) throws IOException { + synchronized (asynchronousQueue) { + asynchronousQueue.add(new AsynchronousEntry(msg)); + asynchronousPending = true; + + /* This limit should be flexible enough. We need this, otherwise the peer + * can flood us with global requests (and other stuff where we have to reply + * with an asynchronous message) and (if the server just sends data and does not + * read what we send) this will probably put us in a low memory situation + * (our send queue would grow and grow and...) */ + + if (asynchronousQueue.size() > 100) { + throw new IOException("The peer is not consuming our asynchronous replies."); + } + + // Check if we have an asynchronous sending thread + if (asynchronousThread == null) { + asynchronousThread = new Thread(new AsynchronousWorker()); + asynchronousThread.setDaemon(true); + asynchronousThread.start(); + // The thread will stop after 2 seconds of inactivity (i.e., empty queue) + } + + asynchronousQueue.notifyAll(); + } + } + + public void setConnectionMonitors(List monitors) { + synchronized (this) { + connectionMonitors = new ArrayList(monitors); + } + } + + /** + * Send a message but ensure that all queued messages are being sent first. + * + * @param msg Message + * @throws IOException + */ + public void sendMessage(byte[] msg) throws IOException { + synchronized (asynchronousQueue) { + while (asynchronousPending) { + try { + asynchronousQueue.wait(); + } + catch (InterruptedException e) { + throw new InterruptedIOException(e.getMessage()); + } + } + } + + sendMessageImmediate(msg); + } + + /** + * Send message, ignore queued async messages that have not been delivered yet. + * Will be called directly from the asynchronousThread thread. + * + * @param msg Message + * @throws IOException + */ + public void sendMessageImmediate(byte[] msg) throws IOException { + synchronized (connectionSemaphore) { + while (true) { + if (connectionClosed) { + throw(IOException) new IOException("Sorry, this connection is closed.").initCause(reasonClosedCause); + } + + if (!flagKexOngoing) { + break; + } + + try { + connectionSemaphore.wait(); + } + catch (InterruptedException e) { + throw new InterruptedIOException(e.getMessage()); + } + } + + try { + tc.sendMessage(msg); + } + catch (IOException e) { + close(e, false); + throw e; + } + } + } + + private void receiveLoop() throws IOException { + while (true) { + final byte[] buffer = new byte[MAX_PACKET_SIZE]; + final int length = tc.receiveMessage(buffer, 0, buffer.length); + final byte[] packet = new byte[length]; + System.arraycopy(buffer, 0, packet, 0, length); + final int type = packet[0] & 0xff; + log.debug(String.format("transport manager receive loop type %d", type)); + + switch (type) { + case Packets.SSH_MSG_IGNORE: + break; + + case Packets.SSH_MSG_DEBUG: { + TypesReader tr = new TypesReader(packet); + tr.readByte(); + // always_display + tr.readBoolean(); + String message = tr.readString(); + + if (log.isDebugEnabled()) { + log.debug(String.format("Debug message from remote: '%s'", message)); + } + + break; + } + + case Packets.SSH_MSG_UNIMPLEMENTED: + throw new PacketTypeException(type); + + case Packets.SSH_MSG_DISCONNECT: { + final PacketDisconnect disconnect = new PacketDisconnect(packet); + throw new DisconnectException(disconnect.getReason(), disconnect.getMessage()); + } + + case Packets.SSH_MSG_KEXINIT: + case Packets.SSH_MSG_NEWKEYS: + case Packets.SSH_MSG_KEXDH_INIT: + case Packets.SSH_MSG_KEXDH_REPLY: + case Packets.SSH_MSG_KEX_DH_GEX_REQUEST: + case Packets.SSH_MSG_KEX_DH_GEX_INIT: + case Packets.SSH_MSG_KEX_DH_GEX_REPLY: + // Is it a KEX Packet + km.handleMessage(packet); + break; + + case Packets.SSH_MSG_USERAUTH_SUCCESS: + tc.startCompression(); + + // Continue with message handlers + default: + boolean handled = false; + + for (HandlerEntry handler : messageHandlers) { + if ((handler.low <= type) && (type <= handler.high)) { + handler.mh.handleMessage(packet); + handled = true; + break; + } + } + + if (!handled) { + throw new PacketTypeException(type); + } + + break; + } + + if (log.isDebugEnabled()) { + log.debug(String.format("Handled packet %d", type)); + } + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/util/StringEncoder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/util/StringEncoder.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. + * All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ +package ch.ethz.ssh2.util; + +import java.io.UnsupportedEncodingException; + +/** + * @author Christian Plattner + * @version $Id: StringEncoder.java 43 2011-06-21 18:34:06Z dkocher@sudo.ch $ + */ +public class StringEncoder { + public static byte[] GetBytes(String data) { + try { + return data.getBytes("UTF-8"); + } + catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + public static String GetString(byte[] data) { + return GetString(data, 0, data.length); + } + + public static String GetString(byte[] data, int off, int len) { + try { + return new String(data, off, len, "UTF-8"); + } + catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/ch/ethz/ssh2/util/TimeoutService.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/ch/ethz/ssh2/util/TimeoutService.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2006-2011 Christian Plattner. All rights reserved. + * Please refer to the LICENSE.txt for licensing details. + */ + +package ch.ethz.ssh2.util; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; + +import ch.ethz.ssh2.log.Logger; + +/** + * TimeoutService (beta). Here you can register a timeout. + *

+ * Implemented having large scale programs in mind: if you open many concurrent SSH connections + * that rely on timeouts, then there will be only one timeout thread. Once all timeouts + * have expired/are cancelled, the thread will (sooner or later) exit. + * Only after new timeouts arrive a new thread (singleton) will be instantiated. + * + * @author Christian Plattner + * @version $Id: TimeoutService.java 89 2014-04-07 14:36:24Z dkocher@sudo.ch $ + */ +public class TimeoutService { + private static final Logger log = Logger.getLogger(TimeoutService.class); + + public static class TimeoutToken { + private long runTime; + private Runnable handler; + + private TimeoutToken(long runTime, Runnable handler) { + this.runTime = runTime; + this.handler = handler; + } + } + + private static class TimeoutThread extends Thread { + @Override + public void run() { + synchronized (todolist) { + while (true) { + if (todolist.size() == 0) { + timeoutThread = null; + return; + } + + long now = System.currentTimeMillis(); + TimeoutToken tt = todolist.getFirst(); + + if (tt.runTime > now) { + /* Not ready yet, sleep a little bit */ + try { + todolist.wait(tt.runTime - now); + } + catch (InterruptedException ignored) { + } + + /* We cannot simply go on, since it could be that the token + * was removed (cancelled) or another one has been inserted in + * the meantime. + */ + continue; + } + + todolist.removeFirst(); + + try { + tt.handler.run(); + } + catch (Exception e) { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + log.warning("Exeception in Timeout handler:" + e.getMessage() + "(" + sw.toString() + ")"); + } + } + } + } + } + + /* The list object is also used for locking purposes */ + private static final LinkedList todolist = new LinkedList(); + + private static Thread timeoutThread = null; + + /** + * It is assumed that the passed handler will not execute for a long time. + * + * @param runTime + * @param handler + * @return a TimeoutToken that can be used to cancel the timeout. + */ + public static TimeoutToken addTimeoutHandler(long runTime, Runnable handler) { + TimeoutToken token = new TimeoutToken(runTime, handler); + + synchronized (todolist) { + todolist.add(token); + Collections.sort(todolist, new Comparator() { + public int compare(TimeoutToken o1, TimeoutToken o2) { + if (o1.runTime > o2.runTime) + return 1; + + if (o1.runTime == o2.runTime) + return 0; + + return -1; + } + }); + + if (timeoutThread != null) + timeoutThread.interrupt(); + else { + timeoutThread = new TimeoutThread(); + timeoutThread.setDaemon(true); + timeoutThread.start(); + } + } + + return token; + } + + public static void cancelTimeoutHandler(TimeoutToken token) { + synchronized (todolist) { + todolist.remove(token); + + if (timeoutThread != null) + timeoutThread.interrupt(); + } + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/ActionBarWrapper.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/ActionBarWrapper.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,84 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot; + +import com.five_ten_sg.connectbot.util.PreferenceConstants; +import android.annotation.TargetApi; +import android.app.ActionBar; +import android.app.Activity; + +public abstract class ActionBarWrapper { + public interface OnMenuVisibilityListener { + public void onMenuVisibilityChanged(boolean isVisible); + } + + public static ActionBarWrapper getActionBar(Activity activity) { + if (PreferenceConstants.PRE_HONEYCOMB) + return new DummyActionBar(); + else + return new RealActionBar(activity); + } + + public void hide() { + } + + public void show() { + } + + public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) { + } + + public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) { + } + + private static class DummyActionBar extends ActionBarWrapper { + } + + @TargetApi(11) + private static class RealActionBar extends ActionBarWrapper { + private final ActionBar actionBar; + + public RealActionBar(Activity activity) { + actionBar = activity.getActionBar(); + } + + @Override + public void hide() { + actionBar.hide(); + } + + @Override + public void show() { + actionBar.show(); + } + + @Override + public void addOnMenuVisibilityListener(final OnMenuVisibilityListener listener) { + actionBar.addOnMenuVisibilityListener(new ActionBar.OnMenuVisibilityListener() { + public void onMenuVisibilityChanged(boolean isVisible) { + listener.onMenuVisibilityChanged(isVisible); + } + }); + } + + @Override + public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) { + actionBar.setDisplayHomeAsUpEnabled(showHomeAsUp); + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/ColorsActivity.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/ColorsActivity.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,319 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot; + +import java.util.Arrays; +import java.util.List; + +import com.five_ten_sg.connectbot.util.Colors; +import com.five_ten_sg.connectbot.util.HostDatabase; +import com.five_ten_sg.connectbot.util.UberColorPickerDialog; +import com.five_ten_sg.connectbot.util.UberColorPickerDialog.OnColorChangedListener; +import android.app.Activity; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.BaseAdapter; +import android.widget.GridView; +import android.widget.Spinner; + +/** + * @author Kenny Root + * + */ +public class ColorsActivity extends Activity implements OnItemClickListener, OnColorChangedListener, OnItemSelectedListener { + private GridView mColorGrid; + private Spinner mFgSpinner; + private Spinner mBgSpinner; + + private int mColorScheme; + + private List mColorList; + private HostDatabase hostdb; + + private int mCurrentColor = 0; + + private int[] mDefaultColors; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.act_colors); + this.setTitle(String.format("%s: %s", + getResources().getText(R.string.app_name), + getResources().getText(R.string.title_colors))); + mColorScheme = HostDatabase.DEFAULT_COLOR_SCHEME; + hostdb = new HostDatabase(this); + mColorList = Arrays.asList(hostdb.getColorsForScheme(mColorScheme)); + mDefaultColors = hostdb.getDefaultColorsForScheme(mColorScheme); + mColorGrid = (GridView) findViewById(R.id.color_grid); + mColorGrid.setAdapter(new ColorsAdapter(true)); + mColorGrid.setOnItemClickListener(this); + mColorGrid.setSelection(0); + mFgSpinner = (Spinner) findViewById(R.id.fg); + mFgSpinner.setAdapter(new ColorsAdapter(false)); + mFgSpinner.setSelection(mDefaultColors[0]); + mFgSpinner.setOnItemSelectedListener(this); + mBgSpinner = (Spinner) findViewById(R.id.bg); + mBgSpinner.setAdapter(new ColorsAdapter(false)); + mBgSpinner.setSelection(mDefaultColors[1]); + mBgSpinner.setOnItemSelectedListener(this); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + if (hostdb != null) { + hostdb.close(); + hostdb = null; + } + } + + @Override + protected void onResume() { + super.onResume(); + + if (hostdb == null) + hostdb = new HostDatabase(this); + } + + private class ColorsAdapter extends BaseAdapter { + private boolean mSquareViews; + + public ColorsAdapter(boolean squareViews) { + mSquareViews = squareViews; + } + + public View getView(int position, View convertView, ViewGroup parent) { + ColorView c; + + if (convertView == null) { + c = new ColorView(ColorsActivity.this, mSquareViews); + } + else { + c = (ColorView) convertView; + } + + c.setColor(mColorList.get(position)); + c.setNumber(position + 1); + return c; + } + + public int getCount() { + return mColorList.size(); + } + + public Object getItem(int position) { + return mColorList.get(position); + } + + public long getItemId(int position) { + return position; + } + } + + private class ColorView extends View { + private boolean mSquare; + + private Paint mTextPaint; + private Paint mShadowPaint; + + // Things we paint + private int mBackgroundColor; + private String mText; + + private int mAscent; + private int mWidthCenter; + private int mHeightCenter; + + public ColorView(Context context, boolean square) { + super(context); + mSquare = square; + mTextPaint = new Paint(); + mTextPaint.setAntiAlias(true); + mTextPaint.setTextSize(16); + mTextPaint.setColor(0xFFFFFFFF); + mTextPaint.setTextAlign(Paint.Align.CENTER); + mShadowPaint = new Paint(mTextPaint); + mShadowPaint.setStyle(Paint.Style.STROKE); + mShadowPaint.setStrokeCap(Paint.Cap.ROUND); + mShadowPaint.setStrokeJoin(Paint.Join.ROUND); + mShadowPaint.setStrokeWidth(4f); + mShadowPaint.setColor(0xFF000000); + setPadding(10, 10, 10, 10); + } + + public void setColor(int color) { + mBackgroundColor = color; + } + + public void setNumber(int number) { + mText = Integer.toString(number); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = measureWidth(widthMeasureSpec); + int height; + + if (mSquare) + height = width; + else + height = measureHeight(heightMeasureSpec); + + mAscent = (int) mTextPaint.ascent(); + mWidthCenter = width / 2; + mHeightCenter = height / 2 - mAscent / 2; + setMeasuredDimension(width, height); + } + + private int measureWidth(int measureSpec) { + int result = 0; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if (specMode == MeasureSpec.EXACTLY) { + // We were told how big to be + result = specSize; + } + else { + // Measure the text + result = (int) mTextPaint.measureText(mText) + getPaddingLeft() + + getPaddingRight(); + + if (specMode == MeasureSpec.AT_MOST) { + // Respect AT_MOST value if that was what is called for by + // measureSpec + result = Math.min(result, specSize); + } + } + + return result; + } + + private int measureHeight(int measureSpec) { + int result = 0; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + mAscent = (int) mTextPaint.ascent(); + + if (specMode == MeasureSpec.EXACTLY) { + // We were told how big to be + result = specSize; + } + else { + // Measure the text (beware: ascent is a negative number) + result = (int)(-mAscent + mTextPaint.descent()) + + getPaddingTop() + getPaddingBottom(); + + if (specMode == MeasureSpec.AT_MOST) { + // Respect AT_MOST value if that was what is called for by + // measureSpec + result = Math.min(result, specSize); + } + } + + return result; + } + + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + canvas.drawColor(mBackgroundColor); + canvas.drawText(mText, mWidthCenter, mHeightCenter, mShadowPaint); + canvas.drawText(mText, mWidthCenter, mHeightCenter, mTextPaint); + } + } + + private void editColor(int colorNumber) { + mCurrentColor = colorNumber; + new UberColorPickerDialog(this, this, mColorList.get(colorNumber)).show(); + } + + public void onItemClick(AdapterView parent, View view, int position, long id) { + editColor(position); + } + + public void onNothingSelected(AdapterView arg0) { } + + public void colorChanged(int value) { + hostdb.setGlobalColor(mCurrentColor, value); + mColorList.set(mCurrentColor, value); + mColorGrid.invalidateViews(); + } + + public void onItemSelected(AdapterView parent, View view, int position, + long id) { + boolean needUpdate = false; + + if (parent == mFgSpinner) { + if (position != mDefaultColors[0]) { + mDefaultColors[0] = position; + needUpdate = true; + } + } + else if (parent == mBgSpinner) { + if (position != mDefaultColors[1]) { + mDefaultColors[1] = position; + needUpdate = true; + } + } + + if (needUpdate) + hostdb.setDefaultColorsForScheme(mColorScheme, mDefaultColors[0], mDefaultColors[1]); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + MenuItem reset = menu.add(R.string.menu_colors_reset); + reset.setAlphabeticShortcut('r'); + reset.setNumericShortcut('1'); + reset.setIcon(android.R.drawable.ic_menu_revert); + reset.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem arg0) { + // Reset each individual color to defaults. + for (int i = 0; i < Colors.defaults.length; i++) { + if (mColorList.get(i) != Colors.defaults[i]) { + hostdb.setGlobalColor(i, Colors.defaults[i]); + mColorList.set(i, Colors.defaults[i]); + } + } + + mColorGrid.invalidateViews(); + // Reset the default FG/BG colors as well. + mFgSpinner.setSelection(HostDatabase.DEFAULT_FG_COLOR); + mBgSpinner.setSelection(HostDatabase.DEFAULT_BG_COLOR); + hostdb.setDefaultColorsForScheme(HostDatabase.DEFAULT_COLOR_SCHEME, + HostDatabase.DEFAULT_FG_COLOR, HostDatabase.DEFAULT_BG_COLOR); + return true; + } + }); + return true; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/ConsoleActivity.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/ConsoleActivity.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,1508 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot; + +import java.io.File; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import com.five_ten_sg.connectbot.bean.SelectionArea; +import com.five_ten_sg.connectbot.service.PromptHelper; +import com.five_ten_sg.connectbot.service.TerminalBridge; +import com.five_ten_sg.connectbot.service.TerminalKeyListener; +import com.five_ten_sg.connectbot.service.TerminalManager; +import com.five_ten_sg.connectbot.util.FileChooser; +import com.five_ten_sg.connectbot.util.FileChooserCallback; +import com.five_ten_sg.connectbot.util.PreferenceConstants; +import com.five_ten_sg.connectbot.util.TransferThread; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.media.AudioManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.preference.PreferenceManager; +import android.text.ClipboardManager; +import android.text.InputType; +import android.text.method.PasswordTransformationMethod; +import android.text.method.SingleLineTransformationMethod; +import android.util.FloatMath; +import android.util.Log; +import android.view.GestureDetector; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnKeyListener; +import android.view.View.OnLongClickListener; +import android.view.View.OnTouchListener; +import android.view.ViewConfiguration; +import android.view.WindowManager; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; +import android.widget.ViewFlipper; +import de.mud.terminal.vt320; + +public class ConsoleActivity extends Activity implements FileChooserCallback { + public final static String TAG = "ConnectBot.ConsoleActivity"; + + protected static final int REQUEST_EDIT = 1; + + private static final int CLICK_TIME = 400; + private static final float MAX_CLICK_DISTANCE = 25f; + private static final int KEYBOARD_DISPLAY_TIME = 1500; + + // Direction to shift the ViewFlipper + private static final int SHIFT_LEFT = 0; + private static final int SHIFT_RIGHT = 1; + + protected ViewFlipper flip = null; + protected TerminalManager bound = null; + protected LayoutInflater inflater = null; + + private SharedPreferences prefs = null; + + // determines whether or not menuitem accelerators are bound + // otherwise they collide with an external keyboard's CTRL-char + private boolean hardKeyboard = false; + + // determines whether we are in the fullscreen mode + private static final int FULLSCREEN_ON = 1; + private static final int FULLSCREEN_OFF = 2; + + private int fullScreen; + + protected Uri requested; + + protected ClipboardManager clipboard; + private RelativeLayout stringPromptGroup; + protected EditText stringPrompt; + private TextView stringPromptInstructions; + + private RelativeLayout booleanPromptGroup; + private TextView booleanPrompt; + private Button booleanYes, booleanNo; + + private RelativeLayout keyboardGroup; + private Runnable keyboardGroupHider; + + private TextView empty; + + private Animation slide_left_in, slide_left_out, slide_right_in, slide_right_out, fade_stay_hidden, fade_out_delayed; + + private Animation keyboard_fade_in, keyboard_fade_out; + private float lastX, lastY; + + private InputMethodManager inputManager; + + private MenuItem disconnect, copy, paste, portForward, resize, urlscan, screenCapture, download, upload; + + protected TerminalBridge copySource = null; + private int lastTouchRow, lastTouchCol; + + private boolean forcedOrientation; + + private Handler handler = new Handler(); + + private ImageView mKeyboardButton; + + private ActionBarWrapper actionBar; + private boolean inActionBarMenu = false; + + private ServiceConnection connection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + bound = ((TerminalManager.TerminalBinder) service).getService(); + // let manager know about our event handling services + bound.disconnectHandler = disconnectHandler; + Log.d(TAG, String.format("Connected to TerminalManager and found bridges.size=%d", bound.bridges.size())); + bound.setResizeAllowed(true); + bound.hardKeyboardHidden = (getResources().getConfiguration().hardKeyboardHidden == + Configuration.HARDKEYBOARDHIDDEN_YES); + + // set fullscreen value + if (bound.getFullScreen() == 0) { + setFullScreen(FULLSCREEN_OFF); + } + else if (fullScreen != bound.getFullScreen()) + setFullScreen(bound.getFullScreen()); + + // clear out any existing bridges and record requested index + flip.removeAllViews(); + final String requestedNickname = (requested != null) ? requested.getFragment() : null; + int requestedIndex = -1; + TerminalBridge requestedBridge = bound.getConnectedBridge(requestedNickname); + + // If we didn't find the requested connection, try opening it + if (requestedNickname != null && requestedBridge == null) { + try { + Log.d(TAG, String.format("We couldnt find an existing bridge with URI=%s (nickname=%s), so creating one now", requested.toString(), requestedNickname)); + requestedBridge = bound.openConnection(requested); + } + catch (Exception e) { + Log.e(TAG, "Problem while trying to create new requested bridge from URI", e); + } + } + + // create views for all bridges on this service + for (TerminalBridge bridge : bound.bridges) { + final int currentIndex = addNewTerminalView(bridge); + + // check to see if this bridge was requested + if (bridge == requestedBridge) { + requestedIndex = currentIndex; + // store this bridge as default bridge + bound.defaultBridge = bridge; + } + } + + // if no bridge was requested, try using default bridge + if (requestedIndex < 0) { + requestedIndex = getFlipIndex(bound.defaultBridge); + + if (requestedIndex < 0) + requestedIndex = 0; + } + + setDisplayedTerminal(requestedIndex); + } + public void onServiceDisconnected(ComponentName className) { + // tell each bridge to forget about our prompt handler + synchronized (bound.bridges) { + for (TerminalBridge bridge : bound.bridges) + bridge.promptHelper.setHandler(null); + } + + flip.removeAllViews(); + updateEmptyVisible(); + bound = null; + } + }; + + protected Handler promptHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + // someone below us requested to display a prompt + updatePromptVisible(); + } + }; + + protected Handler disconnectHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + Log.d(TAG, "Someone sending HANDLE_DISCONNECT to parentHandler"); + // someone below us requested to display a password dialog + // they are sending nickname and requested + TerminalBridge bridge = (TerminalBridge)msg.obj; + + if (bridge.isAwaitingClose()) + closeBridge(bridge); + } + }; + + /** + * @param bridge + */ + private void closeBridge(final TerminalBridge bridge) { + synchronized (flip) { + final int flipIndex = getFlipIndex(bridge); + + if (flipIndex >= 0) { + if (flip.getDisplayedChild() == flipIndex) { + shiftCurrentTerminal(SHIFT_LEFT); + } + + flip.removeViewAt(flipIndex); + /* TODO Remove this workaround when ViewFlipper is fixed to listen + * to view removals. Android Issue 1784 + */ + final int numChildren = flip.getChildCount(); + + if (flip.getDisplayedChild() >= numChildren && + numChildren > 0) { + flip.setDisplayedChild(numChildren - 1); + } + + updateEmptyVisible(); + } + + // If we just closed the last bridge, go back to the previous activity. + if (flip.getChildCount() == 0) { + finish(); + } + } + } + + protected View findCurrentView(int id) { + View view = flip.getCurrentView(); + + if (view == null) return null; + + return view.findViewById(id); + } + + protected PromptHelper getCurrentPromptHelper() { + View view = findCurrentView(R.id.console_flip); + + if (!(view instanceof TerminalView)) return null; + + return ((TerminalView)view).bridge.promptHelper; + } + + protected void hideAllPrompts() { + stringPromptGroup.setVisibility(View.GONE); + booleanPromptGroup.setVisibility(View.GONE); + // adjust window back if size was changed during prompt input + View view = findCurrentView(R.id.console_flip); + + if (!(view instanceof TerminalView)) return; + + ((TerminalView)view).bridge.parentChanged((TerminalView)view); + } + + private void showEmulatedKeys() { + keyboardGroup.startAnimation(keyboard_fade_in); + keyboardGroup.setVisibility(View.VISIBLE); + actionBar.show(); + + if (keyboardGroupHider != null) + handler.removeCallbacks(keyboardGroupHider); + + keyboardGroupHider = new Runnable() { + public void run() { + if (keyboardGroup.getVisibility() == View.GONE || inActionBarMenu) + return; + + keyboardGroup.startAnimation(keyboard_fade_out); + keyboardGroup.setVisibility(View.GONE); + actionBar.hide(); + keyboardGroupHider = null; + } + }; + handler.postDelayed(keyboardGroupHider, KEYBOARD_DISPLAY_TIME); + } + + private void hideEmulatedKeys() { + if (keyboardGroupHider != null) + handler.removeCallbacks(keyboardGroupHider); + + keyboardGroup.setVisibility(View.GONE); + actionBar.hide(); + } + + // more like configureLaxMode -- enable network IO on UI thread + private void configureStrictMode() { + try { + Class.forName("android.os.StrictMode"); + StrictModeSetup.run(); + } + catch (ClassNotFoundException e) { + } + } + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + configureStrictMode(); + hardKeyboard = getResources().getConfiguration().keyboard == + Configuration.KEYBOARD_QWERTY; + this.setContentView(R.layout.act_console); + clipboard = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE); + prefs = PreferenceManager.getDefaultSharedPreferences(this); + // TODO find proper way to disable volume key beep if it exists. + setVolumeControlStream(AudioManager.STREAM_MUSIC); + // handle requested console from incoming intent + requested = getIntent().getData(); + inflater = LayoutInflater.from(this); + flip = (ViewFlipper)findViewById(R.id.console_flip); + empty = (TextView)findViewById(android.R.id.empty); + stringPromptGroup = (RelativeLayout) findViewById(R.id.console_password_group); + stringPromptInstructions = (TextView) findViewById(R.id.console_password_instructions); + stringPrompt = (EditText)findViewById(R.id.console_password); + stringPrompt.setOnKeyListener(new OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_UP) return false; + + if (keyCode != KeyEvent.KEYCODE_ENTER) return false; + + // pass collected password down to current terminal + String value = stringPrompt.getText().toString(); + PromptHelper helper = getCurrentPromptHelper(); + + if (helper == null) return false; + + helper.setResponse(value); + // finally clear password for next user + stringPrompt.setText(""); + updatePromptVisible(); + return true; + } + }); + booleanPromptGroup = (RelativeLayout) findViewById(R.id.console_boolean_group); + booleanPrompt = (TextView)findViewById(R.id.console_prompt); + booleanYes = (Button)findViewById(R.id.console_prompt_yes); + booleanYes.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + PromptHelper helper = getCurrentPromptHelper(); + + if (helper == null) return; + + helper.setResponse(Boolean.TRUE); + updatePromptVisible(); + } + }); + booleanNo = (Button)findViewById(R.id.console_prompt_no); + booleanNo.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + PromptHelper helper = getCurrentPromptHelper(); + + if (helper == null) return; + + helper.setResponse(Boolean.FALSE); + updatePromptVisible(); + } + }); + // preload animations for terminal switching + slide_left_in = AnimationUtils.loadAnimation(this, R.anim.slide_left_in); + slide_left_out = AnimationUtils.loadAnimation(this, R.anim.slide_left_out); + slide_right_in = AnimationUtils.loadAnimation(this, R.anim.slide_right_in); + slide_right_out = AnimationUtils.loadAnimation(this, R.anim.slide_right_out); + fade_out_delayed = AnimationUtils.loadAnimation(this, R.anim.fade_out_delayed); + fade_stay_hidden = AnimationUtils.loadAnimation(this, R.anim.fade_stay_hidden); + // Preload animation for keyboard button + keyboard_fade_in = AnimationUtils.loadAnimation(this, R.anim.keyboard_fade_in); + keyboard_fade_out = AnimationUtils.loadAnimation(this, R.anim.keyboard_fade_out); + inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + keyboardGroup = (RelativeLayout) findViewById(R.id.keyboard_group); + mKeyboardButton = (ImageView) findViewById(R.id.button_keyboard); + mKeyboardButton.setOnClickListener(new OnClickListener() { + public void onClick(View view) { + View flip = findCurrentView(R.id.console_flip); + + if (flip == null) + return; + + inputManager.showSoftInput(flip, InputMethodManager.SHOW_FORCED); + hideEmulatedKeys(); + } + }); + final ImageView symButton = (ImageView) findViewById(R.id.button_sym); + symButton.setOnClickListener(new OnClickListener() { + public void onClick(View view) { + View flip = findCurrentView(R.id.console_flip); + + if (flip == null) return; + + TerminalView terminal = (TerminalView)flip; + terminal.bridge.showCharPickerDialog(); + keyboardGroup.setVisibility(View.GONE); + } + }); + symButton.setOnLongClickListener(new OnLongClickListener() { + public boolean onLongClick(View view) { + View flip = findCurrentView(R.id.console_flip); + + if (flip == null) return false; + + TerminalView terminal = (TerminalView)flip; + terminal.bridge.showArrowsDialog(); + return true; + } + }); + final ImageView mInputButton = (ImageView) findViewById(R.id.button_input); + mInputButton.setOnClickListener(new OnClickListener() { + public void onClick(View view) { + View flip = findCurrentView(R.id.console_flip); + + if (flip == null) return; + + final TerminalView terminal = (TerminalView)flip; + Thread promptThread = new Thread(new Runnable() { + public void run() { + String inj = getCurrentPromptHelper().requestStringPrompt(null, ""); + terminal.bridge.injectString(inj); + } + }); + promptThread.setName("Prompt"); + promptThread.setDaemon(true); + promptThread.start(); + keyboardGroup.setVisibility(View.GONE); + } + }); + final ImageView ctrlButton = (ImageView) findViewById(R.id.button_ctrl); + ctrlButton.setOnClickListener(new OnClickListener() { + public void onClick(View view) { + View flip = findCurrentView(R.id.console_flip); + + if (flip == null) return; + + TerminalView terminal = (TerminalView)flip; + TerminalKeyListener handler = terminal.bridge.getKeyHandler(); + handler.metaPress(TerminalKeyListener.META_CTRL_ON); + hideEmulatedKeys(); + } + }); + ctrlButton.setOnLongClickListener(new OnLongClickListener() { + public boolean onLongClick(View view) { + View flip = findCurrentView(R.id.console_flip); + + if (flip == null) return false; + + TerminalView terminal = (TerminalView)flip; + terminal.bridge.showCtrlDialog(); + return true; + } + }); + final ImageView escButton = (ImageView) findViewById(R.id.button_esc); + escButton.setOnClickListener(new OnClickListener() { + public void onClick(View view) { + View flip = findCurrentView(R.id.console_flip); + + if (flip == null) return; + + TerminalView terminal = (TerminalView)flip; + TerminalKeyListener handler = terminal.bridge.getKeyHandler(); + handler.sendEscape(); + hideEmulatedKeys(); + } + }); + escButton.setOnLongClickListener(new OnLongClickListener() { + public boolean onLongClick(View view) { + View flip = findCurrentView(R.id.console_flip); + + if (flip == null) return false; + + TerminalView terminal = (TerminalView)flip; + terminal.bridge.showFKeysDialog(); + return true; + } + }); + actionBar = ActionBarWrapper.getActionBar(this); + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.hide(); + actionBar.addOnMenuVisibilityListener(new ActionBarWrapper.OnMenuVisibilityListener() { + public void onMenuVisibilityChanged(boolean isVisible) { + inActionBarMenu = isVisible; + + if (isVisible == false) { + hideEmulatedKeys(); + } + } + }); + // detect fling gestures to switch between terminals + final GestureDetector gestDetect = new GestureDetector(new GestureDetector.SimpleOnGestureListener() { + private float totalY = 0; + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + final float distx = e2.getRawX() - e1.getRawX(); + final float disty = e2.getRawY() - e1.getRawY(); + final int goalwidth = flip.getWidth() / 2; + + // need to slide across half of display to trigger console change + // make sure user kept a steady hand horizontally + if (Math.abs(disty) < (flip.getHeight() / 4)) { + if (distx > goalwidth) { + shiftCurrentTerminal(SHIFT_RIGHT); + return true; + } + + if (distx < -goalwidth) { + shiftCurrentTerminal(SHIFT_LEFT); + return true; + } + } + + return false; + } + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + // if copying, then ignore + if (copySource != null && copySource.isSelectingForCopy()) + return false; + + if (e1 == null || e2 == null) + return false; + + // if releasing then reset total scroll + if (e2.getAction() == MotionEvent.ACTION_UP) { + totalY = 0; + } + + // activate consider if within x tolerance + if (Math.abs(e1.getX() - e2.getX()) < ViewConfiguration.getTouchSlop() * 4) { + View flip = findCurrentView(R.id.console_flip); + + if (flip == null) return false; + + TerminalView terminal = (TerminalView)flip; + // estimate how many rows we have scrolled through + // accumulate distance that doesn't trigger immediate scroll + totalY += distanceY; + final int moved = (int)(totalY / terminal.bridge.charHeight); + + // consume as scrollback only if towards right half of screen + if (e2.getX() > flip.getWidth() / 2) { + if (moved != 0) { + int base = terminal.bridge.buffer.getWindowBase(); + terminal.bridge.buffer.setWindowBase(base + moved); + totalY = 0; + return true; + } + } + else { + // otherwise consume as pgup/pgdown for every 5 lines + if (moved > 5) { + ((vt320)terminal.bridge.buffer).keyPressed(vt320.KEY_PAGE_DOWN, ' ', 0); + terminal.bridge.tryKeyVibrate(); + totalY = 0; + return true; + } + else if (moved < -5) { + ((vt320)terminal.bridge.buffer).keyPressed(vt320.KEY_PAGE_UP, ' ', 0); + terminal.bridge.tryKeyVibrate(); + totalY = 0; + return true; + } + } + } + + return false; + } + /* + * Enables longpress and popups menu + * + * @see + * android.view.GestureDetector.SimpleOnGestureListener# + * onLongPress(android.view.MotionEvent) + * + * @return void + */ + @Override + public void onLongPress(MotionEvent e) { + List itemList = new ArrayList(); + final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); + + if (terminalView == null) return; + + final TerminalBridge bridge = terminalView.bridge; + + if (fullScreen == FULLSCREEN_ON) + itemList.add(ConsoleActivity.this + .getResources().getString(R.string.longpress_disable_full_screen_mode)); + else + itemList.add(ConsoleActivity.this + .getResources().getString(R.string.longpress_enable_full_screen_mode)); + + itemList.add(ConsoleActivity.this + .getResources().getString(R.string.longpress_change_font_size)); + + if (prefs.getBoolean(PreferenceConstants.EXTENDED_LONGPRESS, false)) { + itemList.add(ConsoleActivity.this + .getResources().getString(R.string.longpress_arrows_dialog)); + itemList.add(ConsoleActivity.this + .getResources().getString(R.string.longpress_fkeys_dialog)); + itemList.add(ConsoleActivity.this + .getResources().getString(R.string.longpress_ctrl_dialog)); + itemList.add(ConsoleActivity.this + .getResources().getString(R.string.longpress_sym_dialog)); + } + + if (itemList.size() > 0) { + AlertDialog.Builder builder = new AlertDialog.Builder(ConsoleActivity.this); + builder.setTitle(R.string.longpress_select_action); + builder.setItems(itemList.toArray(new CharSequence[itemList.size()]), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + switch (item) { + case 0: + if (fullScreen == FULLSCREEN_ON) { + setFullScreen(FULLSCREEN_OFF); + } + else + setFullScreen(FULLSCREEN_ON); + + break; + + case 1: + bridge.showFontSizeDialog(); + break; + + case 2: + bridge.showArrowsDialog(); + break; + + case 3: + bridge.showFKeysDialog(); + break; + + case 4: + bridge.showCtrlDialog(); + break; + + case 5: + bridge.showCharPickerDialog(); + } + } + }); + AlertDialog alert = builder.create(); + alert.show(); + } + } + }); + flip.setLongClickable(true); + flip.setOnTouchListener(new OnTouchListener() { + public boolean onTouch(View v, MotionEvent event) { + // when copying, highlight the area + if (copySource != null && copySource.isSelectingForCopy()) { + int row = (int)FloatMath.floor(event.getY() / copySource.charHeight); + int col = (int)FloatMath.floor(event.getX() / copySource.charWidth); + SelectionArea area = copySource.getSelectionArea(); + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + + // recording starting area + if (area.isSelectingOrigin()) { + area.setRow(row); + area.setColumn(col); + lastTouchRow = row; + lastTouchCol = col; + copySource.redraw(); + } + + return true; + + case MotionEvent.ACTION_MOVE: + + /* ignore when user hasn't moved since last time so + * we can fine-tune with directional pad + */ + if (row == lastTouchRow && col == lastTouchCol) + return true; + + // if the user moves, start the selection for other corner + area.finishSelectingOrigin(); + // update selected area + area.setRow(row); + area.setColumn(col); + lastTouchRow = row; + lastTouchCol = col; + copySource.redraw(); + return true; + + case MotionEvent.ACTION_UP: + + /* If they didn't move their finger, maybe they meant to + * select the rest of the text with the directional pad. + */ + if (area.getLeft() == area.getRight() && + area.getTop() == area.getBottom()) { + return true; + } + + // copy selected area to clipboard + String copiedText = area.copyFrom(copySource.buffer); + clipboard.setText(copiedText); + Toast.makeText(ConsoleActivity.this, getString(R.string.console_copy_done, copiedText.length()), Toast.LENGTH_LONG).show(); + + // fall through to clear state + case MotionEvent.ACTION_CANCEL: + // make sure we clear any highlighted area + area.reset(); + copySource.setSelectingForCopy(false); + copySource.redraw(); + return true; + } + } + + Configuration config = getResources().getConfiguration(); + + if (event.getAction() == MotionEvent.ACTION_DOWN) { + lastX = event.getX(); + lastY = event.getY(); + } + else if (event.getAction() == MotionEvent.ACTION_UP + && keyboardGroup.getVisibility() == View.GONE + && event.getEventTime() - event.getDownTime() < CLICK_TIME + && Math.abs(event.getX() - lastX) < MAX_CLICK_DISTANCE + && Math.abs(event.getY() - lastY) < MAX_CLICK_DISTANCE) { + showEmulatedKeys(); + } + + // pass any touch events back to BOTH detectors + TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); + boolean rc = false; + if ((terminalView != null) && terminalView.mScaleDetector.onTouchEvent(event)) rc = true; + if (gestDetect.onTouchEvent(event)) rc = true; + return rc; + } + }); + } + + /** + * + */ + private void configureOrientation() { + String rotateDefault; + + if (getResources().getConfiguration().keyboard == Configuration.KEYBOARD_NOKEYS) + rotateDefault = PreferenceConstants.ROTATION_PORTRAIT; + else + rotateDefault = PreferenceConstants.ROTATION_LANDSCAPE; + + String rotate = prefs.getString(PreferenceConstants.ROTATION, rotateDefault); + + if (PreferenceConstants.ROTATION_DEFAULT.equals(rotate)) + rotate = rotateDefault; + + // request a forced orientation if requested by user + if (PreferenceConstants.ROTATION_LANDSCAPE.equals(rotate)) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + forcedOrientation = true; + } + else if (PreferenceConstants.ROTATION_PORTRAIT.equals(rotate)) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + forcedOrientation = true; + } + else { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); + forcedOrientation = false; + } + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + View view = findCurrentView(R.id.console_flip); + final boolean activeTerminal = (view instanceof TerminalView); + boolean sessionOpen = false; + boolean disconnected = false; + boolean canForwardPorts = false; + boolean canTransferFiles = false; + + if (activeTerminal) { + TerminalBridge bridge = ((TerminalView) view).bridge; + sessionOpen = bridge.isSessionOpen(); + disconnected = bridge.isDisconnected(); + canForwardPorts = bridge.canFowardPorts(); + canTransferFiles = bridge.canTransferFiles(); + } + + menu.setQwertyMode(true); + + if (!PreferenceConstants.PRE_HONEYCOMB) { + MenuItem ctrlKey = menu.add(getString(R.string.fullscreen)); + ctrlKey.setEnabled(activeTerminal); + ctrlKey.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + ctrlKey.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem menuItem) { + if (fullScreen == FULLSCREEN_ON) { + setFullScreen(FULLSCREEN_OFF); + } + else + setFullScreen(FULLSCREEN_ON); + + return true; + } + }); + } + + disconnect = menu.add(R.string.list_host_disconnect); + + if (hardKeyboard) + disconnect.setAlphabeticShortcut('w'); + + if (!sessionOpen && disconnected) + disconnect.setTitle(R.string.console_menu_close); + + disconnect.setEnabled(activeTerminal); + disconnect.setIcon(android.R.drawable.ic_menu_close_clear_cancel); + disconnect.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // disconnect or close the currently visible session + TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); + TerminalBridge bridge = terminalView.bridge; + bridge.dispatchDisconnect(true); + return true; + } + }); + copy = menu.add(R.string.console_menu_copy); + + if (hardKeyboard) + copy.setAlphabeticShortcut('c'); + + copy.setIcon(android.R.drawable.ic_menu_set_as); + copy.setEnabled(activeTerminal); + copy.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // mark as copying and reset any previous bounds + TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); + copySource = terminalView.bridge; + SelectionArea area = copySource.getSelectionArea(); + area.reset(); + area.setBounds(copySource.buffer.getColumns(), copySource.buffer.getRows()); + copySource.setSelectingForCopy(true); + // Make sure we show the initial selection + copySource.redraw(); + Toast.makeText(ConsoleActivity.this, getString(R.string.console_copy_start), Toast.LENGTH_LONG).show(); + return true; + } + }); + paste = menu.add(R.string.console_menu_paste); + + if (hardKeyboard) + paste.setAlphabeticShortcut('v'); + + paste.setIcon(android.R.drawable.ic_menu_edit); + paste.setEnabled(clipboard.hasText() && sessionOpen); + paste.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // force insert of clipboard text into current console + TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); + TerminalBridge bridge = terminalView.bridge; + // pull string from clipboard and generate all events to force down + String clip = clipboard.getText().toString(); + bridge.injectString(clip); + return true; + } + }); + resize = menu.add(R.string.console_menu_resize); + + if (hardKeyboard) + resize.setAlphabeticShortcut('r'); + + resize.setIcon(android.R.drawable.ic_menu_crop); + resize.setEnabled(activeTerminal); + resize.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); + final View resizeView = inflater.inflate(R.layout.dia_resize, null, false); + ((EditText) resizeView.findViewById(R.id.width)).setText("80"); + ((EditText) resizeView.findViewById(R.id.height)).setText("25"); + new AlertDialog.Builder(ConsoleActivity.this) + .setView(resizeView) + .setPositiveButton(R.string.button_resize, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + int width, height; + + try { + width = Integer.parseInt(((EditText) resizeView + .findViewById(R.id.width)) + .getText().toString()); + height = Integer.parseInt(((EditText) resizeView + .findViewById(R.id.height)) + .getText().toString()); + } + catch (NumberFormatException nfe) { + // TODO change this to a real dialog where we can + // make the input boxes turn red to indicate an error. + return; + } + + if (width > 0 && height > 0) { + terminalView.forceSize(width, height); + } + else { + new AlertDialog.Builder(ConsoleActivity.this) + .setTitle(R.string.resize_error_title) + .setMessage(R.string.resize_error_width_height) + .setNegativeButton(R.string.button_close, null) + .show(); + } + } + }).setNeutralButton(R.string.button_resize_reset, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + terminalView.bridge.resetSize(terminalView); + } + }).setNegativeButton(android.R.string.cancel, null) + .create().show(); + return true; + } + }); + screenCapture = menu.add(R.string.console_menu_screencapture); + + if (hardKeyboard) + screenCapture.setAlphabeticShortcut('s'); + + screenCapture.setIcon(android.R.drawable.ic_menu_camera); + screenCapture.setEnabled(activeTerminal); + screenCapture.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); + terminalView.bridge.captureScreen(); + return true; + } + }); + portForward = menu.add(R.string.console_menu_portforwards); + + if (hardKeyboard) + portForward.setAlphabeticShortcut('f'); + + portForward.setIcon(android.R.drawable.ic_menu_manage); + portForward.setEnabled(sessionOpen && canForwardPorts); + portForward.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); + TerminalBridge bridge = terminalView.bridge; + Intent intent = new Intent(ConsoleActivity.this, PortForwardListActivity.class); + intent.putExtra(Intent.EXTRA_TITLE, bridge.host.getId()); + ConsoleActivity.this.startActivityForResult(intent, REQUEST_EDIT); + return true; + } + }); + urlscan = menu.add(R.string.console_menu_urlscan); + + if (hardKeyboard) + urlscan.setAlphabeticShortcut('l'); + + urlscan.setIcon(android.R.drawable.ic_menu_search); + urlscan.setEnabled(activeTerminal); + urlscan.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + View flip = findCurrentView(R.id.console_flip); + + if (flip == null) return true; + + TerminalView terminal = (TerminalView)flip; + TerminalKeyListener handler = terminal.bridge.getKeyHandler(); + handler.urlScan(terminal); + return true; + } + }); + download = menu.add(R.string.console_menu_download); + download.setAlphabeticShortcut('d'); + download.setEnabled(sessionOpen && canTransferFiles); + download.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + final String downloadFolder = prefs.getString(PreferenceConstants.DOWNLOAD_FOLDER, ""); + final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); + final TerminalBridge bridge = terminalView.bridge; + final EditText textField = new EditText(ConsoleActivity.this); + new AlertDialog.Builder(ConsoleActivity.this) + .setTitle(R.string.transfer_select_remote_download_title) + .setMessage(R.string.transfer_select_remote_download_desc) + .setView(textField) + .setPositiveButton(R.string.transfer_button_download, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + TransferThread transfer = new TransferThread(ConsoleActivity.this, handler); + + if (!prefs.getBoolean(PreferenceConstants.BACKGROUND_FILE_TRANSFER, true)) + transfer.setProgressDialogMessage(getString(R.string.transfer_downloading)); + + transfer.download(bridge, textField.getText().toString(), null, downloadFolder); + } + }).setNegativeButton(android.R.string.cancel, null).create().show(); + return true; + } + }); + upload = menu.add(R.string.console_menu_upload); + upload.setAlphabeticShortcut('u'); + upload.setEnabled(sessionOpen && canTransferFiles); + upload.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + FileChooser.selectFile(ConsoleActivity.this, ConsoleActivity.this, + FileChooser.REQUEST_CODE_SELECT_FILE, + getString(R.string.file_chooser_select_file, getString(R.string.select_for_upload))); + return true; + } + }); + return true; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent intent) { + super.onActivityResult(requestCode, resultCode, intent); + + switch (requestCode) { + case FileChooser.REQUEST_CODE_SELECT_FILE: + if (resultCode == RESULT_OK && intent != null) { + Uri uri = intent.getData(); + + try { + if (uri != null) { + fileSelected(new File(URI.create(uri.toString()))); + } + else { + String filename = intent.getDataString(); + + if (filename != null) { + fileSelected(new File(URI.create(filename))); + } + } + } + catch (IllegalArgumentException e) { + Log.e(TAG, "Couldn't read from selected file", e); + } + } + + break; + } + } + + public void fileSelected(final File f) { + String destFileName; + String uploadFolder = prefs.getString(PreferenceConstants.REMOTE_UPLOAD_FOLDER, null); + final TransferThread transfer = new TransferThread(ConsoleActivity.this, handler); + Log.d(TAG, "File chooser returned " + f); + + if (uploadFolder == null) + uploadFolder = ""; + + if (!uploadFolder.equals("") && uploadFolder.charAt(uploadFolder.length() - 1) != '/') + destFileName = uploadFolder + "/" + f.getName(); + else + destFileName = uploadFolder + f.getName(); + + if (prefs.getBoolean(PreferenceConstants.UPLOAD_DESTINATION_PROMPT, true)) { + final EditText fileDest = new EditText(ConsoleActivity.this); + fileDest.setSingleLine(); + fileDest.setText(destFileName); + new AlertDialog.Builder(ConsoleActivity.this) + .setTitle(R.string.transfer_select_remote_upload_dest_title) + .setMessage(getResources().getString(R.string.transfer_select_remote_upload_dest_desc) + "\n" + f.getPath()) + .setView(fileDest) + .setPositiveButton(R.string.transfer_button_upload, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + if (!prefs.getBoolean(PreferenceConstants.BACKGROUND_FILE_TRANSFER, true)) + transfer.setProgressDialogMessage(getString(R.string.transfer_uploading)); + + TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); + TerminalBridge bridge = terminalView.bridge; + File uf = new File(fileDest.getText().toString()); + String name = "", parent = ""; + + if (uf.getParent() != null) + parent = uf.getParent().toString(); + + if (uf.getName() != null) + name = uf.getName().toString(); + + transfer.upload(bridge, f.toString(), name, parent); + } + }).setNegativeButton(android.R.string.cancel, null).create().show(); + } + else { + if (!prefs.getBoolean(PreferenceConstants.BACKGROUND_FILE_TRANSFER, true)) + transfer.setProgressDialogMessage(getString(R.string.transfer_uploading)); + + TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); + TerminalBridge bridge = terminalView.bridge; + transfer.upload(bridge, f.toString(), null, uploadFolder); + } + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + setVolumeControlStream(AudioManager.STREAM_NOTIFICATION); + final View view = findCurrentView(R.id.console_flip); + boolean activeTerminal = (view instanceof TerminalView); + boolean sessionOpen = false; + boolean disconnected = false; + boolean canForwardPorts = false; + boolean canTransferFiles = false; + + if (activeTerminal) { + TerminalBridge bridge = ((TerminalView) view).bridge; + sessionOpen = bridge.isSessionOpen(); + disconnected = bridge.isDisconnected(); + canForwardPorts = bridge.canFowardPorts(); + canTransferFiles = bridge.canTransferFiles(); + } + + disconnect.setEnabled(activeTerminal); + + if (sessionOpen || !disconnected) + disconnect.setTitle(R.string.list_host_disconnect); + else + disconnect.setTitle(R.string.console_menu_close); + + copy.setEnabled(activeTerminal); + paste.setEnabled(clipboard.hasText() && sessionOpen); + portForward.setEnabled(sessionOpen && canForwardPorts); + urlscan.setEnabled(activeTerminal); + resize.setEnabled(activeTerminal); + download.setEnabled(sessionOpen && canTransferFiles); + upload.setEnabled(sessionOpen && canTransferFiles); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + Intent intent = new Intent(this, HostListActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + return true; + + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onOptionsMenuClosed(Menu menu) { + super.onOptionsMenuClosed(menu); + setVolumeControlStream(AudioManager.STREAM_MUSIC); + } + + @Override + public void onStart() { + super.onStart(); + // connect with manager service to find all bridges + // when connected it will insert all views + bindService(new Intent(this, TerminalManager.class), connection, Context.BIND_AUTO_CREATE); + + if (getResources().getConfiguration().hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) { + this.mKeyboardButton.setVisibility(View.GONE); + } + } + + @Override + public void onPause() { + super.onPause(); + Log.d(TAG, "onPause called"); + + if (forcedOrientation && bound != null) + bound.setResizeAllowed(false); + } + + @Override + public void onResume() { + super.onResume(); + Log.d(TAG, "onResume called"); + + // Make sure we don't let the screen fall asleep. + // This also keeps the Wi-Fi chipset from disconnecting us. + if (prefs.getBoolean(PreferenceConstants.KEEP_ALIVE, true)) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + else { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + + configureOrientation(); + + if (forcedOrientation && bound != null) + bound.setResizeAllowed(true); + } + + /* (non-Javadoc) + * @see android.app.Activity#onNewIntent(android.content.Intent) + */ + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + Log.d(TAG, "onNewIntent called"); + requested = intent.getData(); + + if (requested == null) { + Log.e(TAG, "Got null intent data in onNewIntent()"); + return; + } + + if (bound == null) { + Log.e(TAG, "We're not bound in onNewIntent()"); + return; + } + + TerminalBridge requestedBridge = bound.getConnectedBridge(requested.getFragment()); + int requestedIndex = 0; + + synchronized (flip) { + if (requestedBridge == null) { + // If we didn't find the requested connection, try opening it + try { + Log.d(TAG, String.format("We couldnt find an existing bridge with URI=%s (nickname=%s)," + + "so creating one now", requested.toString(), requested.getFragment())); + requestedBridge = bound.openConnection(requested); + } + catch (Exception e) { + Log.e(TAG, "Problem while trying to create new requested bridge from URI", e); + // TODO: We should display an error dialog here. + return; + } + + requestedIndex = addNewTerminalView(requestedBridge); + } + else { + final int flipIndex = getFlipIndex(requestedBridge); + + if (flipIndex > requestedIndex) { + requestedIndex = flipIndex; + } + } + + setDisplayedTerminal(requestedIndex); + } + } + + @Override + public void onStop() { + super.onStop(); + unbindService(connection); + } + + protected void shiftCurrentTerminal(final int direction) { + View overlay; + + synchronized (flip) { + boolean shouldAnimate = flip.getChildCount() > 1; + + // Only show animation if there is something else to go to. + if (shouldAnimate) { + // keep current overlay from popping up again + overlay = findCurrentView(R.id.terminal_overlay); + + if (overlay != null) + overlay.startAnimation(fade_stay_hidden); + + if (direction == SHIFT_LEFT) { + flip.setInAnimation(slide_left_in); + flip.setOutAnimation(slide_left_out); + flip.showNext(); + } + else if (direction == SHIFT_RIGHT) { + flip.setInAnimation(slide_right_in); + flip.setOutAnimation(slide_right_out); + flip.showPrevious(); + } + } + + updateDefault(); + + if (shouldAnimate) { + // show overlay on new slide and start fade + overlay = findCurrentView(R.id.terminal_overlay); + + if (overlay != null) + overlay.startAnimation(fade_out_delayed); + } + + updatePromptVisible(); + } + } + + /** + * Save the currently shown {@link TerminalView} as the default. This is + * saved back down into {@link TerminalManager} where we can read it again + * later. + */ + private void updateDefault() { + // update the current default terminal + View view = findCurrentView(R.id.console_flip); + + if (!(view instanceof TerminalView)) return; + + TerminalView terminal = (TerminalView)view; + + if (bound != null) bound.defaultBridge = terminal.bridge; + + // tell the bridge monitor it has the topmost visible window now. + if (terminal.bridge.monitor != null) terminal.bridge.monitor.activate(); + } + + protected void updateEmptyVisible() { + // update visibility of empty status message + empty.setVisibility((flip.getChildCount() == 0) ? View.VISIBLE : View.GONE); + } + + /** + * Show any prompts requested by the currently visible {@link TerminalView}. + */ + protected void updatePromptVisible() { + // check if our currently-visible terminalbridge is requesting any prompt services + View view = findCurrentView(R.id.console_flip); + // Hide all the prompts in case a prompt request was canceled + hideAllPrompts(); + + if (!(view instanceof TerminalView)) { + // we dont have an active view, so hide any prompts + return; + } + + PromptHelper prompt = ((TerminalView)view).bridge.promptHelper; + + if (String.class.equals(prompt.promptRequested)) { + stringPromptGroup.setVisibility(View.VISIBLE); + String instructions = prompt.promptInstructions; + boolean password = prompt.passwordRequested; + + if (instructions != null && instructions.length() > 0) { + stringPromptInstructions.setVisibility(View.VISIBLE); + stringPromptInstructions.setText(instructions); + } + else + stringPromptInstructions.setVisibility(View.GONE); + + if (password) { + stringPrompt.setInputType(InputType.TYPE_CLASS_TEXT | + InputType.TYPE_TEXT_VARIATION_PASSWORD); + stringPrompt.setTransformationMethod(PasswordTransformationMethod.getInstance()); + } + else { + stringPrompt.setInputType(InputType.TYPE_CLASS_TEXT | + InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); + stringPrompt.setTransformationMethod(SingleLineTransformationMethod.getInstance()); + } + + stringPrompt.setText(""); + stringPrompt.setHint(prompt.promptHint); + stringPrompt.requestFocus(); + } + else if (Boolean.class.equals(prompt.promptRequested)) { + booleanPromptGroup.setVisibility(View.VISIBLE); + booleanPrompt.setText(prompt.promptHint); + booleanYes.requestFocus(); + } + else { + hideAllPrompts(); + view.requestFocus(); + } + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + Log.d(TAG, String.format("onConfigurationChanged; requestedOrientation=%d, newConfig.orientation=%d", getRequestedOrientation(), newConfig.orientation)); + + if (bound != null) { + if (forcedOrientation && + (newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE && + getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) || + (newConfig.orientation != Configuration.ORIENTATION_PORTRAIT && + getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)) + bound.setResizeAllowed(false); + else + bound.setResizeAllowed(true); + + bound.hardKeyboardHidden = (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES); + mKeyboardButton.setVisibility(bound.hardKeyboardHidden ? View.VISIBLE : View.GONE); + } + } + + /** + * Adds a new TerminalBridge to the current set of views in our ViewFlipper. + * + * @param bridge TerminalBridge to add to our ViewFlipper + * @return the child index of the new view in the ViewFlipper + */ + private int addNewTerminalView(TerminalBridge bridge) { + // let them know about our prompt handler services + bridge.promptHelper.setHandler(promptHandler); + // inflate each terminal view + RelativeLayout view = (RelativeLayout)inflater.inflate(R.layout.item_terminal, flip, false); + // set the terminal overlay text + TextView overlay = (TextView)view.findViewById(R.id.terminal_overlay); + overlay.setText(bridge.host.getNickname()); + // and add our terminal view control, using index to place behind overlay + TerminalView terminal = new TerminalView(ConsoleActivity.this, bridge); + terminal.setId(R.id.console_flip); + view.addView(terminal, 0); + + synchronized (flip) { + // finally attach to the flipper + flip.addView(view); + return flip.getChildCount() - 1; + } + } + + private int getFlipIndex(TerminalBridge bridge) { + synchronized (flip) { + final int children = flip.getChildCount(); + + for (int i = 0; i < children; i++) { + final View view = flip.getChildAt(i).findViewById(R.id.console_flip); + + if (view == null || !(view instanceof TerminalView)) { + // How did that happen? + continue; + } + + final TerminalView tv = (TerminalView) view; + + if (tv.bridge == bridge) { + return i; + } + } + } + + return -1; + } + + /** + * Displays the child in the ViewFlipper at the requestedIndex and updates the prompts. + * + * @param requestedIndex the index of the terminal view to display + */ + private void setDisplayedTerminal(int requestedIndex) { + synchronized (flip) { + try { + // show the requested bridge if found, also fade out overlay + flip.setDisplayedChild(requestedIndex); + flip.getCurrentView().findViewById(R.id.terminal_overlay) + .startAnimation(fade_out_delayed); + } + catch (NullPointerException npe) { + Log.d(TAG, "View went away when we were about to display it", npe); + } + + updatePromptVisible(); + updateEmptyVisible(); + } + } + + private void setFullScreen(int fullScreen) { + if (fullScreen != this.fullScreen) { + if (fullScreen == FULLSCREEN_ON) { + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + else { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + + this.fullScreen = fullScreen; + + if (bound != null) + bound.setFullScreen(this.fullScreen); + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/GeneratePubkeyActivity.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/GeneratePubkeyActivity.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,321 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; + +import com.five_ten_sg.connectbot.bean.PubkeyBean; +import com.five_ten_sg.connectbot.util.EntropyDialog; +import com.five_ten_sg.connectbot.util.EntropyView; +import com.five_ten_sg.connectbot.util.OnEntropyGatheredListener; +import com.five_ten_sg.connectbot.util.PubkeyDatabase; +import com.five_ten_sg.connectbot.util.PubkeyUtils; +import android.app.Activity; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnFocusChangeListener; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.RadioGroup; +import android.widget.RadioGroup.OnCheckedChangeListener; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; + +import ch.ethz.ssh2.crypto.SecureRandomFix; +import ch.ethz.ssh2.signature.ECDSASHA2Verify; + +public class GeneratePubkeyActivity extends Activity implements OnEntropyGatheredListener { + public final static String TAG = "ConnectBot.GeneratePubkeyActivity"; + + private static final int RSA_MINIMUM_BITS = 768; + final static int DEFAULT_BITS = 2048; + final static int DSA_BITS = 1024; + final static int[] ECDSA_SIZES = ECDSASHA2Verify.getCurveSizes(); + final static int ECDSA_DEFAULT_BITS = ECDSA_SIZES[0]; + + private LayoutInflater inflater = null; + + private EditText nickname; + private RadioGroup keyTypeGroup; + private SeekBar bitsSlider; + private EditText bitsText; + private CheckBox unlockAtStartup; + private CheckBox confirmUse; + private Button save; + private Dialog entropyDialog; + private ProgressDialog progress; + + private EditText password1, password2; + + private String keyType = PubkeyDatabase.KEY_TYPE_RSA; + private int minBits = RSA_MINIMUM_BITS; + private int bits = DEFAULT_BITS; + + private byte[] entropy; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.act_generatepubkey); + nickname = (EditText) findViewById(R.id.nickname); + keyTypeGroup = (RadioGroup) findViewById(R.id.key_type); + bitsText = (EditText) findViewById(R.id.bits); + bitsSlider = (SeekBar) findViewById(R.id.bits_slider); + password1 = (EditText) findViewById(R.id.password1); + password2 = (EditText) findViewById(R.id.password2); + unlockAtStartup = (CheckBox) findViewById(R.id.unlock_at_startup); + confirmUse = (CheckBox) findViewById(R.id.confirm_use); + save = (Button) findViewById(R.id.save); + inflater = LayoutInflater.from(this); + nickname.addTextChangedListener(textChecker); + password1.addTextChangedListener(textChecker); + password2.addTextChangedListener(textChecker); + keyTypeGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() { + public void onCheckedChanged(RadioGroup group, int checkedId) { + if (checkedId == R.id.rsa) { + minBits = RSA_MINIMUM_BITS; + bitsSlider.setEnabled(true); + bitsSlider.setProgress(DEFAULT_BITS - minBits); + bitsText.setText(String.valueOf(DEFAULT_BITS)); + bitsText.setEnabled(true); + keyType = PubkeyDatabase.KEY_TYPE_RSA; + } + else if (checkedId == R.id.dsa) { + // DSA keys can only be 1024 bits + bitsSlider.setEnabled(false); + bitsSlider.setProgress(DSA_BITS - minBits); + bitsText.setText(String.valueOf(DSA_BITS)); + bitsText.setEnabled(false); + keyType = PubkeyDatabase.KEY_TYPE_DSA; + } + else if (checkedId == R.id.ec) { + minBits = ECDSA_DEFAULT_BITS; + bitsSlider.setEnabled(true); + bitsSlider.setProgress(ECDSA_DEFAULT_BITS - minBits); + bitsText.setText(String.valueOf(ECDSA_DEFAULT_BITS)); + bitsText.setEnabled(true); + keyType = PubkeyDatabase.KEY_TYPE_EC; + } + } + }); + bitsSlider.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { + public void onProgressChanged(SeekBar seekBar, int progress, + boolean fromTouch) { + if (PubkeyDatabase.KEY_TYPE_EC.equals(keyType)) { + bits = getClosestFieldSize(progress + minBits); + seekBar.setProgress(bits - minBits); + } + else { + // Stay evenly divisible by 8 because it looks nicer to have + // 2048 than 2043 bits. + final int ourProgress = progress - (progress % 8); + bits = minBits + ourProgress; + } + + bitsText.setText(String.valueOf(bits)); + } + public void onStartTrackingTouch(SeekBar seekBar) { + // We don't care about the start. + } + public void onStopTrackingTouch(SeekBar seekBar) { + // We don't care about the stop. + } + }); + bitsText.setOnFocusChangeListener(new OnFocusChangeListener() { + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus) { + final boolean isEc = PubkeyDatabase.KEY_TYPE_EC.equals(keyType); + + try { + bits = Integer.parseInt(bitsText.getText().toString()); + + if (bits < minBits) { + bits = minBits; + bitsText.setText(String.valueOf(bits)); + } + + if (isEc) { + bits = getClosestFieldSize(bits); + } + } + catch (NumberFormatException nfe) { + bits = isEc ? ECDSA_DEFAULT_BITS : DEFAULT_BITS; + bitsText.setText(String.valueOf(bits)); + } + + bitsSlider.setProgress(bits - minBits); + } + } + }); + save.setOnClickListener(new OnClickListener() { + public void onClick(View view) { + GeneratePubkeyActivity.this.save.setEnabled(false); + GeneratePubkeyActivity.this.startEntropyGather(); + } + }); + } + + private void checkEntries() { + boolean allowSave = true; + + if (!password1.getText().toString().equals(password2.getText().toString())) + allowSave = false; + + if (nickname.getText().length() == 0) + allowSave = false; + + save.setEnabled(allowSave); + } + + private void startEntropyGather() { + final View entropyView = inflater.inflate(R.layout.dia_gatherentropy, null, false); + ((EntropyView)entropyView.findViewById(R.id.entropy)).addOnEntropyGatheredListener(GeneratePubkeyActivity.this); + entropyDialog = new EntropyDialog(GeneratePubkeyActivity.this, entropyView); + entropyDialog.show(); + } + + public void onEntropyGathered(byte[] entropy) { + // For some reason the entropy dialog was aborted, exit activity + if (entropy == null) { + finish(); + return; + } + + this.entropy = entropy.clone(); + int numSetBits = 0; + + for (int i = 0; i < 20; i++) + numSetBits += measureNumberOfSetBits(this.entropy[i]); + + Log.d(TAG, "Entropy distribution=" + (int)(100.0 * numSetBits / 160.0) + "%"); + Log.d(TAG, "entropy gathered; attemping to generate key..."); + startKeyGen(); + } + + private void startKeyGen() { + progress = new ProgressDialog(GeneratePubkeyActivity.this); + progress.setMessage(GeneratePubkeyActivity.this.getResources().getText(R.string.pubkey_generating)); + progress.setIndeterminate(true); + progress.setCancelable(false); + progress.show(); + Thread keyGenThread = new Thread(mKeyGen); + keyGenThread.setName("KeyGen"); + keyGenThread.start(); + } + + final private Runnable mKeyGen = new Runnable() { + public void run() { + try { + boolean encrypted = false; + int tmpbits = bits; + + if (keyType == PubkeyDatabase.KEY_TYPE_DSA) + tmpbits = DSA_BITS; + + SecureRandomFix random = new SecureRandomFix(); + // Work around JVM bug + random.nextInt(); + random.setSeed(entropy); + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(keyType); + keyPairGen.initialize(tmpbits, random); + KeyPair pair = keyPairGen.generateKeyPair(); + PrivateKey priv = pair.getPrivate(); + PublicKey pub = pair.getPublic(); + String secret = password1.getText().toString(); + + if (secret.length() > 0) + encrypted = true; + + Log.d(TAG, "private: " + PubkeyUtils.formatKey(priv)); + Log.d(TAG, "public: " + PubkeyUtils.formatKey(pub)); + PubkeyBean pubkey = new PubkeyBean(); + pubkey.setNickname(nickname.getText().toString()); + pubkey.setType(keyType); + pubkey.setPrivateKey(PubkeyUtils.getEncodedPrivate(priv, secret)); + pubkey.setPublicKey(pub.getEncoded()); + pubkey.setEncrypted(encrypted); + pubkey.setStartup(unlockAtStartup.isChecked()); + pubkey.setConfirmUse(confirmUse.isChecked()); + PubkeyDatabase pubkeydb = new PubkeyDatabase(GeneratePubkeyActivity.this); + pubkeydb.savePubkey(pubkey); + pubkeydb.close(); + } + catch (Exception e) { + Log.e(TAG, "Could not generate key pair"); + e.printStackTrace(); + } + + GeneratePubkeyActivity.this.runOnUiThread(new Runnable() { + public void run() { + progress.dismiss(); + GeneratePubkeyActivity.this.finish(); + } + }); + } + }; + + final private TextWatcher textChecker = new TextWatcher() { + public void afterTextChanged(Editable s) {} + public void beforeTextChanged(CharSequence s, int start, int count, + int after) {} + public void onTextChanged(CharSequence s, int start, int before, + int count) { + checkEntries(); + } + }; + + private int measureNumberOfSetBits(byte b) { + int numSetBits = 0; + + for (int i = 0; i < 8; i++) { + if ((b & 1) == 1) + numSetBits++; + + b >>= 1; + } + + return numSetBits; + } + + private int getClosestFieldSize(int bits) { + int outBits = ECDSA_DEFAULT_BITS; + int distance = Math.abs(bits - ECDSA_DEFAULT_BITS); + + for (int i = 1; i < ECDSA_SIZES.length; i++) { + int thisDistance = Math.abs(bits - ECDSA_SIZES[i]); + + if (thisDistance < distance) { + distance = thisDistance; + outBits = ECDSA_SIZES[i]; + } + } + + return outBits; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/HelpActivity.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/HelpActivity.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,63 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot; + +import java.io.IOException; + +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; + +/** + * @author Kenny Root + * + */ +public class HelpActivity extends Activity { + public final static String TAG = "ConnectBot.HelpActivity"; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.act_help); + this.setTitle(String.format("%s: %s", + getResources().getText(R.string.app_name), + getResources().getText(R.string.title_help))); + LinearLayout content = (LinearLayout)this.findViewById(R.id.topics); + + String[] topics = getResources().getStringArray(R.array.list_wizard_topics); + for (final String topic : topics) { + Button button = new Button(this); + button.setText(topic); + button.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + Intent intent = new Intent(HelpActivity.this, HelpTopicActivity.class); + intent.putExtra(Intent.EXTRA_TITLE, topic); + HelpActivity.this.startActivity(intent); + } + }); + content.addView(button); + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/HelpTopicActivity.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/HelpTopicActivity.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,44 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot; + +import com.five_ten_sg.connectbot.util.HelpTopicView; +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +/** + * @author Kenny Root + * + */ +public class HelpTopicActivity extends Activity { + public final static String TAG = "ConnectBot.HelpActivity"; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.act_help_topic); + String topic = getIntent().getStringExtra(Intent.EXTRA_TITLE); + this.setTitle(String.format("%s: %s - %s", + getResources().getText(R.string.app_name), + getResources().getText(R.string.title_help), + topic)); + HelpTopicView helpTopic = (HelpTopicView) findViewById(R.id.topic_text); + helpTopic.setTopic(topic); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/HostEditorActivity.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/HostEditorActivity.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,445 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot; + +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import com.five_ten_sg.connectbot.bean.HostBean; +import com.five_ten_sg.connectbot.service.TerminalBridge; +import com.five_ten_sg.connectbot.service.TerminalManager; +import com.five_ten_sg.connectbot.util.HostDatabase; +import com.five_ten_sg.connectbot.util.PubkeyDatabase; +import android.content.ComponentName; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.os.Bundle; +import android.os.IBinder; +import android.preference.CheckBoxPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.util.Log; + +public class HostEditorActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener { + public class CursorPreferenceHack implements SharedPreferences { + protected final String table; + protected final long id; + + protected Map values = new HashMap(); + + public CursorPreferenceHack(String table, long id) { + this.table = table; + this.id = id; + cacheValues(); + } + + protected final void cacheValues() { + // fill a cursor and cache the values locally + // this makes sure we dont have any floating cursor to dispose later + SQLiteDatabase db = hostdb.getReadableDatabase(); + Cursor cursor = db.query(table, null, "_id = ?", + new String[] { String.valueOf(id) }, null, null, null); + + if (cursor.moveToFirst()) { + for (int i = 0; i < cursor.getColumnCount(); i++) { + String key = cursor.getColumnName(i); + + if (key.equals(HostDatabase.FIELD_HOST_HOSTKEY)) continue; + + String value = cursor.getString(i); + values.put(key, value); + } + } + + cursor.close(); + db.close(); + } + + public boolean contains(String key) { + return values.containsKey(key); + } + + public class Editor implements SharedPreferences.Editor { + + private ContentValues update = new ContentValues(); + + public SharedPreferences.Editor clear() { + Log.d(this.getClass().toString(), "clear()"); + update = new ContentValues(); + return this; + } + + public boolean commit() { + //Log.d(this.getClass().toString(), "commit() changes back to database"); + SQLiteDatabase db = hostdb.getWritableDatabase(); + db.update(table, update, "_id = ?", new String[] { String.valueOf(id) }); + db.close(); + // make sure we refresh the parent cached values + cacheValues(); + + // and update any listeners + for (OnSharedPreferenceChangeListener listener : listeners) { + listener.onSharedPreferenceChanged(CursorPreferenceHack.this, null); + } + + return true; + } + + // Gingerbread compatibility + public void apply() { + commit(); + } + + public android.content.SharedPreferences.Editor putBoolean(String key, boolean value) { + return this.putString(key, Boolean.toString(value)); + } + + public android.content.SharedPreferences.Editor putFloat(String key, float value) { + return this.putString(key, Float.toString(value)); + } + + public android.content.SharedPreferences.Editor putInt(String key, int value) { + return this.putString(key, Integer.toString(value)); + } + + public android.content.SharedPreferences.Editor putLong(String key, long value) { + return this.putString(key, Long.toString(value)); + } + + public android.content.SharedPreferences.Editor putString(String key, String value) { + //Log.d(this.getClass().toString(), String.format("Editor.putString(key=%s, value=%s)", key, value)); + update.put(key, value); + return this; + } + + public android.content.SharedPreferences.Editor remove(String key) { + //Log.d(this.getClass().toString(), String.format("Editor.remove(key=%s)", key)); + update.remove(key); + return this; + } + + public android.content.SharedPreferences.Editor putStringSet(String key, Set value) { + throw new UnsupportedOperationException("HostEditor Prefs do not support Set"); + } + } + + + public Editor edit() { + //Log.d(this.getClass().toString(), "edit()"); + return new Editor(); + } + + public Map getAll() { + return values; + } + + public boolean getBoolean(String key, boolean defValue) { + return Boolean.valueOf(this.getString(key, Boolean.toString(defValue))); + } + + public float getFloat(String key, float defValue) { + return Float.valueOf(this.getString(key, Float.toString(defValue))); + } + + public int getInt(String key, int defValue) { + return Integer.valueOf(this.getString(key, Integer.toString(defValue))); + } + + public long getLong(String key, long defValue) { + return Long.valueOf(this.getString(key, Long.toString(defValue))); + } + + public String getString(String key, String defValue) { + //Log.d(this.getClass().toString(), String.format("getString(key=%s, defValue=%s)", key, defValue)); + if (!values.containsKey(key)) return defValue; + + return values.get(key); + } + + public Set getStringSet(String key, Set defValue) { + throw new ClassCastException("HostEditor Prefs do not support Set"); + } + + protected List listeners = new LinkedList(); + + public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { + listeners.add(listener); + } + + public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { + listeners.remove(listener); + } + + } + + @Override + public SharedPreferences getSharedPreferences(String name, int mode) { + //Log.d(this.getClass().toString(), String.format("getSharedPreferences(name=%s)", name)); + return this.pref; + } + + protected static final String TAG = "ConnectBot.HostEditorActivity"; + + protected HostDatabase hostdb = null; + private PubkeyDatabase pubkeydb = null; + + private CursorPreferenceHack pref; + private ServiceConnection connection; + private Map summaries = new HashMap(); + + private HostBean host; + private boolean enableSSHFeatures; + private boolean enable5250Features; + private boolean enableAsyncFeatures; + + protected TerminalBridge hostBridge; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + long hostId = this.getIntent().getLongExtra(Intent.EXTRA_TITLE, -1); + // TODO: we could pass through a specific ContentProvider uri here + //this.getPreferenceManager().setSharedPreferencesName(uri); + this.hostdb = new HostDatabase(this); + this.pubkeydb = new PubkeyDatabase(this); + host = hostdb.findHostById(hostId); + enableSSHFeatures = host.isSSH(); + enable5250Features = host.is5250(); + enableAsyncFeatures = host.isAsync(); + connection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + TerminalManager bound = ((TerminalManager.TerminalBinder) service).getService(); + hostBridge = bound.getConnectedBridge(host); + } + public void onServiceDisconnected(ComponentName name) { + hostBridge = null; + } + }; + this.pref = new CursorPreferenceHack(HostDatabase.TABLE_HOSTS, hostId); + this.pref.registerOnSharedPreferenceChangeListener(this); + this.addPreferencesFromResource(R.xml.host_prefs); + // get all the text summaries + getSummaries(); + // disable all preferences that are not applicable to this host + findPreference(HostDatabase.FIELD_HOST_PUBKEYID).setEnabled(enableSSHFeatures); + findPreference(HostDatabase.FIELD_HOST_USEAUTHAGENT).setEnabled(enableSSHFeatures); + findPreference(HostDatabase.FIELD_HOST_POSTLOGIN).setEnabled(!enable5250Features); + findPreference(HostDatabase.FIELD_HOST_COMPRESSION).setEnabled(enableSSHFeatures); + findPreference(HostDatabase.FIELD_HOST_HTTPPROXY).setEnabled(enableAsyncFeatures); + findPreference(HostDatabase.FIELD_HOST_WANTSESSION).setEnabled(enableSSHFeatures); + findPreference(HostDatabase.FIELD_HOST_USERNAME).setEnabled(enableSSHFeatures || enable5250Features); + findPreference(HostDatabase.FIELD_HOST_EMULATION).setEnabled(!enable5250Features); + findPreference(HostDatabase.CATEGORY_5250).setEnabled(enable5250Features); + findPreference(HostDatabase.CATEGORY_X11).setEnabled(enableSSHFeatures); + // add all existing pubkeys to our listpreference for user to choose from + // TODO: may be an issue here when this activity is recycled after adding a new pubkey + // TODO: should consider moving into onStart, but we dont have a good way of resetting the listpref after filling once + ListPreference pubkeyPref = (ListPreference)findPreference(HostDatabase.FIELD_HOST_PUBKEYID); + List pubkeyNicks = new LinkedList (Arrays.asList(pubkeyPref.getEntries())); + pubkeyNicks.addAll(pubkeydb.allValues(PubkeyDatabase.FIELD_PUBKEY_NICKNAME)); + pubkeyPref.setEntries(pubkeyNicks.toArray(new CharSequence[pubkeyNicks.size()])); + List pubkeyIds = new LinkedList (Arrays.asList(pubkeyPref.getEntryValues())); + pubkeyIds.addAll(pubkeydb.allValues("_id")); + pubkeyPref.setEntryValues(pubkeyIds.toArray(new CharSequence[pubkeyIds.size()])); + // Populate the character set encoding list with all available + final ListPreference charsetPref = (ListPreference)findPreference(HostDatabase.FIELD_HOST_ENCODING); + + if (CharsetHolder.isInitialized()) { + initCharsetPref(charsetPref); + } + else { + String[] currentCharsetPref = new String[1]; + currentCharsetPref[0] = charsetPref.getValue(); + charsetPref.setEntryValues(currentCharsetPref); + charsetPref.setEntries(currentCharsetPref); + new Thread(new Runnable() { + public void run() { + initCharsetPref(charsetPref); + } + }).start(); + } + + this.updateSummaries(); + } + + @Override + public void onStart() { + super.onStart(); + bindService(new Intent(this, TerminalManager.class), connection, Context.BIND_AUTO_CREATE); + + if (this.hostdb == null) + this.hostdb = new HostDatabase(this); + + if (this.pubkeydb == null) + this.pubkeydb = new PubkeyDatabase(this); + } + + @Override + public void onStop() { + super.onStop(); + unbindService(connection); + + if (this.hostdb != null) { + this.hostdb.close(); + this.hostdb = null; + } + + if (this.pubkeydb != null) { + this.pubkeydb.close(); + this.pubkeydb = null; + } + } + + private void getSummaries() { + // get all the original text summaries + for (String key : this.pref.values.keySet()) { + Preference pref = this.findPreference(key); + + if (pref == null) continue; + + if (pref instanceof CheckBoxPreference) continue; + + CharSequence value = pref.getSummary(); + summaries.put(key, value); + } + } + + private void updateSummaries() { + // for all text preferences, set hint as current database value if it is non-empty + for (String key : this.pref.values.keySet()) { + Preference pref = this.findPreference(key); + + if (pref == null) continue; + + if (pref instanceof CheckBoxPreference) continue; + + CharSequence value = this.pref.getString(key, ""); + + if (key.equals(HostDatabase.FIELD_HOST_PUBKEYID)) { + try { + int pubkeyId = Integer.parseInt(value.toString()); + + if (pubkeyId >= 0) + pref.setSummary(pubkeydb.getNickname(pubkeyId)); + else if (pubkeyId == HostDatabase.PUBKEYID_ANY) + pref.setSummary(R.string.list_pubkeyids_any); + else if (pubkeyId == HostDatabase.PUBKEYID_NEVER) + pref.setSummary(R.string.list_pubkeyids_none); + + continue; + } + catch (NumberFormatException nfe) { + // Fall through. + } + } + else if ((pref instanceof ListPreference) && (value != null)) { + ListPreference listPref = (ListPreference) pref; + int entryIndex = listPref.findIndexOfValue(value.toString()); + + if (entryIndex >= 0) value = listPref.getEntries()[entryIndex]; + } + + if ((value == null) || (value.length() == 0)) value = summaries.get(key); + + pref.setSummary(value); + } + } + + private void initCharsetPref(final ListPreference charsetPref) { + charsetPref.setEntryValues(CharsetHolder.getCharsetIds()); + charsetPref.setEntries(CharsetHolder.getCharsetNames()); + } + + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + // update values on changed preference + this.updateSummaries(); + + // Our CursorPreferenceHack always send null keys, so try to set charset anyway + if (hostBridge != null) + hostBridge.setCharset(sharedPreferences + .getString(HostDatabase.FIELD_HOST_ENCODING, HostDatabase.ENCODING_DEFAULT)); + } + + public static class CharsetHolder { + private static boolean initialized = false; + + private static CharSequence[] charsetIds; + private static CharSequence[] charsetNames; + + public static CharSequence[] getCharsetNames() { + if (charsetNames == null) + initialize(); + + return charsetNames; + } + + public static CharSequence[] getCharsetIds() { + if (charsetIds == null) + initialize(); + + return charsetIds; + } + + private synchronized static void initialize() { + if (initialized) + return; + + List charsetIdsList = new LinkedList(); + List charsetNamesList = new LinkedList(); + + for (Entry entry : Charset.availableCharsets().entrySet()) { + Charset c = entry.getValue(); + + if (c.canEncode() && c.isRegistered()) { + String key = entry.getKey(); + + if (key.startsWith("cp")) { + // Custom CP437 charset changes + charsetIdsList.add("CP437"); + charsetNamesList.add("CP437"); + } + + charsetIdsList.add(entry.getKey()); + charsetNamesList.add(c.displayName()); + } + } + + charsetIds = charsetIdsList.toArray(new CharSequence[charsetIdsList.size()]); + charsetNames = charsetNamesList.toArray(new CharSequence[charsetNamesList.size()]); + initialized = true; + } + + public static boolean isInitialized() { + return initialized; + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/HostListActivity.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/HostListActivity.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,665 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot; + + +import com.five_ten_sg.connectbot.bean.HostBean; +import com.five_ten_sg.connectbot.service.TerminalBridge; +import com.five_ten_sg.connectbot.service.TerminalManager; +import com.five_ten_sg.connectbot.transport.TransportFactory; +import com.five_ten_sg.connectbot.util.HostDatabase; +import com.five_ten_sg.connectbot.util.PreferenceConstants; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.File; +import java.util.HashMap; +import java.util.List; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.ListActivity; +import android.content.ComponentName; +import android.content.ContentValues; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.Intent.ShortcutIconResource; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.content.res.ColorStateList; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.ContextMenu; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.View; +import android.view.View.OnKeyListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.Spinner; +import android.widget.TextView; + +public class HostListActivity extends ListActivity { + protected static final String TAG = "ConnectBot.HostListActivity"; + public final static int REQUEST_EDIT = 1; + public final static int REQUEST_EULA = 2; + + protected TerminalManager bound = null; + + protected HostDatabase hostdb; + private List hosts; + protected LayoutInflater inflater = null; + + protected boolean sortedByColor = false; + + private MenuItem sortcolor; + + private MenuItem sortlast; + + private Spinner transportSpinner; + private TextView quickconnect; + + private SharedPreferences prefs = null; + + protected boolean makingShortcut = false; + + protected Handler updateHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + HostListActivity.this.updateList(); + } + }; + + private ServiceConnection connection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + bound = ((TerminalManager.TerminalBinder) service).getService(); + // update our listview binder to find the service + updateList(); + } + public void onServiceDisconnected(ComponentName className) { + bound = null; + updateList(); + } + }; + + @Override + public void onStart() { + super.onStart(); + // start the terminal manager service + this.bindService(new Intent(this, TerminalManager.class), connection, Context.BIND_AUTO_CREATE); + + if (this.hostdb == null) + this.hostdb = new HostDatabase(this); + } + + @Override + public void onStop() { + super.onStop(); + this.unbindService(connection); + + if (this.hostdb != null) { + this.hostdb.close(); + this.hostdb = null; + } + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_EULA) { + if (resultCode == Activity.RESULT_OK) { + // yay they agreed, so store that info + Editor editor = prefs.edit(); + editor.putBoolean(PreferenceConstants.EULA, true); + editor.commit(); + } + else { + // user didnt agree, so close + this.finish(); + } + } + else if (requestCode == REQUEST_EDIT) { + updateList(); + } + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.act_hostlist); + this.setTitle(String.format("%s: %s", + getResources().getText(R.string.app_name), + getResources().getText(R.string.title_hosts_list))); + this.prefs = PreferenceManager.getDefaultSharedPreferences(this); + + // detect HTC Dream and apply special preferences + if (Build.MANUFACTURER.equals("HTC") && Build.DEVICE.equals("dream")) { + if (!prefs.contains(PreferenceConstants.SHIFT_FKEYS) && + !prefs.contains(PreferenceConstants.CTRL_FKEYS)) { + Editor editor = prefs.edit(); + editor.putBoolean(PreferenceConstants.SHIFT_FKEYS, true); + editor.putBoolean(PreferenceConstants.CTRL_FKEYS, true); + editor.commit(); + } + } + + makingShortcut = Intent.ACTION_CREATE_SHORTCUT.equals(getIntent().getAction()) + || Intent.ACTION_PICK.equals(getIntent().getAction()); + + // connect with hosts database, read deployment file, and populate list + hostdb = new HostDatabase(this); + createDeploymentHosts(); // build hosts from a deployment text file + updateList(); + + // check for eula agreement, which might be set from the deployment file + boolean agreed = prefs.getBoolean(PreferenceConstants.EULA, false); + if (!agreed) { + startActivityForResult(new Intent(this, WizardActivity.class), REQUEST_EULA); + } + + // show the list + sortedByColor = prefs.getBoolean(PreferenceConstants.SORT_BY_COLOR, false); + registerForContextMenu(getListView()); + getListView().setOnItemClickListener(new OnItemClickListener() { + + public synchronized void onItemClick(AdapterView parent, View view, int position, long id) { + // launch off to console details + HostBean host = (HostBean) getListView().getItemAtPosition(position); + Uri uri = host.getUri(); + Intent contents = new Intent(Intent.ACTION_VIEW, uri); + contents.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + + if (makingShortcut) { + // create shortcut if requested + ShortcutIconResource icon = Intent.ShortcutIconResource.fromContext(HostListActivity.this, R.drawable.icon); + Intent intent = new Intent(); + intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, contents); + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, host.getNickname()); + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icon); + setResult(RESULT_OK, intent); + finish(); + } + else { + // otherwise just launch activity to show this host + startActivity(contents); + } + } + }); + quickconnect = (TextView) this.findViewById(R.id.front_quickconnect); + quickconnect.setVisibility(makingShortcut ? View.GONE : View.VISIBLE); + quickconnect.setOnKeyListener(new OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_UP) return false; + + if (keyCode != KeyEvent.KEYCODE_ENTER) return false; + + return startConsoleActivity(); + } + }); + transportSpinner = (Spinner)findViewById(R.id.transport_selection); + transportSpinner.setVisibility(makingShortcut ? View.GONE : View.VISIBLE); + ArrayAdapter transportSelection = new ArrayAdapter (this, + android.R.layout.simple_spinner_item, TransportFactory.getTransportNames()); + transportSelection.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + transportSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + public void onItemSelected(AdapterView arg0, View view, int position, long id) { + String formatHint = TransportFactory.getFormatHint( + (String) transportSpinner.getSelectedItem(), + HostListActivity.this); + quickconnect.setHint(formatHint); + quickconnect.setError(null); + quickconnect.requestFocus(); + } + public void onNothingSelected(AdapterView arg0) { } + }); + transportSpinner.setAdapter(transportSelection); + this.inflater = LayoutInflater.from(this); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + // don't offer menus when creating shortcut + if (makingShortcut) return true; + + sortcolor.setVisible(!sortedByColor); + sortlast.setVisible(sortedByColor); + return true; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + + // don't offer menus when creating shortcut + if (makingShortcut) return true; + + // add host, ssh keys, about + sortcolor = menu.add(R.string.list_menu_sortcolor); + sortcolor.setIcon(android.R.drawable.ic_menu_share); + sortcolor.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + sortedByColor = true; + updateList(); + return true; + } + }); + sortlast = menu.add(R.string.list_menu_sortname); + sortlast.setIcon(android.R.drawable.ic_menu_share); + sortlast.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + sortedByColor = false; + updateList(); + return true; + } + }); + MenuItem keys = menu.add(R.string.list_menu_pubkeys); + keys.setIcon(android.R.drawable.ic_lock_lock); + keys.setIntent(new Intent(HostListActivity.this, PubkeyListActivity.class)); + MenuItem colors = menu.add(R.string.title_colors); + colors.setIcon(android.R.drawable.ic_menu_slideshow); + colors.setIntent(new Intent(HostListActivity.this, ColorsActivity.class)); + MenuItem settings = menu.add(R.string.list_menu_settings); + settings.setIcon(android.R.drawable.ic_menu_preferences); + settings.setIntent(new Intent(HostListActivity.this, SettingsActivity.class)); + MenuItem help = menu.add(R.string.title_help); + help.setIcon(android.R.drawable.ic_menu_help); + help.setIntent(new Intent(HostListActivity.this, HelpActivity.class)); + return true; + } + + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + // create menu to handle hosts + // create menu to handle deleting and sharing lists + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + final HostBean host = (HostBean) this.getListView().getItemAtPosition(info.position); + menu.setHeaderTitle(host.getNickname()); + // edit, disconnect, delete + MenuItem connect = menu.add(R.string.list_host_disconnect); + final TerminalBridge bridge = bound.getConnectedBridge(host); + connect.setEnabled((bridge != null)); + connect.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + bridge.dispatchDisconnect(true); + updateHandler.sendEmptyMessage(-1); + return true; + } + }); + MenuItem edit = menu.add(R.string.list_host_edit); + edit.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + Intent intent = new Intent(HostListActivity.this, HostEditorActivity.class); + intent.putExtra(Intent.EXTRA_TITLE, host.getId()); + HostListActivity.this.startActivityForResult(intent, REQUEST_EDIT); + return true; + } + }); + MenuItem portForwards = menu.add(R.string.list_host_portforwards); + portForwards.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + Intent intent = new Intent(HostListActivity.this, PortForwardListActivity.class); + intent.putExtra(Intent.EXTRA_TITLE, host.getId()); + HostListActivity.this.startActivityForResult(intent, REQUEST_EDIT); + return true; + } + }); + + if (!TransportFactory.canForwardPorts(host.getProtocol())) + portForwards.setEnabled(false); + + MenuItem delete = menu.add(R.string.list_host_delete); + delete.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // prompt user to make sure they really want this + new AlertDialog.Builder(HostListActivity.this) + .setMessage(getString(R.string.delete_message, host.getNickname())) + .setPositiveButton(R.string.delete_pos, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // make sure we disconnect + if (bridge != null) + bridge.dispatchDisconnect(true); + + hostdb.deleteHost(host); + updateHandler.sendEmptyMessage(-1); + } + }) + .setNegativeButton(R.string.delete_neg, null).create().show(); + return true; + } + }); + } + + /** + * @param text + * @return + */ + private boolean startConsoleActivity() { + Uri uri = TransportFactory.getUri((String) transportSpinner + .getSelectedItem(), quickconnect.getText().toString()); + + if (uri == null) { + quickconnect.setError(getString(R.string.list_format_error, + TransportFactory.getFormatHint( + (String) transportSpinner.getSelectedItem(), + HostListActivity.this))); + return false; + } + + HostBean host = TransportFactory.findHost(hostdb, uri); + + if (host == null) { + host = TransportFactory.getTransport(uri.getScheme()).createHost(uri); + host.setColor(HostDatabase.COLOR_GRAY); + host.setPubkeyId(HostDatabase.PUBKEYID_ANY); + hostdb.saveHost(host); + } + + Intent intent = new Intent(HostListActivity.this, ConsoleActivity.class); + intent.setData(uri); + startActivity(intent); + return true; + } + + private void createDeploymentHosts() { + try { + String fn = Environment.getExternalStorageDirectory().getAbsolutePath() + + File.separator + "deployment.connections"; + BufferedReader reader = new BufferedReader(new FileReader(fn)); + String line = null; + + while ((line = reader.readLine()) != null) { + if (line.length() == 0) continue; // empty + + if (line.substring(0, 1).equals("#")) continue; // comment + + if (!line.contains("://")) continue; // invalid uri + + Uri uri = Uri.parse(line); + ContentValues values = null; + + while ((line = reader.readLine()).length() > 0) { + String [] parts = line.split("="); + + if (parts.length != 2) continue; + + if (values == null) values = new ContentValues(); + + values.put(parts[0].trim(), parts[1].trim()); + } + + if (uri.getScheme().equals("global")) { + Editor editor = prefs.edit(); + HashMap types = new HashMap(); + types.put("memkeys", "boolean"); + types.put("connPersist", "boolean"); + types.put("emulation", "string"); + types.put("scrollback", "string"); + types.put("rotation", "string"); + types.put("shiftfkeys", "boolean"); + types.put("ctrlfkeys", "boolean"); + types.put("keymode", "string"); + types.put("camera", "string"); + types.put("volup", "string"); + types.put("voldn", "string"); + types.put("search", "string"); + types.put("ptt", "string"); + types.put("keepalive", "boolean"); + types.put("wifilock", "boolean"); + types.put("bumpyarrows", "boolean"); + types.put("eula", "boolean"); + types.put("extended_longpress", "boolean"); + types.put("ctrl_string", "string"); + types.put("picker_string", "string"); + types.put("picker_keep_open", "boolean"); + types.put("list_custom_keymap", "string"); + types.put("bell", "boolean"); + types.put("bellVolume", "float"); + types.put("bellVibrate", "boolean"); + types.put("bellNotification", "boolean"); + types.put("screen_capture_folder", "string"); + types.put("screen_capture_popup", "boolean"); + types.put("file_dialog", "string"); + types.put("download_folder", "string"); + types.put("remote_upload_folder", "string"); + types.put("upload_dest_prompt", "boolean"); + types.put("background_file_transfer", "boolean"); + types.put("debug_keycodes", "boolean"); + + for (String key : values.keySet()) { + if (types.containsKey(key)) { + String t = types.get(key); + String sv = values.getAsString(key); + if (t.equals("float")) { + editor.putFloat(key, Float.parseFloat(sv)); + } + else if (t.equals("boolean")) { + editor.putBoolean(key, Boolean.parseBoolean(sv)); + } + else if (t.equals("string")) { + editor.putString(key, sv); + } + } + } + + editor.commit(); + } + else { + HostBean host = TransportFactory.findHost(hostdb, uri); + + if (host == null) { + host = TransportFactory.getTransport(uri.getScheme()).createHost(uri); + host.setColor(HostDatabase.COLOR_GRAY); + host.setPubkeyId(HostDatabase.PUBKEYID_ANY); + hostdb.saveHost(host); + } + + host = TransportFactory.findHost(hostdb, uri); + + if (host == null) continue; + + if (values == null) continue; + + SQLiteDatabase db = hostdb.getWritableDatabase(); + db.update(HostDatabase.TABLE_HOSTS, values, "_id = ?", new String[] { String.valueOf(host.getId()) }); + db.close(); + } + } + + reader.close(); + (new File(fn)).delete(); + } + catch (Exception e) { + Log.d(TAG, "Deployment scan failed.", e); + } + } + + protected void updateList() { + if (prefs.getBoolean(PreferenceConstants.SORT_BY_COLOR, false) != sortedByColor) { + Editor editor = prefs.edit(); + editor.putBoolean(PreferenceConstants.SORT_BY_COLOR, sortedByColor); + editor.commit(); + } + + if (hostdb == null) + hostdb = new HostDatabase(this); + + hosts = hostdb.getHosts(sortedByColor); + + // Don't lose hosts that are connected via shortcuts but not in the database. + if (bound != null) { + for (TerminalBridge bridge : bound.bridges) { + if (!hosts.contains(bridge.host)) + hosts.add(0, bridge.host); + } + } + + HostAdapter adapter = new HostAdapter(this, hosts, bound); + this.setListAdapter(adapter); + } + + class HostAdapter extends ArrayAdapter { + private List hosts; + private final TerminalManager manager; + private final ColorStateList red, green, blue; + + public final static int STATE_UNKNOWN = 1, STATE_CONNECTED = 2, STATE_DISCONNECTED = 3; + + class ViewHolder { + public TextView nickname; + public TextView caption; + public ImageView icon; + } + + public HostAdapter(Context context, List hosts, TerminalManager manager) { + super(context, R.layout.item_host, hosts); + this.hosts = hosts; + this.manager = manager; + red = context.getResources().getColorStateList(R.color.red); + green = context.getResources().getColorStateList(R.color.green); + blue = context.getResources().getColorStateList(R.color.blue); + } + + /** + * Check if we're connected to a terminal with the given host. + */ + private int getConnectedState(HostBean host) { + // always disconnected if we dont have backend service + if (this.manager == null) + return STATE_UNKNOWN; + + if (manager.getConnectedBridge(host) != null) + return STATE_CONNECTED; + + if (manager.disconnected.contains(host)) + return STATE_DISCONNECTED; + + return STATE_UNKNOWN; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder holder; + + if (convertView == null) { + convertView = inflater.inflate(R.layout.item_host, null, false); + holder = new ViewHolder(); + holder.nickname = (TextView)convertView.findViewById(android.R.id.text1); + holder.caption = (TextView)convertView.findViewById(android.R.id.text2); + holder.icon = (ImageView)convertView.findViewById(android.R.id.icon); + convertView.setTag(holder); + } + else + holder = (ViewHolder) convertView.getTag(); + + HostBean host = hosts.get(position); + + if (host == null) { + // Well, something bad happened. We can't continue. + Log.e("HostAdapter", "Host bean is null!"); + holder.nickname.setText("Error during lookup"); + holder.caption.setText("see 'adb logcat' for more"); + return convertView; + } + + holder.nickname.setText(host.getNickname()); + + switch (this.getConnectedState(host)) { + case STATE_UNKNOWN: + holder.icon.setImageState(new int[] { }, true); + break; + + case STATE_CONNECTED: + holder.icon.setImageState(new int[] { android.R.attr.state_checked }, true); + break; + + case STATE_DISCONNECTED: + holder.icon.setImageState(new int[] { android.R.attr.state_expanded }, true); + break; + } + + ColorStateList chosen = null; + + if (HostDatabase.COLOR_RED.equals(host.getColor())) + chosen = this.red; + else if (HostDatabase.COLOR_GREEN.equals(host.getColor())) + chosen = this.green; + else if (HostDatabase.COLOR_BLUE.equals(host.getColor())) + chosen = this.blue; + + Context context = convertView.getContext(); + + if (chosen != null) { + // set color normally if not selected + holder.nickname.setTextColor(chosen); + holder.caption.setTextColor(chosen); + } + else { + // selected, so revert back to default black text + holder.nickname.setTextAppearance(context, android.R.attr.textAppearanceLarge); + holder.caption.setTextAppearance(context, android.R.attr.textAppearanceSmall); + } + + long now = System.currentTimeMillis() / 1000; + String nice = context.getString(R.string.bind_never); + + if (host.getLastConnect() > 0) { + int minutes = (int)((now - host.getLastConnect()) / 60); + + if (minutes >= 60) { + int hours = (minutes / 60); + + if (hours >= 24) { + int days = (hours / 24); + nice = context.getString(R.string.bind_days, days); + } + else + nice = context.getString(R.string.bind_hours, hours); + } + else + nice = context.getString(R.string.bind_minutes, minutes); + } + + holder.caption.setText(nice); + return convertView; + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/PortForwardListActivity.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/PortForwardListActivity.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,406 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot; + +import java.util.List; + +import com.five_ten_sg.connectbot.bean.HostBean; +import com.five_ten_sg.connectbot.bean.PortForwardBean; +import com.five_ten_sg.connectbot.service.TerminalBridge; +import com.five_ten_sg.connectbot.service.TerminalManager; +import com.five_ten_sg.connectbot.util.HostDatabase; +import android.app.AlertDialog; +import android.app.ListActivity; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.res.Resources; +import android.database.SQLException; +import android.graphics.Paint; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.util.Log; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +/** + * List all portForwards for a particular host and provide a way for users to add more portForwards, + * edit existing portForwards, and delete portForwards. + * + * @author Kenny Root + */ +public class PortForwardListActivity extends ListActivity { + public final static String TAG = "ConnectBot.PortForwardListActivity"; + + private static final int LISTENER_CYCLE_TIME = 500; + + protected HostDatabase hostdb; + + private List portForwards; + + private ServiceConnection connection = null; + protected TerminalBridge hostBridge = null; + protected LayoutInflater inflater = null; + + private HostBean host; + + @Override + public void onStart() { + super.onStart(); + this.bindService(new Intent(this, TerminalManager.class), connection, Context.BIND_AUTO_CREATE); + + if (this.hostdb == null) + this.hostdb = new HostDatabase(this); + } + + @Override + public void onStop() { + super.onStop(); + this.unbindService(connection); + + if (this.hostdb != null) { + this.hostdb.close(); + this.hostdb = null; + } + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + long hostId = this.getIntent().getLongExtra(Intent.EXTRA_TITLE, -1); + setContentView(R.layout.act_portforwardlist); + // connect with hosts database and populate list + this.hostdb = new HostDatabase(this); + host = hostdb.findHostById(hostId); + { + final String nickname = host != null ? host.getNickname() : null; + final Resources resources = getResources(); + + if (nickname != null) { + this.setTitle(String.format("%s: %s (%s)", + resources.getText(R.string.app_name), + resources.getText(R.string.title_port_forwards_list), + nickname)); + } + else { + this.setTitle(String.format("%s: %s", + resources.getText(R.string.app_name), + resources.getText(R.string.title_port_forwards_list))); + } + } + connection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + TerminalManager bound = ((TerminalManager.TerminalBinder) service).getService(); + hostBridge = bound.getConnectedBridge(host); + updateHandler.sendEmptyMessage(-1); + } + public void onServiceDisconnected(ComponentName name) { + hostBridge = null; + } + }; + this.updateList(); + this.registerForContextMenu(this.getListView()); + this.getListView().setOnItemClickListener(new OnItemClickListener() { + public void onItemClick(AdapterView adapter, View view, int position, long id) { + ListView lv = PortForwardListActivity.this.getListView(); + PortForwardBean pfb = (PortForwardBean) lv.getItemAtPosition(position); + + if (hostBridge != null) { + if (pfb.isEnabled()) + hostBridge.disablePortForward(pfb); + else { + if (!hostBridge.enablePortForward(pfb)) + Toast.makeText(PortForwardListActivity.this, getString(R.string.portforward_problem), Toast.LENGTH_LONG).show(); + } + + updateHandler.sendEmptyMessage(-1); + } + } + }); + this.inflater = LayoutInflater.from(this); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + MenuItem add = menu.add(R.string.portforward_menu_add); + add.setIcon(android.R.drawable.ic_menu_add); + add.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // build dialog to prompt user about updating + final View portForwardView = inflater.inflate(R.layout.dia_portforward, null, false); + final EditText destEdit = (EditText) portForwardView.findViewById(R.id.portforward_destination); + final Spinner typeSpinner = (Spinner)portForwardView.findViewById(R.id.portforward_type); + typeSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { + public void onItemSelected(AdapterView value, View view, + int position, long id) { + destEdit.setEnabled(position != 2); + } + public void onNothingSelected(AdapterView arg0) { + } + }); + new AlertDialog.Builder(PortForwardListActivity.this) + .setView(portForwardView) + .setPositiveButton(R.string.portforward_pos, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + try { + final EditText nicknameEdit = (EditText) portForwardView.findViewById(R.id.nickname); + final EditText sourcePortEdit = (EditText) portForwardView.findViewById(R.id.portforward_source); + String type = HostDatabase.PORTFORWARD_LOCAL; + + switch (typeSpinner.getSelectedItemPosition()) { + case 0: + type = HostDatabase.PORTFORWARD_LOCAL; + break; + + case 1: + type = HostDatabase.PORTFORWARD_REMOTE; + break; + + case 2: + type = HostDatabase.PORTFORWARD_DYNAMIC5; + break; + } + + PortForwardBean pfb = new PortForwardBean( + host != null ? host.getId() : -1, + nicknameEdit.getText().toString(), type, + sourcePortEdit.getText().toString(), + destEdit.getText().toString()); + + if (hostBridge != null) { + hostBridge.addPortForward(pfb); + hostBridge.enablePortForward(pfb); + } + + if (host != null && !hostdb.savePortForward(pfb)) + throw new SQLException("Could not save port forward"); + + updateHandler.sendEmptyMessage(-1); + } + catch (Exception e) { + Log.e(TAG, "Could not update port forward", e); + // TODO Show failure dialog. + } + } + }) + .setNegativeButton(R.string.delete_neg, null).create().show(); + return true; + } + }); + return true; + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + // Create menu to handle deleting and editing port forward + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + final PortForwardBean pfb = (PortForwardBean) this.getListView().getItemAtPosition(info.position); + menu.setHeaderTitle(pfb.getNickname()); + MenuItem edit = menu.add(R.string.portforward_edit); + edit.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + final View editTunnelView = inflater.inflate(R.layout.dia_portforward, null, false); + final Spinner typeSpinner = (Spinner) editTunnelView.findViewById(R.id.portforward_type); + + if (HostDatabase.PORTFORWARD_LOCAL.equals(pfb.getType())) + typeSpinner.setSelection(0); + else if (HostDatabase.PORTFORWARD_REMOTE.equals(pfb.getType())) + typeSpinner.setSelection(1); + else + typeSpinner.setSelection(2); + + final EditText nicknameEdit = (EditText) editTunnelView.findViewById(R.id.nickname); + nicknameEdit.setText(pfb.getNickname()); + final EditText sourcePortEdit = (EditText) editTunnelView.findViewById(R.id.portforward_source); + sourcePortEdit.setText(String.valueOf(pfb.getSourcePort())); + final EditText destEdit = (EditText) editTunnelView.findViewById(R.id.portforward_destination); + + if (HostDatabase.PORTFORWARD_DYNAMIC5.equals(pfb.getType())) { + destEdit.setEnabled(false); + } + else { + destEdit.setText(String.format("%s:%d", pfb.getDestAddr(), pfb.getDestPort())); + } + + typeSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { + public void onItemSelected(AdapterView value, View view, + int position, long id) { + destEdit.setEnabled(position != 2); + } + public void onNothingSelected(AdapterView arg0) { + } + }); + new AlertDialog.Builder(PortForwardListActivity.this) + .setView(editTunnelView) + .setPositiveButton(R.string.button_change, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + try { + if (hostBridge != null) + hostBridge.disablePortForward(pfb); + + pfb.setNickname(nicknameEdit.getText().toString()); + + switch (typeSpinner.getSelectedItemPosition()) { + case 0: + pfb.setType(HostDatabase.PORTFORWARD_LOCAL); + break; + + case 1: + pfb.setType(HostDatabase.PORTFORWARD_REMOTE); + break; + + case 2: + pfb.setType(HostDatabase.PORTFORWARD_DYNAMIC5); + break; + } + + pfb.setSourcePort(Integer.parseInt(sourcePortEdit.getText().toString())); + pfb.setDest(destEdit.getText().toString()); + + // Use the new settings for the existing connection. + if (hostBridge != null) + updateHandler.postDelayed(new Runnable() { + public void run() { + hostBridge.enablePortForward(pfb); + updateHandler.sendEmptyMessage(-1); + } + }, LISTENER_CYCLE_TIME); + + if (!hostdb.savePortForward(pfb)) + throw new SQLException("Could not save port forward"); + + updateHandler.sendEmptyMessage(-1); + } + catch (Exception e) { + Log.e(TAG, "Could not update port forward", e); + // TODO Show failure dialog. + } + } + }) + .setNegativeButton(android.R.string.cancel, null).create().show(); + return true; + } + }); + MenuItem delete = menu.add(R.string.portforward_delete); + delete.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // prompt user to make sure they really want this + new AlertDialog.Builder(PortForwardListActivity.this) + .setMessage(getString(R.string.delete_message, pfb.getNickname())) + .setPositiveButton(R.string.delete_pos, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + try { + // Delete the port forward from the host if needed. + if (hostBridge != null) + hostBridge.removePortForward(pfb); + + hostdb.deletePortForward(pfb); + } + catch (Exception e) { + Log.e(TAG, "Could not delete port forward", e); + } + + updateHandler.sendEmptyMessage(-1); + } + }) + .setNegativeButton(R.string.delete_neg, null).create().show(); + return true; + } + }); + } + + protected Handler updateHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + PortForwardListActivity.this.updateList(); + } + }; + + protected void updateList() { + if (hostBridge != null) { + this.portForwards = hostBridge.getPortForwards(); + } + else { + if (this.hostdb == null) return; + + this.portForwards = this.hostdb.getPortForwardsForHost(host); + } + + PortForwardAdapter adapter = new PortForwardAdapter(this, portForwards); + this.setListAdapter(adapter); + } + + class PortForwardAdapter extends ArrayAdapter { + class ViewHolder { + public TextView nickname; + public TextView caption; + } + + private List portForwards; + + public PortForwardAdapter(Context context, List portForwards) { + super(context, R.layout.item_portforward, portForwards); + this.portForwards = portForwards; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder holder; + + if (convertView == null) { + convertView = inflater.inflate(R.layout.item_portforward, null, false); + holder = new ViewHolder(); + holder.nickname = (TextView)convertView.findViewById(android.R.id.text1); + holder.caption = (TextView)convertView.findViewById(android.R.id.text2); + convertView.setTag(holder); + } + else + holder = (ViewHolder) convertView.getTag(); + + PortForwardBean pfb = portForwards.get(position); + holder.nickname.setText(pfb.getNickname()); + holder.caption.setText(pfb.getDescription()); + + if (hostBridge != null && !pfb.isEnabled()) { + holder.nickname.setPaintFlags(holder.nickname.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + holder.caption.setPaintFlags(holder.caption.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + } + + return convertView; + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/PubkeyListActivity.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/PubkeyListActivity.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,674 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.EventListener; +import java.util.List; + +import com.five_ten_sg.connectbot.bean.PubkeyBean; +import com.five_ten_sg.connectbot.service.TerminalManager; +import com.five_ten_sg.connectbot.util.FileChooser; +import com.five_ten_sg.connectbot.util.FileChooserCallback; +import com.five_ten_sg.connectbot.util.PubkeyDatabase; +import com.five_ten_sg.connectbot.util.PubkeyUtils; +import android.app.AlertDialog; +import android.app.ListActivity; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.Environment; +import android.os.IBinder; +import android.text.ClipboardManager; +import android.util.Log; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TableRow; +import android.widget.TextView; +import android.widget.Toast; + +import ch.ethz.ssh2.crypto.Base64; +import ch.ethz.ssh2.crypto.PEMDecoder; +import ch.ethz.ssh2.crypto.PEMStructure; + +/** + * List public keys in database by nickname and describe their properties. Allow users to import, + * generate, rename, and delete key pairs. + * + * @author Kenny Root + */ +public class PubkeyListActivity extends ListActivity implements EventListener, FileChooserCallback { + + public final static String TAG = "ConnectBot.PubkeyListActivity"; + + private static final int MAX_KEYFILE_SIZE = 8192; + private static final int KEYTYPE_PUBLIC = 0; + private static final int KEYTYPE_PRIVATE = 1; + + protected PubkeyDatabase pubkeydb; + private List pubkeys; + + protected ClipboardManager clipboard; + + protected LayoutInflater inflater = null; + + protected TerminalManager bound = null; + + private MenuItem onstartToggle = null; + private MenuItem confirmUse = null; + + private ServiceConnection connection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + bound = ((TerminalManager.TerminalBinder) service).getService(); + // update our listview binder to find the service + updateList(); + } + public void onServiceDisconnected(ComponentName className) { + bound = null; + updateList(); + } + }; + + @Override + public void onStart() { + super.onStart(); + bindService(new Intent(this, TerminalManager.class), connection, Context.BIND_AUTO_CREATE); + + if (pubkeydb == null) + pubkeydb = new PubkeyDatabase(this); + } + + @Override + public void onStop() { + super.onStop(); + unbindService(connection); + + if (pubkeydb != null) { + pubkeydb.close(); + pubkeydb = null; + } + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.act_pubkeylist); + this.setTitle(String.format("%s: %s", + getResources().getText(R.string.app_name), + getResources().getText(R.string.title_pubkey_list))); + // connect with hosts database and populate list + pubkeydb = new PubkeyDatabase(this); + updateList(); + registerForContextMenu(getListView()); + getListView().setOnItemClickListener(new OnItemClickListener() { + public void onItemClick(AdapterView adapter, View view, int position, long id) { + PubkeyBean pubkey = (PubkeyBean) getListView().getItemAtPosition(position); + boolean loaded = bound.isKeyLoaded(pubkey.getNickname()); + + // handle toggling key in-memory on/off + if (loaded) { + bound.removeKey(pubkey.getNickname()); + updateList(); + } + else { + handleAddKey(pubkey); + } + } + }); + clipboard = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE); + inflater = LayoutInflater.from(this); + } + + /** + * Read given file into memory as byte[]. + */ + protected static byte[] readRaw(File file) throws Exception { + InputStream is = new FileInputStream(file); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + int bytesRead; + byte[] buffer = new byte[1024]; + + while ((bytesRead = is.read(buffer)) != -1) { + os.write(buffer, 0, bytesRead); + } + + os.flush(); + os.close(); + is.close(); + return os.toByteArray(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + MenuItem generatekey = menu.add(R.string.pubkey_generate); + generatekey.setIcon(android.R.drawable.ic_menu_manage); + generatekey.setIntent(new Intent(PubkeyListActivity.this, GeneratePubkeyActivity.class)); + MenuItem importkey = menu.add(R.string.pubkey_import); + importkey.setIcon(android.R.drawable.ic_menu_upload); + importkey.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + FileChooser.selectFile(PubkeyListActivity.this, PubkeyListActivity.this, + FileChooser.REQUEST_CODE_SELECT_FILE, + getString(R.string.file_chooser_select_file, getString(R.string.select_for_key_import))); + return true; + } + }); + return true; + } + + protected void handleAddKey(final PubkeyBean pubkey) { + if (pubkey.isEncrypted()) { + final View view = inflater.inflate(R.layout.dia_password, null); + final EditText passwordField = (EditText)view.findViewById(android.R.id.text1); + new AlertDialog.Builder(PubkeyListActivity.this) + .setView(view) + .setPositiveButton(R.string.pubkey_unlock, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + handleAddKey(pubkey, passwordField.getText().toString()); + } + }) + .setNegativeButton(android.R.string.cancel, null).create().show(); + } + else { + handleAddKey(pubkey, null); + } + } + + protected void handleAddKey(PubkeyBean keybean, String password) { + KeyPair pair = null; + + if (PubkeyDatabase.KEY_TYPE_IMPORTED.equals(keybean.getType())) { + // load specific key using pem format + try { + pair = PEMDecoder.decode(new String(keybean.getPrivateKey()).toCharArray(), password); + } + catch (Exception e) { + String message = getResources().getString(R.string.pubkey_failed_add, keybean.getNickname()); + Log.e(TAG, message, e); + Toast.makeText(PubkeyListActivity.this, message, Toast.LENGTH_LONG).show(); + } + } + else { + // load using internal generated format + try { + PrivateKey privKey = PubkeyUtils.decodePrivate(keybean.getPrivateKey(), keybean.getType(), password); + PublicKey pubKey = PubkeyUtils.decodePublic(keybean.getPublicKey(), keybean.getType()); + Log.d(TAG, "Unlocked key " + PubkeyUtils.formatKey(pubKey)); + pair = new KeyPair(pubKey, privKey); + } + catch (Exception e) { + String message = getResources().getString(R.string.pubkey_failed_add, keybean.getNickname()); + Log.e(TAG, message, e); + Toast.makeText(PubkeyListActivity.this, message, Toast.LENGTH_LONG).show(); + return; + } + } + + if (pair == null) return; + + Log.d(TAG, String.format("Unlocked key '%s'", keybean.getNickname())); + // save this key in memory + bound.addKey(keybean, pair, true); + updateList(); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + // Create menu to handle deleting and editing pubkey + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + final PubkeyBean pubkey = (PubkeyBean) getListView().getItemAtPosition(info.position); + menu.setHeaderTitle(pubkey.getNickname()); + // TODO: option load/unload key from in-memory list + // prompt for password as needed for passworded keys + // cant change password or clipboard imported keys + final boolean imported = PubkeyDatabase.KEY_TYPE_IMPORTED.equals(pubkey.getType()); + final boolean loaded = bound.isKeyLoaded(pubkey.getNickname()); + MenuItem load = menu.add(loaded ? R.string.pubkey_memory_unload : R.string.pubkey_memory_load); + load.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + if (loaded) { + bound.removeKey(pubkey.getNickname()); + updateList(); + } + else { + handleAddKey(pubkey); + //bound.addKey(nickname, trileadKey); + } + + return true; + } + }); + onstartToggle = menu.add(R.string.pubkey_load_on_start); + onstartToggle.setVisible(!pubkey.isEncrypted()); + onstartToggle.setCheckable(true); + onstartToggle.setChecked(pubkey.isStartup()); + onstartToggle.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // toggle onstart status + pubkey.setStartup(!pubkey.isStartup()); + pubkeydb.savePubkey(pubkey); + updateList(); + return true; + } + }); + MenuItem changePassword = menu.add(R.string.pubkey_change_password); + changePassword.setVisible(!imported); + changePassword.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + final View changePasswordView = inflater.inflate(R.layout.dia_changepassword, null, false); + ((TableRow)changePasswordView.findViewById(R.id.old_password_prompt)) + .setVisibility(pubkey.isEncrypted() ? View.VISIBLE : View.GONE); + new AlertDialog.Builder(PubkeyListActivity.this) + .setView(changePasswordView) + .setPositiveButton(R.string.button_change, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + String oldPassword = ((EditText)changePasswordView.findViewById(R.id.old_password)).getText().toString(); + String password1 = ((EditText)changePasswordView.findViewById(R.id.password1)).getText().toString(); + String password2 = ((EditText)changePasswordView.findViewById(R.id.password2)).getText().toString(); + + if (!password1.equals(password2)) { + new AlertDialog.Builder(PubkeyListActivity.this) + .setMessage(R.string.alert_passwords_do_not_match_msg) + .setPositiveButton(android.R.string.ok, null) + .create().show(); + return; + } + + try { + if (!pubkey.changePassword(oldPassword, password1)) + new AlertDialog.Builder(PubkeyListActivity.this) + .setMessage(R.string.alert_wrong_password_msg) + .setPositiveButton(android.R.string.ok, null) + .create().show(); + else { + pubkeydb.savePubkey(pubkey); + updateList(); + } + } + catch (Exception e) { + Log.e(TAG, "Could not change private key password", e); + new AlertDialog.Builder(PubkeyListActivity.this) + .setMessage(R.string.alert_key_corrupted_msg) + .setPositiveButton(android.R.string.ok, null) + .create().show(); + } + } + }) + .setNegativeButton(android.R.string.cancel, null).create().show(); + return true; + } + }); + confirmUse = menu.add(R.string.pubkey_confirm_use); + confirmUse.setCheckable(true); + confirmUse.setChecked(pubkey.isConfirmUse()); + confirmUse.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // toggle confirm use + pubkey.setConfirmUse(!pubkey.isConfirmUse()); + pubkeydb.savePubkey(pubkey); + updateList(); + return true; + } + }); + MenuItem copyPublicToClipboard = menu.add(R.string.pubkey_copy_public); + copyPublicToClipboard.setVisible(!imported); + copyPublicToClipboard.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + try { + PublicKey pk = PubkeyUtils.decodePublic(pubkey.getPublicKey(), pubkey.getType()); + String openSSHPubkey = PubkeyUtils.convertToOpenSSHFormat(pk, pubkey.getNickname()); + clipboard.setText(openSSHPubkey); + } + catch (Exception e) { + e.printStackTrace(); + } + + return true; + } + }); + MenuItem exportPublic = menu.add(R.string.pubkey_export_public); + exportPublic.setVisible(!imported); + exportPublic.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + String keyString = PubkeyUtils.getPubkeyString(pubkey); + + if (keyString != null) + saveKeyToFile(keyString, pubkey.getNickname(), KEYTYPE_PUBLIC); + + return true; + } + }); + MenuItem copyPrivateToClipboard = menu.add(R.string.pubkey_copy_private); + copyPrivateToClipboard.setVisible(!pubkey.isEncrypted() || imported); + copyPrivateToClipboard.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + String keyString = PubkeyUtils.getPrivkeyString(pubkey, null); + + if (keyString != null) + clipboard.setText(keyString); + + return true; + } + }); + MenuItem exportPrivate = menu.add(R.string.pubkey_export_private); + exportPrivate.setVisible(!pubkey.isEncrypted() || imported); + exportPrivate.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + /* if (pubkey.isEncrypted()) { + final View view = inflater.inflate(R.layout.dia_password, null); + final EditText passwordField = (EditText)view.findViewById(android.R.id.text1); + + new AlertDialog.Builder(PubkeyListActivity.this) + .setView(view) + .setPositiveButton(R.string.pubkey_unlock, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + String keyString = PubkeyUtils.getPrivkeyString(pubkey, passwordField.getText().toString()); + if (keyString != null) + saveKeyToFile(keyString, pubkey.getNickname(), KEYTYPE_PRIVATE); + } + }) + .setNegativeButton(android.R.string.cancel, null).create().show(); + } else { */ + String keyString = PubkeyUtils.getPrivkeyString(pubkey, null); + + if (keyString != null) + saveKeyToFile(keyString, pubkey.getNickname(), KEYTYPE_PRIVATE); + +// } + return true; + } + }); + MenuItem delete = menu.add(R.string.pubkey_delete); + delete.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // prompt user to make sure they really want this + new AlertDialog.Builder(PubkeyListActivity.this) + .setMessage(getString(R.string.delete_message, pubkey.getNickname())) + .setPositiveButton(R.string.delete_pos, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // dont forget to remove from in-memory + if (loaded) + bound.removeKey(pubkey.getNickname()); + + // delete from backend database and update gui + pubkeydb.deletePubkey(pubkey); + updateList(); + } + }) + .setNegativeButton(R.string.delete_neg, null).create().show(); + return true; + } + }); + } + + protected void updateList() { + if (pubkeydb == null) return; + + pubkeys = pubkeydb.allPubkeys(); + PubkeyAdapter adapter = new PubkeyAdapter(this, pubkeys); + this.setListAdapter(adapter); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent intent) { + super.onActivityResult(requestCode, resultCode, intent); + + switch (requestCode) { + case FileChooser.REQUEST_CODE_SELECT_FILE: + if (resultCode == RESULT_OK && intent != null) { + File file = FileChooser.getSelectedFile(intent); + + if (file != null) + readKeyFromFile(file); + } + + break; + } + } + + /** + * @param name + */ + private void readKeyFromFile(File file) { + PubkeyBean pubkey = new PubkeyBean(); + // find the exact file selected + pubkey.setNickname(file.getName()); + + if (file.length() > MAX_KEYFILE_SIZE) { + Toast.makeText(PubkeyListActivity.this, + R.string.pubkey_import_parse_problem, + Toast.LENGTH_LONG).show(); + return; + } + + // parse the actual key once to check if its encrypted + // then save original file contents into our database + try { + byte[] raw = readRaw(file); + String data = new String(raw); + + if (data.startsWith(PubkeyUtils.PKCS8_START)) { + int start = data.indexOf(PubkeyUtils.PKCS8_START) + PubkeyUtils.PKCS8_START.length(); + int end = data.indexOf(PubkeyUtils.PKCS8_END); + + if (end > start) { + char[] encoded = data.substring(start, end - 1).toCharArray(); + Log.d(TAG, "encoded: " + new String(encoded)); + byte[] decoded = Base64.decode(encoded); + KeyPair kp = PubkeyUtils.recoverKeyPair(decoded); + pubkey.setType(kp.getPrivate().getAlgorithm()); + pubkey.setPrivateKey(kp.getPrivate().getEncoded()); + pubkey.setPublicKey(kp.getPublic().getEncoded()); + } + else { + Log.e(TAG, "Problem parsing PKCS#8 file; corrupt?"); + Toast.makeText(PubkeyListActivity.this, + R.string.pubkey_import_parse_problem, + Toast.LENGTH_LONG).show(); + } + } + else { + PEMStructure struct = PEMDecoder.parsePEM(new String(raw).toCharArray()); + pubkey.setEncrypted(PEMDecoder.isPEMEncrypted(struct)); + pubkey.setType(PubkeyDatabase.KEY_TYPE_IMPORTED); + pubkey.setPrivateKey(raw); + } + + // write new value into database + if (pubkeydb == null) + pubkeydb = new PubkeyDatabase(this); + + pubkeydb.savePubkey(pubkey); + updateList(); + } + catch (Exception e) { + Log.e(TAG, "Problem parsing imported private key", e); + Toast.makeText(PubkeyListActivity.this, R.string.pubkey_import_parse_problem, Toast.LENGTH_LONG).show(); + } + } + + private void saveKeyToFile(final String keyString, final String nickName, int keyType) { + final int titleId, messageId, successId, errorId; + final String errorString; + + if (keyType == KEYTYPE_PRIVATE) { + titleId = R.string.pubkey_private_save_as; + messageId = R.string.pubkey_private_save_as_desc; + successId = R.string.pubkey_private_export_success; + errorId = R.string.pubkey_private_export_problem; + errorString = "Error exporting private key"; + } + else { + titleId = R.string.pubkey_public_save_as; + messageId = R.string.pubkey_public_save_as_desc; + errorId = R.string.pubkey_public_export_problem; + successId = R.string.pubkey_public_export_success; + errorString = "Error exporting public key"; + } + + final String sdcard = Environment.getExternalStorageDirectory().getAbsolutePath(); + final EditText fileName = new EditText(PubkeyListActivity.this); + fileName.setSingleLine(); + + if (nickName != null) { + if (keyType == KEYTYPE_PRIVATE) + fileName.setText(sdcard + File.separator + nickName.trim()); + else + fileName.setText(sdcard + File.separator + nickName.trim() + ".pub"); + } + + new AlertDialog.Builder(PubkeyListActivity.this) + .setTitle(titleId) + .setMessage(messageId) + .setView(fileName) + .setPositiveButton(R.string.save, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + File keyFile = new File(fileName.getText().toString()); + + if (!keyFile.exists()) { + try { + keyFile.createNewFile(); + } + catch (IOException e) { + Log.e(TAG, errorString); + Toast.makeText(PubkeyListActivity.this, + errorId, + Toast.LENGTH_LONG).show(); + return; + } + } + + FileOutputStream fout = null; + + try { + fout = new FileOutputStream(keyFile); + fout.write(keyString.getBytes(), 0, keyString.getBytes().length); + fout.flush(); + } + catch (Exception e) { + Log.e(TAG, errorString); + Toast.makeText(PubkeyListActivity.this, + errorId, + Toast.LENGTH_LONG).show(); + return; + } + + Toast.makeText(PubkeyListActivity.this, + getResources().getString(successId, keyFile.getPath().toString()), + Toast.LENGTH_LONG).show(); + } + }).setNegativeButton(android.R.string.cancel, null).create().show(); + } + + public void fileSelected(File f) { + Log.d(TAG, "File chooser returned " + f); + readKeyFromFile(f); + } + + class PubkeyAdapter extends ArrayAdapter { + private List pubkeys; + + class ViewHolder { + public TextView nickname; + public TextView caption; + public ImageView icon; + } + + public PubkeyAdapter(Context context, List pubkeys) { + super(context, R.layout.item_pubkey, pubkeys); + this.pubkeys = pubkeys; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder holder; + + if (convertView == null) { + convertView = inflater.inflate(R.layout.item_pubkey, null, false); + holder = new ViewHolder(); + holder.nickname = (TextView) convertView.findViewById(android.R.id.text1); + holder.caption = (TextView) convertView.findViewById(android.R.id.text2); + holder.icon = (ImageView) convertView.findViewById(android.R.id.icon1); + convertView.setTag(holder); + } + else + holder = (ViewHolder) convertView.getTag(); + + PubkeyBean pubkey = pubkeys.get(position); + holder.nickname.setText(pubkey.getNickname()); + boolean imported = PubkeyDatabase.KEY_TYPE_IMPORTED.equals(pubkey.getType()); + + if (imported) { + try { + PEMStructure struct = PEMDecoder.parsePEM(new String(pubkey.getPrivateKey()).toCharArray()); + String type = (struct.pemType == PEMDecoder.PEM_RSA_PRIVATE_KEY) ? "RSA" : + (struct.pemType == PEMDecoder.PEM_DSA_PRIVATE_KEY) ? "DSA" : "EC"; + holder.caption.setText(String.format("%s unknown-bit", type)); + } + catch (IOException e) { + Log.e(TAG, "Error decoding IMPORTED public key at " + pubkey.getId(), e); + } + } + else { + try { + holder.caption.setText(pubkey.getDescription()); + } + catch (Exception e) { + Log.e(TAG, "Error decoding public key at " + pubkey.getId(), e); + holder.caption.setText(R.string.pubkey_unknown_format); + } + } + + if (bound == null) { + holder.icon.setVisibility(View.GONE); + } + else { + holder.icon.setVisibility(View.VISIBLE); + + if (bound.isKeyLoaded(pubkey.getNickname())) + holder.icon.setImageState(new int[] { android.R.attr.state_checked }, true); + else + holder.icon.setImageState(new int[] { }, true); + } + + return convertView; + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/SettingsActivity.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/SettingsActivity.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,55 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot; + +import com.five_ten_sg.connectbot.util.PreferenceConstants; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.preference.PreferenceManager; +import android.util.Log; + +public class SettingsActivity extends PreferenceActivity { + private static final String TAG = "ConnectBot.Settings"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + try { + addPreferencesFromResource(R.xml.preferences); + } + catch (ClassCastException e) { + Log.e(TAG, "Shared preferences are corrupt! Resetting to default values."); + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + // Blow away all the preferences + SharedPreferences.Editor editor = preferences.edit(); + editor.clear(); + editor.commit(); + PreferenceManager.setDefaultValues(this, R.xml.preferences, true); + // Since they were able to get to the Settings activity, they already agreed to the EULA + editor = preferences.edit(); + editor.putBoolean(PreferenceConstants.EULA, true); + editor.commit(); + addPreferencesFromResource(R.xml.preferences); + } + + // TODO: add parse checking here to make sure we have integer value for scrollback + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/StrictModeSetup.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/StrictModeSetup.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,25 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.five_ten_sg.connectbot; +import android.annotation.TargetApi; +import android.os.StrictMode; +@TargetApi(9) +public class StrictModeSetup { + public static void run() { + StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.LAX); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/TerminalView.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/TerminalView.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,459 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.five_ten_sg.connectbot.bean.SelectionArea; +import com.five_ten_sg.connectbot.service.FontSizeChangedListener; +import com.five_ten_sg.connectbot.service.TerminalBridge; +import com.five_ten_sg.connectbot.service.TerminalKeyListener; +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.database.Cursor; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PixelXorXfermode; +import android.graphics.RectF; +import android.net.Uri; +import android.os.AsyncTask; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.ScaleGestureDetector; +import android.widget.Toast; +import de.mud.terminal.VDUBuffer; + +/** + * User interface {@link View} for showing a TerminalBridge in an + * {@link Activity}. Handles drawing bitmap updates and passing keystrokes down + * to terminal. + * + * @author jsharkey + */ +public class TerminalView extends View implements FontSizeChangedListener { + public final static String TAG = "ConnectBot.TerminalView"; + + private final Context context; + public final TerminalBridge bridge; + private final Paint paint; + private final Paint cursorPaint; + private final Paint cursorStrokePaint; + + // Cursor paints to distinguish modes + private Path ctrlCursor, altCursor, shiftCursor; + private RectF tempSrc, tempDst; + private Matrix scaleMatrix; + private static final Matrix.ScaleToFit scaleType = Matrix.ScaleToFit.FILL; + + private Toast notification = null; + private String lastNotification = null; + private volatile boolean notifications = true; + + // Related to Accessibility Features + private boolean mAccessibilityInitialized = false; + private boolean mAccessibilityActive = true; + private Object[] mAccessibilityLock = new Object[0]; + private StringBuffer mAccessibilityBuffer; + private Pattern mControlCodes = null; + private Matcher mCodeMatcher = null; + private AccessibilityEventSender mEventSender = null; + + + private static final String BACKSPACE_CODE = "\\x08\\x1b\\[K"; + private static final String CONTROL_CODE_PATTERN = "\\x1b\\[K[^m]+[m|:]"; + + private static final int ACCESSIBILITY_EVENT_THRESHOLD = 1000; + private static final String SCREENREADER_INTENT_ACTION = "android.accessibilityservice.AccessibilityService"; + private static final String SCREENREADER_INTENT_CATEGORY = "android.accessibilityservice.category.FEEDBACK_SPOKEN"; + + public ScaleGestureDetector mScaleDetector; + + public TerminalView(Context context, TerminalBridge bridge) { + super(context); + this.context = context; + this.bridge = bridge; + paint = new Paint(); + setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); + setFocusable(true); + setFocusableInTouchMode(true); + cursorPaint = new Paint(); + cursorPaint.setColor(bridge.color[bridge.defaultFg]); + cursorPaint.setXfermode(new PixelXorXfermode(bridge.color[bridge.defaultBg])); + cursorPaint.setAntiAlias(true); + cursorStrokePaint = new Paint(cursorPaint); + cursorStrokePaint.setStrokeWidth(0.1f); + cursorStrokePaint.setStyle(Paint.Style.STROKE); + /* + * Set up our cursor indicators on a 1x1 Path object which we can later + * transform to our character width and height + */ + // TODO make this into a resource somehow + shiftCursor = new Path(); + shiftCursor.lineTo(0.5f, 0.33f); + shiftCursor.lineTo(1.0f, 0.0f); + altCursor = new Path(); + altCursor.moveTo(0.0f, 1.0f); + altCursor.lineTo(0.5f, 0.66f); + altCursor.lineTo(1.0f, 1.0f); + ctrlCursor = new Path(); + ctrlCursor.moveTo(0.0f, 0.25f); + ctrlCursor.lineTo(1.0f, 0.5f); + ctrlCursor.lineTo(0.0f, 0.75f); + // For creating the transform when the terminal resizes + tempSrc = new RectF(); + tempSrc.set(0.0f, 0.0f, 1.0f, 1.0f); + tempDst = new RectF(); + scaleMatrix = new Matrix(); + bridge.addFontSizeChangedListener(this); + // connect our view up to the bridge + setOnKeyListener(bridge.getKeyHandler()); + mAccessibilityBuffer = new StringBuffer(); + // Enable accessibility features if a screen reader is active. + new AccessibilityStateTester().execute((Void) null); + mScaleDetector = new ScaleGestureDetector(context, new ScaleListener()); + } + + public void destroy() { + // tell bridge to destroy its bitmap + bridge.parentDestroyed(); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + bridge.parentChanged(this); + scaleCursors(); + } + + public void onFontSizeChanged(float size) { + scaleCursors(); + } + + private void scaleCursors() { + // Create a scale matrix to scale our 1x1 representation of the cursor + tempDst.set(0.0f, 0.0f, bridge.charWidth, bridge.charHeight); + scaleMatrix.setRectToRect(tempSrc, tempDst, scaleType); + } + + @Override + public void onDraw(Canvas canvas) { + if (bridge.bitmap != null) { + // draw the bitmap + bridge.onDraw(); + // draw the bridge bitmap if it exists + canvas.drawBitmap(bridge.bitmap, 0, 0, paint); + + // also draw cursor if visible + if (bridge.buffer.isCursorVisible()) { + int cursorColumn = bridge.buffer.getCursorColumn(); + final int cursorRow = bridge.buffer.getCursorRow(); + final int columns = bridge.buffer.getColumns(); + + if (cursorColumn == columns) + cursorColumn = columns - 1; + + if (cursorColumn < 0 || cursorRow < 0) + return; + + int currentAttribute = bridge.buffer.getAttributes( + cursorColumn, cursorRow); + boolean onWideCharacter = (currentAttribute & VDUBuffer.FULLWIDTH) != 0; + int x = cursorColumn * bridge.charWidth; + int y = (bridge.buffer.getCursorRow() + + bridge.buffer.screenBase - bridge.buffer.windowBase) + * bridge.charHeight; + // Save the current clip and translation + canvas.save(); + canvas.translate(x, y); + canvas.clipRect(0, 0, + bridge.charWidth * (onWideCharacter ? 2 : 1), + bridge.charHeight); + canvas.drawPaint(cursorPaint); + final int deadKey = bridge.getKeyHandler().getDeadKey(); + + if (deadKey != 0) { + canvas.drawText(new char[] { (char)deadKey }, 0, 1, 0, 0, cursorStrokePaint); + } + + // Make sure we scale our decorations to the correct size. + canvas.concat(scaleMatrix); + int metaState = bridge.getKeyHandler().getMetaState(); + + if ((metaState & TerminalKeyListener.META_SHIFT_ON) != 0) + canvas.drawPath(shiftCursor, cursorStrokePaint); + else if ((metaState & TerminalKeyListener.META_SHIFT_LOCK) != 0) + canvas.drawPath(shiftCursor, cursorPaint); + + if ((metaState & TerminalKeyListener.META_ALT_ON) != 0) + canvas.drawPath(altCursor, cursorStrokePaint); + else if ((metaState & TerminalKeyListener.META_ALT_LOCK) != 0) + canvas.drawPath(altCursor, cursorPaint); + + if ((metaState & TerminalKeyListener.META_CTRL_ON) != 0) + canvas.drawPath(ctrlCursor, cursorStrokePaint); + else if ((metaState & TerminalKeyListener.META_CTRL_LOCK) != 0) + canvas.drawPath(ctrlCursor, cursorPaint); + + // Restore previous clip region + canvas.restore(); + } + + // draw any highlighted area + if (bridge.isSelectingForCopy()) { + SelectionArea area = bridge.getSelectionArea(); + canvas.save(Canvas.CLIP_SAVE_FLAG); + canvas.clipRect( + area.getLeft() * bridge.charWidth, + area.getTop() * bridge.charHeight, + (area.getRight() + 1) * bridge.charWidth, + (area.getBottom() + 1) * bridge.charHeight + ); + canvas.drawPaint(cursorPaint); + canvas.restore(); + } + } + } + + public void notifyUser(String message) { + try { + // ignore if don't want notifications + if (!notifications) return; + // Don't keep telling the user the same thing. + if (lastNotification != null && lastNotification.equals(message)) return; + // create or use old toast + if (notification == null) { + notification = Toast.makeText(context, message, Toast.LENGTH_SHORT); + } + else { + notification.setText(message); + } + notification.show(); + lastNotification = message; + } + catch (Exception e) { + Log.e(TAG, "Problem while trying to notify user", e); + } + } + + /** + * Ask the {@link TerminalBridge} we're connected to to resize to a specific size. + * @param width + * @param height + */ + public void forceSize(int width, int height) { + bridge.resizeComputed(width, height, getWidth(), getHeight()); + } + + /** + * Sets the ability for the TerminalView to display Toast notifications to the user. + * @param value whether to enable notifications or not + */ + public void setNotifications(boolean value) { + notifications = value; + } + + @Override + public boolean onCheckIsTextEditor() { + return true; + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + outAttrs.imeOptions |= + EditorInfo.IME_FLAG_NO_EXTRACT_UI | + EditorInfo.IME_FLAG_NO_ENTER_ACTION | + EditorInfo.IME_ACTION_NONE; + outAttrs.inputType = EditorInfo.TYPE_NULL; + return new BaseInputConnection(this, false) { + @Override + public boolean deleteSurroundingText(int leftLength, int rightLength) { + if (rightLength == 0 && leftLength == 0) { + return this.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); + } + + for (int i = 0; i < leftLength; i++) { + this.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); + } + + // TODO: forward delete + return true; + } + }; + } + + public void propagateConsoleText(char[] rawText, int length) { + if (mAccessibilityActive) { + synchronized (mAccessibilityLock) { + mAccessibilityBuffer.append(rawText, 0, length); + } + + if (mAccessibilityInitialized) { + if (mEventSender != null) { + removeCallbacks(mEventSender); + } + else { + mEventSender = new AccessibilityEventSender(); + } + + postDelayed(mEventSender, ACCESSIBILITY_EVENT_THRESHOLD); + } + } + } + + private class AccessibilityEventSender implements Runnable { + public void run() { + synchronized (mAccessibilityLock) { + if (mCodeMatcher == null) { + mCodeMatcher = mControlCodes.matcher(mAccessibilityBuffer); + } + else { + mCodeMatcher.reset(mAccessibilityBuffer); + } + + // Strip all control codes out. + mAccessibilityBuffer = new StringBuffer(mCodeMatcher.replaceAll(" ")); + // Apply Backspaces using backspace character sequence + int i = mAccessibilityBuffer.indexOf(BACKSPACE_CODE); + + while (i != -1) { + mAccessibilityBuffer = mAccessibilityBuffer.replace(i == 0 ? 0 : i - 1, i + + BACKSPACE_CODE.length(), ""); + i = mAccessibilityBuffer.indexOf(BACKSPACE_CODE); + } + + if (mAccessibilityBuffer.length() > 0) { + AccessibilityEvent event = AccessibilityEvent.obtain( + AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); + event.setFromIndex(0); + event.setAddedCount(mAccessibilityBuffer.length()); + event.getText().add(mAccessibilityBuffer); + sendAccessibilityEventUnchecked(event); + mAccessibilityBuffer.setLength(0); + } + } + } + } + + private class AccessibilityStateTester extends AsyncTask { + @Override + protected Boolean doInBackground(Void... params) { + /* + * Presumably if the accessibility manager is not enabled, we don't + * need to send accessibility events. + */ + final AccessibilityManager accessibility = (AccessibilityManager) context + .getSystemService(Context.ACCESSIBILITY_SERVICE); + + if (!accessibility.isEnabled()) { + return false; + } + + /* + * Restrict the set of intents to only accessibility services that + * have the category FEEDBACK_SPOKEN (aka, screen readers). + */ + final Intent screenReaderIntent = new Intent(SCREENREADER_INTENT_ACTION); + screenReaderIntent.addCategory(SCREENREADER_INTENT_CATEGORY); + final ContentResolver cr = context.getContentResolver(); + final List screenReaders = context.getPackageManager().queryIntentServices( + screenReaderIntent, 0); + boolean foundScreenReader = false; + final int N = screenReaders.size(); + + for (int i = 0; i < N; i++) { + final ResolveInfo screenReader = screenReaders.get(i); + /* + * All screen readers are expected to implement a content + * provider that responds to: + * content://.providers.StatusProvider + */ + final Cursor cursor = cr.query( + Uri.parse("content://" + screenReader.serviceInfo.packageName + + ".providers.StatusProvider"), null, null, null, null); + + if (cursor != null && cursor.moveToFirst()) { + /* + * These content providers use a special cursor that only has + * one element, an integer that is 1 if the screen reader is + * running. + */ + final int status = cursor.getInt(0); + cursor.close(); + + if (status == 1) { + foundScreenReader = true; + break; + } + } + } + + if (foundScreenReader) { + mControlCodes = Pattern.compile(CONTROL_CODE_PATTERN); + } + + return foundScreenReader; + } + + @Override + protected void onPostExecute(Boolean result) { + mAccessibilityActive = result; + mAccessibilityInitialized = true; + + if (result) { + mEventSender = new AccessibilityEventSender(); + postDelayed(mEventSender, ACCESSIBILITY_EVENT_THRESHOLD); + } + else { + mAccessibilityBuffer = null; + } + } + } + + private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { + @Override + public boolean onScale(ScaleGestureDetector detector) { + float mScaleFactor = detector.getScaleFactor(); + + if (mScaleFactor > 1.1) { + bridge.increaseFontSize(); + return true; + } + else if (mScaleFactor < 0.9) { + bridge.decreaseFontSize(); + return true; + } + + return (false); + } + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/WizardActivity.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/WizardActivity.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,105 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot; + +import com.five_ten_sg.connectbot.util.HelpTopicView; +import android.app.Activity; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.TextView; +import android.widget.ViewFlipper; + +/** + * Show a series of wizard-like steps to the user, which might include an EULA, + * program credits, and helpful hints. + * + * @author jsharkey + */ +public class WizardActivity extends Activity { + protected ViewFlipper flipper = null; + private Button next, prev; + private static final String TAG = "ConnectBot.WizardActivity"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.act_wizard); + this.flipper = (ViewFlipper) findViewById(R.id.wizard_flipper); + // inflate the layout for EULA step + LayoutInflater inflater = LayoutInflater.from(this); + View helpView = inflater.inflate(R.layout.wiz_eula, this.flipper, false); + this.flipper.addView(helpView); + // Add a view for each help topic we want the user to see. + String[] topics = getResources().getStringArray(R.array.list_wizard_topics); + + for (String topic : topics) { + flipper.addView(new HelpTopicView(this).setTopic(topic)); + } + + next = (Button)this.findViewById(R.id.action_next); + next.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + if (isLastDisplayed()) { + // user walked past end of wizard, so return okay + WizardActivity.this.setResult(Activity.RESULT_OK); + WizardActivity.this.finish(); + } + else { + // show next step and update buttons + flipper.showNext(); + updateButtons(); + } + } + }); + prev = (Button)this.findViewById(R.id.action_prev); + prev.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + if (isFirstDisplayed()) { + // user walked past beginning of wizard, so return that they cancelled + WizardActivity.this.setResult(Activity.RESULT_CANCELED); + WizardActivity.this.finish(); + } + else { + // show previous step and update buttons + flipper.showPrevious(); + updateButtons(); + } + } + }); + this.updateButtons(); + } + + protected boolean isFirstDisplayed() { + return (flipper.getDisplayedChild() == 0); + } + + protected boolean isLastDisplayed() { + return (flipper.getDisplayedChild() == flipper.getChildCount() - 1); + } + + protected void updateButtons() { + boolean eula = (flipper.getDisplayedChild() == 0); + next.setText(eula ? getString(R.string.wizard_agree) : getString(R.string.wizard_next)); + prev.setText(eula ? getString(R.string.delete_neg) : getString(R.string.wizard_back)); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/bean/AbstractBean.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/bean/AbstractBean.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,48 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.bean; + +import java.util.Map.Entry; + +import com.five_ten_sg.connectbot.util.XmlBuilder; +import android.content.ContentValues; + +/** + * @author Kenny Root + * + */ +abstract class AbstractBean { + public abstract ContentValues getValues(); + public abstract String getBeanName(); + + public String toXML() { + XmlBuilder xml = new XmlBuilder(); + xml.append(String.format("<%s>", getBeanName())); + ContentValues values = getValues(); + + for (Entry entry : values.valueSet()) { + Object value = entry.getValue(); + + if (value != null) + xml.append(entry.getKey(), value); + } + + xml.append(String.format("", getBeanName())); + return xml.toString(); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/bean/HostBean.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/bean/HostBean.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,454 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.bean; + +import com.five_ten_sg.connectbot.util.HostDatabase; +import android.content.ContentValues; +import android.net.Uri; + +/** + * @author Kenny Root + * + */ +public class HostBean extends AbstractBean { + public static final String BEAN_NAME = "host"; + + /* Database fields */ + private long id = -1; + private String nickname = null; + private String username = null; + private String hostname = null; + private int port = 22; + private String protocol = "ssh"; + private String hostKeyAlgo = null; + private byte[] hostKey = null; + private long lastConnect = -1; + private String color; + private boolean useKeys = true; + private String useAuthAgent = HostDatabase.AUTHAGENT_NO; + private String postLogin = null; + private long pubkeyId = -1; + private String delKey = HostDatabase.DELKEY_DEL; + private float fontSize = -1; + private boolean fixedSize = false; + private int fixedWidth = 80; + private int fixedHeight = 25; + private boolean wantSession = true; + private boolean compression = false; + private String httpproxy = null; + private String encoding = HostDatabase.ENCODING_DEFAULT; + private boolean stayConnected = false; + private boolean wantX11Forward = false; + private String x11Host = "localhost"; + private int x11Port = 6000; + private String monitor = null; + private String hostemulation = null; + private String encryption5250 = null; + private String library = null; + private String initialMenu = null; + private String program = null; + + public HostBean() { + } + + @Override + public String getBeanName() { + return BEAN_NAME; + } + + public HostBean(String nickname, String protocol, String username, String hostname, int port) { + this.nickname = nickname; + this.protocol = protocol; + this.username = username; + this.hostname = hostname; + this.port = port; + } + + public boolean isSSH() { + return protocol.equals("ssh"); + } + + public boolean is5250() { + return protocol.equals("tn5250"); + } + + public boolean isTelnet() { + return protocol.equals("telnet"); + } + + public boolean isAsync() { + return isSSH() || isTelnet(); + } + + public void setId(long id) { + this.id = id; + } + public long getId() { + return id; + } + public void setNickname(String nickname) { + this.nickname = nickname; + } + public String getNickname() { + return nickname; + } + public void setUsername(String username) { + this.username = username; + } + public String getUsername() { + return username; + } + public void setHostname(String hostname) { + this.hostname = hostname; + } + public String getHostname() { + return hostname; + } + public void setPort(int port) { + this.port = port; + } + public int getPort() { + return port; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public String getProtocol() { + return protocol; + } + + public void setHostKeyAlgo(String hostKeyAlgo) { + this.hostKeyAlgo = hostKeyAlgo; + } + public String getHostKeyAlgo() { + return hostKeyAlgo; + } + public void setHostKey(byte[] hostKey) { + if (hostKey == null) + this.hostKey = null; + else + this.hostKey = hostKey.clone(); + } + public byte[] getHostKey() { + if (hostKey == null) + return null; + else + return hostKey.clone(); + } + public void setLastConnect(long lastConnect) { + this.lastConnect = lastConnect; + } + public long getLastConnect() { + return lastConnect; + } + public void setColor(String color) { + this.color = color; + } + public String getColor() { + return color; + } + public void setUseKeys(boolean useKeys) { + this.useKeys = useKeys; + } + public boolean getUseKeys() { + return useKeys; + } + public void setUseAuthAgent(String useAuthAgent) { + this.useAuthAgent = useAuthAgent; + } + public String getUseAuthAgent() { + return useAuthAgent; + } + public void setPostLogin(String postLogin) { + this.postLogin = postLogin; + } + public String getPostLogin() { + return postLogin; + } + public void setPubkeyId(long pubkeyId) { + this.pubkeyId = pubkeyId; + } + public long getPubkeyId() { + return pubkeyId; + } + public void setWantSession(boolean wantSession) { + this.wantSession = wantSession; + } + public boolean getWantSession() { + return wantSession; + } + public void setDelKey(String delKey) { + this.delKey = delKey; + } + public String getDelKey() { + return delKey; + } + public void setFontSize(float fontSize) { + this.fontSize = fontSize; + } + public float getFontSize() { + return fontSize; + } + public void setFixedSize(boolean fixedSize) { + this.fixedSize = fixedSize; + } + public boolean getFixedSize() { + return fixedSize; + } + public void setFixedWidth(int fixedWidth) { + this.fixedWidth = fixedWidth; + } + public int getFixedWidth() { + return fixedWidth; + } + public void setFixedHeight(int fixedHeight) { + this.fixedHeight = fixedHeight; + } + public int getFixedHeight() { + return fixedHeight; + } + public void setCompression(boolean compression) { + this.compression = compression; + } + public boolean getCompression() { + return compression; + } + public void setHttpproxy(String httpproxy) { + this.httpproxy = httpproxy; + } + public String getHttpproxy() { + return httpproxy; + } + + public void setEncoding(String encoding) { + this.encoding = encoding; + } + + public String getEncoding() { + return this.encoding; + } + + public void setStayConnected(boolean stayConnected) { + this.stayConnected = stayConnected; + } + + public boolean getStayConnected() { + return stayConnected; + } + + public String getDescription() { + String description = String.format("%s@%s", username, hostname); + + if (port != 22) + description += String.format(":%d", port); + + return description; + } + + public boolean getWantX11Forward() { + return this.wantX11Forward; + } + + public void setWantX11Forward(boolean wantX11Forward) { + this.wantX11Forward = wantX11Forward; + } + + public String getX11Host() { + return this.x11Host; + } + + public void setX11Host(String x11Host) { + this.x11Host = x11Host; + } + + public int getX11Port() { + return this.x11Port; + } + + public void setX11Port(int x11Port) { + this.x11Port = x11Port; + } + + public String getMonitor() { + return this.monitor; + } + + public void setMonitor(String monitor) { + this.monitor = monitor; + } + + public String getHostEmulation() { + return this.hostemulation; + } + + public void setHostEmulation(String hostemulation) { + this.hostemulation = hostemulation; + } + + public String getEncryption5250() { + return this.encryption5250; + } + + public void setEncryption5250(String encryption5250) { + this.encryption5250 = encryption5250; + } + + public String getLibrary() { + return this.library; + } + + public void setLibrary(String library) { + this.library = library; + } + + public String getInitialMenu() { + return this.initialMenu; + } + + public void setInitialMenu(String initialMenu) { + this.initialMenu = initialMenu; + } + + public String getProgram() { + return this.program; + } + + public void setProgram(String program) { + this.program = program; + } + + @Override + public ContentValues getValues() { + ContentValues values = new ContentValues(); + values.put(HostDatabase.FIELD_HOST_NICKNAME, nickname); + values.put(HostDatabase.FIELD_HOST_PROTOCOL, protocol); + values.put(HostDatabase.FIELD_HOST_USERNAME, username); + values.put(HostDatabase.FIELD_HOST_HOSTNAME, hostname); + values.put(HostDatabase.FIELD_HOST_PORT, port); + values.put(HostDatabase.FIELD_HOST_HOSTKEYALGO, hostKeyAlgo); + values.put(HostDatabase.FIELD_HOST_HOSTKEY, hostKey); + values.put(HostDatabase.FIELD_HOST_LASTCONNECT, lastConnect); + values.put(HostDatabase.FIELD_HOST_COLOR, color); + values.put(HostDatabase.FIELD_HOST_USEKEYS, Boolean.toString(useKeys)); + values.put(HostDatabase.FIELD_HOST_USEAUTHAGENT, useAuthAgent); + values.put(HostDatabase.FIELD_HOST_POSTLOGIN, postLogin); + values.put(HostDatabase.FIELD_HOST_PUBKEYID, pubkeyId); + values.put(HostDatabase.FIELD_HOST_WANTSESSION, Boolean.toString(wantSession)); + values.put(HostDatabase.FIELD_HOST_DELKEY, delKey); + values.put(HostDatabase.FIELD_HOST_FONTSIZE, fontSize); + values.put(HostDatabase.FIELD_HOST_FIXEDSIZE, Boolean.toString(fixedSize)); + values.put(HostDatabase.FIELD_HOST_FIXEDWIDTH, fixedWidth); + values.put(HostDatabase.FIELD_HOST_FIXEDHEIGHT, fixedHeight); + values.put(HostDatabase.FIELD_HOST_COMPRESSION, Boolean.toString(compression)); + values.put(HostDatabase.FIELD_HOST_HTTPPROXY, httpproxy); + values.put(HostDatabase.FIELD_HOST_ENCODING, encoding); + values.put(HostDatabase.FIELD_HOST_STAYCONNECTED, stayConnected); + values.put(HostDatabase.FIELD_HOST_WANTX11FORWARD, wantX11Forward); + values.put(HostDatabase.FIELD_HOST_X11HOST, x11Host); + values.put(HostDatabase.FIELD_HOST_X11PORT, x11Port); + values.put(HostDatabase.FIELD_HOST_MONITOR, monitor); + values.put(HostDatabase.FIELD_HOST_EMULATION, hostemulation); + values.put(HostDatabase.FIELD_HOST_ENCRYPTION5250, encryption5250); + values.put(HostDatabase.FIELD_HOST_LIBRARY5250, library); + values.put(HostDatabase.FIELD_HOST_MENU5250, initialMenu); + values.put(HostDatabase.FIELD_HOST_PROGRAM5250, program); + return values; + } + + @Override + public boolean equals(Object o) { + if (o == null || !(o instanceof HostBean)) + return false; + + HostBean host = (HostBean)o; + + if (id != -1 && host.getId() != -1) + return host.getId() == id; + + if (nickname == null) { + if (host.getNickname() != null) + return false; + } + else if (!nickname.equals(host.getNickname())) + return false; + + if (protocol == null) { + if (host.getProtocol() != null) + return false; + } + else if (!protocol.equals(host.getProtocol())) + return false; + + if (username == null) { + if (host.getUsername() != null) + return false; + } + else if (!username.equals(host.getUsername())) + return false; + + if (hostname == null) { + if (host.getHostname() != null) + return false; + } + else if (!hostname.equals(host.getHostname())) + return false; + + if (port != host.getPort()) + return false; + + return true; + } + + @Override + public int hashCode() { + int hash = 7; + + if (id != -1) + return (int)id; + + hash = 31 * hash + (null == nickname ? 0 : nickname.hashCode()); + hash = 31 * hash + (null == protocol ? 0 : protocol.hashCode()); + hash = 31 * hash + (null == username ? 0 : username.hashCode()); + hash = 31 * hash + (null == hostname ? 0 : hostname.hashCode()); + hash = 31 * hash + port; + return hash; + } + + /** + * @return URI identifying this HostBean + */ + public Uri getUri() { + StringBuilder sb = new StringBuilder(); + sb.append(protocol) + .append("://"); + + if (username != null) + sb.append(Uri.encode(username)) + .append('@'); + + sb.append(Uri.encode(hostname)) + .append(':') + .append(port) + .append("/#") + .append(nickname); + return Uri.parse(sb.toString()); + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/bean/PortForwardBean.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/bean/PortForwardBean.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,240 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.bean; + +import com.five_ten_sg.connectbot.util.HostDatabase; +import android.content.ContentValues; + + +/** + * @author Kenny Root + * + */ +public class PortForwardBean extends AbstractBean { + public static final String BEAN_NAME = "portforward"; + + /* Database fields */ + private long id = -1; + private long hostId = -1; + private String nickname = null; + private String type = null; + private int sourcePort = -1; + private String destAddr = null; + private int destPort = -1; + + /* Transient values */ + private boolean enabled = false; + private Object identifier = null; + + /** + * @param id database ID of port forward + * @param nickname Nickname to use to identify port forward + * @param type One of the port forward types from {@link HostDatabase} + * @param sourcePort Source port number + * @param destAddr Destination hostname or IP address + * @param destPort Destination port number + */ + public PortForwardBean(long id, long hostId, String nickname, String type, int sourcePort, String destAddr, int destPort) { + this.id = id; + this.hostId = hostId; + this.nickname = nickname; + this.type = type; + this.sourcePort = sourcePort; + this.destAddr = destAddr; + this.destPort = destPort; + } + + /** + * @param type One of the port forward types from {@link HostDatabase} + * @param source Source port number + * @param dest Destination is "host:port" format + */ + public PortForwardBean(long hostId, String nickname, String type, String source, String dest) { + this.hostId = hostId; + this.nickname = nickname; + this.type = type; + this.sourcePort = Integer.parseInt(source); + setDest(dest); + } + + @Override + public String getBeanName() { + return BEAN_NAME; + } + + /** + * @param id the id to set + */ + public void setId(long id) { + this.id = id; + } + + /** + * @return the id + */ + public long getId() { + return id; + } + + /** + * @param nickname the nickname to set + */ + public void setNickname(String nickname) { + this.nickname = nickname; + } + + /** + * @return the nickname + */ + public String getNickname() { + return nickname; + } + + /** + * @param type the type to set + */ + public void setType(String type) { + this.type = type; + } + + /** + * @return the type + */ + public String getType() { + return type; + } + + /** + * @param sourcePort the sourcePort to set + */ + public void setSourcePort(int sourcePort) { + this.sourcePort = sourcePort; + } + + /** + * @return the sourcePort + */ + public int getSourcePort() { + return sourcePort; + } + + /** + * @param dest The destination in "host:port" format + */ + public final void setDest(String dest) { + String[] destSplit = dest.split(":"); + this.destAddr = destSplit[0]; + + if (destSplit.length > 1) + this.destPort = Integer.parseInt(destSplit[1]); + } + + /** + * @param destAddr the destAddr to set + */ + public void setDestAddr(String destAddr) { + this.destAddr = destAddr; + } + + /** + * @return the destAddr + */ + public String getDestAddr() { + return destAddr; + } + + /** + * @param destPort the destPort to set + */ + public void setDestPort(int destPort) { + this.destPort = destPort; + } + + /** + * @return the destPort + */ + public int getDestPort() { + return destPort; + } + + /** + * @param enabled the enabled to set + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + /** + * @return the enabled + */ + public boolean isEnabled() { + return enabled; + } + + /** + * @param identifier the identifier of this particular type to set + */ + public void setIdentifier(Object identifier) { + this.identifier = identifier; + } + + /** + * @return the identifier used by this particular type + */ + public Object getIdentifier() { + return identifier; + } + + /** + * @return human readable description of the port forward + */ + public CharSequence getDescription() { + String description = "Unknown type"; + + if (HostDatabase.PORTFORWARD_LOCAL.equals(type)) { + description = String.format("Local port %d to %s:%d", sourcePort, destAddr, destPort); + } + else if (HostDatabase.PORTFORWARD_REMOTE.equals(type)) { + description = String.format("Remote port %d to %s:%d", sourcePort, destAddr, destPort); + /* I don't think we need the SOCKS4 type. + } else if (HostDatabase.PORTFORWARD_DYNAMIC4.equals(type)) { + description = String.format("Dynamic port %d (SOCKS4)", sourcePort); + */ + } + else if (HostDatabase.PORTFORWARD_DYNAMIC5.equals(type)) { + description = String.format("Dynamic port %d (SOCKS)", sourcePort); + } + + return description; + } + + /** + * @return + */ + @Override + public ContentValues getValues() { + ContentValues values = new ContentValues(); + values.put(HostDatabase.FIELD_PORTFORWARD_HOSTID, hostId); + values.put(HostDatabase.FIELD_PORTFORWARD_NICKNAME, nickname); + values.put(HostDatabase.FIELD_PORTFORWARD_TYPE, type); + values.put(HostDatabase.FIELD_PORTFORWARD_SOURCEPORT, sourcePort); + values.put(HostDatabase.FIELD_PORTFORWARD_DESTADDR, destAddr); + values.put(HostDatabase.FIELD_PORTFORWARD_DESTPORT, destPort); + return values; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/bean/PubkeyBean.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/bean/PubkeyBean.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,232 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.bean; + +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; + +import com.five_ten_sg.connectbot.util.PubkeyDatabase; +import com.five_ten_sg.connectbot.util.PubkeyUtils; +import android.content.ContentValues; + +/** + * @author Kenny Root + * + */ +public class PubkeyBean extends AbstractBean { + public static final String BEAN_NAME = "pubkey"; + + /* Database fields */ + private long id; + private String nickname; + private String type; + private byte[] privateKey; + private byte[] publicKey; + private boolean encrypted = false; + private boolean startup = false; + private boolean confirmUse = false; + private int lifetime = 0; + + /* Transient values */ + private transient boolean unlocked = false; + private transient Object unlockedPrivate = null; + private transient String description; + + @Override + public String getBeanName() { + return BEAN_NAME; + } + + public void setId(long id) { + this.id = id; + } + + public long getId() { + return id; + } + + public void setNickname(String nickname) { + this.nickname = nickname; + } + + public String getNickname() { + return nickname; + } + + public void setType(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public void setPrivateKey(byte[] privateKey) { + if (privateKey == null) + this.privateKey = null; + else + this.privateKey = privateKey.clone(); + } + + public byte[] getPrivateKey() { + if (privateKey == null) + return null; + else + return privateKey.clone(); + } + + public void setPublicKey(byte[] encoded) { + if (encoded == null) + publicKey = null; + else + publicKey = encoded.clone(); + } + + public byte[] getPublicKey() { + if (publicKey == null) + return null; + else + return publicKey.clone(); + } + + public void setEncrypted(boolean encrypted) { + this.encrypted = encrypted; + } + + public boolean isEncrypted() { + return encrypted; + } + + public void setStartup(boolean startup) { + this.startup = startup; + } + + public boolean isStartup() { + return startup; + } + + public void setConfirmUse(boolean confirmUse) { + this.confirmUse = confirmUse; + } + + public boolean isConfirmUse() { + return confirmUse; + } + + public void setLifetime(int lifetime) { + this.lifetime = lifetime; + } + + public int getLifetime() { + return lifetime; + } + + public void setUnlocked(boolean unlocked) { + this.unlocked = unlocked; + } + + public boolean isUnlocked() { + return unlocked; + } + + public void setUnlockedPrivate(Object unlockedPrivate) { + this.unlockedPrivate = unlockedPrivate; + } + + public Object getUnlockedPrivate() { + return unlockedPrivate; + } + + public String getDescription() { + if (description == null) { + final StringBuilder sb = new StringBuilder(); + + try { + final PublicKey pubKey = PubkeyUtils.decodePublic(publicKey, type); + + if (PubkeyDatabase.KEY_TYPE_RSA.equals(type)) { + int bits = ((RSAPublicKey) pubKey).getModulus().bitLength(); + sb.append("RSA "); + sb.append(bits); + sb.append("-bit"); + } + else if (PubkeyDatabase.KEY_TYPE_DSA.equals(type)) { + sb.append("DSA 1024-bit"); + } + else if (PubkeyDatabase.KEY_TYPE_EC.equals(type)) { + int bits = ((ECPublicKey) pubKey).getParams().getCurve().getField() + .getFieldSize(); + sb.append("EC "); + sb.append(bits); + sb.append("-bit"); + } + else { + sb.append("Unknown Key Type"); + } + } + catch (NoSuchAlgorithmException e) { + sb.append("Unknown Key Type"); + } + catch (InvalidKeySpecException e) { + sb.append("Unknown Key Type"); + } + + if (encrypted) sb.append(" (encrypted)"); + + description = sb.toString(); + } + + return description; + } + + /* (non-Javadoc) + * @see com.five_ten_sg.connectbot.bean.AbstractBean#getValues() + */ + @Override + public ContentValues getValues() { + ContentValues values = new ContentValues(); + values.put(PubkeyDatabase.FIELD_PUBKEY_NICKNAME, nickname); + values.put(PubkeyDatabase.FIELD_PUBKEY_TYPE, type); + values.put(PubkeyDatabase.FIELD_PUBKEY_PRIVATE, privateKey); + values.put(PubkeyDatabase.FIELD_PUBKEY_PUBLIC, publicKey); + values.put(PubkeyDatabase.FIELD_PUBKEY_ENCRYPTED, encrypted ? 1 : 0); + values.put(PubkeyDatabase.FIELD_PUBKEY_STARTUP, startup ? 1 : 0); + values.put(PubkeyDatabase.FIELD_PUBKEY_CONFIRMUSE, confirmUse ? 1 : 0); + values.put(PubkeyDatabase.FIELD_PUBKEY_LIFETIME, lifetime); + return values; + } + + public boolean changePassword(String oldPassword, String newPassword) throws Exception { + PrivateKey priv; + + try { + priv = PubkeyUtils.decodePrivate(getPrivateKey(), getType(), oldPassword); + } + catch (Exception e) { + return false; + } + + setPrivateKey(PubkeyUtils.getEncodedPrivate(priv, newPassword)); + setEncrypted(newPassword.length() > 0); + return true; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/bean/SelectionArea.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/bean/SelectionArea.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,198 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.bean; + +import de.mud.terminal.VDUBuffer; + +/** + * @author Kenny Root + * Keep track of a selection area for the terminal copying mechanism. + * If the orientation is flipped one way, swap the bottom and top or + * left and right to keep it in the correct orientation. + */ +public class SelectionArea { + private int top; + private int bottom; + private int left; + private int right; + private int maxColumns; + private int maxRows; + private boolean selectingOrigin; + + public SelectionArea() { + reset(); + } + + public final void reset() { + top = left = bottom = right = 0; + selectingOrigin = true; + } + + /** + * @param columns + * @param rows + */ + public void setBounds(int columns, int rows) { + maxColumns = columns - 1; + maxRows = rows - 1; + } + + private int checkBounds(int value, int max) { + if (value < 0) + return 0; + else if (value > max) + return max; + else + return value; + } + + public boolean isSelectingOrigin() { + return selectingOrigin; + } + + public void finishSelectingOrigin() { + selectingOrigin = false; + } + + public void decrementRow() { + if (selectingOrigin) + setTop(top - 1); + else + setBottom(bottom - 1); + } + + public void incrementRow() { + if (selectingOrigin) + setTop(top + 1); + else + setBottom(bottom + 1); + } + + public void setRow(int row) { + if (selectingOrigin) + setTop(row); + else + setBottom(row); + } + + private void setTop(int top) { + this.top = bottom = checkBounds(top, maxRows); + } + + public int getTop() { + return Math.min(top, bottom); + } + + private void setBottom(int bottom) { + this.bottom = checkBounds(bottom, maxRows); + } + + public int getBottom() { + return Math.max(top, bottom); + } + + public void decrementColumn() { + if (selectingOrigin) + setLeft(left - 1); + else + setRight(right - 1); + } + + public void incrementColumn() { + if (selectingOrigin) + setLeft(left + 1); + else + setRight(right + 1); + } + + public void setColumn(int column) { + if (selectingOrigin) + setLeft(column); + else + setRight(column); + } + + private void setLeft(int left) { + this.left = right = checkBounds(left, maxColumns); + } + + public int getLeft() { + return Math.min(left, right); + } + + private void setRight(int right) { + this.right = checkBounds(right, maxColumns); + } + + public int getRight() { + return Math.max(left, right); + } + + public String copyFrom(VDUBuffer vb) { + int size = (getRight() - getLeft() + 1) * (getBottom() - getTop() + 1); + StringBuffer buffer = new StringBuffer(size); + + for (int y = getTop(); y <= getBottom(); y++) { + int lastNonSpace = buffer.length(); + + for (int x = getLeft(); x <= getRight(); x++) { + // only copy printable chars + char c = vb.getChar(x, y); + + if (!Character.isDefined(c) || + (Character.isISOControl(c) && c != '\t')) + c = ' '; + + if (c != ' ') + lastNonSpace = buffer.length(); + + buffer.append(c); + } + + // Don't leave a bunch of spaces in our copy buffer. + if (buffer.length() > lastNonSpace) + buffer.delete(lastNonSpace + 1, buffer.length()); + + if (y != bottom) + buffer.append("\n"); + } + + return buffer.toString(); + } + + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(); + buffer.append("SelectionArea[top="); + buffer.append(top); + buffer.append(", bottom="); + buffer.append(bottom); + buffer.append(", left="); + buffer.append(left); + buffer.append(", right="); + buffer.append(right); + buffer.append(", maxColumns="); + buffer.append(maxColumns); + buffer.append(", maxRows="); + buffer.append(maxRows); + buffer.append(", isSelectingOrigin="); + buffer.append(isSelectingOrigin()); + buffer.append("]"); + return buffer.toString(); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/service/AuthAgentService.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/service/AuthAgentService.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,193 @@ +package com.five_ten_sg.connectbot.service; + +import java.io.IOException; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import com.five_ten_sg.connectbot.service.TerminalManager; +import com.five_ten_sg.connectbot.service.TerminalManager.KeyHolder; +import android.app.Service; +import android.content.ComponentName; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import com.madgag.ssh.android.authagent.AndroidAuthAgent; +import ch.ethz.ssh2.crypto.SecureRandomFix; +import ch.ethz.ssh2.signature.DSASHA1Verify; +import ch.ethz.ssh2.signature.ECDSASHA2Verify; +import ch.ethz.ssh2.signature.RSASHA1Verify; + + +public class AuthAgentService extends Service { + private static final String TAG = "ConnectBot.AuthAgentService"; + protected TerminalManager manager; + final Lock lock = new ReentrantLock(); + final Condition managerReady = lock.newCondition(); + + private ServiceConnection connection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + Log.d(TAG, "Terminal manager available! Hurrah"); + manager = ((TerminalManager.TerminalBinder) service).getService(); + lock.lock(); + + try { + managerReady.signal(); + } + finally { + lock.unlock(); + } + } + public void onServiceDisconnected(ComponentName className) { + manager = null; + Log.d(TAG, "Terminal manager gone..."); + } + }; + + @Override + public IBinder onBind(Intent intent) { + Log.d(TAG, "onBind() called"); + bindService(new Intent(this, TerminalManager.class), connection, BIND_AUTO_CREATE); + return mBinder; + } + + private final AndroidAuthAgent.Stub mBinder = new AndroidAuthAgent.Stub() { + public Map getIdentities() throws RemoteException { + Log.d(TAG, "getIdentities() called"); + waitForTerminalManager(); + Log.d(TAG, "getIdentities() manager.loadedKeypairs : " + manager.loadedKeypairs); + return sshEncodedPubKeysFrom(manager.loadedKeypairs); + } + public byte[] sign(byte[] publicKey, byte[] data) throws RemoteException { + Log.d(TAG, "sign() called"); + waitForTerminalManager(); + KeyPair pair = keyPairFor(publicKey); + Log.d(TAG, "sign() - signing keypair found : " + pair); + + if (pair == null) { + return null; + } + + PrivateKey privKey = pair.getPrivate(); + + if (privKey instanceof RSAPrivateKey) { + return sshEncodedSignatureFor(data, (RSAPrivateKey) privKey); + } + else if (privKey instanceof DSAPrivateKey) { + return sshEncodedSignatureFor(data, (DSAPrivateKey) privKey); + } + else if (privKey instanceof ECPrivateKey) { + return sshEncodedSignatureFor(data, (ECPrivateKey) privKey); + } + + return null; + } + private void waitForTerminalManager() throws RemoteException { + lock.lock(); + + try { + while (manager == null) { + Log.d(TAG, "Waiting for TerminalManager..."); + managerReady.await(); + } + } + catch (InterruptedException e) { + throw new RemoteException(); + } + finally { + lock.unlock(); + } + + Log.d(TAG, "Got TerminalManager : " + manager); + } + private Map sshEncodedPubKeysFrom(Map keypairs) { + Map pubkeys = new HashMap (keypairs.size()); + + for (Entry entry : keypairs.entrySet()) { + byte[] encodedKey = sshEncodedPubKeyFrom(entry.getValue().pair); + + if (encodedKey != null) { + pubkeys.put(entry.getKey(), encodedKey); + } + } + + return pubkeys; + } + private byte[] sshEncodedPubKeyFrom(KeyPair pair) { + try { + PrivateKey privKey = pair.getPrivate(); + + if (privKey instanceof RSAPrivateKey) { + RSAPublicKey pubkey = (RSAPublicKey)pair.getPublic(); + return RSASHA1Verify.encodeSSHRSAPublicKey(pubkey); + } + else if (privKey instanceof DSAPrivateKey) { + DSAPublicKey pubkey = (DSAPublicKey)pair.getPublic(); + return DSASHA1Verify.encodeSSHDSAPublicKey(pubkey); + } + else if (privKey instanceof ECPrivateKey) { + ECPublicKey pubkey = (ECPublicKey) pair.getPublic(); + return ECDSASHA2Verify.encodeSSHECDSAPublicKey(pubkey); + } + } + catch (IOException e) { + Log.e(TAG, "Couldn't encode " + pair, e); + } + + return null; + } + private byte[] sshEncodedSignatureFor(byte[] data, RSAPrivateKey privKey) { + try { + byte[] signature = RSASHA1Verify.generateSignature(data, privKey); + return RSASHA1Verify.encodeSSHRSASignature(signature); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + private byte[] sshEncodedSignatureFor(byte[] data, DSAPrivateKey privKey) { + try { + byte[] signature = DSASHA1Verify.generateSignature(data, privKey, new SecureRandomFix()); + return DSASHA1Verify.encodeSSHDSASignature(signature); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + private byte[] sshEncodedSignatureFor(byte[] data, ECPrivateKey privKey) { + try { + byte[] signature = ECDSASHA2Verify.generateSignature(data, privKey); + return ECDSASHA2Verify.encodeSSHECDSASignature(signature, privKey.getParams()); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + private KeyPair keyPairFor(byte[] publicKey) { + String nickname = manager.getKeyNickname(publicKey); + + if (nickname == null) { + Log.w(TAG, "No key-pair found for public-key."); + return null; + } + + // check manager.loadedKeypairs.get(nickname).bean.isConfirmUse() and promptForPubkeyUse(nickname) ? + return manager.getKey(nickname); + } + }; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/service/BackupAgent.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/service/BackupAgent.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,67 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2010 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.service; + +import java.io.IOException; + +import com.five_ten_sg.connectbot.util.HostDatabase; +import com.five_ten_sg.connectbot.util.PreferenceConstants; +import com.five_ten_sg.connectbot.util.PubkeyDatabase; +import android.app.backup.BackupAgentHelper; +import android.app.backup.BackupDataInput; +import android.app.backup.BackupDataOutput; +import android.app.backup.FileBackupHelper; +import android.app.backup.SharedPreferencesBackupHelper; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +/** + * @author kroot + * + */ +public class BackupAgent extends BackupAgentHelper { + @Override + public void onCreate() { + Log.d("ConnectBot.BackupAgent", "onCreate called"); + SharedPreferencesBackupHelper prefs = new SharedPreferencesBackupHelper(this, getPackageName() + "_preferences"); + addHelper(PreferenceConstants.BACKUP_PREF_KEY, prefs); + FileBackupHelper hosts = new FileBackupHelper(this, "../databases/" + HostDatabase.DB_NAME); + addHelper(HostDatabase.DB_NAME, hosts); + FileBackupHelper pubkeys = new FileBackupHelper(this, "../databases/" + PubkeyDatabase.DB_NAME); + addHelper(PubkeyDatabase.DB_NAME, pubkeys); + } + + @Override + public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, + ParcelFileDescriptor newState) throws IOException { + synchronized (HostDatabase.dbLock) { + super.onBackup(oldState, data, newState); + } + } + + @Override + public void onRestore(BackupDataInput data, int appVersionCode, + ParcelFileDescriptor newState) throws IOException { + Log.d("ConnectBot.BackupAgent", "onRestore called"); + + synchronized (HostDatabase.dbLock) { + Log.d("ConnectBot.BackupAgent", "onRestore in-lock"); + super.onRestore(data, appVersionCode, newState); + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/service/BridgeDisconnectedListener.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/service/BridgeDisconnectedListener.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,22 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.service; + +public interface BridgeDisconnectedListener { + public void onDisconnected(TerminalBridge bridge); +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/service/ConnectionNotifier.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/service/ConnectionNotifier.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,113 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2010 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.service; + +import com.five_ten_sg.connectbot.ConsoleActivity; +import com.five_ten_sg.connectbot.R; +import com.five_ten_sg.connectbot.bean.HostBean; +import com.five_ten_sg.connectbot.util.HostDatabase; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.Color; + +/** + * @author Kenny Root + * + * Based on the concept from jasta's blog post. + */ +public class ConnectionNotifier { + private static final int ONLINE_NOTIFICATION = 1; + private static final int ACTIVITY_NOTIFICATION = 2; + + protected NotificationManager getNotificationManager(Context context) { + return (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE); + } + + protected Notification newNotification(Context context) { + Notification notification = new Notification(); + notification.icon = R.drawable.notification_icon; + notification.when = System.currentTimeMillis(); + return notification; + } + + protected Notification newActivityNotification(Context context, HostBean host) { + Notification notification = newNotification(context); + Resources res = context.getResources(); + String contentText = res.getString( + R.string.notification_text, host.getNickname()); + Intent notificationIntent = new Intent(context, ConsoleActivity.class); + notificationIntent.setAction(Intent.ACTION_VIEW); + notificationIntent.setData(host.getUri()); + PendingIntent contentIntent = PendingIntent.getActivity(context, 0, + notificationIntent, 0); + notification.setLatestEventInfo(context, res.getString(R.string.app_name), contentText, contentIntent); + notification.flags = Notification.FLAG_AUTO_CANCEL; + notification.flags |= Notification.DEFAULT_LIGHTS; + + if (HostDatabase.COLOR_RED.equals(host.getColor())) + notification.ledARGB = Color.RED; + else if (HostDatabase.COLOR_GREEN.equals(host.getColor())) + notification.ledARGB = Color.GREEN; + else if (HostDatabase.COLOR_BLUE.equals(host.getColor())) + notification.ledARGB = Color.BLUE; + else + notification.ledARGB = Color.WHITE; + + notification.ledOnMS = 300; + notification.ledOffMS = 1000; + notification.flags |= Notification.FLAG_SHOW_LIGHTS; + return notification; + } + + protected Notification newRunningNotification(Context context) { + Notification notification = newNotification(context); + notification.flags = Notification.FLAG_ONGOING_EVENT + | Notification.FLAG_NO_CLEAR; + notification.when = 0; + notification.contentIntent = PendingIntent.getActivity(context, + ONLINE_NOTIFICATION, + new Intent(context, ConsoleActivity.class), 0); + Resources res = context.getResources(); + notification.setLatestEventInfo(context, + res.getString(R.string.app_name), + res.getString(R.string.app_is_running), + notification.contentIntent); + return notification; + } + + public void showActivityNotification(Service context, HostBean host) { + getNotificationManager(context).notify(ACTIVITY_NOTIFICATION, newActivityNotification(context, host)); + } + + public void hideActivityNotification(Service context) { + getNotificationManager(context).cancel(ACTIVITY_NOTIFICATION); + } + + public void showRunningNotification(Service context) { + context.startForeground(ONLINE_NOTIFICATION, newRunningNotification(context)); + } + + public void hideRunningNotification(Service context) { + context.stopForeground(true); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/service/ConnectivityReceiver.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/service/ConnectivityReceiver.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,150 @@ +/** + * + */ +package com.five_ten_sg.connectbot.service; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.NetworkInfo.State; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiManager.WifiLock; +import android.util.Log; + +/** + * @author kroot + * + */ +public class ConnectivityReceiver extends BroadcastReceiver { + private static final String TAG = "ConnectBot.ConnectivityManager"; + + private boolean mIsConnected = false; + + final private TerminalManager mTerminalManager; + + final private WifiLock mWifiLock; + + private int mNetworkRef = 0; + + private boolean mLockingWifi; + + private Object[] mLock = new Object[0]; + + public ConnectivityReceiver(TerminalManager manager, boolean lockingWifi) { + mTerminalManager = manager; + final ConnectivityManager cm = + (ConnectivityManager) manager.getSystemService(Context.CONNECTIVITY_SERVICE); + final WifiManager wm = (WifiManager) manager.getSystemService(Context.WIFI_SERVICE); + mWifiLock = wm.createWifiLock(TAG); + final NetworkInfo info = cm.getActiveNetworkInfo(); + + if (info != null) { + mIsConnected = (info.getState() == State.CONNECTED); + } + + mLockingWifi = lockingWifi; + final IntentFilter filter = new IntentFilter(); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + manager.registerReceiver(this, filter); + } + + /* (non-Javadoc) + * @see android.content.BroadcastReceiver#onReceive(android.content.Context, android.content.Intent) + */ + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + + if (!action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { + Log.w(TAG, "onReceived() called: " + intent); + return; + } + + boolean noConnectivity = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); + boolean isFailover = intent.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, false); + Log.d(TAG, "onReceived() called; noConnectivity? " + noConnectivity + "; isFailover? " + isFailover); + + if (noConnectivity && !isFailover && mIsConnected) { + mIsConnected = false; + mTerminalManager.onConnectivityLost(); + } + else if (!mIsConnected) { + NetworkInfo info = (NetworkInfo) intent.getExtras() + .get(ConnectivityManager.EXTRA_NETWORK_INFO); + + if (mIsConnected = (info.getState() == State.CONNECTED)) { + mTerminalManager.onConnectivityRestored(); + } + } + } + + /** + * + */ + public void cleanup() { + if (mWifiLock.isHeld()) + mWifiLock.release(); + + mTerminalManager.unregisterReceiver(this); + } + + /** + * Increase the number of things using the network. Acquire a Wi-Fi lock + * if necessary. + */ + public void incRef() { + synchronized (mLock) { + mNetworkRef += 1; + acquireWifiLockIfNecessaryLocked(); + } + } + + /** + * Decrease the number of things using the network. Release the Wi-Fi lock + * if necessary. + */ + public void decRef() { + synchronized (mLock) { + mNetworkRef -= 1; + releaseWifiLockIfNecessaryLocked(); + } + } + + /** + * @param mLockingWifi + */ + public void setWantWifiLock(boolean lockingWifi) { + synchronized (mLock) { + mLockingWifi = lockingWifi; + + if (mLockingWifi) { + acquireWifiLockIfNecessaryLocked(); + } + else { + releaseWifiLockIfNecessaryLocked(); + } + } + } + + private void acquireWifiLockIfNecessaryLocked() { + if (mLockingWifi && mNetworkRef > 0 && !mWifiLock.isHeld()) { + mWifiLock.acquire(); + } + } + + private void releaseWifiLockIfNecessaryLocked() { + if (mNetworkRef == 0 && mWifiLock.isHeld()) { + mWifiLock.release(); + } + } + + /** + * @return whether we're connected to a network + */ + public boolean isConnected() { + return mIsConnected; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/service/FontSizeChangedListener.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/service/FontSizeChangedListener.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,31 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.service; + +/** + * @author Kenny Root + * + */ +public interface FontSizeChangedListener { + + /** + * @param size + * new font size + */ + void onFontSizeChanged(float size); +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/service/PromptHelper.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/service/PromptHelper.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,184 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.service; + +import java.util.concurrent.Semaphore; + +import android.os.Handler; +import android.os.Message; + +/** + * Helps provide a relay for prompts and responses between a possible user + * interface and some underlying service. + * + * @author jsharkey + */ +public class PromptHelper { + private final Object tag; + + private Handler handler = null; + + private Semaphore promptToken; + private Semaphore promptResponse; + + public String promptInstructions = null; + public String promptHint = null; + public Object promptRequested = null; + public boolean passwordRequested = true; + + private Object response = null; + + public PromptHelper(Object tag) { + this.tag = tag; + // Threads must acquire this before they can send a prompt. + promptToken = new Semaphore(1); + // Responses will release this semaphore. + promptResponse = new Semaphore(0); + } + + + /** + * Register a user interface handler, if available. + */ + public void setHandler(Handler handler) { + this.handler = handler; + } + + /** + * Set an incoming value from an above user interface. Will automatically + * notify any waiting requests. + */ + public void setResponse(Object value) { + response = value; + promptRequested = null; + promptInstructions = null; + promptHint = null; + promptResponse.release(); + } + + /** + * Return the internal response value just before erasing and returning it. + */ + protected Object popResponse() { + Object value = response; + response = null; + return value; + } + + + /** + * Request a prompt response from parent. This is a blocking call until user + * interface returns a value. + * Only one thread can call this at a time. cancelPrompt() will force this to + * immediately return. + */ + private Object requestPrompt(String instructions, String hint, Object type) throws InterruptedException { + Object response = null; + promptToken.acquire(); + + try { + promptInstructions = instructions; + promptHint = hint; + promptRequested = type; + + // notify any parent watching for live events + if (handler != null) + Message.obtain(handler, -1, tag).sendToTarget(); + + // acquire lock until user passes back value + promptResponse.acquire(); + response = popResponse(); + } + finally { + promptToken.release(); + } + + return response; + } + + /** + * Request a string response from parent. This is a blocking call until user + * interface returns a value. + * @param hint prompt hint for user to answer + * @return string user has entered + */ + public String requestStringPrompt(String instructions, String hint) { + String value = null; + passwordRequested = false; + + try { + value = (String)this.requestPrompt(instructions, hint, String.class); + } + catch (Exception e) { + } + + return value; + } + + /** + * Request a password response from parent. This is a blocking call until user + * interface returns a value. + * @param hint prompt hint for user to answer + * @return string user has entered + */ + public String requestPasswordPrompt(String instructions, String hint) { + String value = null; + passwordRequested = true; + + try { + value = (String)this.requestPrompt(instructions, hint, String.class); + } + catch (Exception e) { + } + + return value; + } + + /** + * Request a boolean response from parent. This is a blocking call until user + * interface returns a value. + * @param hint prompt hint for user to answer + * @return choice user has made (yes/no) + */ + public Boolean requestBooleanPrompt(String instructions, String hint) { + Boolean value = null; + + try { + value = (Boolean)this.requestPrompt(instructions, hint, Boolean.class); + } + catch (Exception e) { + } + + return value; + } + + /** + * Cancel an in-progress prompt. + */ + public void cancelPrompt() { + if (!promptToken.tryAcquire()) { + // A thread has the token, so try to interrupt it + response = null; + promptResponse.release(); + } + else { + // No threads have acquired the token + promptToken.release(); + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/service/Relay.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/service/Relay.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,167 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.service; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CoderResult; +import java.nio.charset.CodingErrorAction; +import java.util.Timer; +import java.util.TimerTask; + +import org.apache.harmony.niochar.charset.additional.IBM437; + +import com.five_ten_sg.connectbot.transport.AbsTransport; +import android.graphics.Paint; +import android.text.AndroidCharacter; +import android.util.Log; +import de.mud.terminal.vt320; + +/** + * @author Kenny Root + */ +public class Relay implements Runnable { + private static final String TAG = "ConnectBot.Relay"; + + private static final int BUFFER_SIZE = 4096; + + private TerminalBridge bridge; + + private Charset currentCharset; + private CharsetDecoder decoder; + + private AbsTransport transport; + + private vt320 buffer; + + private ByteBuffer byteBuffer; + private CharBuffer charBuffer; + + private byte[] byteArray; + private char[] charArray; + + private void eastAsianWidthMeasure(char[] charArray, int start, int end, + byte[] wideAttribute, Paint paint, int charWidth) { + AndroidCharacter.getEastAsianWidths(charArray, start, end - start, wideAttribute); + } + + public Relay(TerminalBridge bridge, AbsTransport transport, vt320 buffer, String encoding) { + setCharset(encoding); + this.bridge = bridge; + this.transport = transport; + this.buffer = buffer; + } + + public void setCharset(String encoding) { + Log.d("ConnectBot.Relay", "changing charset to " + encoding); + Charset charset; + + if (encoding.equals("CP437")) + charset = new IBM437("IBM437", + new String[] { "IBM437", "CP437" }); + else + charset = Charset.forName(encoding); + + if (charset == currentCharset || charset == null) + return; + + CharsetDecoder newCd = charset.newDecoder(); + newCd.onUnmappableCharacter(CodingErrorAction.REPLACE); + newCd.onMalformedInput(CodingErrorAction.REPLACE); + currentCharset = charset; + + synchronized (this) { + decoder = newCd; + } + } + + public Charset getCharset() { + return currentCharset; + } + + public void run() { + byteBuffer = ByteBuffer.allocate(BUFFER_SIZE); + charBuffer = CharBuffer.allocate(BUFFER_SIZE); + /* for East Asian character widths */ + byte[] wideAttribute = new byte[BUFFER_SIZE]; + byteArray = byteBuffer.array(); + charArray = charBuffer.array(); + CoderResult result; + int bytesRead = 0; + byteBuffer.limit(0); + int bytesToRead; + int offset; + int charWidth; + Timer timer = new Timer("relay.blocker", true); + TimerTask task = null; + + try { + while (true) { + charWidth = bridge.charWidth; + bytesToRead = byteBuffer.capacity() - byteBuffer.limit(); + offset = byteBuffer.arrayOffset() + byteBuffer.limit(); + + if (transport.willBlock()) { + task = new TimerTask() { + public void run() { + buffer.testChanged(); + } + }; + timer.schedule(task, 10); // 10 ms delay + } + + bytesRead = transport.read(byteArray, offset, bytesToRead); + + if (task != null) { + task.cancel(); + task = null; + } + + if (bytesRead > 0) { + byteBuffer.limit(byteBuffer.limit() + bytesRead); + + synchronized (this) { + result = decoder.decode(byteBuffer, charBuffer, false); + } + + if (result.isUnderflow() && + byteBuffer.limit() == byteBuffer.capacity()) { + byteBuffer.compact(); + byteBuffer.limit(byteBuffer.position()); + byteBuffer.position(0); + } + + offset = charBuffer.position(); + eastAsianWidthMeasure(charArray, 0, offset, wideAttribute, bridge.defaultPaint, charWidth); + buffer.putString(charArray, wideAttribute, 0, charBuffer.position()); + bridge.propagateConsoleText(charArray, charBuffer.position()); + charBuffer.clear(); + bridge.redraw(); + } + } + } + catch (IOException e) { + Log.e(TAG, "Problem while handling incoming data in relay thread", e); + } + + timer.cancel(); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/service/TerminalBridge.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/service/TerminalBridge.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,1412 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.service; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.Charset; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.five_ten_sg.connectbot.R; +import com.five_ten_sg.connectbot.TerminalView; +import com.five_ten_sg.connectbot.bean.HostBean; +import com.five_ten_sg.connectbot.bean.PortForwardBean; +import com.five_ten_sg.connectbot.bean.SelectionArea; +import com.five_ten_sg.connectbot.transport.AbsTransport; +import com.five_ten_sg.connectbot.transport.TransportFactory; +import com.five_ten_sg.connectbot.util.HostDatabase; +import com.five_ten_sg.connectbot.util.PreferenceConstants; +import com.five_ten_sg.connectbot.util.StringPickerDialog; +import android.app.AlertDialog; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.FontMetrics; +import android.graphics.Typeface; +import android.os.Binder; +import android.os.Environment; +import android.text.ClipboardManager; +import android.text.Editable; +import android.text.method.CharacterPickerDialog; +import android.util.FloatMath; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.widget.AdapterView; +import android.widget.Button; +import de.mud.terminal.VDUBuffer; +import de.mud.terminal.VDUDisplay; +import de.mud.terminal.vt320; + + +/** + * Provides a bridge between a MUD terminal buffer and a possible TerminalView. + * This separation allows us to keep the TerminalBridge running in a background + * service. A TerminalView shares down a bitmap that we can use for rendering + * when available. + * + * This class also provides SSH hostkey verification prompting, and password + * prompting. + */ +@SuppressWarnings("deprecation") // for ClipboardManager +public class TerminalBridge implements VDUDisplay { + public final static String TAG = "ConnectBot.TerminalBridge"; + + private final static float FONT_SIZE_FACTOR = 1.1f; + + public Integer[] color; + + public int defaultFg = HostDatabase.DEFAULT_FG_COLOR; + public int defaultBg = HostDatabase.DEFAULT_BG_COLOR; + + protected final TerminalManager manager; + public final HostBean host; + public final String homeDirectory; + + AbsTransport transport; + + final Paint defaultPaint; + + private Relay relay; + + private String emulation; // aka answerback string, aka terminal type + + public Bitmap bitmap = null; + public vt320 buffer = null; + + public TerminalView parent = null; + private final Canvas canvas = new Canvas(); + + private boolean disconnected = false; + private boolean awaitingClose = false; + + private boolean forcedSize = false; + private int columns; + private int rows; + + public TerminalMonitor monitor = null; + private TerminalKeyListener keyListener = null; + + private boolean selectingForCopy = false; + private final SelectionArea selectionArea; + + // TODO add support for the new clipboard API + private ClipboardManager clipboard; + + public int charWidth = -1; + public int charHeight = -1; + private int charTop = -1; + private float fontSize = -1; + + private final List fontSizeChangedListeners; + + private final List localOutput; + + /** + * Flag indicating if we should perform a full-screen redraw during our next + * rendering pass. + */ + private boolean fullRedraw = false; + + public PromptHelper promptHelper; + + protected BridgeDisconnectedListener disconnectListener = null; + + /** + * Create a new terminal bridge suitable for unit testing. + */ + public TerminalBridge() { + buffer = new vt320() { + @Override + public void write(byte[] b) {} + @Override + public void write(int b) {} + @Override + public void sendTelnetCommand(byte cmd) {} + @Override + public void setWindowSize(int c, int r) {} + @Override + public void debug(String s) {} + }; + emulation = null; + manager = null; + host = null; + homeDirectory = null; + defaultPaint = new Paint(); + selectionArea = new SelectionArea(); + localOutput = new LinkedList(); + fontSizeChangedListeners = new LinkedList(); + transport = null; + keyListener = new TerminalKeyListener(manager, this, buffer, null); + monitor = null; + } + + /** + * Create new terminal bridge with following parameters. + */ + public TerminalBridge(final TerminalManager manager, final HostBean host, final String homeDirectory) throws IOException { + this.manager = manager; + this.host = host; + this.homeDirectory = homeDirectory; + emulation = host.getHostEmulation(); + + if ((emulation == null) || (emulation.length() == 0)) emulation = manager.getEmulation(); + + // create prompt helper to relay password and hostkey requests up to gui + promptHelper = new PromptHelper(this); + // create our default paint + defaultPaint = new Paint(); + defaultPaint.setAntiAlias(true); + defaultPaint.setTypeface(Typeface.MONOSPACE); + defaultPaint.setFakeBoldText(true); // more readable? + localOutput = new LinkedList(); + fontSizeChangedListeners = new LinkedList(); + setMyFontSize(); + resetColors(); + selectionArea = new SelectionArea(); + } + + public PromptHelper getPromptHelper() { + return promptHelper; + } + + /** + * Spawn thread to open connection and start login process. + */ + protected void startConnection() { + transport = TransportFactory.getTransport(host.getProtocol()); + transport.setLinks(manager, this, homeDirectory, host, emulation); + buffer = transport.getTransportBuffer(); + keyListener = transport.getTerminalKeyListener(); + String monitor_init = host.getMonitor(); + + if ((monitor_init != null) && (monitor_init.length() > 0)) { + monitor = new TerminalMonitor(manager, buffer, parent, host, monitor_init); + } + + transport.setCompression(host.getCompression()); + transport.setHttpproxy(host.getHttpproxy()); + transport.setUseAuthAgent(host.getUseAuthAgent()); + + if (transport.canForwardPorts()) { + for (PortForwardBean portForward : manager.hostdb.getPortForwardsForHost(host)) + transport.addPortForward(portForward); + } + + outputLine(manager.res.getString(R.string.terminal_connecting, host.getHostname(), host.getPort(), host.getProtocol())); + Thread connectionThread = new Thread(new Runnable() { + public void run() { + transport.connect(); + } + }); + connectionThread.setName("Connection"); + connectionThread.setDaemon(true); + connectionThread.start(); + } + + /** + * Handle challenges from keyboard-interactive authentication mode. + */ + public String[] replyToChallenge(String name, String instruction, int numPrompts, String[] prompt, boolean[] echo) { + String[] responses = new String[numPrompts]; + + for (int i = 0; i < numPrompts; i++) { + // request response from user for each prompt + responses[i] = promptHelper.requestPasswordPrompt(instruction, prompt[i]); + } + + return responses; + } + + /** + * @return charset in use by bridge + */ + public Charset getCharset() { + if (relay != null) return relay.getCharset(); + + return keyListener.getCharset(); + } + + /** + * Sets the encoding used by the terminal. If the connection is live, + * then the character set is changed for the next read. + * @param encoding the canonical name of the character encoding + */ + public void setCharset(String encoding) { + if (relay != null) relay.setCharset(encoding); + + keyListener.setCharset(encoding); + } + + /** + * Convenience method for writing a line into the underlying MUD buffer. + * Should never be called once the session is established. + */ + public final void outputLine(String line) { + if (transport != null && transport.isSessionOpen()) + Log.e(TAG, "Session established, cannot use outputLine!", new IOException("outputLine call traceback")); + + synchronized (localOutput) { + final String s = line + "\r\n"; + localOutput.add(s); + buffer.putString(s); + // For accessibility + final char[] charArray = s.toCharArray(); + propagateConsoleText(charArray, charArray.length); + } + } + + /** + * Inject a specific string into this terminal. Used for post-login strings + * and pasting clipboard. + */ + public void injectString(final String string) { + if (string == null || string.length() == 0) + return; + + Thread injectStringThread = new Thread(new Runnable() { + public void run() { + try { + transport.write(string.getBytes(host.getEncoding())); + } + catch (Exception e) { + Log.e(TAG, "Couldn't inject string to remote host: ", e); + } + } + }); + injectStringThread.setName("InjectString"); + injectStringThread.start(); + } + + /** + * Internal method to request actual PTY terminal once we've finished + * authentication. If called before authenticated, it will just fail. + */ + public void onConnected() { + disconnected = false; + buffer.reset(); + buffer.setAnswerBack(emulation); + localOutput.clear(); // We no longer need our local output. + + if (HostDatabase.DELKEY_BACKSPACE.equals(host.getDelKey())) + buffer.setBackspace(vt320.DELETE_IS_BACKSPACE); + else + buffer.setBackspace(vt320.DELETE_IS_DEL); + + // create thread to relay incoming connection data to buffer + // only if needed by the transport + if (transport.needsRelay()) { + relay = new Relay(this, transport, buffer, host.getEncoding()); + Thread relayThread = new Thread(relay); + relayThread.setDaemon(true); + relayThread.setName("Relay"); + relayThread.start(); + } + + // get proper font size + setMyFontSize(); + // finally send any post-login string, if requested + injectString(host.getPostLogin()); + } + + private void setMyFontSize() { + if ((parent != null) && (host.getFixedSize())) { + resizeComputed(host.getFixedWidth(), host.getFixedHeight(), parent.getWidth(), parent.getHeight()); + } + else { + setFontSize(host.getFontSize()); + } + } + + /** + * @return whether a session is open or not + */ + public boolean isSessionOpen() { + if (transport != null) return transport.isSessionOpen(); + + return false; + } + + public void setOnDisconnectedListener(BridgeDisconnectedListener disconnectListener) { + this.disconnectListener = disconnectListener; + } + + /** + * Force disconnection of this terminal bridge. + */ + public void dispatchDisconnect(boolean immediate) { + // We don't need to do this multiple times. + synchronized (this) { + if (disconnected && !immediate) return; + + disconnected = true; + } + + // Cancel any pending prompts. + promptHelper.cancelPrompt(); + // disconnection request hangs if we havent really connected to a host yet + // temporary fix is to just spawn disconnection into a thread + Thread disconnectThread = new Thread(new Runnable() { + public void run() { + if (transport != null && transport.isConnected()) + transport.close(); + } + }); + disconnectThread.setName("Disconnect"); + disconnectThread.start(); + + if (immediate) { + awaitingClose = true; + + if (disconnectListener != null) + disconnectListener.onDisconnected(TerminalBridge.this); + } + else { + final String line = manager.res.getString(R.string.alert_disconnect_msg); + buffer.putString("\r\n" + line + "\r\n"); + + if (host.getStayConnected()) { + manager.requestReconnect(this); + return; + } + + Thread disconnectPromptThread = new Thread(new Runnable() { + public void run() { + Boolean result = promptHelper.requestBooleanPrompt(null, + manager.res.getString(R.string.prompt_host_disconnected)); + + if (result == null || result.booleanValue()) { + awaitingClose = true; + + // Tell the TerminalManager that we can be destroyed now. + if (disconnectListener != null) + disconnectListener.onDisconnected(TerminalBridge.this); + } + } + }); + disconnectPromptThread.setName("DisconnectPrompt"); + disconnectPromptThread.setDaemon(true); + disconnectPromptThread.start(); + } + + // close the monitor + if (monitor != null) monitor.Disconnect(); + + monitor = null; + } + + public void setSelectingForCopy(boolean selectingForCopy) { + this.selectingForCopy = selectingForCopy; + } + + public boolean isSelectingForCopy() { + return selectingForCopy; + } + + public SelectionArea getSelectionArea() { + return selectionArea; + } + + public synchronized void tryKeyVibrate() { + manager.tryKeyVibrate(); + } + + /** + * Request a different font size. Will make call to parentChanged() to make + * sure we resize PTY if needed. + */ + final void setFontSize(float size) { + if (size <= 0.0) size = 12.0f; + + size = (float)(int)((size * 10.0f) + 0.5f) / 10.0f; + defaultPaint.setTextSize(size); + fontSize = size; + // read new metrics to get exact pixel dimensions + FontMetrics fm = defaultPaint.getFontMetrics(); + charTop = (int)FloatMath.ceil(fm.top); + float[] widths = new float[1]; + defaultPaint.getTextWidths("X", widths); + charWidth = (int)FloatMath.ceil(widths[0]); + charHeight = (int)FloatMath.ceil(fm.descent - fm.top); + + // refresh any bitmap with new font size + if (parent != null) parentChanged(parent); + + synchronized(fontSizeChangedListeners) { + for (FontSizeChangedListener ofscl : fontSizeChangedListeners) + ofscl.onFontSizeChanged(size); + } + + host.setFontSize(size); + manager.hostdb.updateFontSize(host); + } + + /** + * Add an {@link FontSizeChangedListener} to the list of listeners for this + * bridge. + * + * @param listener + * listener to add + */ + public void addFontSizeChangedListener(FontSizeChangedListener listener) { + synchronized(fontSizeChangedListeners) { + fontSizeChangedListeners.add(listener); + } + } + + /** + * Remove an {@link FontSizeChangedListener} from the list of listeners for + * this bridge. + * + * @param listener + */ + public void removeFontSizeChangedListener(FontSizeChangedListener listener) { + synchronized(fontSizeChangedListeners) { + fontSizeChangedListeners.remove(listener); + } + } + + /** + * Something changed in our parent {@link TerminalView}, maybe it's a new + * parent, or maybe it's an updated font size. We should recalculate + * terminal size information and request a PTY resize. + */ + + public final synchronized void parentChanged(TerminalView parent) { + if (manager != null && !manager.isResizeAllowed()) { + Log.d(TAG, "Resize is not allowed now"); + return; + } + + this.parent = parent; + final int width = parent.getWidth(); + final int height = parent.getHeight(); + + // Something has gone wrong with our layout; we're 0 width or height! + if (width <= 0 || height <= 0) + return; + + clipboard = (ClipboardManager) parent.getContext().getSystemService(Context.CLIPBOARD_SERVICE); + keyListener.setClipboardManager(clipboard); + + if (!forcedSize) { + // recalculate buffer size + int newColumns, newRows; + newColumns = width / charWidth; + newRows = height / charHeight; + + // If nothing has changed in the terminal dimensions and not an intial + // draw then don't blow away scroll regions and such. + if (newColumns == columns && newRows == rows) + return; + + columns = newColumns; + rows = newRows; + } + + // reallocate new bitmap if needed + boolean newBitmap = (bitmap == null); + + if (bitmap != null) + newBitmap = (bitmap.getWidth() != width || bitmap.getHeight() != height); + + if (newBitmap) { + discardBitmap(); + bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888); + canvas.setBitmap(bitmap); + } + + // clear out any old buffer information + defaultPaint.setColor(Color.BLACK); + canvas.drawPaint(defaultPaint); + + // Stroke the border of the terminal if the size is being forced; + if (forcedSize) { + int borderX = (columns * charWidth) + 1; + int borderY = (rows * charHeight) + 1; + defaultPaint.setColor(Color.GRAY); + defaultPaint.setStrokeWidth(0.0f); + + if (width >= borderX) + canvas.drawLine(borderX, 0, borderX, borderY + 1, defaultPaint); + + if (height >= borderY) + canvas.drawLine(0, borderY, borderX + 1, borderY, defaultPaint); + } + + try { + // request a terminal pty resize + if (buffer != null) { + synchronized (buffer) { + buffer.setScreenSize(columns, rows, true); + } + } + + if (transport != null) + transport.setDimensions(columns, rows, width, height); + } + catch (Exception e) { + Log.e(TAG, "Problem while trying to resize screen or PTY", e); + } + + // redraw local output if we don't have a session to receive our resize request + if (transport == null) { + synchronized (localOutput) { + buffer.reset(); + + for (String line : localOutput) + buffer.putString(line); + } + } + + // force full redraw with new buffer size + fullRedraw = true; + redraw(); + + // initial sequence from + // transport.connect() + // bridge.onConnected() + // bridge.setMyFontSize() + // bridge.resizeComputed() + // bridge.setFontSize() + // bridge.parentChanged() here is on the wrong thread + try { + parent.notifyUser(String.format("%d x %d", columns, rows)); + } + catch (Exception e) { + Log.e(TAG, "Problem while trying to notify user", e); + } + + Log.i(TAG, String.format("parentChanged() now width=%d, height=%d", columns, rows)); + } + + /** + * Somehow our parent {@link TerminalView} was destroyed. Now we don't need + * to redraw anywhere, and we can recycle our internal bitmap. + */ + + public synchronized void parentDestroyed() { + parent = null; + discardBitmap(); + } + + private void discardBitmap() { + if (bitmap != null) + bitmap.recycle(); + + bitmap = null; + } + + public void propagateConsoleText(char[] rawText, int length) { + if (parent != null) { + parent.propagateConsoleText(rawText, length); + } + } + + public void onDraw() { + int fg, bg; + + synchronized (buffer) { + boolean entireDirty = buffer.update[0] || fullRedraw; + boolean isWideCharacter = false; + + // walk through all lines in the buffer + for (int l = 0; l < buffer.height; l++) { + // check if this line is dirty and needs to be repainted + // also check for entire-buffer dirty flags + if (!entireDirty && !buffer.update[l + 1]) continue; + + // reset dirty flag for this line + buffer.update[l + 1] = false; + + // walk through all characters in this line + for (int c = 0; c < buffer.width; c++) { + int addr = 0; + int currAttr = buffer.charAttributes[buffer.windowBase + l][c]; + { + int fgcolor = defaultFg; + + // check if foreground color attribute is set + if ((currAttr & VDUBuffer.COLOR_FG) != 0) + fgcolor = ((currAttr & VDUBuffer.COLOR_FG) >> VDUBuffer.COLOR_FG_SHIFT) - 1; + + if (fgcolor < 8 && (currAttr & VDUBuffer.BOLD) != 0) + fg = color[fgcolor + 8]; + else + fg = color[fgcolor]; + } + + // check if background color attribute is set + if ((currAttr & VDUBuffer.COLOR_BG) != 0) + bg = color[((currAttr & VDUBuffer.COLOR_BG) >> VDUBuffer.COLOR_BG_SHIFT) - 1]; + else + bg = color[defaultBg]; + + // support character inversion by swapping background and foreground color + if ((currAttr & VDUBuffer.INVERT) != 0) { + int swapc = bg; + bg = fg; + fg = swapc; + } + + // set underlined attributes if requested + defaultPaint.setUnderlineText((currAttr & VDUBuffer.UNDERLINE) != 0); + isWideCharacter = (currAttr & VDUBuffer.FULLWIDTH) != 0; + + if (isWideCharacter) + addr++; + else { + // determine the amount of continuous characters with the same settings and print them all at once + while (c + addr < buffer.width + && buffer.charAttributes[buffer.windowBase + l][c + addr] == currAttr) { + addr++; + } + } + + // Save the current clip region + canvas.save(Canvas.CLIP_SAVE_FLAG); + // clear this dirty area with background color + defaultPaint.setColor(bg); + + if (isWideCharacter) { + canvas.clipRect(c * charWidth, + l * charHeight, + (c + 2) * charWidth, + (l + 1) * charHeight); + } + else { + canvas.clipRect(c * charWidth, + l * charHeight, + (c + addr) * charWidth, + (l + 1) * charHeight); + } + + canvas.drawPaint(defaultPaint); + // write the text string starting at 'c' for 'addr' number of characters + defaultPaint.setColor(fg); + + if ((currAttr & VDUBuffer.INVISIBLE) == 0) + canvas.drawText(buffer.charArray[buffer.windowBase + l], c, + addr, c * charWidth, (l * charHeight) - charTop, + defaultPaint); + + // Restore the previous clip region + canvas.restore(); + // advance to the next text block with different characteristics + c += addr - 1; + + if (isWideCharacter) + c++; + } + } + + // reset entire-buffer flags + buffer.update[0] = false; + } + + fullRedraw = false; + } + + public void redraw() { + if (parent != null) + parent.postInvalidate(); + } + + // We don't have a scroll bar. + public void updateScrollBar() { + } + + /** + * Resize terminal to fit [rows]x[cols] in screen of size [width]x[height] + * @param rows + * @param cols + * @param width + * @param height + */ + + public synchronized void resizeComputed(int cols, int rows, int width, int height) { + float size = 8.0f; + float step = 8.0f; + float limit = 0.125f; + int direction; + boolean fixed = true; + + if (!fixed) { + while ((direction = fontSizeCompare(size, cols, rows, width, height)) < 0) + size += step; + + if (direction == 0) { + Log.d("fontsize", String.format("Found match at %f", size)); + return; + } + + step /= 2.0f; + size -= step; + + while ((direction = fontSizeCompare(size, cols, rows, width, height)) != 0 + && step >= limit) { + step /= 2.0f; + + if (direction > 0) { + size -= step; + } + else { + size += step; + } + } + + if (direction > 0) size -= step; + } + + this.columns = cols; + this.rows = rows; + forcedSize = true; + + if (fixed) setFontSize(host.getFontSize()); + else setFontSize(size); + } + + private int fontSizeCompare(float size, int cols, int rows, int width, int height) { + // read new metrics to get exact pixel dimensions + defaultPaint.setTextSize(size); + FontMetrics fm = defaultPaint.getFontMetrics(); + float[] widths = new float[1]; + defaultPaint.getTextWidths("X", widths); + int termWidth = (int)widths[0] * cols; + int termHeight = (int)FloatMath.ceil(fm.descent - fm.top) * rows; + Log.d("fontsize", String.format("font size %f resulted in %d x %d", size, termWidth, termHeight)); + + // Check to see if it fits in resolution specified. + if (termWidth > width || termHeight > height) + return 1; + + if (termWidth == width || termHeight == height) + return 0; + + return -1; + } + + /** + * @return whether underlying transport can forward ports + */ + public boolean canFowardPorts() { + return transport.canForwardPorts(); + } + + /** + * Adds the {@link PortForwardBean} to the list. + * @param portForward the port forward bean to add + * @return true on successful addition + */ + public boolean addPortForward(PortForwardBean portForward) { + return transport.addPortForward(portForward); + } + + /** + * Removes the {@link PortForwardBean} from the list. + * @param portForward the port forward bean to remove + * @return true on successful removal + */ + public boolean removePortForward(PortForwardBean portForward) { + return transport.removePortForward(portForward); + } + + /** + * @return the list of port forwards + */ + public List getPortForwards() { + return transport.getPortForwards(); + } + + /** + * Enables a port forward member. After calling this method, the port forward should + * be operational. + * @param portForward member of our current port forwards list to enable + * @return true on successful port forward setup + */ + public boolean enablePortForward(PortForwardBean portForward) { + if (!transport.isConnected()) { + Log.i(TAG, "Attempt to enable port forward while not connected"); + return false; + } + + return transport.enablePortForward(portForward); + } + + /** + * Disables a port forward member. After calling this method, the port forward should + * be non-functioning. + * @param portForward member of our current port forwards list to enable + * @return true on successful port forward tear-down + */ + public boolean disablePortForward(PortForwardBean portForward) { + if (!transport.isConnected()) { + Log.i(TAG, "Attempt to disable port forward while not connected"); + return false; + } + + return transport.disablePortForward(portForward); + } + + /** + * @return whether underlying transport can transfer files + */ + public boolean canTransferFiles() { + return transport.canTransferFiles(); + } + + /** + * Downloads the specified remote file to the local connectbot folder. + * @return true on success, false on failure + */ + public boolean downloadFile(String remoteFile, String localFolder) { + return transport.downloadFile(remoteFile, localFolder); + } + + /** + * Uploads the specified local file to the remote host's default directory. + * @return true on success, false on failure + */ + public boolean uploadFile(String localFile, String remoteFolder, String remoteFile, String mode) { + if (mode == null) + mode = "0600"; + + return transport.uploadFile(localFile, remoteFolder, remoteFile, mode); + } + + /** + * @return whether the TerminalBridge should close + */ + public boolean isAwaitingClose() { + return awaitingClose; + } + + /** + * @return whether this connection had started and subsequently disconnected + */ + public boolean isDisconnected() { + return disconnected; + } + + /* (non-Javadoc) + * @see de.mud.terminal.VDUDisplay#setColor(byte, byte, byte, byte) + */ + public void setColor(int index, int red, int green, int blue) { + // Don't allow the system colors to be overwritten for now. May violate specs. + if (index < color.length && index >= 16) + color[index] = 0xff000000 | red << 16 | green << 8 | blue; + } + + public final void resetColors() { + int[] defaults = manager.hostdb.getDefaultColorsForScheme(HostDatabase.DEFAULT_COLOR_SCHEME); + defaultFg = defaults[0]; + defaultBg = defaults[1]; + color = manager.hostdb.getColorsForScheme(HostDatabase.DEFAULT_COLOR_SCHEME); + } + + private static Pattern urlPattern = null; + + /** + * @return + */ + public List scanForURLs() { + Set urls = new LinkedHashSet(); + + if (urlPattern == null) { + // based on http://www.ietf.org/rfc/rfc2396.txt + String scheme = "[A-Za-z][-+.0-9A-Za-z]*"; + String unreserved = "[-._~0-9A-Za-z]"; + String pctEncoded = "%[0-9A-Fa-f]{2}"; + String subDelims = "[!$&'()*+,;:=]"; + String userinfo = "(?:" + unreserved + "|" + pctEncoded + "|" + subDelims + "|:)*"; + String h16 = "[0-9A-Fa-f]{1,4}"; + String decOctet = "(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])"; + String ipv4address = decOctet + "\\." + decOctet + "\\." + decOctet + "\\." + decOctet; + String ls32 = "(?:" + h16 + ":" + h16 + "|" + ipv4address + ")"; + String ipv6address = "(?:(?:" + h16 + "){6}" + ls32 + ")"; + String ipvfuture = "v[0-9A-Fa-f]+.(?:" + unreserved + "|" + subDelims + "|:)+"; + String ipLiteral = "\\[(?:" + ipv6address + "|" + ipvfuture + ")\\]"; + String regName = "(?:" + unreserved + "|" + pctEncoded + "|" + subDelims + ")*"; + String host = "(?:" + ipLiteral + "|" + ipv4address + "|" + regName + ")"; + String port = "[0-9]*"; + String authority = "(?:" + userinfo + "@)?" + host + "(?::" + port + ")?"; + String pchar = "(?:" + unreserved + "|" + pctEncoded + "|" + subDelims + "|@)"; + String segment = pchar + "*"; + String pathAbempty = "(?:/" + segment + ")*"; + String segmentNz = pchar + "+"; + String pathAbsolute = "/(?:" + segmentNz + "(?:/" + segment + ")*)?"; + String pathRootless = segmentNz + "(?:/" + segment + ")*"; + String hierPart = "(?://" + authority + pathAbempty + "|" + pathAbsolute + "|" + pathRootless + ")"; + String query = "(?:" + pchar + "|/|\\?)*"; + String fragment = "(?:" + pchar + "|/|\\?)*"; + String uriRegex = scheme + ":" + hierPart + "(?:" + query + ")?(?:#" + fragment + ")?"; + urlPattern = Pattern.compile(uriRegex); + } + + char[] visibleBuffer = new char[buffer.height * buffer.width]; + + for (int l = 0; l < buffer.height; l++) + System.arraycopy(buffer.charArray[buffer.windowBase + l], 0, + visibleBuffer, l * buffer.width, buffer.width); + + Matcher urlMatcher = urlPattern.matcher(new String(visibleBuffer)); + + while (urlMatcher.find()) + urls.add(urlMatcher.group()); + + return (new LinkedList (urls)); + } + + /** + * @return + */ + public boolean isUsingNetwork() { + return transport.usesNetwork(); + } + + /** + * @return + */ + public TerminalKeyListener getKeyHandler() { + return keyListener; + } + + /** + * + */ + public void resetScrollPosition() { + // if we're in scrollback, scroll to bottom of window on input + if (buffer.windowBase != buffer.screenBase) + buffer.setWindowBase(buffer.screenBase); + } + + /** + * + */ + public void increaseFontSize() { + setFontSize(fontSize * FONT_SIZE_FACTOR); + } + + /** + * + */ + public void decreaseFontSize() { + setFontSize(fontSize / FONT_SIZE_FACTOR); + } + + /** + * Auto-size window back to default + */ + public void resetSize(TerminalView parent) { + this.forcedSize = false; + setMyFontSize(); + } + + /** + * Create a screenshot of the current view + */ + public void captureScreen() { + String msg; + File dir, path; + boolean success = true; + Bitmap screenshot = this.bitmap; + + if (manager == null || parent == null || screenshot == null) + return; + + SimpleDateFormat s = new SimpleDateFormat("yyyyMMdd_HHmmss"); + String date = s.format(new Date()); + String pref_path = manager.prefs.getString(PreferenceConstants.SCREEN_CAPTURE_FOLDER, ""); + File default_path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); + + if (pref_path.equals("")) + dir = default_path; + else + dir = new File(pref_path); + + path = new File(dir, "vx-" + date + ".png"); + + try { + dir.mkdirs(); + FileOutputStream out = new FileOutputStream(path); + screenshot.compress(Bitmap.CompressFormat.PNG, 90, out); + out.close(); + } + catch (Exception e) { + e.printStackTrace(); + success = false; + } + + if (success) { + msg = manager.getResources().getString(R.string.screenshot_saved_as) + " " + path; + + if (manager.prefs.getBoolean(PreferenceConstants.SCREEN_CAPTURE_POPUP, true)) { + new AlertDialog.Builder(parent.getContext()) + .setTitle(R.string.screenshot_success_title) + .setMessage(msg) + .setPositiveButton(R.string.button_close, null) + .show(); + } + } + else { + msg = manager.getResources().getString(R.string.screenshot_not_saved_as) + " " + path; + new AlertDialog.Builder(parent.getContext()) + .setTitle(R.string.screenshot_error_title) + .setMessage(msg) + .setNegativeButton(R.string.button_close, null) + .show(); + } + + return; + } + + /** + * Show change font size dialog + */ + public boolean showFontSizeDialog() { + final String pickerString = "+-"; + CharSequence str = ""; + Editable content = Editable.Factory.getInstance().newEditable(str); + + if (parent == null) + return false; + + CharacterPickerDialog cpd = new CharacterPickerDialog(parent.getContext(), + parent, content, pickerString, true) { + private void changeFontSize(CharSequence result) { + if (result.equals("+")) + increaseFontSize(); + else if (result.equals("-")) + decreaseFontSize(); + } + @Override + public void onItemClick(AdapterView p, View v, int pos, long id) { + final String result = String.valueOf(pickerString.charAt(pos)); + changeFontSize(result); + } + @Override + public void onClick(View v) { + if (v instanceof Button) { + final CharSequence result = ((Button) v).getText(); + + if (result.equals("")) + dismiss(); + else + changeFontSize(result); + } + } + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) + dismiss(); + + return keyListener.onKey(parent, event.getKeyCode(), event); + } + + return true; + } + }; + cpd.show(); + return true; + } + + /** + * Show arrows dialog + */ + public boolean showArrowsDialog() { + final String []pickerStrings = {"←", "→", "↑", "↓", "tab", "ins", "del", "ret"}; + final HashMap keymap = new HashMap(); + keymap.put("←", vt320.KEY_LEFT); + keymap.put("→", vt320.KEY_RIGHT); + keymap.put("↑", vt320.KEY_UP); + keymap.put("↓", vt320.KEY_DOWN); + keymap.put("tab", vt320.KEY_TAB); + keymap.put("ins", vt320.KEY_INSERT); + keymap.put("del", vt320.KEY_DELETE); + keymap.put("ret", vt320.KEY_ENTER); + CharSequence str = ""; + Editable content = Editable.Factory.getInstance().newEditable(str); + + if (parent == null) return false; + + StringPickerDialog cpd = new StringPickerDialog(parent.getContext(), + parent, content, + pickerStrings, true) { + private void buttonPressed(String s) { + if (keymap.containsKey(s)) buffer.keyPressed(keymap.get(s), ' ', 0); + } + @Override + public void onItemClick(AdapterView p, View v, int pos, long id) { + buttonPressed(pickerStrings[pos]); + } + @Override + public void onClick(View v) { + if (v instanceof Button) { + final String s = ((Button) v).getText().toString(); + + if (s.equals("")) dismiss(); + else buttonPressed(s); + } + } + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) + dismiss(); + + return keyListener.onKey(parent, event.getKeyCode(), event); + } + + return true; + } + }; + cpd.show(); + return true; + } + + + /** + * CTRL dialog + */ + private String getCtrlString() { + final String defaultSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + String set = manager.prefs.getString(PreferenceConstants.CTRL_STRING, defaultSet); + + if (set == null || set.equals("")) { + set = defaultSet; + } + + return set; + } + + public boolean showCtrlDialog() { + CharSequence str = ""; + Editable content = Editable.Factory.getInstance().newEditable(str); + + if (parent == null) + return false; + + CharacterPickerDialog cpd = new CharacterPickerDialog(parent.getContext(), + parent, content, getCtrlString(), true) { + private void buttonPressed(CharSequence result) { + int code = result.toString().toUpperCase().charAt(0) - 64; + + if (code > 0 && code < 80) { + try { + transport.write(code); + } + catch (IOException e) { + Log.d(TAG, "Error writing CTRL+" + result.toString().toUpperCase().charAt(0)); + } + } + + dismiss(); + } + @Override + public void onItemClick(AdapterView p, View v, int pos, long id) { + final String result = String.valueOf(getCtrlString().charAt(pos)); + buttonPressed(result); + } + @Override + public void onClick(View v) { + if (v instanceof Button) { + final CharSequence result = ((Button) v).getText(); + + if (result.equals("")) + dismiss(); + else + buttonPressed(result); + } + } + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) + dismiss(); + + return keyListener.onKey(parent, event.getKeyCode(), event); + } + + return true; + } + }; + cpd.show(); + return true; + } + + /** + * Function keys dialog + */ + public boolean showFKeysDialog() { + final String []pickerStrings = {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "F19", "F20", "F21", "F22", "F23", "F24", "←", "→", "↑", "↓", "tab", "ins", "del", "ret"}; + final HashMap keymap = new HashMap(); + keymap.put("F1", vt320.KEY_F1); + keymap.put("F2", vt320.KEY_F2); + keymap.put("F3", vt320.KEY_F3); + keymap.put("F4", vt320.KEY_F4); + keymap.put("F5", vt320.KEY_F5); + keymap.put("F6", vt320.KEY_F6); + keymap.put("F7", vt320.KEY_F7); + keymap.put("F8", vt320.KEY_F8); + keymap.put("F9", vt320.KEY_F9); + keymap.put("F10", vt320.KEY_F10); + keymap.put("F11", vt320.KEY_F11); + keymap.put("F12", vt320.KEY_F12); + keymap.put("F13", vt320.KEY_F13); + keymap.put("F14", vt320.KEY_F14); + keymap.put("F15", vt320.KEY_F15); + keymap.put("F16", vt320.KEY_F16); + keymap.put("F17", vt320.KEY_F17); + keymap.put("F18", vt320.KEY_F18); + keymap.put("F19", vt320.KEY_F19); + keymap.put("F20", vt320.KEY_F20); + keymap.put("F21", vt320.KEY_F21); + keymap.put("F22", vt320.KEY_F22); + keymap.put("F23", vt320.KEY_F23); + keymap.put("F24", vt320.KEY_F24); + keymap.put("←", vt320.KEY_LEFT); + keymap.put("→", vt320.KEY_RIGHT); + keymap.put("↑", vt320.KEY_UP); + keymap.put("↓", vt320.KEY_DOWN); + keymap.put("tab", vt320.KEY_TAB); + keymap.put("ins", vt320.KEY_INSERT); + keymap.put("del", vt320.KEY_DELETE); + keymap.put("ret", vt320.KEY_ENTER); + CharSequence str = ""; + Editable content = Editable.Factory.getInstance().newEditable(str); + + if (parent == null) return false; + + StringPickerDialog cpd = new StringPickerDialog(parent.getContext(), + parent, content, + pickerStrings, true) { + private void buttonPressed(String s) { + if (keymap.containsKey(s)) buffer.keyPressed(keymap.get(s), ' ', 0); + + dismiss(); + } + @Override + public void onItemClick(AdapterView p, View v, int pos, long id) { + buttonPressed(pickerStrings[pos]); + } + @Override + public void onClick(View v) { + if (v instanceof Button) { + final String s = ((Button) v).getText().toString(); + + if (s.equals("")) dismiss(); + else buttonPressed(s); + } + } + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) + dismiss(); + + return keyListener.onKey(parent, event.getKeyCode(), event); + } + + return true; + } + }; + cpd.show(); + return true; + } + + private String getPickerString() { + final String defaultSet = "~\\^()[]{}<>|/:_;,.!@#$%&*?\"'-+="; + String set = manager.prefs.getString(PreferenceConstants.PICKER_STRING, defaultSet); + + if (set == null || set.equals("")) { + set = defaultSet; + } + + return set; + } + + public boolean showCharPickerDialog() { + CharSequence str = ""; + Editable content = Editable.Factory.getInstance().newEditable(str); + + if (parent == null || !transport.isAuthenticated()) + return false; + + CharacterPickerDialog cpd = new CharacterPickerDialog(parent.getContext(), + parent, content, getPickerString(), true) { + private void writeChar(CharSequence result) { + try { + if (transport.isAuthenticated()) + transport.write(result.toString().getBytes(getCharset().name())); + } + catch (IOException e) { + Log.e(TAG, "Problem with the CharacterPickerDialog", e); + } + + if (!manager.prefs.getBoolean(PreferenceConstants.PICKER_KEEP_OPEN, false)) + dismiss(); + } + @Override + public void onItemClick(AdapterView p, View v, int pos, long id) { + String result = String.valueOf(getPickerString().charAt(pos)); + writeChar(result); + } + @Override + public void onClick(View v) { + if (v instanceof Button) { + CharSequence result = ((Button) v).getText(); + + if (result.equals("")) + dismiss(); + else + writeChar(result); + } + } + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + int keyCode = event.getKeyCode(); + + if (event.getAction() == KeyEvent.ACTION_DOWN) { + // close window if SYM or BACK keys are pressed + if (keyListener.isSymKey(keyCode) || + keyCode == KeyEvent.KEYCODE_BACK) { + dismiss(); + return true; + } + } + + return super.dispatchKeyEvent(event); + } + }; + cpd.show(); + return true; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/service/TerminalKeyListener.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/service/TerminalKeyListener.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,1217 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2010 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.five_ten_sg.connectbot.service; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.lang.ref.WeakReference; +import java.util.List; + +import com.five_ten_sg.connectbot.R; +import com.five_ten_sg.connectbot.TerminalView; +import com.five_ten_sg.connectbot.bean.SelectionArea; +import com.five_ten_sg.connectbot.util.PreferenceConstants; +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.content.res.Configuration; +import android.net.Uri; +import android.preference.PreferenceManager; +import android.text.ClipboardManager; +import android.util.Log; +import android.view.Gravity; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.View; +import android.view.View.OnKeyListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; +import de.mud.terminal.VDUBuffer; +import de.mud.terminal.vt320; + +/** + * @author kenny + * + */ +@SuppressWarnings("deprecation") // for ClipboardManager +public class TerminalKeyListener implements OnKeyListener, OnSharedPreferenceChangeListener { + private static final String TAG = "ConnectBot.OnKeyListener"; + + public final static int META_CTRL_ON = 0x01; + public final static int META_CTRL_LOCK = 0x02; + public final static int META_ALT_ON = 0x04; + public final static int META_ALT_LOCK = 0x08; + public final static int META_SHIFT_ON = 0x10; + public final static int META_SHIFT_LOCK = 0x20; + public final static int META_SLASH = 0x40; + public final static int META_TAB = 0x80; + + // The bit mask of momentary and lock states for each + public final static int META_CTRL_MASK = META_CTRL_ON | META_CTRL_LOCK; + public final static int META_ALT_MASK = META_ALT_ON | META_ALT_LOCK; + public final static int META_SHIFT_MASK = META_SHIFT_ON | META_SHIFT_LOCK; + + // backport constants from api level 11 + public final static int KEYCODE_ESCAPE = 111; + public final static int HC_META_CTRL_ON = 4096; + public final static int KEYCODE_PAGE_UP = 92; + public final static int KEYCODE_PAGE_DOWN = 93; + + // All the transient key codes + public final static int META_TRANSIENT = META_CTRL_ON | META_ALT_ON + | META_SHIFT_ON; + + protected final TerminalManager manager; + protected final TerminalBridge bridge; + protected final vt320 buffer; + protected String encoding; + + protected String keymode = null; + protected boolean hardKeyboard = false; + protected boolean hardKeyboardHidden; + protected String customKeyboard = null; + + protected int metaState = 0; + protected int mDeadKey = 0; + + // TODO add support for the new API. + private ClipboardManager clipboard = null; + private boolean selectingForCopy = false; + private final SelectionArea selectionArea; + protected final SharedPreferences prefs; + + + public TerminalKeyListener(TerminalManager manager, + TerminalBridge bridge, + vt320 buffer, + String encoding) { + this.manager = manager; + this.bridge = bridge; + this.buffer = buffer; + this.encoding = encoding; + selectionArea = new SelectionArea(); + prefs = PreferenceManager.getDefaultSharedPreferences(manager); + prefs.registerOnSharedPreferenceChangeListener(this); + hardKeyboard = (manager.res.getConfiguration().keyboard == Configuration.KEYBOARD_QWERTY); + hardKeyboardHidden = manager.hardKeyboardHidden; + updateKeymode(); + updateCustomKeymap(); + } + + public void sendEscape() { + buffer.keyPressed(vt320.KEY_ESCAPE, ' ', getStateForBuffer()); + } + + protected void sendEncoded(String s) { + byte [] b = null; + + try { + b = s.getBytes(encoding); + } + catch (UnsupportedEncodingException e) { + } + + if (b != null) buffer.write(b); + } + + /** + * Handle onKey() events coming down from a {@link com.five_ten_sg.connectbot.TerminalView} above us. + * Modify the keys to make more sense to a host then pass it to the vt320. + */ + public boolean onKey(View v, int keyCode, KeyEvent event) { + try { + int repeat = event.getRepeatCount(); + + // skip keys if we aren't connected yet or have been disconnected + if (bridge.isDisconnected()) return false; + + // short cuts can see repeat counts and key up/down + if (handleShortcuts(v, keyCode, event, repeat, (event.getAction() == KeyEvent.ACTION_DOWN))) return true; + + // Ignore all key-up events except for the special keys + if (event.getAction() == KeyEvent.ACTION_UP) { + // There's nothing else here for virtual keyboard users. + if (!hardKeyboard || hardKeyboardHidden) return false; + + // if keycode debugging enabled, log and print the pressed key + if (prefs.getBoolean(PreferenceConstants.DEBUG_KEYCODES, false)) { + String keyCodeString = String.format(": %d", keyCode); + String toastText = v.getContext().getString(R.string.keycode_pressed) + keyCodeString; + Log.d(TAG, toastText); + } + + if (fullKeyboard()) { + switch (keyCode) { + case KeyEvent.KEYCODE_CTRL_LEFT: + case KeyEvent.KEYCODE_CTRL_RIGHT: + metaKeyUp(META_CTRL_ON); + return true; + + case KeyEvent.KEYCODE_ALT_LEFT: + case KeyEvent.KEYCODE_ALT_RIGHT: + metaKeyUp(META_ALT_ON); + return true; + + case KeyEvent.KEYCODE_SHIFT_LEFT: + case KeyEvent.KEYCODE_SHIFT_RIGHT: + metaKeyUp(META_SHIFT_ON); + return true; + + default: + } + } + else if (PreferenceConstants.KEYMODE_RIGHT.equals(keymode)) { + if (keyCode == KeyEvent.KEYCODE_ALT_RIGHT + && (metaState & META_SLASH) != 0) { + metaState &= ~(META_SLASH | META_TRANSIENT); + buffer.write('/'); + return true; + } + else if (keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT + && (metaState & META_TAB) != 0) { + metaState &= ~(META_TAB | META_TRANSIENT); + buffer.keyPressed(vt320.KEY_TAB, ' ', getStateForBuffer()); + return true; + } + } + else if (PreferenceConstants.KEYMODE_LEFT.equals(keymode)) { + if (keyCode == KeyEvent.KEYCODE_ALT_LEFT + && (metaState & META_SLASH) != 0) { + metaState &= ~(META_SLASH | META_TRANSIENT); + buffer.write('/'); + return true; + } + else if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT + && (metaState & META_TAB) != 0) { + metaState &= ~(META_TAB | META_TRANSIENT); + buffer.keyPressed(vt320.KEY_TAB, ' ', getStateForBuffer()); + return true; + } + } + + return false; + } + + bridge.resetScrollPosition(); + + if (keyCode == KeyEvent.KEYCODE_UNKNOWN && + event.getAction() == KeyEvent.ACTION_MULTIPLE) { + sendEncoded(event.getCharacters()); + return true; + } + + int curMetaState = event.getMetaState(); + final int orgMetaState = curMetaState; + + if ((metaState & META_SHIFT_MASK) != 0) { + curMetaState |= KeyEvent.META_SHIFT_ON; + } + + if ((metaState & META_ALT_MASK) != 0) { + curMetaState |= KeyEvent.META_ALT_ON; + } + + int uchar = event.getUnicodeChar(curMetaState); + + // no hard keyboard? ALT-k should pass through to below + if ((orgMetaState & KeyEvent.META_ALT_ON) != 0 && + (!hardKeyboard || hardKeyboardHidden)) { + uchar = 0; + } + + if ((uchar & KeyCharacterMap.COMBINING_ACCENT) != 0) { + mDeadKey = uchar & KeyCharacterMap.COMBINING_ACCENT_MASK; + return true; + } + + if (mDeadKey != 0 && uchar != 0) { + uchar = KeyCharacterMap.getDeadChar(mDeadKey, uchar); + mDeadKey = 0; + } + + // handle customized keymaps + if (customKeymapAction(v, keyCode, event)) + return true; + + if (v != null) { + //Show up the CharacterPickerDialog when the SYM key is pressed + if ((isSymKey(keyCode) || uchar == KeyCharacterMap.PICKER_DIALOG_INPUT)) { + bridge.showCharPickerDialog(); + + if (metaState == 4) { // reset fn-key state + metaState = 0; + bridge.redraw(); + } + + return true; + } + else if (keyCode == KeyEvent.KEYCODE_SEARCH) { + //Show up the URL scan dialog when the search key is pressed + urlScan(v); + return true; + } + } + + // otherwise pass through to existing session + // print normal keys + if (uchar > 0x00 && keyCode != KeyEvent.KEYCODE_ENTER) { + metaState &= ~(META_SLASH | META_TAB); + // Remove shift and alt modifiers + final int lastMetaState = metaState; + metaState &= ~(META_SHIFT_ON | META_ALT_ON); + + if (metaState != lastMetaState) { + bridge.redraw(); + } + + if ((metaState & META_CTRL_MASK) != 0) { + metaState &= ~META_CTRL_ON; + bridge.redraw(); + + // If there is no hard keyboard or there is a hard keyboard currently hidden, + // CTRL-1 through CTRL-9 will send F1 through F9 + if ((!hardKeyboard || hardKeyboardHidden) && sendFunctionKey(keyCode)) + return true; + + uchar = keyAsControl(uchar); + } + + // handle pressing f-keys + if ((hardKeyboard && !hardKeyboardHidden) + && (curMetaState & KeyEvent.META_ALT_ON) != 0 + && (curMetaState & KeyEvent.META_SHIFT_ON) != 0 + && sendFunctionKey(keyCode)) + return true; + + if (uchar < 0x80) + buffer.write(uchar); + else + sendEncoded(new String(Character.toChars(uchar))); + + return true; + } + + // send ctrl and meta-keys as appropriate + if (!hardKeyboard || hardKeyboardHidden) { + int k = event.getUnicodeChar(0); + int k0 = k; + boolean sendCtrl = false; + boolean sendMeta = false; + + if (k != 0) { + if ((orgMetaState & HC_META_CTRL_ON) != 0) { + k = keyAsControl(k); + if (k != k0) sendCtrl = true; + // send F1-F10 via CTRL-1 through CTRL-0 + if (!sendCtrl && sendFunctionKey(keyCode)) + return true; + } + else if ((orgMetaState & KeyEvent.META_ALT_ON) != 0) { + sendMeta = true; + sendEscape(); + } + + if (sendMeta || sendCtrl) { + buffer.write(k); + return true; + } + } + } + + // handle meta and f-keys for full hardware keyboard + if (hardKeyboard && !hardKeyboardHidden && fullKeyboard()) { + int k = event.getUnicodeChar(orgMetaState & KeyEvent.META_SHIFT_ON); + int k0 = k; + + if (k != 0) { + if ((orgMetaState & HC_META_CTRL_ON) != 0) { + k = keyAsControl(k); + if (k != k0) buffer.write(k); + return true; + } + else if ((orgMetaState & KeyEvent.META_ALT_ON) != 0) { + sendEscape(); + buffer.write(k); + return true; + } + } + + if (sendFullSpecialKey(keyCode)) + return true; + } + + // try handling keymode shortcuts + if (hardKeyboard && !hardKeyboardHidden && (repeat == 0)) { + if (PreferenceConstants.KEYMODE_RIGHT.equals(keymode)) { + switch (keyCode) { + case KeyEvent.KEYCODE_ALT_RIGHT: + metaState |= META_SLASH; + return true; + + case KeyEvent.KEYCODE_SHIFT_RIGHT: + metaState |= META_TAB; + return true; + + case KeyEvent.KEYCODE_SHIFT_LEFT: + metaPress(META_SHIFT_ON); + return true; + + case KeyEvent.KEYCODE_ALT_LEFT: + metaPress(META_ALT_ON); + return true; + } + } + else if (PreferenceConstants.KEYMODE_LEFT.equals(keymode)) { + switch (keyCode) { + case KeyEvent.KEYCODE_ALT_LEFT: + metaState |= META_SLASH; + return true; + + case KeyEvent.KEYCODE_SHIFT_LEFT: + metaState |= META_TAB; + return true; + + case KeyEvent.KEYCODE_SHIFT_RIGHT: + metaPress(META_SHIFT_ON); + return true; + + case KeyEvent.KEYCODE_ALT_RIGHT: + metaPress(META_ALT_ON); + return true; + } + } + else { + switch (keyCode) { + case KeyEvent.KEYCODE_ALT_RIGHT: + case KeyEvent.KEYCODE_ALT_LEFT: + metaPress(META_ALT_ON); + return true; + + case KeyEvent.KEYCODE_SHIFT_LEFT: + case KeyEvent.KEYCODE_SHIFT_RIGHT: + metaPress(META_SHIFT_ON); + return true; + } + } + + // Handle hardware CTRL keys + if (keyCode == KeyEvent.KEYCODE_CTRL_LEFT || + keyCode == KeyEvent.KEYCODE_CTRL_RIGHT) { + ctrlKeySpecial(); + return true; + } + } + + // look for special chars + switch (keyCode) { + case KeyEvent.KEYCODE_ESCAPE: + sendEscape(); + return true; + + case KeyEvent.KEYCODE_TAB: + buffer.keyPressed(vt320.KEY_TAB, ' ', getStateForBuffer()); + return true; + + case KeyEvent.KEYCODE_PAGE_DOWN: + buffer.keyPressed(vt320.KEY_PAGE_DOWN, ' ', getStateForBuffer()); + metaState &= ~META_TRANSIENT; + bridge.tryKeyVibrate(); + return true; + + case KeyEvent.KEYCODE_PAGE_UP: + buffer.keyPressed(vt320.KEY_PAGE_UP, ' ', getStateForBuffer()); + metaState &= ~META_TRANSIENT; + bridge.tryKeyVibrate(); + return true; + + case KeyEvent.KEYCODE_MOVE_HOME: + buffer.keyPressed(vt320.KEY_HOME, ' ', getStateForBuffer()); + metaState &= ~META_TRANSIENT; + bridge.tryKeyVibrate(); + return true; + + case KeyEvent.KEYCODE_MOVE_END: + buffer.keyPressed(vt320.KEY_END, ' ', getStateForBuffer()); + metaState &= ~META_TRANSIENT; + bridge.tryKeyVibrate(); + return true; + + case KeyEvent.KEYCODE_DEL: + if ((metaState & META_ALT_MASK) != 0) { + buffer.keyPressed(vt320.KEY_INSERT, ' ', getStateForBuffer()); + } + else { + buffer.keyPressed(vt320.KEY_BACK_SPACE, ' ', getStateForBuffer()); + } + + metaState &= ~META_TRANSIENT; + return true; + + case KeyEvent.KEYCODE_ENTER: + buffer.keyPressed(vt320.KEY_ENTER, ' ', getStateForBuffer()); + metaState &= ~META_TRANSIENT; + return true; + + case KeyEvent.KEYCODE_DPAD_LEFT: + if (selectingForCopy) { + selectionArea.decrementColumn(); + bridge.redraw(); + } + else { + if ((metaState & META_ALT_MASK) != 0) { + buffer.keyPressed(vt320.KEY_HOME, ' ', getStateForBuffer()); + } + else { + buffer.keyPressed(vt320.KEY_LEFT, ' ', getStateForBuffer()); + } + + metaState &= ~META_TRANSIENT; + bridge.tryKeyVibrate(); + } + + return true; + + case KeyEvent.KEYCODE_DPAD_UP: + if (selectingForCopy) { + selectionArea.decrementRow(); + bridge.redraw(); + } + else { + if ((metaState & META_ALT_MASK) != 0) { + buffer.keyPressed(vt320.KEY_PAGE_UP, ' ', getStateForBuffer()); + } + else { + buffer.keyPressed(vt320.KEY_UP, ' ', getStateForBuffer()); + } + + metaState &= ~META_TRANSIENT; + bridge.tryKeyVibrate(); + } + + return true; + + case KeyEvent.KEYCODE_DPAD_DOWN: + if (selectingForCopy) { + selectionArea.incrementRow(); + bridge.redraw(); + } + else { + if ((metaState & META_ALT_MASK) != 0) { + buffer.keyPressed(vt320.KEY_PAGE_DOWN, ' ', getStateForBuffer()); + } + else { + buffer.keyPressed(vt320.KEY_DOWN, ' ', getStateForBuffer()); + } + + metaState &= ~META_TRANSIENT; + bridge.tryKeyVibrate(); + } + + return true; + + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (selectingForCopy) { + selectionArea.incrementColumn(); + bridge.redraw(); + } + else { + if ((metaState & META_ALT_MASK) != 0) { + buffer.keyPressed(vt320.KEY_END, ' ', getStateForBuffer()); + } + else { + buffer.keyPressed(vt320.KEY_RIGHT, ' ', getStateForBuffer()); + } + + metaState &= ~META_TRANSIENT; + bridge.tryKeyVibrate(); + } + + return true; + + case KeyEvent.KEYCODE_DPAD_CENTER: + ctrlKeySpecial(); + return true; + } + } + catch (NullPointerException npe) { + Log.d(TAG, "Input before connection established ignored."); + return true; + } + + return false; + } + + private boolean handleShortcuts(View v, int keyCode, KeyEvent event, int repeat, boolean down) { + String hwbuttonShortcut; + + switch (keyCode) { + case KeyEvent.KEYCODE_CAMERA: + // check to see which shortcut the camera button triggers + hwbuttonShortcut = manager.prefs.getString( + PreferenceConstants.CAMERA, + PreferenceConstants.HWBUTTON_SCREEN_CAPTURE); + return (handleShortcut(v, hwbuttonShortcut, repeat, down)); + + case KeyEvent.KEYCODE_VOLUME_UP: + // check to see which shortcut the volume button triggers + hwbuttonShortcut = manager.prefs.getString( + PreferenceConstants.VOLUP, + PreferenceConstants.HWBUTTON_FUNCTION_KEYS); + return (handleShortcut(v, hwbuttonShortcut, repeat, down)); + + case KeyEvent.KEYCODE_VOLUME_DOWN: + // check to see which shortcut the camera button triggers + hwbuttonShortcut = manager.prefs.getString( + PreferenceConstants.VOLDN, + PreferenceConstants.HWBUTTON_TAB); + return (handleShortcut(v, hwbuttonShortcut, repeat, down)); + + case KeyEvent.KEYCODE_SEARCH: + // check to see which shortcut the search button triggers + hwbuttonShortcut = manager.prefs.getString( + PreferenceConstants.SEARCH, + PreferenceConstants.HWBUTTON_ESC); + return (handleShortcut(v, hwbuttonShortcut, repeat, down)); + + case KeyEvent.KEYCODE_BUTTON_L2: + // check to see which shortcut the ptt button triggers + hwbuttonShortcut = manager.prefs.getString( + PreferenceConstants.PTT, + PreferenceConstants.HWBUTTON_MONITOR); + return (handleShortcut(v, hwbuttonShortcut, repeat, down)); + + default: return false; + } + } + + private boolean handleShortcut(View v, String shortcut, int repeat, boolean down) { + if (PreferenceConstants.HWBUTTON_DECREASE_FONTSIZE.equals(shortcut)) { + if (!down) return false; + + bridge.decreaseFontSize(); + } + else if (PreferenceConstants.HWBUTTON_INCREASE_FONTSIZE.equals(shortcut)) { + if (!down) return false; + + bridge.increaseFontSize(); + } + else if (PreferenceConstants.HWBUTTON_FUNCTION_KEYS.equals(shortcut)) { + if (repeat > 0) return false; + + if (!down) return false; + + bridge.showFKeysDialog(); + } + else if (PreferenceConstants.HWBUTTON_MONITOR.equals(shortcut)) { + if (repeat > 0) return false; + + buffer.monitorKey(down); + } + else if (PreferenceConstants.HWBUTTON_SCREEN_CAPTURE.equals(shortcut)) { + if (repeat > 0) return false; + + if (!down) return false; + + bridge.captureScreen(); + } + else if (PreferenceConstants.HWBUTTON_CTRL.equals(shortcut)) { + if (!down) return false; + + showMetakeyToast(v, PreferenceConstants.HWBUTTON_CTRL); + metaPress(META_CTRL_ON); + } + else if (PreferenceConstants.HWBUTTON_TAB.equals(shortcut)) { + if (!down) return false; + + buffer.keyPressed(vt320.KEY_TAB, ' ', getStateForBuffer()); + } + else if (PreferenceConstants.HWBUTTON_CTRLA_SPACE.equals(shortcut)) { + if (!down) return false; + + buffer.write(0x01); + buffer.write(' '); + } + else if (PreferenceConstants.HWBUTTON_CTRLA.equals(shortcut)) { + if (!down) return false; + + buffer.write(0x01); + } + else if (PreferenceConstants.HWBUTTON_ESC.equals(shortcut)) { + if (!down) return false; + + showMetakeyToast(v, PreferenceConstants.HWBUTTON_ESC); + sendEscape(); + } + else if (PreferenceConstants.HWBUTTON_ESC_A.equals(shortcut)) { + if (!down) return false; + + sendEscape(); + buffer.write('a'); + } + else { + return (false); + } + + return (true); + } + + private void showMetakeyToast(View v, String keyname) { + Log.d(TAG, keyname); + } + + public int keyAsControl(int key) { + // Support CTRL-a through CTRL-z + if (key >= 0x60 && key <= 0x7A) + key -= 0x60; + // Support CTRL-A through CTRL-_ + else if (key >= 0x40 && key <= 0x5F) + key -= 0x40; + // CTRL-space sends NULL + else if (key == 0x20) + key = 0x00; + // CTRL-? sends DEL + else if (key == 0x3F) + key = 0x7F; + + return key; + } + + /** + * @param key + * @return successful + */ + private boolean sendFunctionKey(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_1: + buffer.keyPressed(vt320.KEY_F1, ' ', 0); + return true; + + case KeyEvent.KEYCODE_2: + buffer.keyPressed(vt320.KEY_F2, ' ', 0); + return true; + + case KeyEvent.KEYCODE_3: + buffer.keyPressed(vt320.KEY_F3, ' ', 0); + return true; + + case KeyEvent.KEYCODE_4: + buffer.keyPressed(vt320.KEY_F4, ' ', 0); + return true; + + case KeyEvent.KEYCODE_5: + buffer.keyPressed(vt320.KEY_F5, ' ', 0); + return true; + + case KeyEvent.KEYCODE_6: + buffer.keyPressed(vt320.KEY_F6, ' ', 0); + return true; + + case KeyEvent.KEYCODE_7: + buffer.keyPressed(vt320.KEY_F7, ' ', 0); + return true; + + case KeyEvent.KEYCODE_8: + buffer.keyPressed(vt320.KEY_F8, ' ', 0); + return true; + + case KeyEvent.KEYCODE_9: + buffer.keyPressed(vt320.KEY_F9, ' ', 0); + return true; + + case KeyEvent.KEYCODE_0: + buffer.keyPressed(vt320.KEY_F10, ' ', 0); + return true; + + default: + return false; + } + } + + private boolean sendFullSpecialKey(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_F1: + buffer.keyPressed(vt320.KEY_F1, ' ', 0); + return true; + + case KeyEvent.KEYCODE_F2: + buffer.keyPressed(vt320.KEY_F2, ' ', 0); + return true; + + case KeyEvent.KEYCODE_F3: + buffer.keyPressed(vt320.KEY_F3, ' ', 0); + return true; + + case KeyEvent.KEYCODE_F4: + buffer.keyPressed(vt320.KEY_F4, ' ', 0); + return true; + + case KeyEvent.KEYCODE_F5: + buffer.keyPressed(vt320.KEY_F5, ' ', 0); + return true; + + case KeyEvent.KEYCODE_F6: + buffer.keyPressed(vt320.KEY_F6, ' ', 0); + return true; + + case KeyEvent.KEYCODE_F7: + buffer.keyPressed(vt320.KEY_F7, ' ', 0); + return true; + + case KeyEvent.KEYCODE_F8: + buffer.keyPressed(vt320.KEY_F8, ' ', 0); + return true; + + case KeyEvent.KEYCODE_F9: + buffer.keyPressed(vt320.KEY_F9, ' ', 0); + return true; + + case KeyEvent.KEYCODE_F10: + buffer.keyPressed(vt320.KEY_F10, ' ', 0); + return true; + + case KeyEvent.KEYCODE_F11: + buffer.keyPressed(vt320.KEY_F11, ' ', 0); + return true; + + case KeyEvent.KEYCODE_F12: + buffer.keyPressed(vt320.KEY_F12, ' ', 0); + return true; + + case KeyEvent.KEYCODE_INSERT: + buffer.keyPressed(vt320.KEY_INSERT, ' ', 0); + return true; + + case KeyEvent.KEYCODE_FORWARD_DEL: + buffer.keyPressed(vt320.KEY_DELETE, ' ', 0); + return true; + + /* + case KeyEvent.KEYCODE_PAGE_UP: + buffer.keyPressed(vt320.KEY_PAGE_UP, ' ', 0); + return true; + case KeyEvent.KEYCODE_PAGE_DOWN: + buffer.keyPressed(vt320.KEY_PAGE_DOWN, ' ', 0); + return true; + case KeyEvent.KEYCODE_MOVE_HOME: + buffer.keyPressed(vt320.KEY_HOME, ' ', getStateForBuffer()); + return true; + case KeyEvent.KEYCODE_MOVE_END: + buffer.keyPressed(vt320.KEY_END, ' ', getStateForBuffer()); + return true; + */ + default: + return false; + } + } + + /** + * Handle meta key presses for full hardware keyboard + */ + private void metaKeyDown(int code) { + if ((metaState & code) == 0) { + metaState |= code; + bridge.redraw(); + } + } + + protected void metaKeyUp(int code) { + if ((metaState & code) != 0) { + metaState &= ~code; + bridge.redraw(); + } + } + + /** + * Handle meta key presses where the key can be locked on. + *

+ * 1st press: next key to have meta state
+ * 2nd press: meta state is locked on
+ * 3rd press: disable meta state + * + * @param code + */ + public void metaPress(int code) { + if ((metaState & (code << 1)) != 0) { + metaState &= ~(code << 1); + } + else if ((metaState & code) != 0) { + metaState &= ~code; + + if (!fullKeyboard()) + metaState |= code << 1; + } + else + metaState |= code; + + bridge.redraw(); + } + + public void setTerminalKeyMode(String keymode) { + this.keymode = keymode; + } + + private int getStateForBuffer() { + int bufferState = 0; + + if ((metaState & META_CTRL_MASK) != 0) + bufferState |= vt320.KEY_CONTROL; + + if ((metaState & META_SHIFT_MASK) != 0) + bufferState |= vt320.KEY_SHIFT; + + if ((metaState & META_ALT_MASK) != 0) + bufferState |= vt320.KEY_ALT; + + return bufferState; + } + + public int getMetaState() { + return metaState; + } + + public int getDeadKey() { + return mDeadKey; + } + + public void setClipboardManager(ClipboardManager clipboard) { + this.clipboard = clipboard; + } + + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, + String key) { + if (PreferenceConstants.KEYMODE.equals(key)) { + updateKeymode(); + } + else if (PreferenceConstants.CUSTOM_KEYMAP.equals(key)) { + updateCustomKeymap(); + } + } + + private void updateKeymode() { + keymode = prefs.getString(PreferenceConstants.KEYMODE, PreferenceConstants.KEYMODE_RIGHT); + } + + private void updateCustomKeymap() { + customKeyboard = prefs.getString(PreferenceConstants.CUSTOM_KEYMAP, + PreferenceConstants.CUSTOM_KEYMAP_DISABLED); + } + + public void setCharset(String encoding) { + this.encoding = encoding; + } + + public Charset getCharset() { + return Charset.forName(encoding); + } + + protected void ctrlKeySpecial() { + if (selectingForCopy) { + if (selectionArea.isSelectingOrigin()) + selectionArea.finishSelectingOrigin(); + else { + if (clipboard != null) { + // copy selected area to clipboard + String copiedText = selectionArea.copyFrom(buffer); + clipboard.setText(copiedText); + selectingForCopy = false; + selectionArea.reset(); + } + } + } + else { + if ((metaState & META_CTRL_ON) != 0) { + sendEscape(); + metaState &= ~META_CTRL_ON; + } + else + metaPress(META_CTRL_ON); + } + + bridge.redraw(); + } + + protected boolean customKeymapAction(View v, int keyCode, KeyEvent event) { + if (bridge == null || customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_DISABLED)) + return false; + + byte c = 0x00; + int termKey = 0; + + if (fullKeyboard()) { + switch (keyCode) { + case KeyEvent.KEYCODE_CTRL_LEFT: + case KeyEvent.KEYCODE_CTRL_RIGHT: + metaKeyDown(META_CTRL_ON); + return true; + + case KeyEvent.KEYCODE_ALT_LEFT: + case KeyEvent.KEYCODE_ALT_RIGHT: + metaKeyDown(META_ALT_ON); + return true; + + case KeyEvent.KEYCODE_SHIFT_LEFT: + case KeyEvent.KEYCODE_SHIFT_RIGHT: + metaKeyDown(META_SHIFT_ON); + return true; + + case KeyEvent.KEYCODE_BACK: + if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_ASUS_TF)) { + // Check to see whether this is the back button on the + // screen (-1) or the Asus Transformer Keyboard Dock. + // Treat the HW button as ESC. + if (event.getDeviceId() > 0) { + sendEscape(); + return true; + } + } + + default: + } + } + + if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_ASUS_TF)) { + if ((metaState & META_ALT_MASK) != 0 + && (metaState & META_SHIFT_MASK) != 0 + && sendFunctionKey(keyCode)) + return true; + } + else if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_SE_XPPRO)) { + // Sony Ericsson Xperia pro (MK16i) and Xperia mini Pro (SK17i) + // Language key acts as CTRL + if (keyCode == KeyEvent.KEYCODE_SWITCH_CHARSET) { + ctrlKeySpecial(); + return true; + } + + if ((metaState & META_ALT_MASK) != 0) { + if ((metaState & META_SHIFT_MASK) != 0) { + // ALT + shift + key + switch (keyCode) { + case KeyEvent.KEYCODE_U: + c = 0x5B; + break; + + case KeyEvent.KEYCODE_I: + c = 0x5D; + break; + + case KeyEvent.KEYCODE_O: + c = 0x7B; + break; + + case KeyEvent.KEYCODE_P: + c = 0x7D; + break; + } + } + else { + // ALT + key + switch (keyCode) { + case KeyEvent.KEYCODE_S: + c = 0x7c; + break; + + case KeyEvent.KEYCODE_Z: + c = 0x5c; + break; + + case KeyEvent.KEYCODE_DEL: + termKey = vt320.KEY_DELETE; + break; + } + } + } + else if ((metaState & META_SHIFT_MASK) != 0) { + // shift + key + switch (keyCode) { + case KeyEvent.KEYCODE_AT: + c = 0x3c; + break; + + case KeyEvent.KEYCODE_COMMA: + c = 0x3e; + break; + + case KeyEvent.KEYCODE_PERIOD: + c = 0x5e; + break; + + case KeyEvent.KEYCODE_GRAVE: + c = 0x60; + break; + + case KeyEvent.KEYCODE_APOSTROPHE: + c = 0x7e; + break; + + case KeyEvent.KEYCODE_DEL: + termKey = vt320.KEY_BACK_SPACE; + break; + } + } + } + else if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_SGH_I927)) { + // Samsung Captivate Glide (SGH-i927) + if (keyCode == 115) { + // .com key = ESC + c = 0x00; + termKey = vt320.KEY_ESCAPE; + return true; + } + else if (keyCode == 116) { + // Microphone key = TAB + c = 0x00; + termKey = vt320.KEY_TAB; + } + else if ((metaState & META_ALT_MASK) != 0 && (metaState & META_SHIFT_MASK) != 0) { + switch (keyCode) { + case KeyEvent.KEYCODE_O: + c = 0x5B; + break; + + case KeyEvent.KEYCODE_P: + c = 0x5D; + break; + + case KeyEvent.KEYCODE_A: + c = 0x3C; + break; + + case KeyEvent.KEYCODE_D: + c = 0x3E; + break; + } + } + } + else if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_SGH_I927_ICS)) { + // Samsung Captivate Glide (SGH-i927) Ice Cream Sandwich (4.0.x) + if (keyCode == 226) { + // .com key = ESC + c = 0x00; + termKey = vt320.KEY_ESCAPE; + } + else if (keyCode == 220) { + // Microphone key = TAB + c = 0x00; + termKey = vt320.KEY_TAB; + } + else if ((metaState & META_ALT_MASK) != 0 && (metaState & META_SHIFT_MASK) != 0) { + switch (keyCode) { + case KeyEvent.KEYCODE_O: + c = 0x5B; + break; + + case KeyEvent.KEYCODE_P: + c = 0x5D; + break; + + case KeyEvent.KEYCODE_A: + c = 0x3C; + break; + + case KeyEvent.KEYCODE_D: + c = 0x3E; + break; + } + } + } + + if ((c != 0x00) || termKey != 0) { + if (c != 0x00) + buffer.write(c); + else + buffer.keyPressed(termKey, ' ', 0); + + metaState &= ~(META_SHIFT_ON | META_ALT_ON); + bridge.redraw(); + return true; + } + + return false; + } + + public void urlScan(View v) { + //final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); + List urls = bridge.scanForURLs(); + Dialog urlDialog = new Dialog(v.getContext()); + urlDialog.setTitle(R.string.console_menu_urlscan); + ListView urlListView = new ListView(v.getContext()); + URLItemListener urlListener = new URLItemListener(v.getContext()); + urlListView.setOnItemClickListener(urlListener); + urlListView.setAdapter(new ArrayAdapter (v.getContext(), android.R.layout.simple_list_item_1, urls)); + urlDialog.setContentView(urlListView); + urlDialog.show(); + } + + public boolean isSymKey(int keyCode) { + if (keyCode == KeyEvent.KEYCODE_SYM || + keyCode == KeyEvent.KEYCODE_PICTSYMBOLS) + return true; + + if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_SGH_I927_ICS) && + keyCode == 227) + return true; + + return false; + } + + protected boolean fullKeyboard() { + if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_FULL) || + (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_ASUS_TF))) + return true; + + return false; + } + + private class URLItemListener implements OnItemClickListener { + private WeakReference contextRef; + + URLItemListener(Context context) { + this.contextRef = new WeakReference (context); + } + + public void onItemClick(AdapterView arg0, View view, int position, + long id) { + Context context = contextRef.get(); + + if (context == null) + return; + + try { + TextView urlView = (TextView) view; + String url = urlView.getText().toString(); + + if (url.indexOf("://") < 0) + url = "http://" + url; + + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + context.startActivity(intent); + } + catch (Exception e) { + Log.e(TAG, "couldn't open URL", e); + // We should probably tell the user that we couldn't find a + // handler... + } + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/service/TerminalManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/service/TerminalManager.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,730 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.service; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Timer; +import java.util.TimerTask; + +import com.five_ten_sg.connectbot.R; +import com.five_ten_sg.connectbot.bean.HostBean; +import com.five_ten_sg.connectbot.bean.PubkeyBean; +import com.five_ten_sg.connectbot.transport.TransportFactory; +import com.five_ten_sg.connectbot.util.HostDatabase; +import com.five_ten_sg.connectbot.util.PreferenceConstants; +import com.five_ten_sg.connectbot.util.PubkeyDatabase; +import com.five_ten_sg.connectbot.util.PubkeyUtils; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.content.res.AssetFileDescriptor; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.MediaPlayer.OnCompletionListener; +import android.net.Uri; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Vibrator; +import android.preference.PreferenceManager; +import android.util.Log; + +/** + * Manager for SSH connections that runs as a service. This service holds a list + * of currently connected SSH bridges that are ready for connection up to a GUI + * if needed. + * + * @author jsharkey + */ +public class TerminalManager extends Service implements BridgeDisconnectedListener, OnSharedPreferenceChangeListener { + public final static String TAG = "ConnectBot.TerminalManager"; + + public List bridges = new LinkedList(); + public Map> mHostBridgeMap = + new HashMap>(); + public Map> mNicknameBridgeMap = + new HashMap>(); + + public TerminalBridge defaultBridge = null; + + public List disconnected = new LinkedList(); + + public Handler disconnectHandler = null; + + public Map loadedKeypairs = new HashMap(); + + public Resources res; + + public HostDatabase hostdb; + public PubkeyDatabase pubkeydb; + + protected SharedPreferences prefs; + + final private IBinder binder = new TerminalBinder(); + + private ConnectivityReceiver connectivityManager; + private ConnectionNotifier connectionNotifier = new ConnectionNotifier(); + + private MediaPlayer mediaPlayer; + + private Timer pubkeyTimer; + + private Timer idleTimer; + private final long IDLE_TIMEOUT = 300000; // 5 minutes + + private Vibrator vibrator; + private volatile boolean wantKeyVibration; + public static final long VIBRATE_DURATION = 30; + + private boolean wantBellVibration; + + private boolean resizeAllowed = true; + + private int fullScreen = 0; + + private boolean savingKeys; + + protected List> mPendingReconnect + = new LinkedList>(); + + public boolean hardKeyboardHidden; + + @Override + public void onCreate() { + Log.i(TAG, "Starting service"); + prefs = PreferenceManager.getDefaultSharedPreferences(this); + prefs.registerOnSharedPreferenceChangeListener(this); + res = getResources(); + pubkeyTimer = new Timer("pubkeyTimer", true); + hostdb = new HostDatabase(this); + pubkeydb = new PubkeyDatabase(this); + // load all marked pubkeys into memory + updateSavingKeys(); + List pubkeys = pubkeydb.getAllStartPubkeys(); + + for (PubkeyBean pubkey : pubkeys) { + try { + PrivateKey privKey = PubkeyUtils.decodePrivate(pubkey.getPrivateKey(), pubkey.getType()); + PublicKey pubKey = PubkeyUtils.decodePublic(pubkey.getPublicKey(), pubkey.getType()); + KeyPair pair = new KeyPair(pubKey, privKey); + addKey(pubkey, pair); + } + catch (Exception e) { + Log.d(TAG, String.format("Problem adding key '%s' to in-memory cache", pubkey.getNickname()), e); + } + } + + vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); + wantKeyVibration = prefs.getBoolean(PreferenceConstants.BUMPY_ARROWS, true); + wantBellVibration = prefs.getBoolean(PreferenceConstants.BELL_VIBRATE, true); + enableMediaPlayer(); + hardKeyboardHidden = (res.getConfiguration().hardKeyboardHidden == + Configuration.HARDKEYBOARDHIDDEN_YES); + final boolean lockingWifi = prefs.getBoolean(PreferenceConstants.WIFI_LOCK, true); + connectivityManager = new ConnectivityReceiver(this, lockingWifi); + } + + private void updateSavingKeys() { + savingKeys = prefs.getBoolean(PreferenceConstants.MEMKEYS, true); + } + + @Override + public void onDestroy() { + Log.i(TAG, "Destroying service"); + disconnectAll(true); + + if (hostdb != null) { + hostdb.close(); + hostdb = null; + } + + if (pubkeydb != null) { + pubkeydb.close(); + pubkeydb = null; + } + + synchronized (this) { + if (idleTimer != null) + idleTimer.cancel(); + + if (pubkeyTimer != null) + pubkeyTimer.cancel(); + } + + connectivityManager.cleanup(); + connectionNotifier.hideRunningNotification(this); + disableMediaPlayer(); + } + + /** + * Disconnect all currently connected bridges. + */ + private void disconnectAll(final boolean immediate) { + disconnectAll(immediate, false); + } + + /** + * Disconnect all currently connected bridges. + */ + private void disconnectAll(final boolean immediate, boolean onlyRemote) { + TerminalBridge[] tmpBridges = null; + + synchronized (bridges) { + if (bridges.size() > 0) { + tmpBridges = bridges.toArray(new TerminalBridge[bridges.size()]); + } + } + + if (tmpBridges != null) { + // disconnect and dispose of any existing bridges + for (int i = 0; i < tmpBridges.length; i++) { + if (!onlyRemote || !(tmpBridges[i].transport instanceof com.five_ten_sg.connectbot.transport.Local)) + tmpBridges[i].dispatchDisconnect(immediate); + } + } + } + + /** + * Open a new SSH session using the given parameters. + */ + private TerminalBridge openConnection(HostBean host) throws IllegalArgumentException, IOException { + // throw exception if terminal already open + if (getConnectedBridge(host) != null) { + throw new IllegalArgumentException("Connection already open for that nickname"); + } + + TerminalBridge bridge = new TerminalBridge(this, host, getApplicationInfo().dataDir); + bridge.setOnDisconnectedListener(this); + bridge.startConnection(); + + synchronized (bridges) { + bridges.add(bridge); + WeakReference wr = new WeakReference (bridge); + mHostBridgeMap.put(bridge.host, wr); + mNicknameBridgeMap.put(bridge.host.getNickname(), wr); + } + + synchronized (disconnected) { + disconnected.remove(bridge.host); + } + + if (bridge.isUsingNetwork()) { + connectivityManager.incRef(); + } + + if (prefs.getBoolean(PreferenceConstants.CONNECTION_PERSIST, true)) { + connectionNotifier.showRunningNotification(this); + } + + // also update database with new connected time + touchHost(host); + return bridge; + } + + public String getEmulation() { + return prefs.getString(PreferenceConstants.EMULATION, "xterm-256color"); + } + + public int getScrollback() { + int scrollback = 140; + + try { + scrollback = Integer.parseInt(prefs.getString(PreferenceConstants.SCROLLBACK, "140")); + } + catch (Exception e) { + } + + return scrollback; + } + + /** + * Open a new connection by reading parameters from the given URI. Follows + * format specified by an individual transport. + */ + public TerminalBridge openConnection(Uri uri) throws Exception { + HostBean host = TransportFactory.findHost(hostdb, uri); + + if (host == null) + host = TransportFactory.getTransport(uri.getScheme()).createHost(uri); + + return openConnection(host); + } + + /** + * Update the last-connected value for the given nickname by passing through + * to {@link HostDatabase}. + */ + private void touchHost(HostBean host) { + hostdb.touchHost(host); + } + + /** + * Find a connected {@link TerminalBridge} with the given HostBean. + * + * @param host the HostBean to search for + * @return TerminalBridge that uses the HostBean + */ + public TerminalBridge getConnectedBridge(HostBean host) { + WeakReference wr = mHostBridgeMap.get(host); + + if (wr != null) { + return wr.get(); + } + else { + return null; + } + } + + /** + * Find a connected {@link TerminalBridge} using its nickname. + * + * @param nickname + * @return TerminalBridge that matches nickname + */ + public TerminalBridge getConnectedBridge(final String nickname) { + if (nickname == null) { + return null; + } + + WeakReference wr = mNicknameBridgeMap.get(nickname); + + if (wr != null) { + return wr.get(); + } + else { + return null; + } + } + + /** + * Called by child bridge when somehow it's been disconnected. + */ + public void onDisconnected(TerminalBridge bridge) { + boolean shouldHideRunningNotification = false; + + synchronized (bridges) { + // remove this bridge from our list + bridges.remove(bridge); + mHostBridgeMap.remove(bridge.host); + mNicknameBridgeMap.remove(bridge.host.getNickname()); + + if (bridge.isUsingNetwork()) { + connectivityManager.decRef(); + } + + if (bridges.size() == 0 && + mPendingReconnect.size() == 0) { + shouldHideRunningNotification = true; + } + } + + synchronized (disconnected) { + disconnected.add(bridge.host); + } + + if (shouldHideRunningNotification) { + connectionNotifier.hideRunningNotification(this); + } + + // pass notification back up to gui + if (disconnectHandler != null) + Message.obtain(disconnectHandler, -1, bridge).sendToTarget(); + } + + public boolean isKeyLoaded(String nickname) { + return loadedKeypairs.containsKey(nickname); + } + + public void addKey(PubkeyBean pubkey, KeyPair pair) { + addKey(pubkey, pair, false); + } + + public void addKey(PubkeyBean pubkey, KeyPair pair, boolean force) { + if (!savingKeys && !force) + return; + + removeKey(pubkey.getNickname()); + byte[] sshPubKey = PubkeyUtils.extractOpenSSHPublic(pair); + KeyHolder keyHolder = new KeyHolder(); + keyHolder.bean = pubkey; + keyHolder.pair = pair; + keyHolder.openSSHPubkey = sshPubKey; + loadedKeypairs.put(pubkey.getNickname(), keyHolder); + + if (pubkey.getLifetime() > 0) { + final String nickname = pubkey.getNickname(); + pubkeyTimer.schedule(new TimerTask() { + @Override + public void run() { + Log.d(TAG, "Unloading from memory key: " + nickname); + removeKey(nickname); + } + }, pubkey.getLifetime() * 1000); + } + + Log.d(TAG, String.format("Added key '%s' to in-memory cache", pubkey.getNickname())); + } + + public boolean removeKey(String nickname) { + Log.d(TAG, String.format("Removed key '%s' from in-memory cache", nickname)); + return loadedKeypairs.remove(nickname) != null; + } + + public boolean removeKey(byte[] publicKey) { + String nickname = null; + + for (Entry entry : loadedKeypairs.entrySet()) { + if (Arrays.equals(entry.getValue().openSSHPubkey, publicKey)) { + nickname = entry.getKey(); + break; + } + } + + if (nickname != null) { + Log.d(TAG, String.format("Removed key '%s' to in-memory cache", nickname)); + return removeKey(nickname); + } + else + return false; + } + + public KeyPair getKey(String nickname) { + if (loadedKeypairs.containsKey(nickname)) { + KeyHolder keyHolder = loadedKeypairs.get(nickname); + return keyHolder.pair; + } + else + return null; + } + + public KeyPair getKey(byte[] publicKey) { + for (KeyHolder keyHolder : loadedKeypairs.values()) { + if (Arrays.equals(keyHolder.openSSHPubkey, publicKey)) + return keyHolder.pair; + } + + return null; + } + + public String getKeyNickname(byte[] publicKey) { + for (Entry entry : loadedKeypairs.entrySet()) { + if (Arrays.equals(entry.getValue().openSSHPubkey, publicKey)) + return entry.getKey(); + } + + return null; + } + + private void stopWithDelay() { + // TODO add in a way to check whether keys loaded are encrypted and only + // set timer when we have an encrypted key loaded + if (loadedKeypairs.size() > 0) { + synchronized (this) { + if (idleTimer == null) + idleTimer = new Timer("idleTimer", true); + + idleTimer.schedule(new IdleTask(), IDLE_TIMEOUT); + } + } + else { + Log.d(TAG, "Stopping service immediately"); + stopSelf(); + } + } + + protected void stopNow() { + if (bridges.size() == 0) { + stopSelf(); + } + } + + private synchronized void stopIdleTimer() { + if (idleTimer != null) { + idleTimer.cancel(); + idleTimer = null; + } + } + + public class TerminalBinder extends Binder { + public TerminalManager getService() { + return TerminalManager.this; + } + } + + @Override + public IBinder onBind(Intent intent) { + Log.i(TAG, "Someone bound to TerminalManager"); + setResizeAllowed(true); + stopIdleTimer(); + // Make sure we stay running to maintain the bridges + startService(new Intent(this, TerminalManager.class)); + return binder; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + /* + * We want this service to continue running until it is explicitly + * stopped, so return sticky. + */ + return START_STICKY; + } + + @Override + public void onRebind(Intent intent) { + super.onRebind(intent); + setResizeAllowed(true); + Log.i(TAG, "Someone rebound to TerminalManager"); + stopIdleTimer(); + } + + @Override + public boolean onUnbind(Intent intent) { + Log.i(TAG, "Someone unbound from TerminalManager"); + setResizeAllowed(true); + + if (bridges.size() == 0) { + stopWithDelay(); + } + + return true; + } + + private class IdleTask extends TimerTask { + /* (non-Javadoc) + * @see java.util.TimerTask#run() + */ + @Override + public void run() { + Log.d(TAG, String.format("Stopping service after timeout of ~%d seconds", IDLE_TIMEOUT / 1000)); + TerminalManager.this.stopNow(); + } + } + + public void tryKeyVibrate() { + if (wantKeyVibration) + vibrate(); + } + + private void vibrate() { + if (vibrator != null) + vibrator.vibrate(VIBRATE_DURATION); + } + + private void enableMediaPlayer() { + mediaPlayer = new MediaPlayer(); + float volume = prefs.getFloat(PreferenceConstants.BELL_VOLUME, + PreferenceConstants.DEFAULT_BELL_VOLUME); + mediaPlayer.setAudioStreamType(AudioManager.STREAM_NOTIFICATION); + mediaPlayer.setOnCompletionListener(new BeepListener()); + AssetFileDescriptor file = res.openRawResourceFd(R.raw.bell); + + try { + mediaPlayer.setDataSource(file.getFileDescriptor(), file + .getStartOffset(), file.getLength()); + file.close(); + mediaPlayer.setVolume(volume, volume); + mediaPlayer.prepare(); + } + catch (IOException e) { + Log.e(TAG, "Error setting up bell media player", e); + } + } + + private void disableMediaPlayer() { + if (mediaPlayer != null) { + mediaPlayer.release(); + mediaPlayer = null; + } + } + + public void playBeep() { + if (mediaPlayer != null) + mediaPlayer.start(); + + if (wantBellVibration) + vibrate(); + } + + private static class BeepListener implements OnCompletionListener { + public void onCompletion(MediaPlayer mp) { + mp.seekTo(0); + } + } + + /** + * Send system notification to user for a certain host. When user selects + * the notification, it will bring them directly to the ConsoleActivity + * displaying the host. + * + * @param host + */ + public void sendActivityNotification(HostBean host) { + if (!prefs.getBoolean(PreferenceConstants.BELL_NOTIFICATION, false)) + return; + + connectionNotifier.showActivityNotification(this, host); + } + + /* (non-Javadoc) + * @see android.content.SharedPreferences.OnSharedPreferenceChangeListener#onSharedPreferenceChanged(android.content.SharedPreferences, java.lang.String) + */ + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, + String key) { + if (PreferenceConstants.BELL.equals(key)) { + boolean wantAudible = sharedPreferences.getBoolean( + PreferenceConstants.BELL, true); + + if (wantAudible && mediaPlayer == null) + enableMediaPlayer(); + else if (!wantAudible && mediaPlayer != null) + disableMediaPlayer(); + } + else if (PreferenceConstants.BELL_VOLUME.equals(key)) { + if (mediaPlayer != null) { + float volume = sharedPreferences.getFloat( + PreferenceConstants.BELL_VOLUME, + PreferenceConstants.DEFAULT_BELL_VOLUME); + mediaPlayer.setVolume(volume, volume); + } + } + else if (PreferenceConstants.BELL_VIBRATE.equals(key)) { + wantBellVibration = sharedPreferences.getBoolean( + PreferenceConstants.BELL_VIBRATE, true); + } + else if (PreferenceConstants.BUMPY_ARROWS.equals(key)) { + wantKeyVibration = sharedPreferences.getBoolean( + PreferenceConstants.BUMPY_ARROWS, true); + } + else if (PreferenceConstants.WIFI_LOCK.equals(key)) { + final boolean lockingWifi = prefs.getBoolean(PreferenceConstants.WIFI_LOCK, true); + connectivityManager.setWantWifiLock(lockingWifi); + } + else if (PreferenceConstants.MEMKEYS.equals(key)) { + updateSavingKeys(); + } + } + + /** + * Allow {@link TerminalBridge} to resize when the parent has changed. + * @param resizeAllowed + */ + public void setResizeAllowed(boolean resizeAllowed) { + this.resizeAllowed = resizeAllowed; + } + + public boolean isResizeAllowed() { + return resizeAllowed; + } + + public void setFullScreen(int fullScreen) { + this.fullScreen = fullScreen; + } + + public int getFullScreen() { + return this.fullScreen; + } + + public static class KeyHolder { + public PubkeyBean bean; + public KeyPair pair; + public byte[] openSSHPubkey; + } + + /** + * Called when connectivity to the network is lost and it doesn't appear + * we'll be getting a different connection any time soon. + */ + public void onConnectivityLost() { + final Thread t = new Thread() { + @Override + public void run() { + disconnectAll(false, true); + } + }; + t.setName("Disconnector"); + t.start(); + } + + /** + * Called when connectivity to the network is restored. + */ + public void onConnectivityRestored() { + final Thread t = new Thread() { + @Override + public void run() { + reconnectPending(); + } + }; + t.setName("Reconnector"); + t.start(); + } + + /** + * Insert request into reconnect queue to be executed either immediately + * or later when connectivity is restored depending on whether we're + * currently connected. + * + * @param bridge the TerminalBridge to reconnect when possible + */ + public void requestReconnect(TerminalBridge bridge) { + synchronized (mPendingReconnect) { + mPendingReconnect.add(new WeakReference (bridge)); + + if (!bridge.isUsingNetwork() || + connectivityManager.isConnected()) { + reconnectPending(); + } + } + } + + /** + * Reconnect all bridges that were pending a reconnect when connectivity + * was lost. + */ + private void reconnectPending() { + synchronized (mPendingReconnect) { + for (WeakReference ref : mPendingReconnect) { + TerminalBridge bridge = ref.get(); + + if (bridge == null) { + continue; + } + + bridge.startConnection(); + } + + mPendingReconnect.clear(); + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/service/TerminalMonitor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/service/TerminalMonitor.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,522 @@ +package com.five_ten_sg.connectbot.service; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.net.Uri; +import android.os.IBinder; +import android.util.Log; +import android.view.View; +import de.mud.terminal.vt320; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.HashMap; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; + +import com.five_ten_sg.connectbot.ConsoleActivity; +import com.five_ten_sg.connectbot.bean.HostBean; + + +public class TerminalMonitor { + public final static String TAG = "ConnectBot.TerminalMonitor"; + + public static final char MONITOR_CMD_INIT = 0; + public static final char MONITOR_CMD_ACTIVATE = 1; + public static final char MONITOR_CMD_KEYSTATE = 2; + public static final char MONITOR_CMD_CURSORMOVE = 3; + public static final char MONITOR_CMD_SCREENCHANGE = 4; + public static final char MONITOR_CMD_FIELDVALUE = 5; + public static final char MONITOR_CMD_SETFIELD = 5; + public static final char MONITOR_CMD_GETFIELD = 6; + public static final char MONITOR_CMD_SCREENWATCH = 7; + public static final char MONITOR_CMD_DEPRESS = 8; + public static final char MONITOR_CMD_SHOWURL = 9; + public static final char MONITOR_CMD_SWITCHSESSION = 10; + public static final char MONITOR_CMD_CURSORREQUEST = 11; + + public static final String[] commands = { + "cmd_init", + "cmd_activate", + "cmd_keystate", + "cmd_cursormove", + "cmd_screenchange", + "cmd_fieldvalue/setfield", + "cmd_getfield", + "cmd_screenwatch", + "cmd_depress", + "cmd_showurl", + "cmd_switchsession", + "cmd_cursorrequest" + }; + + public static final char CURSOR_REQUESTED = 0; + public static final char CURSOR_SCREEN_CHANGE = 1; + public static final char CURSOR_USER_KEY = 2; + + private static final int MONITORPORT = 6000; + private static final String LOCALHOST = "127.0.0.1"; + + private Context parent = null; + private vt320 buffer = null; + private View view = null; + private HostBean host = null; + private String init = null; + private int start_line = 0; // monitor part of the screen for changes + private int end_line = 500; // "" + private int start_column = 0; // "" + private int end_column = 500; // "" + private boolean modified = false; // used to delay screen change notifications + private boolean moved = false; // used to delay cursor moved notifications + private int to_line = 0; // "" + private int to_column = 0; // "" + private HashMap keymap = null; // map MS VK_ keys to vt320 virtual keys + private IBinder bound = null; + private Socket monitor_socket = null; + private InputStream monitor_in = null; + private OutputStream monitor_out = null; + private MyReader monitor_reader = null; + private BlockingQueue pending_commands = new ArrayBlockingQueue(100); + private MyServiceConnection monitor_connection = new MyServiceConnection(); + + class MyReader extends Thread { + private InputStream monitor_in; + private byte[] b; + private boolean is_closing = false; + + public MyReader(InputStream monitor_in) { + this.monitor_in = monitor_in; + b = new byte[100]; + Log.i(TAG, "MyReader constructor"); + } + + public void closing() { + is_closing = true; + } + + private char[] forceRead(int len) throws IOException { + int len2 = len * 2; + int off = 0; + + if (b.length < len2) b = new byte[len2]; + + while (off < len2) { + int l = monitor_in.read(b, off, len2 - off); + + if (l < 0) throw new IOException("eof"); + + off += l; + } + + return bytesToChars(b, len2); + } + + public void run() { + while (true) { + try { + char[] len = forceRead(1); + char[] packet = forceRead(len[0]); + char cmd = packet[0]; + Log.i(TAG, String.format("received %s", commands[cmd])); + + switch (cmd) { + case MONITOR_CMD_SETFIELD: + if (packet.length >= 3) + setField(packet[1], packet[2], packet, 3); + + break; + + case MONITOR_CMD_GETFIELD: + if (packet.length == 4) + getField(packet[1], packet[2], packet[3]); + + break; + + case MONITOR_CMD_SCREENWATCH: + if (packet.length == 4) + screenWatch(packet[1], packet[2], packet[3]); + + break; + + case MONITOR_CMD_DEPRESS: + if (packet.length == 2) + depress(packet[1]); + + break; + + case MONITOR_CMD_SHOWURL: + if (packet.length > 1) + showUrl(packet, 1); + + break; + + case MONITOR_CMD_SWITCHSESSION: + if (packet.length == 1) + switchSession(); + + break; + + case MONITOR_CMD_CURSORREQUEST: + if (packet.length == 1) + cursorRequest(); + + break; + + default: + break; + } + } + catch (IOException e) { + if (!is_closing) Log.e(TAG, "exception in MyReader.run()", e); + + break; + } + } + } + } + + class MyServiceConnection implements ServiceConnection { + public void onServiceConnected(ComponentName className, IBinder service) { + bound = service; + Log.i(TAG, "bound to service"); + + try { + InetAddress serverAddr = InetAddress.getByName(LOCALHOST); + int tries = 0; + while (tries < 10) { + try { + Thread.sleep(100); + monitor_socket = new Socket(serverAddr, MONITORPORT); + break; + } + catch (Exception e) { + monitor_socket = null; + Log.e(TAG, "exception connecting to monitor socket", e); + tries = tries + 1; + } + } + if (monitor_socket != null) { + Log.i(TAG, "connected to monitor socket, send init " + init); + monitor_in = monitor_socket.getInputStream(); + monitor_out = monitor_socket.getOutputStream(); + monitor_reader = new MyReader(monitor_in); + monitor_reader.start(); + String x = " " + init; + monitorWrite(MONITOR_CMD_INIT, x.toCharArray()); + char [] c; + + while (true) { + c = pending_commands.poll(); + + if (c == null) break; + + monitorWrite(c[1], c); + } + } + } + catch (IOException e) { + Log.e(TAG, "exception in onServiceConnected()", e); + } + } + public void onServiceDisconnected(ComponentName classNam) { + bound = null; + Log.i(TAG, "unbound from service"); + } + }; + + + public TerminalMonitor(Context parent, vt320 buffer, View view, HostBean host, String init) { + this.parent = parent; + this.buffer = buffer; + this.view = view; + this.host = host; + this.init = init; + // setup the windows->android keymapping + // http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731 + keymap = new HashMap(); + keymap.put(0x08, vt320.KEY_BACK_SPACE); // vk_back + keymap.put(0x09, vt320.KEY_TAB); // vk_tab + keymap.put(0x0d, vt320.KEY_ENTER); // vk_return + keymap.put(0x1b, vt320.KEY_ESCAPE); // vk_escape + keymap.put(0x21, vt320.KEY_PAGE_UP); // vk_prior + keymap.put(0x22, vt320.KEY_PAGE_DOWN); // vk_next + keymap.put(0x23, vt320.KEY_END); // vk_end + keymap.put(0x24, vt320.KEY_HOME); // vk_home + keymap.put(0x25, vt320.KEY_LEFT); // vk_left + keymap.put(0x26, vt320.KEY_UP); // vk_up + keymap.put(0x27, vt320.KEY_RIGHT); // vk_right + keymap.put(0x28, vt320.KEY_DOWN); // vk_down + keymap.put(0x2d, vt320.KEY_INSERT); // vk_insert + keymap.put(0x2e, vt320.KEY_DELETE); // vk_delete + keymap.put(0x70, vt320.KEY_F1); // vk_f1 + keymap.put(0x71, vt320.KEY_F2); // vk_f2 + keymap.put(0x72, vt320.KEY_F3); // vk_f3 + keymap.put(0x73, vt320.KEY_F4); // vk_f4 + keymap.put(0x74, vt320.KEY_F5); // vk_f5 + keymap.put(0x75, vt320.KEY_F6); // vk_f6 + keymap.put(0x76, vt320.KEY_F7); // vk_f7 + keymap.put(0x77, vt320.KEY_F8); // vk_f8 + keymap.put(0x78, vt320.KEY_F9); // vk_f9 + keymap.put(0x79, vt320.KEY_F10); // vk_f10 + keymap.put(0x7a, vt320.KEY_F11); // vk_f11 + keymap.put(0x7b, vt320.KEY_F12); // vk_f12 + keymap.put(0x7c, vt320.KEY_F13); // vk_f13 + keymap.put(0x7d, vt320.KEY_F14); // vk_f14 + keymap.put(0x7e, vt320.KEY_F15); // vk_f15 + keymap.put(0x7f, vt320.KEY_F16); // vk_f16 + keymap.put(0x80, vt320.KEY_F17); // vk_f17 + keymap.put(0x81, vt320.KEY_F18); // vk_f18 + keymap.put(0x82, vt320.KEY_F19); // vk_f19 + keymap.put(0x83, vt320.KEY_F20); // vk_f20 + keymap.put(0x84, vt320.KEY_F21); // vk_f21 + keymap.put(0x85, vt320.KEY_F22); // vk_f22 + keymap.put(0x86, vt320.KEY_F23); // vk_f23 + keymap.put(0x87, vt320.KEY_F24); // vk_f24 + // bind to the monitor service + Intent intent = new Intent("com.five_ten_sg.connectbot.monitor.MonitorService"); + parent.bindService(intent, monitor_connection, Context.BIND_AUTO_CREATE); + Log.i(TAG, "constructor"); + } + + + public void Disconnect() { + if (monitor_reader != null) monitor_reader.closing(); + + try { + if (monitor_out != null) monitor_out.close(); + + if (monitor_in != null) monitor_in.close(); + + if (monitor_socket != null) monitor_socket.close(); + + Log.i(TAG, "disconnected from monitor socket"); + } + catch (IOException e) { + Log.e(TAG, "exception in Disconnect() closing sockets", e); + } + + monitor_reader = null; + monitor_out = null; + monitor_in = null; + monitor_socket = null; + + if (bound != null) parent.unbindService(monitor_connection); + + monitor_connection = null; + } + + + public char[] bytesToChars(byte[] b, int len) { + char[] c = new char[len >> 1]; + int bp = 0; + + for (int i = 0; i < c.length; i++) { + byte b1 = b[bp++]; + byte b2 = b[bp++]; + c[i] = (char)(((b1 & 0x00FF) << 8) + (b2 & 0x00FF)); + } + + return c; + } + + + public byte[] charsToBytes(char[] c) { + byte[] b = new byte[c.length << 1]; + int bp = 0; + + for (int i = 0; i < c.length; i++) { + b[bp++] = (byte)((c[i] & 0xff00) >> 8); + b[bp++] = (byte)(c[i] & 0x00ff); + } + + return b; + } + + + public synchronized void monitorWrite(char cmd, char[] c) { + try { + if (monitor_out != null) { + c[0] = (char)(c.length - 1); // number of chars following + c[1] = cmd; + Log.i(TAG, String.format("sending %s", commands[cmd])); + monitor_out.write(charsToBytes(c)); + monitor_out.flush(); + } + else { + c[1] = cmd; + pending_commands.offer(c); + } + } + catch (IOException e) { + Log.i(TAG, "exception in monitorWrite(), monitor died or closed the socket", e); + + try { + monitor_out.close(); + } + catch (IOException ee) { + Log.e(TAG, "exception in monitorWrite() closing output stream", ee); + } + + monitor_out = null; + } + }; + + public void resetWatch() { + start_line = 0; + end_line = 500; + start_column = 0; + end_column = 500; + }; + + public void sendScreen(char cmd) { + char lines = (char)(buffer.height & 0x0000ffff); + char columns = (char)(buffer.width & 0x0000ffff); + char[] arg = new char[4 + lines * columns]; + arg[2] = lines; + arg[3] = columns; + int base = 4; + + for (int i = 0; i < lines; i++) { + System.arraycopy(buffer.charArray[buffer.screenBase + i], 0, arg, base, columns); + base += columns; + } + + monitorWrite(cmd, arg); + resetWatch(); + } + + public synchronized void activate() { + sendScreen(MONITOR_CMD_ACTIVATE); + cursorMoved(CURSOR_SCREEN_CHANGE); + } + + public synchronized void keyState(boolean down) { + char[] arg = new char[3]; + arg[2] = (char)((down) ? 1 : 0); + monitorWrite(MONITOR_CMD_KEYSTATE, arg); + } + + public synchronized void cursorMove(int l, int c) { + if ((to_line != l) || (to_column != c)) moved = true; + + to_line = l; + to_column = c; + } + + public void cursorMoved(char why) { + char[] arg = new char[5]; + arg[2] = (char)(to_line & 0x0000ffff); + arg[3] = (char)(to_column & 0x0000ffff); + arg[4] = why; + monitorWrite(MONITOR_CMD_CURSORMOVE, arg); + moved = false; + } + + public void testMoved() { + if (moved) cursorMoved(CURSOR_USER_KEY); + } + + public synchronized void testChanged() { + if (modified) { + modified = false; + sendScreen(MONITOR_CMD_SCREENCHANGE); + cursorMoved(CURSOR_SCREEN_CHANGE); + } + else { + if (moved) cursorMoved(CURSOR_SCREEN_CHANGE); + } + } + + public synchronized void screenChanged(int llow, int lhigh, int clow, int chigh) { + if ((start_line <= lhigh) && (llow <= end_line) && (start_column <= chigh) && (clow <= end_column)) { + modified = true; + } + } + + public synchronized void screenChanged(int l, int c) { + screenChanged(l, l, c, c); + } + + public synchronized void setField(int l, int c, char[] data, int offset) { + int len = data.length - offset; + char[] da = new char[len]; + System.arraycopy(data, offset, da, 0, len); + Log.i(TAG, String.format("setField(line %d, col %d, value %s)", l, c, new String(da))); + + if ((l > 60000) || (c > 60000)) { + l = -1; + c = -1; + } + else { + // ignore setfield outside screen boundaries + if ((l >= buffer.height) || (c + len > buffer.width)) return; + } + + buffer.setField(l, c, da); + } + + public synchronized void showUrl(char [] data, int offset) { + char[] da = new char[data.length - offset]; + System.arraycopy(data, offset, da, 0, data.length - offset); + String url = new String(da); + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + parent.startActivity(intent); + } + + public synchronized void getField(int l, int c, int len) { + Log.i(TAG, String.format("getField(line %d, col %d, len %d)", l, c, len)); + char[] arg2 = new char[4 + len]; + arg2[2] = (char)(l & 0x0000ffff); + arg2[3] = (char)(c & 0x0000ffff); + int base = 4; + + if ((l >= buffer.height) || (c + len > buffer.width)) { + Arrays.fill(arg2, base, base + len, ' '); + } + else { + System.arraycopy(buffer.charArray[buffer.screenBase + l], c, arg2, base, len); + } + + char[] da = new char[len]; + System.arraycopy(arg2, base, da, 0, len); + Log.i(TAG, String.format("getField value %s", new String(da))); + + monitorWrite(MONITOR_CMD_FIELDVALUE, arg2); + } + + public synchronized void screenWatch(int l, int c, int len) { + Log.i(TAG, String.format("screenWatch(line %d, col %d, len %d)", l, c, len)); + start_line = l; + end_line = l; + start_column = c; + end_column = c + len - 1; + } + + public synchronized void depress(int vk_key) { + Log.i(TAG, String.format("depress(%d)", vk_key)); + Integer x = keymap.get(new Integer(vk_key)); + + if (x != null) buffer.keyDepressed(x, ' ', 0); + } + + public synchronized void switchSession() { + Log.i(TAG, "switchSession()"); + Intent intent = new Intent(parent, ConsoleActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(host.getUri()); + parent.startActivity(intent); + } + + + public synchronized void cursorRequest() { + cursorMoved(CURSOR_REQUESTED); + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/transport/AbsTransport.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/transport/AbsTransport.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,413 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.transport; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import com.five_ten_sg.connectbot.bean.HostBean; +import com.five_ten_sg.connectbot.bean.PortForwardBean; +import com.five_ten_sg.connectbot.service.TerminalBridge; +import com.five_ten_sg.connectbot.service.TerminalKeyListener; +import com.five_ten_sg.connectbot.service.TerminalManager; +import android.content.Context; +import android.net.Uri; +import android.util.Log; +import de.mud.terminal.vt320; + +/** + * @author Kenny Root + * + */ +public abstract class AbsTransport { + protected String TAG; + protected TerminalManager manager; + protected TerminalBridge bridge; + protected String homeDirectory; + protected HostBean host; + protected vt320 buffer = null; + protected String emulation; + + class vt320Default extends vt320 { + @Override + public void debug(String s) { + Log.d(TAG, s); + } + + // monitor injecting a field + //@Override + //public void setField(int l, int c, char [] data) + // implementation in the base vt320 + + // terminal key listener found special key, send notification to monitor + @Override + public void monitorKey(boolean down) { + if (bridge.monitor != null) bridge.monitor.keyState(down); + } + + // terminal key listener sending to the host + @Override + public void write(byte[] b) { + try { + AbsTransport.this.write(b); + } + catch (IOException e) { + Log.e(TAG, "Problem writing outgoing data in vt320() thread", e); + } + } + @Override + public void write(int b) { + try { + AbsTransport.this.write(b); + } + catch (IOException e) { + Log.e(TAG, "Problem writing outgoing data in vt320() thread", e); + } + } + + // We don't use telnet sequences. + @Override + public void sendTelnetCommand(byte cmd) { + } + // We don't want remote to resize our window. + @Override + public void setWindowSize(int c, int r) { + } + // play beep noise + @Override + public void beep() { + if ((bridge.parent != null) && (bridge.parent.isShown())) + manager.playBeep(); + else + manager.sendActivityNotification(host); + } + + // test for changed screen contents + @Override + public void testChanged() { + if (bridge.monitor != null) bridge.monitor.testChanged(); + } + // relay socket writing to the screen + // bridge.monitor placement of new characters + @Override + public void putChar(int c, int l, char ch, int attributes) { + if (bridge.monitor != null) bridge.monitor.screenChanged(l, c); + + super.putChar(c, l, ch, attributes); + } + @Override + public void insertChar(int c, int l, char ch, int attributes) { + if (bridge.monitor != null) bridge.monitor.screenChanged(l, l, c, width - 1); + + super.insertChar(c, l, ch, attributes); + } + @Override + public void insertLine(int l, int n, boolean scrollDown) { + if (bridge.monitor != null) { + if (scrollDown) bridge.monitor.screenChanged(l, height - 1, 0, width - 1); + else bridge.monitor.screenChanged(0, l, 0, width - 1); + } + + super.insertLine(l, n, scrollDown); + } + @Override + public void deleteLine(int l) { + if (bridge.monitor != null) bridge.monitor.screenChanged(l, height - 1, 0, width - 1); + + super.deleteLine(l); + } + @Override + public void deleteChar(int c, int l) { + if (bridge.monitor != null) bridge.monitor.screenChanged(l, l, c, width - 1); + + super.deleteChar(c, l); + } + @Override + public void setCursorPosition(int c, int l) { + if (bridge.monitor != null) bridge.monitor.cursorMove(l, c); + + super.setCursorPosition(c, l); + } + + }; + + + public AbsTransport() {} + + /** + * @return protocol part of the URI + */ + public static String getProtocolName() { + return "unknown"; + } + + /** + * Encode the current transport into a URI that can be passed via intent calls. + * @return URI to host + */ + public abstract Uri getUri(String input); + + + /** + * Causes transport to connect to the target host. After connecting but before a + * session is started, must call back to {@link TerminalBridge#onConnected()}. + * After that call a session may be opened. + */ + public abstract void connect(); + + /** + * Checks if read() will block. If there are no bytes remaining in + * the underlying transport, return true. + */ + public abstract boolean willBlock(); + + /** + * Reads from the transport. Transport must support reading into a byte array + * buffer at the start of offset and a maximum of + * length bytes. If the remote host disconnects, throw an + * {@link IOException}. + * @param buffer byte buffer to store read bytes into + * @param offset where to start writing in the buffer + * @param length maximum number of bytes to read + * @return number of bytes read + * @throws IOException when remote host disconnects + */ + public abstract int read(byte[] buffer, int offset, int length) throws IOException; + + /** + * Writes to the transport. If the host is not yet connected, simply return without + * doing anything. An {@link IOException} should be thrown if there is an error after + * connection. + * @param buffer bytes to write to transport + * @throws IOException when there is a problem writing after connection + */ + public abstract void write(byte[] buffer) throws IOException; + + /** + * Writes to the transport. See {@link #write(byte[])} for behavior details. + * @param c character to write to the transport + * @throws IOException when there is a problem writing after connection + */ + public abstract void write(int c) throws IOException; + + /** + * Flushes the write commands to the transport. + * @throws IOException when there is a problem writing after connection + */ + public abstract void flush() throws IOException; + + /** + * Closes the connection to the terminal. Note that the resulting failure to read + * should call {@link TerminalBridge#dispatchDisconnect(boolean)}. + */ + public abstract void close(); + + /** + * Tells the transport what dimensions the display is currently + * @param columns columns of text + * @param rows rows of text + * @param width width in pixels + * @param height height in pixels + */ + public abstract void setDimensions(int columns, int rows, int width, int height); + + public void setOptions(Map options) { + // do nothing + } + + public Map getOptions() { + return null; + } + + public void setCompression(boolean compression) { + // do nothing + } + + public void setHttpproxy(String httpproxy) { + // do nothing + } + + public void setUseAuthAgent(String useAuthAgent) { + // do nothing + } + + public String getEmulation() { + return emulation; + } + + protected vt320 setupTransportBuffer() { + int scrollback = (host.getWantSession()) ? manager.getScrollback() : 0; + buffer.setBufferSize(scrollback); + buffer.setDisplay(bridge); + return buffer; + } + + public vt320 getTransportBuffer() { + buffer = new vt320Default(); + return setupTransportBuffer(); + } + + public void setLinks(TerminalManager manager, TerminalBridge bridge, String homeDirectory, HostBean host, String emulation) { + this.manager = manager; + this.bridge = bridge; + this.homeDirectory = homeDirectory; + this.host = host; + this.emulation = emulation; + } + + /** + * Whether or not this transport type can forward ports. + * @return true on ability to forward ports + */ + public boolean canForwardPorts() { + return false; + } + + /** + * Adds the {@link PortForwardBean} to the list. + * @param portForward the port forward bean to add + * @return true on successful addition + */ + public boolean addPortForward(PortForwardBean portForward) { + return false; + } + + /** + * Enables a port forward member. After calling this method, the port forward should + * be operational iff it could be enabled by the transport. + * @param portForward member of our current port forwards list to enable + * @return true on successful port forward setup + */ + public boolean enablePortForward(PortForwardBean portForward) { + return false; + } + + /** + * Disables a port forward member. After calling this method, the port forward should + * be non-functioning iff it could be disabled by the transport. + * @param portForward member of our current port forwards list to enable + * @return true on successful port forward tear-down + */ + public boolean disablePortForward(PortForwardBean portForward) { + return false; + } + + /** + * Removes the {@link PortForwardBean} from the available port forwards. + * @param portForward the port forward bean to remove + * @return true on successful removal + */ + public boolean removePortForward(PortForwardBean portForward) { + return false; + } + + /** + * Gets a list of the {@link PortForwardBean} currently used by this transport. + * @return the list of port forwards + */ + public List getPortForwards() { + return null; + } + + /** + * Whether or not this transport type can transfer files. + * @return true on ability to transfer files + */ + public boolean canTransferFiles() { + return false; + } + + /** + * Downloads the specified remote file to a local folder. + * @param remoteFile The path to the remote file to be downloaded. Must be non-null. + * @param localFolder The path to local folder. Null = default external storage folder. + * @return true on success, false on failure + */ + public boolean downloadFile(String remoteFile, String localFolder) { + return false; + } + + /** + * Uploads the specified local file to the remote host. + * @param localFile The path to the local file to be uploaded. Must be non-null. + * @param remoteFolder The path to the remote directory. Null == default remote directory. + * @return true on success, false on failure + */ + public boolean uploadFile(String localFile, String remoteFile, + String remoteFolder, String mode) { + return false; + } + + + /** + * @return int default port for protocol + */ + public abstract int getDefaultPort(); + public abstract boolean isConnected(); + public abstract boolean isSessionOpen(); + public abstract boolean isAuthenticated(); + + /** + * @param username + * @param hostname + * @param port + * @return + */ + public abstract String getDefaultNickname(String username, String hostname, int port); + + /** + * @param uri + * @param selectionKeys + * @param selectionValues + */ + public abstract void getSelectionArgs(Uri uri, Map selection); + + /** + * @param uri + * @return + */ + public abstract HostBean createHost(Uri uri); + + /** + * @param context context containing the correct resources + * @return string that hints at the format for connection + */ + public abstract String getFormatHint(Context context); + + /** + * @return do we use the network + */ + public abstract boolean usesNetwork(); + + /** + * @return do we need a relay object to read from the transport + * and send the data into the vt320 buffer + */ + public boolean needsRelay() { + return true; + } + + /** + * @return a key listener + */ + public TerminalKeyListener getTerminalKeyListener() { + return new TerminalKeyListener(manager, bridge, buffer, host.getEncoding()); + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/transport/Local.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/transport/Local.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,226 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.transport; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Map; + +import com.five_ten_sg.connectbot.R; +import com.five_ten_sg.connectbot.bean.HostBean; +import com.five_ten_sg.connectbot.service.TerminalBridge; +import com.five_ten_sg.connectbot.service.TerminalManager; +import com.five_ten_sg.connectbot.util.HostDatabase; +import android.content.Context; +import android.net.Uri; +import android.util.Log; + +import com.google.ase.Exec; + +/** + * @author Kenny Root + * + */ +public class Local extends AbsTransport { + private static final String TAG = "ConnectBot.Local"; + private static final String PROTOCOL = "local"; + private static final String DEFAULT_URI = "local:#Local"; + + private FileDescriptor shellFd; + private FileInputStream is; + private FileOutputStream os; + + /** + * + */ + public Local() { + } + + + public static String getProtocolName() { + return PROTOCOL; + } + + + public Uri getUri(String input) { + Uri uri = Uri.parse(DEFAULT_URI); + + if (input != null && input.length() > 0) { + uri = uri.buildUpon().fragment(input).build(); + } + + return uri; + } + + + @Override + public void connect() { + int[] pids = new int[1]; + + try { + shellFd = Exec.createSubprocess("/system/bin/sh", "-", null, pids); + } + catch (Exception e) { + bridge.outputLine(manager.res.getString(R.string.local_shell_unavailable)); + Log.e(TAG, "Cannot start local shell", e); + return; + } + + final int shellPid = pids[0]; + Runnable exitWatcher = new Runnable() { + public void run() { + Exec.waitFor(shellPid); + bridge.dispatchDisconnect(false); + } + }; + Thread exitWatcherThread = new Thread(exitWatcher); + exitWatcherThread.setName("LocalExitWatcher"); + exitWatcherThread.setDaemon(true); + exitWatcherThread.start(); + is = new FileInputStream(shellFd); + os = new FileOutputStream(shellFd); + bridge.onConnected(); + } + + + @Override + public boolean willBlock() { + if (is == null) return true; + + try { + return is.available() == 0; + } + catch (Exception e) { + return true; + } + } + + @Override + public int read(byte[] buffer, int start, int len) throws IOException { + if (is == null) { + bridge.dispatchDisconnect(false); + throw new IOException("session closed"); + } + + return is.read(buffer, start, len); + } + + @Override + public void write(byte[] buffer) throws IOException { + if (os != null) + os.write(buffer); + } + + @Override + public void write(int c) throws IOException { + if (os != null) + os.write(c); + } + + @Override + public void flush() throws IOException { + os.flush(); + } + + @Override + public void close() { + try { + if (os != null) { + os.close(); + os = null; + } + + if (is != null) { + is.close(); + is = null; + } + } + catch (IOException e) { + Log.e(TAG, "Couldn't close shell", e); + } + } + + @Override + public void setDimensions(int columns, int rows, int width, int height) { + try { + Exec.setPtyWindowSize(shellFd, rows, columns, width, height); + } + catch (Exception e) { + Log.e(TAG, "Couldn't resize pty", e); + } + } + + @Override + public int getDefaultPort() { + return 0; + } + + @Override + public boolean isConnected() { + return is != null && os != null; + } + + @Override + public boolean isSessionOpen() { + return isConnected(); + } + + @Override + public boolean isAuthenticated() { + return isConnected(); + } + + @Override + public String getDefaultNickname(String username, String hostname, int port) { + return DEFAULT_URI; + } + + @Override + public void getSelectionArgs(Uri uri, Map selection) { + selection.put(HostDatabase.FIELD_HOST_PROTOCOL, PROTOCOL); + selection.put(HostDatabase.FIELD_HOST_NICKNAME, uri.getFragment()); + } + + @Override + public HostBean createHost(Uri uri) { + HostBean host = new HostBean(); + host.setProtocol(PROTOCOL); + String nickname = uri.getFragment(); + + if (nickname == null || nickname.length() == 0) { + host.setNickname(getDefaultNickname(host.getUsername(), + host.getHostname(), host.getPort())); + } + else { + host.setNickname(uri.getFragment()); + } + + return host; + } + + public String getFormatHint(Context context) { + return context.getString(R.string.hostpref_nickname_title); + } + + @Override + public boolean usesNetwork() { + return false; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/transport/SSH.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/transport/SSH.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,1152 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.transport; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.URL; +import java.net.MalformedURLException; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.five_ten_sg.connectbot.R; +import com.five_ten_sg.connectbot.bean.HostBean; +import com.five_ten_sg.connectbot.bean.PortForwardBean; +import com.five_ten_sg.connectbot.bean.PubkeyBean; +import com.five_ten_sg.connectbot.service.TerminalBridge; +import com.five_ten_sg.connectbot.service.TerminalManager; +import com.five_ten_sg.connectbot.service.TerminalManager.KeyHolder; +import com.five_ten_sg.connectbot.util.HostDatabase; +import com.five_ten_sg.connectbot.util.PubkeyDatabase; +import com.five_ten_sg.connectbot.util.PubkeyUtils; +import android.content.Context; +import android.net.Uri; +import android.os.Environment; +import android.util.Log; + +import ch.ethz.ssh2.AuthAgentCallback; +import ch.ethz.ssh2.ChannelCondition; +import ch.ethz.ssh2.Connection; +import ch.ethz.ssh2.ConnectionInfo; +import ch.ethz.ssh2.ConnectionMonitor; +import ch.ethz.ssh2.DynamicPortForwarder; +import ch.ethz.ssh2.InteractiveCallback; +import ch.ethz.ssh2.KnownHosts; +import ch.ethz.ssh2.LocalPortForwarder; +import ch.ethz.ssh2.SCPClient; +import ch.ethz.ssh2.ServerHostKeyVerifier; +import ch.ethz.ssh2.Session; +import ch.ethz.ssh2.HTTPProxyData; +import ch.ethz.ssh2.HTTPProxyException; +import ch.ethz.ssh2.crypto.PEMDecoder; +import ch.ethz.ssh2.signature.DSASHA1Verify; +import ch.ethz.ssh2.signature.ECDSASHA2Verify; +import ch.ethz.ssh2.signature.RSASHA1Verify; + +/** + * @author Kenny Root + * + */ +public class SSH extends AbsTransport implements ConnectionMonitor, InteractiveCallback, AuthAgentCallback { + private static final String PROTOCOL = "ssh"; + private static final String TAG = "ConnectBot.SSH"; + private static final int DEFAULT_PORT = 22; + + private static final String AUTH_PUBLICKEY = "publickey", + AUTH_PASSWORD = "password", + AUTH_KEYBOARDINTERACTIVE = "keyboard-interactive"; + + private final static int AUTH_TRIES = 20; + + static final Pattern hostmask; + static { + hostmask = Pattern.compile("^(.+)@([0-9a-z.-]+)(:(\\d+))?$", Pattern.CASE_INSENSITIVE); + } + + private boolean compression = false; + private String httpproxy = null; + private volatile boolean authenticated = false; + private volatile boolean connected = false; + private volatile boolean sessionOpen = false; + + private boolean pubkeysExhausted = false; + private boolean interactiveCanContinue = true; + + private Connection connection; + private Session session; + private ConnectionInfo connectionInfo; + + private OutputStream stdin; + private InputStream stdout; + private InputStream stderr; + + private static final int conditions = ChannelCondition.STDOUT_DATA + | ChannelCondition.STDERR_DATA + | ChannelCondition.CLOSED + | ChannelCondition.EOF; + + private List portForwards = new LinkedList(); + + private int columns; + private int rows; + + private int width; + private int height; + + private String useAuthAgent = HostDatabase.AUTHAGENT_NO; + private String agentLockPassphrase; + + public class HostKeyVerifier implements ServerHostKeyVerifier { + public boolean verifyServerHostKey(String hostname, int port, + String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException { + // read in all known hosts from hostdb + KnownHosts hosts = manager.hostdb.getKnownHosts(); + Boolean result; + String matchName = String.format(Locale.US, "%s:%d", hostname, port); + String fingerprint = KnownHosts.createHexFingerprint(serverHostKeyAlgorithm, serverHostKey); + String algorithmName; + + if ("ssh-rsa".equals(serverHostKeyAlgorithm)) + algorithmName = "RSA"; + else if ("ssh-dss".equals(serverHostKeyAlgorithm)) + algorithmName = "DSA"; + else if (serverHostKeyAlgorithm.startsWith("ecdsa-sha2-")) + algorithmName = "EC"; + else + algorithmName = serverHostKeyAlgorithm; + + switch (hosts.verifyHostkey(matchName, serverHostKeyAlgorithm, serverHostKey)) { + case KnownHosts.HOSTKEY_IS_OK: + bridge.outputLine(manager.res.getString(R.string.terminal_sucess, algorithmName, fingerprint)); + return true; + + case KnownHosts.HOSTKEY_IS_NEW: + // prompt user + bridge.outputLine(manager.res.getString(R.string.host_authenticity_warning, hostname)); + bridge.outputLine(manager.res.getString(R.string.host_fingerprint, algorithmName, fingerprint)); + result = bridge.promptHelper.requestBooleanPrompt(null, manager.res.getString(R.string.prompt_continue_connecting)); + + if (result == null) return false; + + if (result.booleanValue()) { + // save this key in known database + manager.hostdb.saveKnownHost(hostname, port, serverHostKeyAlgorithm, serverHostKey); + } + + return result.booleanValue(); + + case KnownHosts.HOSTKEY_HAS_CHANGED: + String header = String.format("@ %s @", + manager.res.getString(R.string.host_verification_failure_warning_header)); + char[] atsigns = new char[header.length()]; + Arrays.fill(atsigns, '@'); + String border = new String(atsigns); + bridge.outputLine(border); + bridge.outputLine(manager.res.getString(R.string.host_verification_failure_warning)); + bridge.outputLine(border); + bridge.outputLine(String.format(manager.res.getString(R.string.host_fingerprint), + algorithmName, fingerprint)); + // Users have no way to delete keys, so we'll prompt them for now. + result = bridge.promptHelper.requestBooleanPrompt(null, manager.res.getString(R.string.prompt_continue_connecting)); + + if (result == null) return false; + + if (result.booleanValue()) { + // save this key in known database + manager.hostdb.saveKnownHost(hostname, port, serverHostKeyAlgorithm, serverHostKey); + } + + return result.booleanValue(); + + default: + return false; + } + } + + } + + + public SSH() { + super(); + } + + + /** + * @return protocol part of the URI + */ + public static String getProtocolName() { + return PROTOCOL; + } + + + public Uri getUri(String input) { + Matcher matcher = hostmask.matcher(input); + + if (!matcher.matches()) + return null; + + StringBuilder sb = new StringBuilder(); + sb.append(PROTOCOL) + .append("://") + .append(Uri.encode(matcher.group(1))) + .append('@') + .append(matcher.group(2)); + String portString = matcher.group(4); + int port = DEFAULT_PORT; + + if (portString != null) { + try { + port = Integer.parseInt(portString); + + if (port < 1 || port > 65535) { + port = DEFAULT_PORT; + } + } + catch (NumberFormatException nfe) { + // Keep the default port + } + } + + if (port != DEFAULT_PORT) { + sb.append(':') + .append(port); + } + + sb.append("/#") + .append(Uri.encode(input)); + Uri uri = Uri.parse(sb.toString()); + return uri; + } + + + private void authenticate() { + try { + if (connection.authenticateWithNone(host.getUsername())) { + finishConnection(); + return; + } + } + catch (Exception e) { + Log.d(TAG, "Host does not support 'none' authentication."); + } + + bridge.outputLine(manager.res.getString(R.string.terminal_auth)); + + try { + long pubkeyId = host.getPubkeyId(); + + if (!pubkeysExhausted && + pubkeyId != HostDatabase.PUBKEYID_NEVER && + connection.isAuthMethodAvailable(host.getUsername(), AUTH_PUBLICKEY)) { + // if explicit pubkey defined for this host, then prompt for password as needed + // otherwise just try all in-memory keys held in terminalmanager + if (pubkeyId == HostDatabase.PUBKEYID_ANY) { + // try each of the in-memory keys + bridge.outputLine(manager.res + .getString(R.string.terminal_auth_pubkey_any)); + + for (Entry entry : manager.loadedKeypairs.entrySet()) { + if (entry.getValue().bean.isConfirmUse() + && !promptForPubkeyUse(entry.getKey())) + continue; + + if (this.tryPublicKey(host.getUsername(), entry.getKey(), + entry.getValue().pair)) { + finishConnection(); + break; + } + } + } + else { + bridge.outputLine(manager.res.getString(R.string.terminal_auth_pubkey_specific)); + // use a specific key for this host, as requested + PubkeyBean pubkey = manager.pubkeydb.findPubkeyById(pubkeyId); + + if (pubkey == null) + bridge.outputLine(manager.res.getString(R.string.terminal_auth_pubkey_invalid)); + else if (tryPublicKey(pubkey)) + finishConnection(); + } + + pubkeysExhausted = true; + } + else if (interactiveCanContinue && + connection.isAuthMethodAvailable(host.getUsername(), AUTH_KEYBOARDINTERACTIVE)) { + // this auth method will talk with us using InteractiveCallback interface + // it blocks until authentication finishes + bridge.outputLine(manager.res.getString(R.string.terminal_auth_ki)); + interactiveCanContinue = false; + + if (connection.authenticateWithKeyboardInteractive(host.getUsername(), this)) { + finishConnection(); + } + else { + bridge.outputLine(manager.res.getString(R.string.terminal_auth_ki_fail)); + } + } + else if (connection.isAuthMethodAvailable(host.getUsername(), AUTH_PASSWORD)) { + bridge.outputLine(manager.res.getString(R.string.terminal_auth_pass)); + String password = bridge.getPromptHelper().requestPasswordPrompt(null, + manager.res.getString(R.string.prompt_password)); + + if (password != null + && connection.authenticateWithPassword(host.getUsername(), password)) { + finishConnection(); + } + else { + bridge.outputLine(manager.res.getString(R.string.terminal_auth_pass_fail)); + } + } + else { + bridge.outputLine(manager.res.getString(R.string.terminal_auth_fail)); + } + } + catch (IllegalStateException e) { + Log.e(TAG, "Connection went away while we were trying to authenticate", e); + return; + } + catch (Exception e) { + Log.e(TAG, "Problem during handleAuthentication()", e); + } + } + + + /** + * Attempt connection with database row pointed to by cursor. + * @param cursor + * @return true for successful authentication + * @throws NoSuchAlgorithmException + * @throws InvalidKeySpecException + * @throws IOException + */ + private boolean tryPublicKey(PubkeyBean pubkey) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException { + KeyPair pair = null; + + if (manager.isKeyLoaded(pubkey.getNickname())) { + // load this key from memory if its already there + Log.d(TAG, String.format("Found unlocked key '%s' already in-memory", pubkey.getNickname())); + + if (pubkey.isConfirmUse()) { + if (!promptForPubkeyUse(pubkey.getNickname())) + return false; + } + + pair = manager.getKey(pubkey.getNickname()); + } + else { + // otherwise load key from database and prompt for password as needed + String password = null; + + if (pubkey.isEncrypted()) { + password = bridge.getPromptHelper().requestPasswordPrompt(null, + manager.res.getString(R.string.prompt_pubkey_password, pubkey.getNickname())); + + // Something must have interrupted the prompt. + if (password == null) + return false; + } + + if (PubkeyDatabase.KEY_TYPE_IMPORTED.equals(pubkey.getType())) { + // load specific key using pem format + pair = PEMDecoder.decode(new String(pubkey.getPrivateKey()).toCharArray(), password); + } + else { + // load using internal generated format + PrivateKey privKey; + + try { + privKey = PubkeyUtils.decodePrivate(pubkey.getPrivateKey(), + pubkey.getType(), password); + } + catch (Exception e) { + String message = String.format("Bad password for key '%s'. Authentication failed.", pubkey.getNickname()); + Log.e(TAG, message, e); + bridge.outputLine(message); + return false; + } + + PublicKey pubKey = PubkeyUtils.decodePublic(pubkey.getPublicKey(), pubkey.getType()); + // convert key to trilead format + pair = new KeyPair(pubKey, privKey); + Log.d(TAG, "Unlocked key " + PubkeyUtils.formatKey(pubKey)); + } + + Log.d(TAG, String.format("Unlocked key '%s'", pubkey.getNickname())); + // save this key in memory + manager.addKey(pubkey, pair); + } + + return tryPublicKey(host.getUsername(), pubkey.getNickname(), pair); + } + + + private boolean tryPublicKey(String username, String keyNickname, KeyPair pair) throws IOException { + //bridge.outputLine(String.format("Attempting 'publickey' with key '%s' [%s]...", keyNickname, trileadKey.toString())); + boolean success = connection.authenticateWithPublicKey(username, pair); + + if (!success) + bridge.outputLine(manager.res.getString(R.string.terminal_auth_pubkey_fail, keyNickname)); + + return success; + } + + + /** + * Internal method to request actual PTY terminal once we've finished + * authentication. If called before authenticated, it will just fail. + */ + private void finishConnection() { + authenticated = true; + + for (PortForwardBean portForward : portForwards) { + try { + enablePortForward(portForward); + bridge.outputLine(manager.res.getString(R.string.terminal_enable_portfoward, portForward.getDescription())); + } + catch (Exception e) { + Log.e(TAG, "Error setting up port forward during connect", e); + } + } + + if (!host.getWantSession()) { + bridge.outputLine(manager.res.getString(R.string.terminal_no_session)); + bridge.onConnected(); + return; + } + + try { + session = connection.openSession(); + + if (!useAuthAgent.equals(HostDatabase.AUTHAGENT_NO)) + session.requestAuthAgentForwarding(this); + + if (host.getWantX11Forward()) { + try { + session.requestX11Forwarding(host.getX11Host(), host.getX11Port(), null, false); + } + catch (IOException e2) { + Log.e(TAG, "Problem while trying to setup X11 forwarding in finishConnection()", e2); + } + } + + session.requestPTY(getEmulation(), columns, rows, width, height, null); + session.startShell(); + stdin = session.getStdin(); + stdout = session.getStdout(); + stderr = session.getStderr(); + sessionOpen = true; + bridge.onConnected(); + } + catch (IOException e1) { + Log.e(TAG, "Problem while trying to create PTY in finishConnection()", e1); + } + } + + + @Override + public void connect() { + connection = new Connection(host.getHostname(), host.getPort()); + connection.addConnectionMonitor(this); + + try { + connection.setCompression(compression); + } + catch (IOException e) { + Log.e(TAG, "Could not enable compression!", e); + } + + if (httpproxy != null && httpproxy.length() > 0) { + Log.d(TAG, "Want HTTP Proxy: " + httpproxy, null); + + try { + URL u; + + if (httpproxy.startsWith("http://")) { + u = new URL(httpproxy); + } + else { + u = new URL("http://" + httpproxy); + } + + connection.setProxyData(new HTTPProxyData( + u.getHost(), + u.getPort(), + u.getUserInfo(), + u.getAuthority())); + bridge.outputLine("Connecting via proxy: " + httpproxy); + } + catch (MalformedURLException e) { + Log.e(TAG, "Could not parse proxy " + httpproxy, e); + // Display the reason in the text. + bridge.outputLine("Bad proxy URL: " + httpproxy); + onDisconnect(); + return; + } + } + + try { + /* Uncomment when debugging SSH protocol: + DebugLogger logger = new DebugLogger() { + + public void log(int level, String className, String message) { + Log.d("SSH", message); + } + + }; + Logger.enabled = true; + Logger.logger = logger; + */ + connectionInfo = connection.connect(new HostKeyVerifier()); + connected = true; + + if (connectionInfo.clientToServerCryptoAlgorithm + .equals(connectionInfo.serverToClientCryptoAlgorithm) + && connectionInfo.clientToServerMACAlgorithm + .equals(connectionInfo.serverToClientMACAlgorithm)) { + bridge.outputLine(manager.res.getString(R.string.terminal_using_algorithm, + connectionInfo.clientToServerCryptoAlgorithm, + connectionInfo.clientToServerMACAlgorithm)); + } + else { + bridge.outputLine(manager.res.getString( + R.string.terminal_using_c2s_algorithm, + connectionInfo.clientToServerCryptoAlgorithm, + connectionInfo.clientToServerMACAlgorithm)); + bridge.outputLine(manager.res.getString( + R.string.terminal_using_s2c_algorithm, + connectionInfo.serverToClientCryptoAlgorithm, + connectionInfo.serverToClientMACAlgorithm)); + } + } + catch (HTTPProxyException e) { + Log.e(TAG, "Failed to connect to HTTP Proxy", e); + // Display the reason in the text. + bridge.outputLine("Failed to connect to HTTP Proxy."); + onDisconnect(); + return; + } + catch (IOException e) { + Log.e(TAG, "Problem in SSH connection thread during authentication", e); + // Display the reason in the text. + Throwable t = e.getCause(); + String m = (t == null) ? e.getMessage() : t.getMessage(); + bridge.outputLine(m); + onDisconnect(); + return; + } + + try { + // enter a loop to keep trying until authentication + int tries = 0; + + while (connected && !connection.isAuthenticationComplete() && tries++ < AUTH_TRIES) { + authenticate(); + // sleep to make sure we dont kill system + Thread.sleep(1000); + } + } + catch (Exception e) { + Log.e(TAG, "Problem in SSH connection thread during authentication", e); + } + } + + + @Override + public boolean willBlock() { + if (stdout == null) return true; + + try { + return stdout.available() == 0; + } + catch (Exception e) { + return true; + } + } + + + @Override + public int read(byte[] buffer, int start, int len) throws IOException { + int bytesRead = 0; + + if (session == null) + return 0; + + int newConditions = session.waitForCondition(conditions, 0); + + if ((newConditions & ChannelCondition.STDOUT_DATA) != 0) { + bytesRead = stdout.read(buffer, start, len); + } + + if ((newConditions & ChannelCondition.STDERR_DATA) != 0) { + byte discard[] = new byte[256]; + + while (stderr.available() > 0) { + stderr.read(discard); + } + } + + if ((newConditions & ChannelCondition.EOF) != 0) { + onDisconnect(); + throw new IOException("Remote end closed connection"); + } + + return bytesRead; + } + + + @Override + public void write(byte[] buffer) throws IOException { + if (stdin != null) + stdin.write(buffer); + } + + + @Override + public void write(int c) throws IOException { + if (stdin != null) + stdin.write(c); + } + + + @Override + public void flush() throws IOException { + if (stdin != null) + stdin.flush(); + } + + + public void connectionLost(Throwable reason) { + onDisconnect(); + } + + + private void onDisconnect() { + close(); + bridge.dispatchDisconnect(false); + } + + + @Override + public void close() { + connected = false; + + if (session != null) { + session.close(); + session = null; + } + + if (connection != null) { + connection.close(); + connection = null; + } + } + + + @Override + public void setDimensions(int columns, int rows, int width, int height) { + this.columns = columns; + this.rows = rows; + + if (sessionOpen) { + try { + session.resizePTY(columns, rows, width, height); + } + catch (IOException e) { + Log.e(TAG, "Couldn't send resize PTY packet", e); + } + } + } + + + @Override + public void setOptions(Map options) { + if (options.containsKey("compression")) + compression = Boolean.parseBoolean(options.get("compression")); + + if (options.containsKey("httpproxy")) + httpproxy = options.get("httpproxy"); + } + + + @Override + public Map getOptions() { + Map options = new HashMap(); + options.put("compression", Boolean.toString(compression)); + + if (httpproxy != null) + options.put("httpproxy", httpproxy); + + return options; + } + + + @Override + public void setCompression(boolean compression) { + this.compression = compression; + } + + + @Override + public void setHttpproxy(String httpproxy) { + this.httpproxy = httpproxy; + } + + + @Override + public void setUseAuthAgent(String useAuthAgent) { + this.useAuthAgent = useAuthAgent; + } + + @Override + public boolean canForwardPorts() { + return true; + } + + @Override + public List getPortForwards() { + return portForwards; + } + + @Override + public boolean addPortForward(PortForwardBean portForward) { + return portForwards.add(portForward); + } + + @Override + public boolean removePortForward(PortForwardBean portForward) { + // Make sure we don't have a phantom forwarder. + disablePortForward(portForward); + return portForwards.remove(portForward); + } + + @Override + public boolean enablePortForward(PortForwardBean portForward) { + if (!portForwards.contains(portForward)) { + Log.e(TAG, "Attempt to enable port forward not in list"); + return false; + } + + if (!authenticated) + return false; + + if (HostDatabase.PORTFORWARD_LOCAL.equals(portForward.getType())) { + LocalPortForwarder lpf = null; + + try { + lpf = connection.createLocalPortForwarder( + new InetSocketAddress(InetAddress.getLocalHost(), portForward.getSourcePort()), + portForward.getDestAddr(), portForward.getDestPort()); + } + catch (Exception e) { + Log.e(TAG, "Could not create local port forward", e); + return false; + } + + if (lpf == null) { + Log.e(TAG, "returned LocalPortForwarder object is null"); + return false; + } + + portForward.setIdentifier(lpf); + portForward.setEnabled(true); + return true; + } + else if (HostDatabase.PORTFORWARD_REMOTE.equals(portForward.getType())) { + try { + connection.requestRemotePortForwarding("", portForward.getSourcePort(), portForward.getDestAddr(), portForward.getDestPort()); + } + catch (Exception e) { + Log.e(TAG, "Could not create remote port forward", e); + return false; + } + + portForward.setEnabled(true); + return true; + } + else if (HostDatabase.PORTFORWARD_DYNAMIC5.equals(portForward.getType())) { + DynamicPortForwarder dpf = null; + + try { + dpf = connection.createDynamicPortForwarder( + new InetSocketAddress(InetAddress.getLocalHost(), portForward.getSourcePort())); + } + catch (Exception e) { + Log.e(TAG, "Could not create dynamic port forward", e); + return false; + } + + portForward.setIdentifier(dpf); + portForward.setEnabled(true); + return true; + } + else { + // Unsupported type + Log.e(TAG, String.format("attempt to forward unknown type %s", portForward.getType())); + return false; + } + } + + @Override + public boolean disablePortForward(PortForwardBean portForward) { + if (!portForwards.contains(portForward)) { + Log.e(TAG, "Attempt to disable port forward not in list"); + return false; + } + + if (!authenticated) + return false; + + if (HostDatabase.PORTFORWARD_LOCAL.equals(portForward.getType())) { + LocalPortForwarder lpf = null; + lpf = (LocalPortForwarder)portForward.getIdentifier(); + + if (!portForward.isEnabled() || lpf == null) { + Log.d(TAG, String.format("Could not disable %s; it appears to be not enabled or have no handler", portForward.getNickname())); + return false; + } + + portForward.setEnabled(false); + + try { + lpf.close(); + } + catch (IOException e) { + Log.e(TAG, "Could not stop local port forwarder, setting enabled to false", e); + return false; + } + + return true; + } + else if (HostDatabase.PORTFORWARD_REMOTE.equals(portForward.getType())) { + portForward.setEnabled(false); + + try { + connection.cancelRemotePortForwarding(portForward.getSourcePort()); + } + catch (IOException e) { + Log.e(TAG, "Could not stop remote port forwarding, setting enabled to false", e); + return false; + } + + return true; + } + else if (HostDatabase.PORTFORWARD_DYNAMIC5.equals(portForward.getType())) { + DynamicPortForwarder dpf = null; + dpf = (DynamicPortForwarder)portForward.getIdentifier(); + + if (!portForward.isEnabled() || dpf == null) { + Log.d(TAG, String.format("Could not disable %s; it appears to be not enabled or have no handler", portForward.getNickname())); + return false; + } + + portForward.setEnabled(false); + + try { + dpf.close(); + } + catch (IOException e) { + Log.e(TAG, "Could not stop dynamic port forwarder, setting enabled to false", e); + return false; + } + + return true; + } + else { + // Unsupported type + Log.e(TAG, String.format("attempt to forward unknown type %s", portForward.getType())); + return false; + } + } + + @Override + public boolean canTransferFiles() { + return true; + } + + @Override + public boolean downloadFile(String remoteFile, String localFolder) { + try { + SCPClient client = new SCPClient(connection); + + if (localFolder == null || localFolder == "") + localFolder = Environment.getExternalStorageDirectory().getAbsolutePath(); + + File dir = new File(localFolder); + dir.mkdirs(); + client.get(remoteFile, localFolder); + return true; + } + catch (IOException e) { + Log.e(TAG, "Could not download remote file", e); + return false; + } + } + + @Override + public boolean uploadFile(String localFile, String remoteFile, + String remoteFolder, String mode) { + try { + SCPClient client = new SCPClient(connection); + + if (remoteFolder == null) + remoteFolder = ""; + + if (remoteFile == null || remoteFile == "") + client.put(localFile, remoteFolder, mode); + else + client.put(localFile, remoteFile, remoteFolder, mode); + + return true; + } + catch (IOException e) { + Log.e(TAG, "Could not upload local file", e); + return false; + } + } + + + @Override + public int getDefaultPort() { + return DEFAULT_PORT; + } + + + @Override + public boolean isConnected() { + return connected; + } + + + @Override + public boolean isSessionOpen() { + return sessionOpen; + } + + + @Override + public boolean isAuthenticated() { + return authenticated; + } + + + @Override + public String getDefaultNickname(String username, String hostname, int port) { + if (port == DEFAULT_PORT) { + return String.format(Locale.US, "%s@%s", username, hostname); + } + else { + return String.format(Locale.US, "%s@%s:%d", username, hostname, port); + } + } + + + @Override + public void getSelectionArgs(Uri uri, Map selection) { + selection.put(HostDatabase.FIELD_HOST_PROTOCOL, PROTOCOL); + selection.put(HostDatabase.FIELD_HOST_NICKNAME, uri.getFragment()); + selection.put(HostDatabase.FIELD_HOST_HOSTNAME, uri.getHost()); + int port = uri.getPort(); + + if (port < 0) + port = DEFAULT_PORT; + + selection.put(HostDatabase.FIELD_HOST_PORT, Integer.toString(port)); + selection.put(HostDatabase.FIELD_HOST_USERNAME, uri.getUserInfo()); + } + + + @Override + public HostBean createHost(Uri uri) { + HostBean host = new HostBean(); + host.setProtocol(PROTOCOL); + host.setHostname(uri.getHost()); + int port = uri.getPort(); + + if (port < 0) + port = DEFAULT_PORT; + + host.setPort(port); + host.setUsername(uri.getUserInfo()); + String nickname = uri.getFragment(); + + if (nickname == null || nickname.length() == 0) { + host.setNickname(getDefaultNickname(host.getUsername(), + host.getHostname(), host.getPort())); + } + else { + host.setNickname(uri.getFragment()); + } + + return host; + } + + + public String getFormatHint(Context context) { + return String.format("%s@%s:%s", + context.getString(R.string.format_username), + context.getString(R.string.format_hostname), + context.getString(R.string.format_port)); + } + + + /** + * @return do we use the network + */ + @Override + public boolean usesNetwork() { + return true; + } + + + /** + * Handle challenges from keyboard-interactive authentication mode. + */ + public String[] replyToChallenge(String name, String instruction, int numPrompts, String[] prompt, boolean[] echo) { + interactiveCanContinue = true; + String[] responses = new String[numPrompts]; + + for (int i = 0; i < numPrompts; i++) { + // request response from user for each prompt + responses[i] = bridge.promptHelper.requestPasswordPrompt(instruction, prompt[i]); + } + + return responses; + } + + public Map retrieveIdentities() { + Map pubKeys = new HashMap (manager.loadedKeypairs.size()); + + for (Entry entry : manager.loadedKeypairs.entrySet()) { + KeyPair pair = entry.getValue().pair; + + try { + PrivateKey privKey = pair.getPrivate(); + + if (privKey instanceof RSAPrivateKey) { + RSAPublicKey pubkey = (RSAPublicKey) pair.getPublic(); + pubKeys.put(entry.getKey(), RSASHA1Verify.encodeSSHRSAPublicKey(pubkey)); + } + else if (privKey instanceof DSAPrivateKey) { + DSAPublicKey pubkey = (DSAPublicKey) pair.getPublic(); + pubKeys.put(entry.getKey(), DSASHA1Verify.encodeSSHDSAPublicKey(pubkey)); + } + else if (privKey instanceof ECPrivateKey) { + ECPublicKey pubkey = (ECPublicKey) pair.getPublic(); + pubKeys.put(entry.getKey(), ECDSASHA2Verify.encodeSSHECDSAPublicKey(pubkey)); + } + else + continue; + } + catch (IOException e) { + continue; + } + } + + return pubKeys; + } + + public KeyPair getKeyPair(byte[] publicKey) { + String nickname = manager.getKeyNickname(publicKey); + + if (nickname == null) + return null; + + if (useAuthAgent.equals(HostDatabase.AUTHAGENT_NO)) { + Log.e(TAG, ""); + return null; + } + else if (useAuthAgent.equals(HostDatabase.AUTHAGENT_CONFIRM) || + manager.loadedKeypairs.get(nickname).bean.isConfirmUse()) { + if (!promptForPubkeyUse(nickname)) + return null; + } + + return manager.getKey(nickname); + } + + private boolean promptForPubkeyUse(String nickname) { + Boolean result = bridge.promptHelper.requestBooleanPrompt(null, + manager.res.getString(R.string.prompt_allow_agent_to_use_key, + nickname)); + return result; + } + + public boolean addIdentity(KeyPair pair, String comment, boolean confirmUse, int lifetime) { + PubkeyBean pubkey = new PubkeyBean(); +// pubkey.setType(PubkeyDatabase.KEY_TYPE_IMPORTED); + pubkey.setNickname(comment); + pubkey.setConfirmUse(confirmUse); + pubkey.setLifetime(lifetime); + manager.addKey(pubkey, pair); + return true; + } + + public boolean removeAllIdentities() { + manager.loadedKeypairs.clear(); + return true; + } + + public boolean removeIdentity(byte[] publicKey) { + return manager.removeKey(publicKey); + } + + public boolean isAgentLocked() { + return agentLockPassphrase != null; + } + + public boolean requestAgentUnlock(String unlockPassphrase) { + if (agentLockPassphrase == null) + return false; + + if (agentLockPassphrase.equals(unlockPassphrase)) + agentLockPassphrase = null; + + return agentLockPassphrase == null; + } + + public boolean setAgentLock(String lockPassphrase) { + if (agentLockPassphrase != null) + return false; + + agentLockPassphrase = lockPassphrase; + return true; + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/transport/TN5250.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/transport/TN5250.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,472 @@ +/* + * 510ConnectBot + * Copyright 2014 Carl Byington + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.transport; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.tn5250j.framework.tn5250.Screen5250; +import org.tn5250j.framework.tn5250.tnvt; + +import com.five_ten_sg.connectbot.R; +import com.five_ten_sg.connectbot.bean.HostBean; +import com.five_ten_sg.connectbot.bean.PortForwardBean; +import com.five_ten_sg.connectbot.service.TerminalBridge; +import com.five_ten_sg.connectbot.service.TerminalKeyListener; +import com.five_ten_sg.connectbot.service.TerminalManager; +import com.five_ten_sg.connectbot.util.HostDatabase; +import com.five_ten_sg.connectbot.util.PreferenceConstants; +import android.content.Context; +import android.net.Uri; +import android.util.Log; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.View; +import de.mud.terminal.vt320; + + +/** + * @author Carl Byington + * + */ +public class TN5250 extends AbsTransport { + private static final String PROTOCOL = "tn5250"; + private static final String TAG = "ConnectBot.tn5250"; + private static final int DEFAULT_PORT = 23; + + private Screen5250 screen52; + private tnvt handler = null; + private Socket socket; + private boolean connected = false; + + static final Pattern hostmask; + static { + hostmask = Pattern.compile("^([0-9a-z.-]+)(:(\\d+))?$", Pattern.CASE_INSENSITIVE); + } + + + class vt320x5250 extends vt320 { + private HashMap controls; + private HashMap mnemonics; + + public vt320x5250() { + this(80, 24); + } + + public vt320x5250(int width, int height) { + super(width, height); + controls = new HashMap(); + controls.put(0x01, KEY_PAUSE); // ctrl-a -> [attn] + controls.put(0x08, KEY_BACK_SPACE); + controls.put(0x09, KEY_TAB); + controls.put(0x0d, KEY_ENTER); + controls.put(0x12, KEY_ESCAPE); // ctrl-r -> [reset] + controls.put(0x13, KEY_SYSREQ); // ctrl-s -> [sysreq] + controls.put(0x1b, KEY_ESCAPE); // esc -> [reset] + mnemonics = new HashMap(); + mnemonics.put(KEY_PAUSE , "[attn]"); + mnemonics.put(KEY_F1 , "[pf1]"); + mnemonics.put(KEY_F2 , "[pf2]"); + mnemonics.put(KEY_F3 , "[pf3]"); + mnemonics.put(KEY_F4 , "[pf4]"); + mnemonics.put(KEY_F5 , "[pf5]"); + mnemonics.put(KEY_F6 , "[pf6]"); + mnemonics.put(KEY_F7 , "[pf7]"); + mnemonics.put(KEY_F8 , "[pf8]"); + mnemonics.put(KEY_F9 , "[pf9]"); + mnemonics.put(KEY_F10 , "[pf10]"); + mnemonics.put(KEY_F11 , "[pf11]"); + mnemonics.put(KEY_F12 , "[pf12]"); + mnemonics.put(KEY_F13 , "[pf13]"); + mnemonics.put(KEY_F14 , "[pf14]"); + mnemonics.put(KEY_F15 , "[pf15]"); + mnemonics.put(KEY_F16 , "[pf16]"); + mnemonics.put(KEY_F17 , "[pf17]"); + mnemonics.put(KEY_F18 , "[pf18]"); + mnemonics.put(KEY_F19 , "[pf19]"); + mnemonics.put(KEY_F20 , "[pf20]"); + mnemonics.put(KEY_F21 , "[pf21]"); + mnemonics.put(KEY_F22 , "[pf22]"); + mnemonics.put(KEY_F23 , "[pf23]"); + mnemonics.put(KEY_F24 , "[pf24]"); + mnemonics.put(KEY_UP , "[up]"); + mnemonics.put(KEY_DOWN , "[down]"); + mnemonics.put(KEY_LEFT , "[left]"); + mnemonics.put(KEY_RIGHT , "[right]"); + mnemonics.put(KEY_PAGE_DOWN , "[pgdown]"); + mnemonics.put(KEY_PAGE_UP , "[pgup]"); + mnemonics.put(KEY_INSERT , "[insert]"); + mnemonics.put(KEY_DELETE , "[delete]"); + mnemonics.put(KEY_BACK_SPACE , "[backspace]"); + mnemonics.put(KEY_HOME , "[home]"); + mnemonics.put(KEY_END , "[end]"); + mnemonics.put(KEY_NUM_LOCK , ""); + mnemonics.put(KEY_CAPS_LOCK , ""); + mnemonics.put(KEY_SHIFT , ""); + mnemonics.put(KEY_CONTROL , ""); + mnemonics.put(KEY_ALT , ""); + mnemonics.put(KEY_ENTER , "[enter]"); + mnemonics.put(KEY_NUMPAD0 , "0"); + mnemonics.put(KEY_NUMPAD1 , "1"); + mnemonics.put(KEY_NUMPAD2 , "2"); + mnemonics.put(KEY_NUMPAD3 , "3"); + mnemonics.put(KEY_NUMPAD4 , "4"); + mnemonics.put(KEY_NUMPAD5 , "5"); + mnemonics.put(KEY_NUMPAD6 , "6"); + mnemonics.put(KEY_NUMPAD7 , "7"); + mnemonics.put(KEY_NUMPAD8 , "8"); + mnemonics.put(KEY_NUMPAD9 , "9"); + mnemonics.put(KEY_DECIMAL , "."); + mnemonics.put(KEY_ADD , "+"); + mnemonics.put(KEY_ESCAPE , "[reset]"); + mnemonics.put(KEY_TAB , "[tab]"); + mnemonics.put(KEY_SYSREQ , "[sysreq]"); + } + + @Override + public void debug(String s) { + Log.d(TAG, s); + } + + // monitor injecting a field + @Override + public void setField(int l, int c, char [] data) { + screen52.setField(l, c, data); + } + + // monitor simulating key depress + @Override + public void keyDepressed(int keyCode, char keyChar, int modifiers) { + if (mnemonics.containsKey(keyCode)) { + String s = mnemonics.get(keyCode); + + if (s != "") screen52.sendKeys(s); + } + } + + // terminal key listener found special key, send notification to monitor + @Override + public void monitorKey(boolean down) { + if (bridge.monitor != null) bridge.monitor.keyState(down); + } + + // terminal key listener sending to local screen + @Override + public void write(byte[] b) { + screen52.sendKeys(new String(b)); + + if (bridge.monitor != null) bridge.monitor.testMoved(); + } + @Override + public void write(int b) { + if (controls.containsKey(b)) keyPressed(controls.get(b), ' ', 0); + else screen52.sendKeys(new String(new byte[] {(byte)b})); + + if (bridge.monitor != null) bridge.monitor.testMoved(); + } + @Override + public void keyPressed(int keyCode, char keyChar, int modifiers) { + keyDepressed(keyCode, keyChar, modifiers); + + if (bridge.monitor != null) bridge.monitor.testMoved(); + } + + // 5250 writing to the screen + // test for changed screen contents + @Override + public void testChanged() { + if (bridge.monitor != null) bridge.monitor.testChanged(); + } + @Override + public void putChar(int c, int l, char ch, int attributes) { + if (bridge.monitor != null) bridge.monitor.screenChanged(l, c); + + super.putChar(c, l, ch, attributes); + } + @Override + public void setCursorPosition(int c, int l) { + if (bridge.monitor != null) bridge.monitor.cursorMove(l, c); + + super.setCursorPosition(c, l); + redraw(); + } + }; + + + public TN5250() { + super(); + } + + + /** + * @return protocol part of the URI + */ + public static String getProtocolName() { + return PROTOCOL; + } + + + /** + * Encode the current transport into a URI that can be passed via intent calls. + * @return URI to host + */ + public Uri getUri(String input) { + Matcher matcher = hostmask.matcher(input); + + if (!matcher.matches()) + return null; + + StringBuilder sb = new StringBuilder(); + sb.append(PROTOCOL) + .append("://") + .append(matcher.group(1)); + String portString = matcher.group(3); + int port = DEFAULT_PORT; + + if (portString != null) { + try { + port = Integer.parseInt(portString); + + if (port < 1 || port > 65535) { + port = DEFAULT_PORT; + } + } + catch (NumberFormatException nfe) { + // Keep the default port + } + } + + if (port != DEFAULT_PORT) { + sb.append(':'); + sb.append(port); + } + + sb.append("/#") + .append(Uri.encode(input)); + Uri uri = Uri.parse(sb.toString()); + return uri; + } + + + /** + * Causes transport to connect to the target host. After connecting but before a + * session is started, must call back to {@link TerminalBridge#onConnected()}. + * After that call a session may be opened. + */ + @Override + public void connect() { + screen52 = new Screen5250(); + handler = new tnvt(screen52, true, true, bridge, manager); + screen52.setVT(handler); + screen52.setBuffer(buffer); + bridge.addFontSizeChangedListener(screen52); + connected = handler.connect(host, homeDirectory, buffer); + + if (connected) bridge.onConnected(); + } + + + /** + * Checks if read() will block. If there are no bytes remaining in + * the underlying transport, return true. + */ + @Override + public boolean willBlock() { + // we don't use a relay thread between the transport and the vt320 buffer + return true; + } + + + /** + * Reads from the transport. Transport must support reading into a byte array + * buffer at the start of offset and a maximum of + * length bytes. If the remote host disconnects, throw an + * {@link IOException}. + * @param buffer byte buffer to store read bytes into + * @param offset where to start writing in the buffer + * @param length maximum number of bytes to read + * @return number of bytes read + * @throws IOException when remote host disconnects + */ + public int read(byte[] buffer, int offset, int length) throws IOException { + // we don't use a relay thread between the transport and the vt320 buffer + return 0; + } + + + /** + * Writes to the transport. If the host is not yet connected, simply return without + * doing anything. An {@link IOException} should be thrown if there is an error after + * connection. + * @param buffer bytes to write to transport + * @throws IOException when there is a problem writing after connection + */ + public void write(byte[] buffer) throws IOException { + } + + + /** + * Writes to the transport. See {@link #write(byte[])} for behavior details. + * @param c character to write to the transport + * @throws IOException when there is a problem writing after connection + */ + public void write(int c) throws IOException { + } + + + /** + * Flushes the write commands to the transport. + * @throws IOException when there is a problem writing after connection + */ + public void flush() throws IOException { + } + + + /** + * Closes the connection to the terminal. + */ + public void close() { + handler.disconnect(); + connected = false; + bridge.removeFontSizeChangedListener(screen52); + bridge.dispatchDisconnect(false); + } + + + /** + * Tells the transport what dimensions the display is currently + * @param columns columns of text + * @param rows rows of text + * @param width width in pixels + * @param height height in pixels + */ + @Override + public void setDimensions(int columns, int rows, int width, int height) { + // do nothing + } + + + @Override + public vt320 getTransportBuffer() { + buffer = new vt320x5250(); + return setupTransportBuffer(); + } + + + @Override + public int getDefaultPort() { + return DEFAULT_PORT; + } + + + @Override + public boolean isConnected() { + return connected; + } + + + @Override + public boolean isSessionOpen() { + return connected; + } + + + @Override + public boolean isAuthenticated() { + return connected; + } + + + @Override + public String getDefaultNickname(String username, String hostname, int port) { + if (port == DEFAULT_PORT) { + return String.format("%s", hostname); + } + else { + return String.format("%s:%d", hostname, port); + } + } + + + @Override + public void getSelectionArgs(Uri uri, Map selection) { + selection.put(HostDatabase.FIELD_HOST_PROTOCOL, PROTOCOL); + selection.put(HostDatabase.FIELD_HOST_NICKNAME, uri.getFragment()); + selection.put(HostDatabase.FIELD_HOST_HOSTNAME, uri.getHost()); + int port = uri.getPort(); + + if (port < 0) port = DEFAULT_PORT; + + selection.put(HostDatabase.FIELD_HOST_PORT, Integer.toString(port)); + } + + + @Override + public HostBean createHost(Uri uri) { + HostBean host = new HostBean(); + host.setProtocol(PROTOCOL); + host.setHostname(uri.getHost()); + int port = uri.getPort(); + + if (port < 0) port = DEFAULT_PORT; + + host.setPort(port); + String nickname = uri.getFragment(); + + if (nickname == null || nickname.length() == 0) { + nickname = getDefaultNickname(host.getUsername(), host.getHostname(), host.getPort()); + } + + host.setNickname(nickname); + return host; + } + + + public String getFormatHint(Context context) { + return String.format("%s:%s", + context.getString(R.string.format_hostname), + context.getString(R.string.format_port)); + } + + + @Override + public boolean usesNetwork() { + return true; + } + + + @Override + public boolean needsRelay() { + // we don't use a relay thread between the transport and the vt320 buffer + return false; + } + + public TerminalKeyListener getTerminalKeyListener() { + return new TerminalKeyListener(manager, bridge, buffer, host.getEncoding()); + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/transport/Telnet.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/transport/Telnet.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,357 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.transport; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.nio.charset.Charset; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.five_ten_sg.connectbot.R; +import com.five_ten_sg.connectbot.bean.HostBean; +import com.five_ten_sg.connectbot.service.TerminalBridge; +import com.five_ten_sg.connectbot.service.TerminalManager; +import com.five_ten_sg.connectbot.util.HostDatabase; + +import android.content.Context; +import android.net.Uri; +import android.util.Log; + +import de.mud.telnet.TelnetProtocolHandler; + + +/** + * Telnet transport implementation.
+ * Original idea from the JTA telnet package (de.mud.telnet) + * + * @author Kenny Root + * + */ +public class Telnet extends AbsTransport { + private static final String TAG = "ConnectBot.Telnet"; + private static final String PROTOCOL = "telnet"; + private static final int DEFAULT_PORT = 23; + + private TelnetProtocolHandler handler; + private Socket socket; + private InputStream is; + private OutputStream os; + private int width; + private int height; + private boolean connected = false; + + static final Pattern hostmask; + static { + hostmask = Pattern.compile("^([0-9a-z.-]+)(:(\\d+))?$", Pattern.CASE_INSENSITIVE); + } + + public Telnet() { + handler = new TelnetProtocolHandler() { + /** get the current terminal type */ + @Override + public String getTerminalType() { + return getEmulation(); + } + /** get the current window size */ + @Override + public int[] getWindowSize() { + return new int[] { width, height }; + } + /** notify about local echo */ + @Override + public void setLocalEcho(boolean echo) { + /* EMPTY */ + } + /** write data to our back end */ + @Override + public void write(byte[] b) throws IOException { + if (os != null) + os.write(b); + } + /** sent on IAC EOR (prompt terminator for remote access systems). */ + @Override + public void notifyEndOfRecord() { + } + @Override + protected String getCharsetName() { + Charset charset = bridge.getCharset(); + + if (charset != null) + return charset.name(); + else + return ""; + } + }; + } + + + public static String getProtocolName() { + return PROTOCOL; + } + + + public Uri getUri(String input) { + Matcher matcher = hostmask.matcher(input); + + if (!matcher.matches()) + return null; + + StringBuilder sb = new StringBuilder(); + sb.append(PROTOCOL) + .append("://") + .append(matcher.group(1)); + String portString = matcher.group(3); + int port = DEFAULT_PORT; + + if (portString != null) { + try { + port = Integer.parseInt(portString); + + if (port < 1 || port > 65535) { + port = DEFAULT_PORT; + } + } + catch (NumberFormatException nfe) { + // Keep the default port + } + } + + if (port != DEFAULT_PORT) { + sb.append(':'); + sb.append(port); + } + + sb.append("/#") + .append(Uri.encode(input)); + Uri uri = Uri.parse(sb.toString()); + return uri; + } + + + @Override + public void connect() { + try { + socket = new Socket(host.getHostname(), host.getPort()); + connected = true; + is = socket.getInputStream(); + os = socket.getOutputStream(); + bridge.onConnected(); + } + catch (UnknownHostException e) { + Log.d(TAG, "IO Exception connecting to host", e); + } + catch (IOException e) { + Log.d(TAG, "IO Exception connecting to host", e); + } + } + + + @Override + public boolean willBlock() { + if (is == null) return true; + + try { + return is.available() == 0; + } + catch (Exception e) { + return true; + } + } + + + @Override + public int read(byte[] buffer, int start, int len) throws IOException { + /* process all already read bytes */ + int n = 0; + + do { + n = handler.negotiate(buffer, start); + + if (n > 0) + return n; + } + while (n == 0); + + while (n <= 0) { + do { + n = handler.negotiate(buffer, start); + + if (n > 0) + return n; + } + while (n == 0); + + n = is.read(buffer, start, len); + + if (n < 0) { + bridge.dispatchDisconnect(false); + throw new IOException("Remote end closed connection."); + } + + handler.inputfeed(buffer, start, n); + n = handler.negotiate(buffer, start); + } + + return n; + } + + + @Override + public void write(byte[] buffer) throws IOException { + try { + if (os != null) + os.write(buffer); + } + catch (SocketException e) { + bridge.dispatchDisconnect(false); + } + } + + + @Override + public void write(int c) throws IOException { + try { + if (os != null) + os.write(c); + } + catch (SocketException e) { + bridge.dispatchDisconnect(false); + } + } + + + @Override + public void flush() throws IOException { + os.flush(); + } + + + @Override + public void close() { + connected = false; + + if (socket != null) + try { + socket.close(); + socket = null; + } + catch (IOException e) { + Log.d(TAG, "Error closing telnet socket.", e); + } + } + + + @Override + public void setDimensions(int columns, int rows, int width, int height) { + try { + handler.setWindowSize(columns, rows); + } + catch (IOException e) { + Log.e(TAG, "Couldn't resize remote terminal", e); + } + } + + + @Override + public int getDefaultPort() { + return DEFAULT_PORT; + } + + @Override + public boolean isConnected() { + return connected; + } + + @Override + public boolean isSessionOpen() { + return isConnected(); + } + + @Override + public boolean isAuthenticated() { + return isConnected(); + } + + + @Override + public String getDefaultNickname(String username, String hostname, int port) { + if (port == DEFAULT_PORT) { + return String.format("%s", hostname); + } + else { + return String.format("%s:%d", hostname, port); + } + } + + + @Override + public void getSelectionArgs(Uri uri, Map selection) { + selection.put(HostDatabase.FIELD_HOST_PROTOCOL, PROTOCOL); + selection.put(HostDatabase.FIELD_HOST_NICKNAME, uri.getFragment()); + selection.put(HostDatabase.FIELD_HOST_HOSTNAME, uri.getHost()); + int port = uri.getPort(); + + if (port < 0) + port = DEFAULT_PORT; + + selection.put(HostDatabase.FIELD_HOST_PORT, Integer.toString(port)); + } + + + @Override + public HostBean createHost(Uri uri) { + HostBean host = new HostBean(); + host.setProtocol(PROTOCOL); + host.setHostname(uri.getHost()); + int port = uri.getPort(); + + if (port < 0) + port = DEFAULT_PORT; + + host.setPort(port); + String nickname = uri.getFragment(); + + if (nickname == null || nickname.length() == 0) { + host.setNickname(getDefaultNickname(host.getUsername(), + host.getHostname(), host.getPort())); + } + else { + host.setNickname(uri.getFragment()); + } + + return host; + } + + + public String getFormatHint(Context context) { + return String.format("%s:%s", + context.getString(R.string.format_hostname), + context.getString(R.string.format_port)); + } + + @Override + public boolean usesNetwork() { + return true; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/transport/TransportFactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/transport/TransportFactory.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,127 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.transport; + +import java.util.HashMap; +import java.util.Map; + +import com.five_ten_sg.connectbot.bean.HostBean; +import com.five_ten_sg.connectbot.util.HostDatabase; +import android.content.Context; +import android.net.Uri; +import android.util.Log; + + +/** + * @author Kenny Root + * + */ +public class TransportFactory { + private static final String TAG = "ConnectBot.TransportFactory"; + + private static String[] transportNames = { + SSH.getProtocolName(), + TN5250.getProtocolName(), + Telnet.getProtocolName(), + Local.getProtocolName(), + }; + + /** + * @param protocol + * @return + */ + public static AbsTransport getTransport(String protocol) { + if (SSH.getProtocolName().equals(protocol)) { + return new SSH(); + } + else if (TN5250.getProtocolName().equals(protocol)) { + return new TN5250(); + } + else if (Telnet.getProtocolName().equals(protocol)) { + return new Telnet(); + } + else if (Local.getProtocolName().equals(protocol)) { + return new Local(); + } + else { + return null; + } + } + + public static Uri getUri(String protocol, String input) { + Log.d("TransportFactory", String.format( + "Attempting to discover URI for protocol=%s on input=%s", + protocol, input)); + AbsTransport t = getTransport(protocol); + + if (t == null) return null; + + return t.getUri(input); + } + + public static String[] getTransportNames() { + return transportNames; + } + + public static boolean isSameTransportType(AbsTransport a, AbsTransport b) { + if (a == null || b == null) + return false; + + return a.getClass().equals(b.getClass()); + } + + public static boolean canForwardPorts(String protocol) { + AbsTransport t = getTransport(protocol); + + if (t == null) return false; + + return t.canForwardPorts(); + } + + /** + * @param protocol text name of protocol + * @param context + * @return expanded format hint + */ + public static String getFormatHint(String protocol, Context context) { + AbsTransport t = getTransport(protocol); + + if (t == null) return "???"; + + return t.getFormatHint(context); + } + + /** + * @param hostdb Handle to HostDatabase + * @param uri URI to target server + * @return HostBean or null + */ + public static HostBean findHost(HostDatabase hostdb, Uri uri) { + AbsTransport transport = getTransport(uri.getScheme()); + Map selection = new HashMap(); + transport.getSelectionArgs(uri, selection); + + if (selection.size() == 0) { + Log.e(TAG, String.format("Transport %s failed to do something useful with URI=%s", + uri.getScheme(), uri.toString())); + throw new IllegalStateException("Failed to get needed selection arguments"); + } + + return hostdb.findHost(selection); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/util/Colors.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/util/Colors.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,91 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.util; + +/** + * @author Kenny Root + * + */ +public class Colors { + public final static Integer[] defaults = new Integer[] { + 0xff000000, // black // 0 + 0xffcc0000, // red // 1 + 0xff00cc00, // green // 2 + 0xffcccc00, // brown // 3 + 0xff0000cc, // blue // 4 + 0xffcc00cc, // purple // 5 + 0xff00cccc, // cyan // 6 + 0xffcccccc, // light grey // 7 + 0xff444444, // dark grey // 8 + 0xffff4444, // light red // 9 + 0xff44ff44, // light green // a + 0xffffff44, // yellow // b + 0xff4444ff, // light blue // c + 0xffff44ff, // light purple // d + 0xff44ffff, // light cyan // e + 0xffffffff, // white // f + 0xff000000, 0xff00005f, 0xff000087, 0xff0000af, 0xff0000d7, + 0xff0000ff, 0xff005f00, 0xff005f5f, 0xff005f87, 0xff005faf, + 0xff005fd7, 0xff005fff, 0xff008700, 0xff00875f, 0xff008787, + 0xff0087af, 0xff0087d7, 0xff0087ff, 0xff00af00, 0xff00af5f, + 0xff00af87, 0xff00afaf, 0xff00afd7, 0xff00afff, 0xff00d700, + 0xff00d75f, 0xff00d787, 0xff00d7af, 0xff00d7d7, 0xff00d7ff, + 0xff00ff00, 0xff00ff5f, 0xff00ff87, 0xff00ffaf, 0xff00ffd7, + 0xff00ffff, 0xff5f0000, 0xff5f005f, 0xff5f0087, 0xff5f00af, + 0xff5f00d7, 0xff5f00ff, 0xff5f5f00, 0xff5f5f5f, 0xff5f5f87, + 0xff5f5faf, 0xff5f5fd7, 0xff5f5fff, 0xff5f8700, 0xff5f875f, + 0xff5f8787, 0xff5f87af, 0xff5f87d7, 0xff5f87ff, 0xff5faf00, + 0xff5faf5f, 0xff5faf87, 0xff5fafaf, 0xff5fafd7, 0xff5fafff, + 0xff5fd700, 0xff5fd75f, 0xff5fd787, 0xff5fd7af, 0xff5fd7d7, + 0xff5fd7ff, 0xff5fff00, 0xff5fff5f, 0xff5fff87, 0xff5fffaf, + 0xff5fffd7, 0xff5fffff, 0xff870000, 0xff87005f, 0xff870087, + 0xff8700af, 0xff8700d7, 0xff8700ff, 0xff875f00, 0xff875f5f, + 0xff875f87, 0xff875faf, 0xff875fd7, 0xff875fff, 0xff878700, + 0xff87875f, 0xff878787, 0xff8787af, 0xff8787d7, 0xff8787ff, + 0xff87af00, 0xff87af5f, 0xff87af87, 0xff87afaf, 0xff87afd7, + 0xff87afff, 0xff87d700, 0xff87d75f, 0xff87d787, 0xff87d7af, + 0xff87d7d7, 0xff87d7ff, 0xff87ff00, 0xff87ff5f, 0xff87ff87, + 0xff87ffaf, 0xff87ffd7, 0xff87ffff, 0xffaf0000, 0xffaf005f, + 0xffaf0087, 0xffaf00af, 0xffaf00d7, 0xffaf00ff, 0xffaf5f00, + 0xffaf5f5f, 0xffaf5f87, 0xffaf5faf, 0xffaf5fd7, 0xffaf5fff, + 0xffaf8700, 0xffaf875f, 0xffaf8787, 0xffaf87af, 0xffaf87d7, + 0xffaf87ff, 0xffafaf00, 0xffafaf5f, 0xffafaf87, 0xffafafaf, + 0xffafafd7, 0xffafafff, 0xffafd700, 0xffafd75f, 0xffafd787, + 0xffafd7af, 0xffafd7d7, 0xffafd7ff, 0xffafff00, 0xffafff5f, + 0xffafff87, 0xffafffaf, 0xffafffd7, 0xffafffff, 0xffd70000, + 0xffd7005f, 0xffd70087, 0xffd700af, 0xffd700d7, 0xffd700ff, + 0xffd75f00, 0xffd75f5f, 0xffd75f87, 0xffd75faf, 0xffd75fd7, + 0xffd75fff, 0xffd78700, 0xffd7875f, 0xffd78787, 0xffd787af, + 0xffd787d7, 0xffd787ff, 0xffd7af00, 0xffd7af5f, 0xffd7af87, + 0xffd7afaf, 0xffd7afd7, 0xffd7afff, 0xffd7d700, 0xffd7d75f, + 0xffd7d787, 0xffd7d7af, 0xffd7d7d7, 0xffd7d7ff, 0xffd7ff00, + 0xffd7ff5f, 0xffd7ff87, 0xffd7ffaf, 0xffd7ffd7, 0xffd7ffff, + 0xffff0000, 0xffff005f, 0xffff0087, 0xffff00af, 0xffff00d7, + 0xffff00ff, 0xffff5f00, 0xffff5f5f, 0xffff5f87, 0xffff5faf, + 0xffff5fd7, 0xffff5fff, 0xffff8700, 0xffff875f, 0xffff8787, + 0xffff87af, 0xffff87d7, 0xffff87ff, 0xffffaf00, 0xffffaf5f, + 0xffffaf87, 0xffffafaf, 0xffffafd7, 0xffffafff, 0xffffd700, + 0xffffd75f, 0xffffd787, 0xffffd7af, 0xffffd7d7, 0xffffd7ff, + 0xffffff00, 0xffffff5f, 0xffffff87, 0xffffffaf, 0xffffffd7, + 0xffffffff, 0xff080808, 0xff121212, 0xff1c1c1c, 0xff262626, + 0xff303030, 0xff3a3a3a, 0xff444444, 0xff4e4e4e, 0xff585858, + 0xff626262, 0xff6c6c6c, 0xff767676, 0xff808080, 0xff8a8a8a, + 0xff949494, 0xff9e9e9e, 0xffa8a8a8, 0xffb2b2b2, 0xffbcbcbc, + 0xffc6c6c6, 0xffd0d0d0, 0xffdadada, 0xffe4e4e4, 0xffeeeeee, + }; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/util/Encryptor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/util/Encryptor.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,189 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.util; + +/** + * This class is from: + * + * Encryptor.java + * Copyright 2008 Zach Scrivena + * zachscrivena@gmail.com + * http://zs.freeshell.org/ + */ + +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + + +/** + * Perform AES-128 encryption. + */ +public final class Encryptor { + /** name of the character set to use for converting between characters and bytes */ + private static final String CHARSET_NAME = "UTF-8"; + + /** random number generator algorithm */ + private static final String RNG_ALGORITHM = "SHA1PRNG"; + + /** message digest algorithm (must be sufficiently long to provide the key and initialization vector) */ + private static final String DIGEST_ALGORITHM = "SHA-256"; + + /** key algorithm (must be compatible with CIPHER_ALGORITHM) */ + private static final String KEY_ALGORITHM = "AES"; + + /** cipher algorithm (must be compatible with KEY_ALGORITHM) */ + private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; + + + /** + * Private constructor that should never be called. + */ + private Encryptor() + {} + + + /** + * Encrypt the specified cleartext using the given password. + * With the correct salt, number of iterations, and password, the decrypt() method reverses + * the effect of this method. + * This method generates and uses a random salt, and the user-specified number of iterations + * and password to create a 16-byte secret key and 16-byte initialization vector. + * The secret key and initialization vector are then used in the AES-128 cipher to encrypt + * the given cleartext. + * + * @param salt + * salt that was used in the encryption (to be populated) + * @param iterations + * number of iterations to use in salting + * @param password + * password to be used for encryption + * @param cleartext + * cleartext to be encrypted + * @return + * ciphertext + * @throws Exception + * on any error encountered in encryption + */ + public static byte[] encrypt( + final byte[] salt, + final int iterations, + final String password, + final byte[] cleartext) + throws Exception { + /* generate salt randomly */ + SecureRandom.getInstance(RNG_ALGORITHM).nextBytes(salt); + /* compute key and initialization vector */ + final MessageDigest shaDigest = MessageDigest.getInstance(DIGEST_ALGORITHM); + byte[] pw = password.getBytes(CHARSET_NAME); + + for (int i = 0; i < iterations; i++) { + /* add salt */ + final byte[] salted = new byte[pw.length + salt.length]; + System.arraycopy(pw, 0, salted, 0, pw.length); + System.arraycopy(salt, 0, salted, pw.length, salt.length); + Arrays.fill(pw, (byte) 0x00); + /* compute SHA-256 digest */ + shaDigest.reset(); + pw = shaDigest.digest(salted); + Arrays.fill(salted, (byte) 0x00); + } + + /* extract the 16-byte key and initialization vector from the SHA-256 digest */ + final byte[] key = new byte[16]; + final byte[] iv = new byte[16]; + System.arraycopy(pw, 0, key, 0, 16); + System.arraycopy(pw, 16, iv, 0, 16); + Arrays.fill(pw, (byte) 0x00); + /* perform AES-128 encryption */ + final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); + cipher.init( + Cipher.ENCRYPT_MODE, + new SecretKeySpec(key, KEY_ALGORITHM), + new IvParameterSpec(iv)); + Arrays.fill(key, (byte) 0x00); + Arrays.fill(iv, (byte) 0x00); + return cipher.doFinal(cleartext); + } + + + /** + * Decrypt the specified ciphertext using the given password. + * With the correct salt, number of iterations, and password, this method reverses the effect + * of the encrypt() method. + * This method uses the user-specified salt, number of iterations, and password + * to recreate the 16-byte secret key and 16-byte initialization vector. + * The secret key and initialization vector are then used in the AES-128 cipher to decrypt + * the given ciphertext. + * + * @param salt + * salt to be used in decryption + * @param iterations + * number of iterations to use in salting + * @param password + * password to be used for decryption + * @param ciphertext + * ciphertext to be decrypted + * @return + * cleartext + * @throws Exception + * on any error encountered in decryption + */ + public static byte[] decrypt( + final byte[] salt, + final int iterations, + final String password, + final byte[] ciphertext) + throws Exception { + /* compute key and initialization vector */ + final MessageDigest shaDigest = MessageDigest.getInstance(DIGEST_ALGORITHM); + byte[] pw = password.getBytes(CHARSET_NAME); + + for (int i = 0; i < iterations; i++) { + /* add salt */ + final byte[] salted = new byte[pw.length + salt.length]; + System.arraycopy(pw, 0, salted, 0, pw.length); + System.arraycopy(salt, 0, salted, pw.length, salt.length); + Arrays.fill(pw, (byte) 0x00); + /* compute SHA-256 digest */ + shaDigest.reset(); + pw = shaDigest.digest(salted); + Arrays.fill(salted, (byte) 0x00); + } + + /* extract the 16-byte key and initialization vector from the SHA-256 digest */ + final byte[] key = new byte[16]; + final byte[] iv = new byte[16]; + System.arraycopy(pw, 0, key, 0, 16); + System.arraycopy(pw, 16, iv, 0, 16); + Arrays.fill(pw, (byte) 0x00); + /* perform AES-128 decryption */ + final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); + cipher.init( + Cipher.DECRYPT_MODE, + new SecretKeySpec(key, KEY_ALGORITHM), + new IvParameterSpec(iv)); + Arrays.fill(key, (byte) 0x00); + Arrays.fill(iv, (byte) 0x00); + return cipher.doFinal(ciphertext); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/util/EntropyDialog.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/util/EntropyDialog.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,45 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.util; + +import com.five_ten_sg.connectbot.R; +import android.app.Dialog; +import android.content.Context; +import android.view.View; + +public class EntropyDialog extends Dialog implements OnEntropyGatheredListener { + + public EntropyDialog(Context context) { + super(context); + this.setContentView(R.layout.dia_gatherentropy); + this.setTitle(R.string.pubkey_gather_entropy); + ((EntropyView) findViewById(R.id.entropy)).addOnEntropyGatheredListener(this); + } + + public EntropyDialog(Context context, View view) { + super(context); + this.setContentView(view); + this.setTitle(R.string.pubkey_gather_entropy); + ((EntropyView) findViewById(R.id.entropy)).addOnEntropyGatheredListener(this); + } + + public void onEntropyGathered(byte[] entropy) { + this.dismiss(); + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/util/EntropyView.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/util/EntropyView.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,167 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.util; + +import java.util.Vector; + +import com.five_ten_sg.connectbot.R; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.FontMetrics; +import android.graphics.Typeface; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +public class EntropyView extends View { + private static final int SHA1_MAX_BYTES = 20; + private static final int MILLIS_BETWEEN_INPUTS = 50; + + private Paint mPaint; + private FontMetrics mFontMetrics; + private boolean mFlipFlop; + private long mLastTime; + private Vector listeners; + + private byte[] mEntropy; + private int mEntropyByteIndex; + private int mEntropyBitIndex; + + private int splitText = 0; + + private float lastX = 0.0f, lastY = 0.0f; + + public EntropyView(Context context) { + super(context); + setUpEntropy(); + } + + public EntropyView(Context context, AttributeSet attrs) { + super(context, attrs); + setUpEntropy(); + } + + private void setUpEntropy() { + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setTypeface(Typeface.DEFAULT); + mPaint.setTextAlign(Paint.Align.CENTER); + mPaint.setTextSize(16); + mPaint.setColor(Color.WHITE); + mFontMetrics = mPaint.getFontMetrics(); + mEntropy = new byte[SHA1_MAX_BYTES]; + mEntropyByteIndex = 0; + mEntropyBitIndex = 0; + listeners = new Vector(); + } + + public void addOnEntropyGatheredListener(OnEntropyGatheredListener listener) { + listeners.add(listener); + } + + public void removeOnEntropyGatheredListener(OnEntropyGatheredListener listener) { + listeners.remove(listener); + } + + @Override + public void onDraw(Canvas c) { + String prompt = String.format(getResources().getString(R.string.pubkey_touch_prompt), + (int)(100.0 * (mEntropyByteIndex / 20.0)) + (int)(5.0 * (mEntropyBitIndex / 8.0))); + + if (splitText > 0 || + mPaint.measureText(prompt) > (getWidth() * 0.8)) { + if (splitText == 0) + splitText = prompt.indexOf(" ", prompt.length() / 2); + + c.drawText(prompt.substring(0, splitText), + getWidth() / 2.0f, + getHeight() / 2.0f + (mPaint.ascent() + mPaint.descent()), + mPaint); + c.drawText(prompt.substring(splitText), + getWidth() / 2.0f, + getHeight() / 2.0f - (mPaint.ascent() + mPaint.descent()), + mPaint); + } + else { + c.drawText(prompt, + getWidth() / 2.0f, + getHeight() / 2.0f - (mFontMetrics.ascent + mFontMetrics.descent) / 2, + mPaint); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mEntropyByteIndex >= SHA1_MAX_BYTES + || lastX == event.getX() + || lastY == event.getY()) + return true; + + // Only get entropy every 200 milliseconds to ensure the user has moved around. + long now = System.currentTimeMillis(); + + if ((now - mLastTime) < MILLIS_BETWEEN_INPUTS) + return true; + else + mLastTime = now; + + byte input; + lastX = event.getX(); + lastY = event.getY(); + + // Get the lowest 4 bits of each X, Y input and concat to the entropy-gathering + // string. + if (mFlipFlop) + input = (byte)((((int)lastX & 0x0F) << 4) | ((int)lastY & 0x0F)); + else + input = (byte)((((int)lastY & 0x0F) << 4) | ((int)lastX & 0x0F)); + + mFlipFlop = !mFlipFlop; + + for (int i = 0; i < 4 && mEntropyByteIndex < SHA1_MAX_BYTES; i++) { + if ((input & 0x3) == 0x1) { + mEntropy[mEntropyByteIndex] <<= 1; + mEntropy[mEntropyByteIndex] |= 1; + mEntropyBitIndex++; + input >>= 2; + } + else if ((input & 0x3) == 0x2) { + mEntropy[mEntropyByteIndex] <<= 1; + mEntropyBitIndex++; + input >>= 2; + } + + if (mEntropyBitIndex >= 8) { + mEntropyBitIndex = 0; + mEntropyByteIndex++; + } + } + + // SHA1PRNG only keeps 160 bits of entropy. + if (mEntropyByteIndex >= SHA1_MAX_BYTES) { + for (OnEntropyGatheredListener listener : listeners) { + listener.onEntropyGathered(mEntropy); + } + } + + invalidate(); + return true; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/util/FileChooser.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/util/FileChooser.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,127 @@ +/* + * File Chooser Class for VX ConnectBot + * Copyright 2012 Martin Matuska + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.five_ten_sg.connectbot.util; + +import java.io.File; +import java.net.URI; + +import org.openintents.intents.FileManagerIntents; + +import com.five_ten_sg.connectbot.R; +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Environment; +import android.preference.PreferenceManager; +import android.util.Log; +import android.widget.Toast; + +import com.lamerman.FileDialog; +import com.lamerman.SelectionMode; + +public class FileChooser { + public final static String TAG = "ConnectBot.FileChooser"; + + public static final int REQUEST_CODE_SELECT_FILE = 1; + + // Constants for AndExplorer's file picking intent + private static final String ANDEXPLORER_TITLE = "explorer_title"; + private static final String MIME_TYPE_ANDEXPLORER_FILE = "vnd.android.cursor.dir/lysesoft.andexplorer.file"; + + public static void selectFile(Activity source, FileChooserCallback callback, int requestcode) { + selectFile(source, callback, requestcode, null); + } + + public static void selectFile(Activity source, FileChooserCallback callback, int requestcode, String title) { + final File sdcard = Environment.getExternalStorageDirectory(); + + if (title == null) + title = source.getString(R.string.file_chooser_select_file); + + int mode = SelectionMode.MODE_OPEN; + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(source); + Intent intent = null; + String filedialog; + String appString = null; + + if (prefs == null) + return; + + filedialog = prefs.getString(PreferenceConstants.FILE_DIALOG, "built-in"); + + if (filedialog.equals("OI")) { + appString = "OpenIntents File Manager"; + intent = new Intent(FileManagerIntents.ACTION_PICK_FILE); + intent.setData(Uri.fromFile(sdcard)); + intent.putExtra(FileManagerIntents.EXTRA_TITLE, title); + intent.putExtra(FileManagerIntents.EXTRA_BUTTON_TEXT, source.getString(android.R.string.ok)); + } + else if (filedialog.equals("AE")) { + appString = "AndExplorer"; + intent = new Intent(Intent.ACTION_PICK); + intent.setDataAndType(Uri.fromFile(sdcard), MIME_TYPE_ANDEXPLORER_FILE); + intent.putExtra(ANDEXPLORER_TITLE, title); + } + + if (intent != null && appString != null) { + try { + source.startActivityForResult(intent, requestcode); + return; + } + catch (ActivityNotFoundException e1) { + Toast.makeText(source, + source.getString(R.string.error_starting_app, appString), + Toast.LENGTH_LONG).show(); + } + } + + intent = new Intent(source.getBaseContext(), FileDialog.class); + intent.putExtra(FileDialog.START_PATH, sdcard.toString()); + intent.putExtra(FileDialog.TITLE, title); + intent.putExtra(FileDialog.SELECTION_MODE, mode); + source.startActivityForResult(intent, requestcode); + } + + public static File getSelectedFile(Intent intent) { + File file = null; + + if (intent == null) + return null; + + Uri uri = intent.getData(); + + try { + if (uri != null) { + file = new File(URI.create(uri.toString())); + } + else { + String filename = intent.getDataString(); + + if (filename != null) + file = new File(URI.create(filename)); + } + } + catch (IllegalArgumentException e) { + Log.e(TAG, "Couldn't read selected file", e); + return null; + } + + return file; + } +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/util/FileChooserCallback.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/util/FileChooserCallback.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,23 @@ +/* + * File Chooser Callback Class for VX ConnectBot + * Copyright 2012 Martin Matuska + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.five_ten_sg.connectbot.util; + +import java.io.File; + +public interface FileChooserCallback { + public void fileSelected(File f); +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/util/HelpTopicView.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/util/HelpTopicView.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,61 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.util; + +import com.five_ten_sg.connectbot.HelpActivity; +import android.content.Context; +import android.util.AttributeSet; +import android.webkit.WebSettings; +import android.webkit.WebView; + +/** + * @author Kenny Root + * + */ +public class HelpTopicView extends WebView { + public final static String HELPDIR = "help"; + public final static String SUFFIX = ".html"; + + public HelpTopicView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initialize(); + } + + public HelpTopicView(Context context, AttributeSet attrs) { + super(context, attrs); + initialize(); + } + + public HelpTopicView(Context context) { + super(context); + initialize(); + } + + private void initialize() { + WebSettings wSet = getSettings(); + wSet.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS); + wSet.setUseWideViewPort(false); + } + + public HelpTopicView setTopic(String topic) { + String path = String.format("file:///android_asset/%s/%s%s", HELPDIR, topic, SUFFIX); + loadUrl(path); + computeScroll(); + return this; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/util/HostDatabase.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/util/HostDatabase.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,762 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.util; + +import java.nio.charset.Charset; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.five_ten_sg.connectbot.bean.HostBean; +import com.five_ten_sg.connectbot.bean.PortForwardBean; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.util.Log; + +import ch.ethz.ssh2.KnownHosts; + +/** + * Contains information about various SSH hosts, include public hostkey if known + * from previous sessions. + * + * @author jsharkey + */ +public class HostDatabase extends RobustSQLiteOpenHelper { + + public final static String TAG = "ConnectBot.HostDatabase"; + + public final static String DB_NAME = "hosts"; + public final static int DB_VERSION = 28; + + public final static String TABLE_HOSTS = "hosts"; + public final static String FIELD_HOST_NICKNAME = "nickname"; + public final static String FIELD_HOST_PROTOCOL = "protocol"; + public final static String FIELD_HOST_USERNAME = "username"; + public final static String FIELD_HOST_HOSTNAME = "hostname"; + public final static String FIELD_HOST_PORT = "port"; + public final static String FIELD_HOST_HOSTKEYALGO = "hostkeyalgo"; + public final static String FIELD_HOST_HOSTKEY = "hostkey"; + public final static String FIELD_HOST_LASTCONNECT = "lastconnect"; + public final static String FIELD_HOST_COLOR = "color"; + public final static String FIELD_HOST_USEKEYS = "usekeys"; + public final static String FIELD_HOST_USEAUTHAGENT = "useauthagent"; + public final static String FIELD_HOST_POSTLOGIN = "postlogin"; + public final static String FIELD_HOST_PUBKEYID = "pubkeyid"; + public final static String FIELD_HOST_WANTSESSION = "wantsession"; + public final static String FIELD_HOST_DELKEY = "delkey"; + public final static String FIELD_HOST_FONTSIZE = "fontsize"; + public final static String FIELD_HOST_FIXEDSIZE = "fixed_size"; + public final static String FIELD_HOST_FIXEDWIDTH = "fixed_width"; + public final static String FIELD_HOST_FIXEDHEIGHT = "fixed_height"; + public final static String FIELD_HOST_COMPRESSION = "compression"; + public final static String FIELD_HOST_HTTPPROXY = "httpproxy"; + public final static String FIELD_HOST_ENCODING = "encoding"; + public final static String FIELD_HOST_STAYCONNECTED = "stayconnected"; + public final static String FIELD_HOST_WANTX11FORWARD = "wantx11forward"; + public final static String FIELD_HOST_X11HOST = "x11host"; + public final static String FIELD_HOST_X11PORT = "x11port"; + public final static String FIELD_HOST_MONITOR = "monitor"; + public final static String FIELD_HOST_EMULATION = "emulation"; + public final static String FIELD_HOST_ENCRYPTION5250 = "encryption5250"; + public final static String FIELD_HOST_LIBRARY5250 = "library5250"; + public final static String FIELD_HOST_MENU5250 = "menu5250"; + public final static String FIELD_HOST_PROGRAM5250 = "program5250"; + public final static String CATEGORY_5250 = "5250"; + public final static String CATEGORY_X11 = "x11"; + + public final static String TABLE_PORTFORWARDS = "portforwards"; + public final static String FIELD_PORTFORWARD_HOSTID = "hostid"; + public final static String FIELD_PORTFORWARD_NICKNAME = "nickname"; + public final static String FIELD_PORTFORWARD_TYPE = "type"; + public final static String FIELD_PORTFORWARD_SOURCEPORT = "sourceport"; + public final static String FIELD_PORTFORWARD_DESTADDR = "destaddr"; + public final static String FIELD_PORTFORWARD_DESTPORT = "destport"; + + public final static String TABLE_COLORS = "colors"; + public final static String FIELD_COLOR_SCHEME = "scheme"; + public final static String FIELD_COLOR_NUMBER = "number"; + public final static String FIELD_COLOR_VALUE = "value"; + + public final static String TABLE_COLOR_DEFAULTS = "colorDefaults"; + public final static String FIELD_COLOR_FG = "fg"; + public final static String FIELD_COLOR_BG = "bg"; + + public final static int DEFAULT_FG_COLOR = 7; + public final static int DEFAULT_BG_COLOR = 0; + + public final static String COLOR_RED = "red"; + public final static String COLOR_GREEN = "green"; + public final static String COLOR_BLUE = "blue"; + public final static String COLOR_GRAY = "gray"; + + public final static String PORTFORWARD_LOCAL = "local"; + public final static String PORTFORWARD_REMOTE = "remote"; + public final static String PORTFORWARD_DYNAMIC4 = "dynamic4"; + public final static String PORTFORWARD_DYNAMIC5 = "dynamic5"; + + public final static String DELKEY_DEL = "del"; + public final static String DELKEY_BACKSPACE = "backspace"; + + public final static String AUTHAGENT_NO = "no"; + public final static String AUTHAGENT_CONFIRM = "confirm"; + public final static String AUTHAGENT_YES = "yes"; + + public final static String ENCODING_DEFAULT = Charset.defaultCharset().name(); + + public final static String X11HOST_DEFAULT = "localhost"; + public final static int X11PORT_DEFAULT = 6000; + + public final static long PUBKEYID_NEVER = -2; + public final static long PUBKEYID_ANY = -1; + + public static final int DEFAULT_COLOR_SCHEME = 0; + + // Table creation strings + public static final String CREATE_TABLE_COLOR_DEFAULTS = + "CREATE TABLE " + TABLE_COLOR_DEFAULTS + + " (" + FIELD_COLOR_SCHEME + " INTEGER NOT NULL, " + + FIELD_COLOR_FG + " INTEGER NOT NULL DEFAULT " + DEFAULT_FG_COLOR + ", " + + FIELD_COLOR_BG + " INTEGER NOT NULL DEFAULT " + DEFAULT_BG_COLOR + ")"; + public static final String CREATE_TABLE_COLOR_DEFAULTS_INDEX = + "CREATE INDEX " + TABLE_COLOR_DEFAULTS + FIELD_COLOR_SCHEME + "index ON " + + TABLE_COLOR_DEFAULTS + " (" + FIELD_COLOR_SCHEME + ");"; + + private static final String WHERE_SCHEME_AND_COLOR = FIELD_COLOR_SCHEME + " = ? AND " + + FIELD_COLOR_NUMBER + " = ?"; + + static { + addTableName(TABLE_HOSTS); + addTableName(TABLE_PORTFORWARDS); + addIndexName(TABLE_PORTFORWARDS + FIELD_PORTFORWARD_HOSTID + "index"); + addTableName(TABLE_COLORS); + addIndexName(TABLE_COLORS + FIELD_COLOR_SCHEME + "index"); + addTableName(TABLE_COLOR_DEFAULTS); + addIndexName(TABLE_COLOR_DEFAULTS + FIELD_COLOR_SCHEME + "index"); + } + + public static final Object[] dbLock = new Object[0]; + + public HostDatabase(Context context) { + super(context, DB_NAME, null, DB_VERSION); + getWritableDatabase().close(); + } + + @Override + public void onCreate(SQLiteDatabase db) { + super.onCreate(db); + db.execSQL("CREATE TABLE " + TABLE_HOSTS + + " (_id INTEGER PRIMARY KEY, " + + FIELD_HOST_NICKNAME + " TEXT, " + + FIELD_HOST_PROTOCOL + " TEXT DEFAULT 'ssh', " + + FIELD_HOST_USERNAME + " TEXT, " + + FIELD_HOST_HOSTNAME + " TEXT, " + + FIELD_HOST_PORT + " INTEGER, " + + FIELD_HOST_HOSTKEYALGO + " TEXT, " + + FIELD_HOST_HOSTKEY + " BLOB, " + + FIELD_HOST_LASTCONNECT + " INTEGER, " + + FIELD_HOST_COLOR + " TEXT, " + + FIELD_HOST_USEKEYS + " TEXT, " + + FIELD_HOST_USEAUTHAGENT + " TEXT, " + + FIELD_HOST_POSTLOGIN + " TEXT, " + + FIELD_HOST_PUBKEYID + " INTEGER DEFAULT " + PUBKEYID_ANY + ", " + + FIELD_HOST_DELKEY + " TEXT DEFAULT '" + DELKEY_DEL + "', " + + FIELD_HOST_FONTSIZE + " REAL, " + + FIELD_HOST_FIXEDSIZE + " TEXT DEFAULT '" + Boolean.toString(false) + "', " + + FIELD_HOST_FIXEDWIDTH + " INTEGER, " + + FIELD_HOST_FIXEDHEIGHT + " INTEGER, " + + FIELD_HOST_WANTSESSION + " TEXT DEFAULT '" + Boolean.toString(true) + "', " + + FIELD_HOST_COMPRESSION + " TEXT DEFAULT '" + Boolean.toString(false) + "', " + + FIELD_HOST_HTTPPROXY + " TEXT, " + + FIELD_HOST_ENCODING + " TEXT DEFAULT '" + ENCODING_DEFAULT + "', " + + FIELD_HOST_STAYCONNECTED + " TEXT, " + + FIELD_HOST_WANTX11FORWARD + " TEXT DEFAULT '" + Boolean.toString(false) + "', " + + FIELD_HOST_X11HOST + " TEXT DEFAULT '" + X11HOST_DEFAULT + "', " + + FIELD_HOST_X11PORT + " INTEGER DEFAULT " + X11PORT_DEFAULT + ", " + + FIELD_HOST_MONITOR + " TEXT, " + + FIELD_HOST_EMULATION + " TEXT, " + + FIELD_HOST_ENCRYPTION5250 + " TEXT, " + + FIELD_HOST_LIBRARY5250 + " TEXT, " + + FIELD_HOST_MENU5250 + " TEXT, " + + FIELD_HOST_PROGRAM5250 + " TEXT)"); + db.execSQL("CREATE TABLE " + TABLE_PORTFORWARDS + + " (_id INTEGER PRIMARY KEY, " + + FIELD_PORTFORWARD_HOSTID + " INTEGER, " + + FIELD_PORTFORWARD_NICKNAME + " TEXT, " + + FIELD_PORTFORWARD_TYPE + " TEXT NOT NULL DEFAULT " + PORTFORWARD_LOCAL + ", " + + FIELD_PORTFORWARD_SOURCEPORT + " INTEGER NOT NULL DEFAULT 8080, " + + FIELD_PORTFORWARD_DESTADDR + " TEXT, " + + FIELD_PORTFORWARD_DESTPORT + " TEXT)"); + db.execSQL("CREATE INDEX " + TABLE_PORTFORWARDS + FIELD_PORTFORWARD_HOSTID + "index ON " + + TABLE_PORTFORWARDS + " (" + FIELD_PORTFORWARD_HOSTID + ");"); + db.execSQL("CREATE TABLE " + TABLE_COLORS + + " (_id INTEGER PRIMARY KEY, " + + FIELD_COLOR_NUMBER + " INTEGER, " + + FIELD_COLOR_VALUE + " INTEGER, " + + FIELD_COLOR_SCHEME + " INTEGER)"); + db.execSQL("CREATE INDEX " + TABLE_COLORS + FIELD_COLOR_SCHEME + "index ON " + + TABLE_COLORS + " (" + FIELD_COLOR_SCHEME + ");"); + db.execSQL(CREATE_TABLE_COLOR_DEFAULTS); + db.execSQL(CREATE_TABLE_COLOR_DEFAULTS_INDEX); + } + + @Override + public void onRobustUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) throws SQLiteException { + // Versions of the database before 510ConnectBot will be shot without warning. + if (oldVersion < 24) { + db.execSQL("DROP TABLE IF EXISTS " + TABLE_HOSTS); + onCreate(db); + return; + } + + switch (oldVersion) { + case 24: + db.execSQL("ALTER TABLE " + TABLE_HOSTS + + " ADD COLUMN " + FIELD_HOST_MONITOR + " TEXT"); + + case 25: + db.execSQL("ALTER TABLE " + TABLE_HOSTS + + " ADD COLUMN " + FIELD_HOST_EMULATION + " TEXT"); + db.execSQL("ALTER TABLE " + TABLE_HOSTS + + " ADD COLUMN " + FIELD_HOST_ENCRYPTION5250 + " TEXT"); + + case 26: + db.execSQL("ALTER TABLE " + TABLE_HOSTS + + " ADD COLUMN " + FIELD_HOST_LIBRARY5250 + " TEXT"); + db.execSQL("ALTER TABLE " + TABLE_HOSTS + + " ADD COLUMN " + FIELD_HOST_MENU5250 + " TEXT"); + db.execSQL("ALTER TABLE " + TABLE_HOSTS + + " ADD COLUMN " + FIELD_HOST_PROGRAM5250 + " TEXT"); + + case 27: + db.execSQL("ALTER TABLE " + TABLE_HOSTS + + " ADD COLUMN " + FIELD_HOST_FIXEDSIZE + " TEXT DEFAULT '" + Boolean.toString(false) + "'"); + db.execSQL("ALTER TABLE " + TABLE_HOSTS + + " ADD COLUMN " + FIELD_HOST_FIXEDWIDTH + " INTEGER"); + db.execSQL("ALTER TABLE " + TABLE_HOSTS + + " ADD COLUMN " + FIELD_HOST_FIXEDHEIGHT + " INTEGER"); + } + } + + /** + * Touch a specific host to update its "last connected" field. + * @param nickname Nickname field of host to update + */ + public void touchHost(HostBean host) { + long now = System.currentTimeMillis() / 1000; + ContentValues values = new ContentValues(); + values.put(FIELD_HOST_LASTCONNECT, now); + + synchronized (dbLock) { + SQLiteDatabase db = this.getWritableDatabase(); + db.update(TABLE_HOSTS, values, "_id = ?", new String[] { String.valueOf(host.getId()) }); + } + } + + /** + * Create a new host using the given parameters. + */ + public HostBean saveHost(HostBean host) { + long id; + + synchronized (dbLock) { + SQLiteDatabase db = this.getWritableDatabase(); + id = db.insert(TABLE_HOSTS, null, host.getValues()); + } + + host.setId(id); + return host; + } + + /** + * Update a field in a host record. + */ + public boolean updateFontSize(HostBean host) { + long id = host.getId(); + + if (id < 0) + return false; + + ContentValues updates = new ContentValues(); + updates.put(FIELD_HOST_FONTSIZE, host.getFontSize()); + + synchronized (dbLock) { + SQLiteDatabase db = getWritableDatabase(); + db.update(TABLE_HOSTS, updates, "_id = ?", + new String[] { String.valueOf(id) }); + } + + return true; + } + + /** + * Delete a specific host by its _id value. + */ + public void deleteHost(HostBean host) { + if (host.getId() < 0) + return; + + synchronized (dbLock) { + SQLiteDatabase db = this.getWritableDatabase(); + db.delete(TABLE_HOSTS, "_id = ?", new String[] { String.valueOf(host.getId()) }); + } + } + + /** + * Return a cursor that contains information about all known hosts. + * @param sortColors If true, sort by color, otherwise sort by nickname. + */ + public List getHosts(boolean sortColors) { + String sortField = sortColors ? FIELD_HOST_COLOR : FIELD_HOST_NICKNAME; + List hosts; + + synchronized (dbLock) { + SQLiteDatabase db = this.getReadableDatabase(); + Cursor c = db.query(TABLE_HOSTS, null, null, null, null, null, sortField + " ASC"); + hosts = createHostBeans(c); + c.close(); + } + + return hosts; + } + + /** + * @param hosts + * @param c + */ + private List createHostBeans(Cursor c) { + List hosts = new LinkedList(); + final int COL_ID = c.getColumnIndexOrThrow("_id"), + COL_NICKNAME = c.getColumnIndexOrThrow(FIELD_HOST_NICKNAME), + COL_PROTOCOL = c.getColumnIndexOrThrow(FIELD_HOST_PROTOCOL), + COL_USERNAME = c.getColumnIndexOrThrow(FIELD_HOST_USERNAME), + COL_HOSTNAME = c.getColumnIndexOrThrow(FIELD_HOST_HOSTNAME), + COL_PORT = c.getColumnIndexOrThrow(FIELD_HOST_PORT), + COL_LASTCONNECT = c.getColumnIndexOrThrow(FIELD_HOST_LASTCONNECT), + COL_COLOR = c.getColumnIndexOrThrow(FIELD_HOST_COLOR), + COL_USEKEYS = c.getColumnIndexOrThrow(FIELD_HOST_USEKEYS), + COL_USEAUTHAGENT = c.getColumnIndexOrThrow(FIELD_HOST_USEAUTHAGENT), + COL_POSTLOGIN = c.getColumnIndexOrThrow(FIELD_HOST_POSTLOGIN), + COL_PUBKEYID = c.getColumnIndexOrThrow(FIELD_HOST_PUBKEYID), + COL_WANTSESSION = c.getColumnIndexOrThrow(FIELD_HOST_WANTSESSION), + COL_DELKEY = c.getColumnIndexOrThrow(FIELD_HOST_DELKEY), + COL_FONTSIZE = c.getColumnIndexOrThrow(FIELD_HOST_FONTSIZE), + COL_FIXEDSIZE = c.getColumnIndexOrThrow(FIELD_HOST_FIXEDSIZE), + COL_FIXEDWIDTH = c.getColumnIndexOrThrow(FIELD_HOST_FIXEDWIDTH), + COL_FIXEDHEIGHT = c.getColumnIndexOrThrow(FIELD_HOST_FIXEDHEIGHT), + COL_COMPRESSION = c.getColumnIndexOrThrow(FIELD_HOST_COMPRESSION), + COL_HTTPPROXY = c.getColumnIndexOrThrow(FIELD_HOST_HTTPPROXY), + COL_ENCODING = c.getColumnIndexOrThrow(FIELD_HOST_ENCODING), + COL_STAYCONNECTED = c.getColumnIndexOrThrow(FIELD_HOST_STAYCONNECTED), + COL_WANTX11FORWARD = c.getColumnIndexOrThrow(FIELD_HOST_WANTX11FORWARD), + COL_X11HOST = c.getColumnIndexOrThrow(FIELD_HOST_X11HOST), + COL_X11PORT = c.getColumnIndexOrThrow(FIELD_HOST_X11PORT), + COL_MONITOR = c.getColumnIndexOrThrow(FIELD_HOST_MONITOR), + COL_EMULATION = c.getColumnIndexOrThrow(FIELD_HOST_EMULATION), + COL_ENCRYPTION5250 = c.getColumnIndexOrThrow(FIELD_HOST_ENCRYPTION5250), + COL_LIBRARY5250 = c.getColumnIndexOrThrow(FIELD_HOST_LIBRARY5250), + COL_MENU5250 = c.getColumnIndexOrThrow(FIELD_HOST_MENU5250), + COL_PROGRAM5250 = c.getColumnIndexOrThrow(FIELD_HOST_PROGRAM5250); + + while (c.moveToNext()) { + HostBean host = new HostBean(); + host.setId(c.getLong(COL_ID)); + host.setNickname(c.getString(COL_NICKNAME)); + host.setProtocol(c.getString(COL_PROTOCOL)); + host.setUsername(c.getString(COL_USERNAME)); + host.setHostname(c.getString(COL_HOSTNAME)); + host.setPort(c.getInt(COL_PORT)); + host.setLastConnect(c.getLong(COL_LASTCONNECT)); + host.setColor(c.getString(COL_COLOR)); + host.setUseKeys(Boolean.valueOf(c.getString(COL_USEKEYS))); + host.setUseAuthAgent(c.getString(COL_USEAUTHAGENT)); + host.setPostLogin(c.getString(COL_POSTLOGIN)); + host.setPubkeyId(c.getLong(COL_PUBKEYID)); + host.setDelKey(c.getString(COL_DELKEY)); + host.setFontSize(c.getFloat(COL_FONTSIZE)); + host.setFixedSize(Boolean.valueOf(c.getString(COL_FIXEDSIZE))); + host.setFixedWidth(c.getInt(COL_FIXEDWIDTH)); + host.setFixedHeight(c.getInt(COL_FIXEDHEIGHT)); + host.setWantSession(Boolean.valueOf(c.getString(COL_WANTSESSION))); + host.setCompression(Boolean.valueOf(c.getString(COL_COMPRESSION))); + host.setHttpproxy(c.getString(COL_HTTPPROXY)); + host.setEncoding(c.getString(COL_ENCODING)); + host.setStayConnected(Boolean.valueOf(c.getString(COL_STAYCONNECTED))); + host.setWantX11Forward(Boolean.valueOf(c.getString(COL_WANTX11FORWARD))); + host.setX11Host(c.getString(COL_X11HOST)); + host.setX11Port(c.getInt(COL_X11PORT)); + host.setMonitor(c.getString(COL_MONITOR)); + host.setHostEmulation(c.getString(COL_EMULATION)); + host.setEncryption5250(c.getString(COL_ENCRYPTION5250)); + host.setLibrary(c.getString(COL_LIBRARY5250)); + host.setInitialMenu(c.getString(COL_MENU5250)); + host.setProgram(c.getString(COL_PROGRAM5250)); + hosts.add(host); + } + + return hosts; + } + + /** + * @param c + * @return + */ + private HostBean getFirstHostBean(Cursor c) { + HostBean host = null; + List hosts = createHostBeans(c); + + if (hosts.size() > 0) + host = hosts.get(0); + + c.close(); + return host; + } + + /** + * @param nickname + * @param protocol + * @param username + * @param hostname + * @param hostname2 + * @param port + * @return + */ + public HostBean findHost(Map selection) { + StringBuilder selectionBuilder = new StringBuilder(); + Iterator> i = selection.entrySet().iterator(); + List selectionValuesList = new LinkedList(); + int n = 0; + + while (i.hasNext()) { + Entry entry = i.next(); + + if (entry.getValue() == null) + continue; + + if (n++ > 0) + selectionBuilder.append(" AND "); + + selectionBuilder.append(entry.getKey()) + .append(" = ?"); + selectionValuesList.add(entry.getValue()); + } + + String selectionValues[] = new String[selectionValuesList.size()]; + selectionValuesList.toArray(selectionValues); + selectionValuesList = null; + HostBean host; + + synchronized (dbLock) { + SQLiteDatabase db = getReadableDatabase(); + Cursor c = db.query(TABLE_HOSTS, null, + selectionBuilder.toString(), + selectionValues, + null, null, null); + host = getFirstHostBean(c); + } + + return host; + } + + /** + * @param hostId + * @return + */ + public HostBean findHostById(long hostId) { + HostBean host; + + synchronized (dbLock) { + SQLiteDatabase db = getReadableDatabase(); + Cursor c = db.query(TABLE_HOSTS, null, + "_id = ?", new String[] { String.valueOf(hostId) }, + null, null, null); + host = getFirstHostBean(c); + } + + return host; + } + + /** + * Record the given hostkey into database under this nickname. + * @param hostname + * @param port + * @param hostkeyalgo + * @param hostkey + */ + public void saveKnownHost(String hostname, int port, String hostkeyalgo, byte[] hostkey) { + ContentValues values = new ContentValues(); + values.put(FIELD_HOST_HOSTKEYALGO, hostkeyalgo); + values.put(FIELD_HOST_HOSTKEY, hostkey); + + synchronized (dbLock) { + SQLiteDatabase db = getReadableDatabase(); + db.update(TABLE_HOSTS, values, + FIELD_HOST_HOSTNAME + " = ? AND " + FIELD_HOST_PORT + " = ?", + new String[] { hostname, String.valueOf(port) }); + Log.d(TAG, String.format("Finished saving hostkey information for '%s'", hostname)); + } + } + + /** + * Build list of known hosts for Trilead library. + * @return + */ + public KnownHosts getKnownHosts() { + KnownHosts known = new KnownHosts(); + + synchronized (dbLock) { + SQLiteDatabase db = this.getReadableDatabase(); + Cursor c = db.query(TABLE_HOSTS, new String[] { FIELD_HOST_HOSTNAME, + FIELD_HOST_PORT, FIELD_HOST_HOSTKEYALGO, FIELD_HOST_HOSTKEY + }, + null, null, null, null, null); + + if (c != null) { + int COL_HOSTNAME = c.getColumnIndexOrThrow(FIELD_HOST_HOSTNAME), + COL_PORT = c.getColumnIndexOrThrow(FIELD_HOST_PORT), + COL_HOSTKEYALGO = c.getColumnIndexOrThrow(FIELD_HOST_HOSTKEYALGO), + COL_HOSTKEY = c.getColumnIndexOrThrow(FIELD_HOST_HOSTKEY); + + while (c.moveToNext()) { + String hostname = c.getString(COL_HOSTNAME), + hostkeyalgo = c.getString(COL_HOSTKEYALGO); + int port = c.getInt(COL_PORT); + byte[] hostkey = c.getBlob(COL_HOSTKEY); + + if (hostkeyalgo == null || hostkeyalgo.length() == 0) continue; + + if (hostkey == null || hostkey.length == 0) continue; + + try { + known.addHostkey(new String[] { String.format("%s:%d", hostname, port) }, hostkeyalgo, hostkey); + } + catch (Exception e) { + Log.e(TAG, "Problem while adding a known host from database", e); + } + } + + c.close(); + } + } + + return known; + } + + /** + * Unset any hosts using a pubkey ID that has been deleted. + * @param pubkeyId + */ + public void stopUsingPubkey(long pubkeyId) { + if (pubkeyId < 0) return; + + ContentValues values = new ContentValues(); + values.put(FIELD_HOST_PUBKEYID, PUBKEYID_ANY); + + synchronized (dbLock) { + SQLiteDatabase db = this.getWritableDatabase(); + db.update(TABLE_HOSTS, values, FIELD_HOST_PUBKEYID + " = ?", new String[] { String.valueOf(pubkeyId) }); + } + + Log.d(TAG, String.format("Set all hosts using pubkey id %d to -1", pubkeyId)); + } + + /* + * Methods for dealing with port forwards attached to hosts + */ + + /** + * Returns a list of all the port forwards associated with a particular host ID. + * @param host the host for which we want the port forward list + * @return port forwards associated with host ID + */ + public List getPortForwardsForHost(HostBean host) { + List portForwards = new LinkedList(); + + synchronized (dbLock) { + SQLiteDatabase db = this.getReadableDatabase(); + Cursor c = db.query(TABLE_PORTFORWARDS, new String[] { + "_id", FIELD_PORTFORWARD_NICKNAME, FIELD_PORTFORWARD_TYPE, FIELD_PORTFORWARD_SOURCEPORT, + FIELD_PORTFORWARD_DESTADDR, FIELD_PORTFORWARD_DESTPORT + }, + FIELD_PORTFORWARD_HOSTID + " = ?", new String[] { String.valueOf(host.getId()) }, + null, null, null); + + while (c.moveToNext()) { + PortForwardBean pfb = new PortForwardBean( + c.getInt(0), + host.getId(), + c.getString(1), + c.getString(2), + c.getInt(3), + c.getString(4), + c.getInt(5)); + portForwards.add(pfb); + } + + c.close(); + } + + return portForwards; + } + + /** + * Update the parameters of a port forward in the database. + * @param pfb {@link PortForwardBean} to save + * @return true on success + */ + public boolean savePortForward(PortForwardBean pfb) { + boolean success = false; + + synchronized (dbLock) { + SQLiteDatabase db = getWritableDatabase(); + + if (pfb.getId() < 0) { + long id = db.insert(TABLE_PORTFORWARDS, null, pfb.getValues()); + pfb.setId(id); + success = true; + } + else { + if (db.update(TABLE_PORTFORWARDS, pfb.getValues(), "_id = ?", new String[] { String.valueOf(pfb.getId()) }) > 0) + success = true; + } + } + + return success; + } + + /** + * Deletes a port forward from the database. + * @param pfb {@link PortForwardBean} to delete + */ + public void deletePortForward(PortForwardBean pfb) { + if (pfb.getId() < 0) + return; + + synchronized (dbLock) { + SQLiteDatabase db = this.getWritableDatabase(); + db.delete(TABLE_PORTFORWARDS, "_id = ?", new String[] { String.valueOf(pfb.getId()) }); + } + } + + public Integer[] getColorsForScheme(int scheme) { + Integer[] colors = Colors.defaults.clone(); + + synchronized (dbLock) { + SQLiteDatabase db = getReadableDatabase(); + Cursor c = db.query(TABLE_COLORS, new String[] { + FIELD_COLOR_NUMBER, FIELD_COLOR_VALUE + }, + FIELD_COLOR_SCHEME + " = ?", + new String[] { String.valueOf(scheme) }, + null, null, null); + + while (c.moveToNext()) { + colors[c.getInt(0)] = Integer.valueOf(c.getInt(1)); + } + + c.close(); + } + + return colors; + } + + public void setColorForScheme(int scheme, int number, int value) { + final SQLiteDatabase db; + final String[] whereArgs = new String[] { String.valueOf(scheme), String.valueOf(number) }; + + if (value == Colors.defaults[number]) { + synchronized (dbLock) { + db = getWritableDatabase(); + db.delete(TABLE_COLORS, + WHERE_SCHEME_AND_COLOR, whereArgs); + } + } + else { + final ContentValues values = new ContentValues(); + values.put(FIELD_COLOR_VALUE, value); + + synchronized (dbLock) { + db = getWritableDatabase(); + final int rowsAffected = db.update(TABLE_COLORS, values, + WHERE_SCHEME_AND_COLOR, whereArgs); + + if (rowsAffected == 0) { + values.put(FIELD_COLOR_SCHEME, scheme); + values.put(FIELD_COLOR_NUMBER, number); + db.insert(TABLE_COLORS, null, values); + } + } + } + } + + public void setGlobalColor(int number, int value) { + setColorForScheme(DEFAULT_COLOR_SCHEME, number, value); + } + + public int[] getDefaultColorsForScheme(int scheme) { + int[] colors = new int[] { DEFAULT_FG_COLOR, DEFAULT_BG_COLOR }; + + synchronized (dbLock) { + SQLiteDatabase db = getReadableDatabase(); + Cursor c = db.query(TABLE_COLOR_DEFAULTS, + new String[] { FIELD_COLOR_FG, FIELD_COLOR_BG }, + FIELD_COLOR_SCHEME + " = ?", + new String[] { String.valueOf(scheme) }, + null, null, null); + + if (c.moveToFirst()) { + colors[0] = c.getInt(0); + colors[1] = c.getInt(1); + } + + c.close(); + } + + return colors; + } + + public int[] getGlobalDefaultColors() { + return getDefaultColorsForScheme(DEFAULT_COLOR_SCHEME); + } + + public void setDefaultColorsForScheme(int scheme, int fg, int bg) { + SQLiteDatabase db; + String schemeWhere = null; + String[] whereArgs; + schemeWhere = FIELD_COLOR_SCHEME + " = ?"; + whereArgs = new String[] { String.valueOf(scheme) }; + ContentValues values = new ContentValues(); + values.put(FIELD_COLOR_FG, fg); + values.put(FIELD_COLOR_BG, bg); + + synchronized (dbLock) { + db = getWritableDatabase(); + int rowsAffected = db.update(TABLE_COLOR_DEFAULTS, values, + schemeWhere, whereArgs); + + if (rowsAffected == 0) { + values.put(FIELD_COLOR_SCHEME, scheme); + db.insert(TABLE_COLOR_DEFAULTS, null, values); + } + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/util/OnDbWrittenListener.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/util/OnDbWrittenListener.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,26 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2010 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.util; + +/** + * @author kroot + * + */ +public interface OnDbWrittenListener { + public void onDbWritten(); +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/util/OnEntropyGatheredListener.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/util/OnEntropyGatheredListener.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,22 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.util; + +public interface OnEntropyGatheredListener { + void onEntropyGathered(byte[] entropy); +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/util/PreferenceConstants.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/util/PreferenceConstants.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,117 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.util; + +import android.os.Build; + + +/** + * @author Kenny Root + * + */ +public class PreferenceConstants { + public static final boolean PRE_HONEYCOMB = + (Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.HONEYCOMB); + + public static final String CATEGORY_UI = "category_ui"; + + public static final String MEMKEYS = "memkeys"; + public static final String SCROLLBACK = "scrollback"; + public static final String EMULATION = "emulation"; + public static final String ROTATION = "rotation"; + + public static final String ROTATION_DEFAULT = "Default"; + public static final String ROTATION_LANDSCAPE = "Force landscape"; + public static final String ROTATION_PORTRAIT = "Force portrait"; + public static final String ROTATION_AUTOMATIC = "Automatic"; + + public static final String KEYMODE = "keymode"; + + public static final String KEYMODE_RIGHT = "Use right-side keys"; + public static final String KEYMODE_LEFT = "Use left-side keys"; + + // must match android:key values in preferences.xml + public static final String CAMERA = "camera"; + public static final String VOLUP = "volup"; + public static final String VOLDN = "voldn"; + public static final String SEARCH = "search"; + public static final String PTT = "ptt"; + + // must match arrays.xml/list_hw_button_values + public static final String HWBUTTON_CTRL = "CTRL"; + public static final String HWBUTTON_ESC = "Esc"; + public static final String HWBUTTON_TAB = "Tab"; + public static final String HWBUTTON_SCREEN_CAPTURE = "Screen Capture"; + public static final String HWBUTTON_CTRLA_SPACE = "Ctrl+A then Space"; + public static final String HWBUTTON_CTRLA = "Ctrl+A"; + public static final String HWBUTTON_ESC_A = "Esc+A"; + public static final String HWBUTTON_MONITOR = "Monitor Key"; + public static final String HWBUTTON_FUNCTION_KEYS = "Soft Function Keypad"; + public static final String HWBUTTON_INCREASE_FONTSIZE = "Increase Font Size"; + public static final String HWBUTTON_DECREASE_FONTSIZE = "Decrease Font Size"; + public static final String HWBUTTON_NONE = "None"; + + public static final String KEEP_ALIVE = "keepalive"; + + public static final String WIFI_LOCK = "wifilock"; + + public static final String BUMPY_ARROWS = "bumpyarrows"; + + public static final String EULA = "eula"; + + public static final String SORT_BY_COLOR = "sortByColor"; + + public static final String BELL = "bell"; + public static final String BELL_VOLUME = "bellVolume"; + public static final String BELL_VIBRATE = "bellVibrate"; + public static final String BELL_NOTIFICATION = "bellNotification"; + public static final float DEFAULT_BELL_VOLUME = 0.25f; + + public static final String CONNECTION_PERSIST = "connPersist"; + + public static final String SHIFT_FKEYS = "shiftfkeys"; + public static final String CTRL_FKEYS = "ctrlfkeys"; + + /* Backup identifiers */ + public static final String BACKUP_PREF_KEY = "prefs"; + + public static final String CTRL_STRING = "ctrl_string"; + public static final String PICKER_STRING = "picker_string"; + public static final String PICKER_KEEP_OPEN = "picker_keep_open"; + public static final String EXTENDED_LONGPRESS = "extended_longpress"; + public static final String SCREEN_CAPTURE_POPUP = "screen_capture_popup"; + public static final String SCREEN_CAPTURE_FOLDER = "screen_capture_folder"; + public static final String FILE_DIALOG = "file_dialog"; + public static final String DOWNLOAD_FOLDER = "download_folder"; + public static final String REMOTE_UPLOAD_FOLDER = "remote_upload_folder"; + public static final String BACKGROUND_FILE_TRANSFER = "background_file_transfer"; + public static final String UPLOAD_DESTINATION_PROMPT = "upload_dest_prompt"; + + /* Debug */ + public static final String DEBUG_KEYCODES = "debug_keycodes"; + + /* Device keyboard mapping */ + public static final String CUSTOM_KEYMAP = "list_custom_keymap"; + // must match arrays.xml/list_custom_keymap_values + public static final String CUSTOM_KEYMAP_DISABLED = "none"; + public static final String CUSTOM_KEYMAP_FULL = "full"; + public static final String CUSTOM_KEYMAP_ASUS_TF = "asus_tf"; + public static final String CUSTOM_KEYMAP_SGH_I927 = "sgh_i927"; + public static final String CUSTOM_KEYMAP_SGH_I927_ICS = "sgh_i927_ics"; + public static final String CUSTOM_KEYMAP_SE_XPPRO = "se_xppro"; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/util/PubkeyDatabase.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/util/PubkeyDatabase.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,311 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.util; + +import java.util.LinkedList; +import java.util.List; + +import com.five_ten_sg.connectbot.bean.PubkeyBean; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; + +/** + * Public Key Encryption database. Contains private and public key pairs + * for public key authentication. + * + * @author Kenny Root + */ +public class PubkeyDatabase extends RobustSQLiteOpenHelper { + public final static String TAG = "ConnectBot.PubkeyDatabase"; + + public final static String DB_NAME = "pubkeys"; + public final static int DB_VERSION = 2; + + public final static String TABLE_PUBKEYS = "pubkeys"; + public final static String FIELD_PUBKEY_NICKNAME = "nickname"; + public final static String FIELD_PUBKEY_TYPE = "type"; + public final static String FIELD_PUBKEY_PRIVATE = "private"; + public final static String FIELD_PUBKEY_PUBLIC = "public"; + public final static String FIELD_PUBKEY_ENCRYPTED = "encrypted"; + public final static String FIELD_PUBKEY_STARTUP = "startup"; + public final static String FIELD_PUBKEY_CONFIRMUSE = "confirmuse"; + public final static String FIELD_PUBKEY_LIFETIME = "lifetime"; + + public final static String KEY_TYPE_RSA = "RSA", + KEY_TYPE_DSA = "DSA", + KEY_TYPE_IMPORTED = "IMPORTED", + KEY_TYPE_EC = "EC"; + + private Context context; + + static { + addTableName(TABLE_PUBKEYS); + } + + public PubkeyDatabase(Context context) { + super(context, DB_NAME, null, DB_VERSION); + this.context = context; + } + + @Override + public void onCreate(SQLiteDatabase db) { + super.onCreate(db); + db.execSQL("CREATE TABLE " + TABLE_PUBKEYS + + " (_id INTEGER PRIMARY KEY, " + + FIELD_PUBKEY_NICKNAME + " TEXT, " + + FIELD_PUBKEY_TYPE + " TEXT, " + + FIELD_PUBKEY_PRIVATE + " BLOB, " + + FIELD_PUBKEY_PUBLIC + " BLOB, " + + FIELD_PUBKEY_ENCRYPTED + " INTEGER, " + + FIELD_PUBKEY_STARTUP + " INTEGER, " + + FIELD_PUBKEY_CONFIRMUSE + " INTEGER DEFAULT 0, " + + FIELD_PUBKEY_LIFETIME + " INTEGER DEFAULT 0)"); + } + + @Override + public void onRobustUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) throws SQLiteException { + switch (oldVersion) { + case 1: + db.execSQL("ALTER TABLE " + TABLE_PUBKEYS + + " ADD COLUMN " + FIELD_PUBKEY_CONFIRMUSE + " INTEGER DEFAULT 0"); + db.execSQL("ALTER TABLE " + TABLE_PUBKEYS + + " ADD COLUMN " + FIELD_PUBKEY_LIFETIME + " INTEGER DEFAULT 0"); + } + } + + /** + * Delete a specific host by its _id value. + */ + public void deletePubkey(PubkeyBean pubkey) { + HostDatabase hostdb = new HostDatabase(context); + hostdb.stopUsingPubkey(pubkey.getId()); + hostdb.close(); + SQLiteDatabase db = getWritableDatabase(); + db.delete(TABLE_PUBKEYS, "_id = ?", new String[] { Long.toString(pubkey.getId()) }); + db.close(); + } + + /** + * Return a cursor that contains information about all known hosts. + */ + /* + public Cursor allPubkeys() { + SQLiteDatabase db = this.getReadableDatabase(); + return db.query(TABLE_PUBKEYS, new String[] { "_id", + FIELD_PUBKEY_NICKNAME, FIELD_PUBKEY_TYPE, FIELD_PUBKEY_PRIVATE, + FIELD_PUBKEY_PUBLIC, FIELD_PUBKEY_ENCRYPTED, FIELD_PUBKEY_STARTUP }, + null, null, null, null, null); + }*/ + + public List allPubkeys() { + return getPubkeys(null, null); + } + + public List getAllStartPubkeys() { + return getPubkeys(FIELD_PUBKEY_STARTUP + " = 1 AND " + FIELD_PUBKEY_ENCRYPTED + " = 0", null); + } + + private List getPubkeys(String selection, String[] selectionArgs) { + SQLiteDatabase db = getReadableDatabase(); + List pubkeys = new LinkedList(); + Cursor c = db.query(TABLE_PUBKEYS, null, selection, selectionArgs, null, null, null); + + if (c != null) { + final int COL_ID = c.getColumnIndexOrThrow("_id"), + COL_NICKNAME = c.getColumnIndexOrThrow(FIELD_PUBKEY_NICKNAME), + COL_TYPE = c.getColumnIndexOrThrow(FIELD_PUBKEY_TYPE), + COL_PRIVATE = c.getColumnIndexOrThrow(FIELD_PUBKEY_PRIVATE), + COL_PUBLIC = c.getColumnIndexOrThrow(FIELD_PUBKEY_PUBLIC), + COL_ENCRYPTED = c.getColumnIndexOrThrow(FIELD_PUBKEY_ENCRYPTED), + COL_STARTUP = c.getColumnIndexOrThrow(FIELD_PUBKEY_STARTUP), + COL_CONFIRMUSE = c.getColumnIndexOrThrow(FIELD_PUBKEY_CONFIRMUSE), + COL_LIFETIME = c.getColumnIndexOrThrow(FIELD_PUBKEY_LIFETIME); + + while (c.moveToNext()) { + PubkeyBean pubkey = new PubkeyBean(); + pubkey.setId(c.getLong(COL_ID)); + pubkey.setNickname(c.getString(COL_NICKNAME)); + pubkey.setType(c.getString(COL_TYPE)); + pubkey.setPrivateKey(c.getBlob(COL_PRIVATE)); + pubkey.setPublicKey(c.getBlob(COL_PUBLIC)); + pubkey.setEncrypted(c.getInt(COL_ENCRYPTED) > 0); + pubkey.setStartup(c.getInt(COL_STARTUP) > 0); + pubkey.setConfirmUse(c.getInt(COL_CONFIRMUSE) > 0); + pubkey.setLifetime(c.getInt(COL_LIFETIME)); + pubkeys.add(pubkey); + } + + c.close(); + } + + db.close(); + return pubkeys; + } + + /** + * @param hostId + * @return + */ + public PubkeyBean findPubkeyById(long pubkeyId) { + SQLiteDatabase db = getReadableDatabase(); + Cursor c = db.query(TABLE_PUBKEYS, null, + "_id = ?", new String[] { String.valueOf(pubkeyId) }, + null, null, null); + PubkeyBean pubkey = null; + + if (c != null) { + if (c.moveToFirst()) + pubkey = createPubkeyBean(c); + + c.close(); + } + + db.close(); + return pubkey; + } + + private PubkeyBean createPubkeyBean(Cursor c) { + PubkeyBean pubkey = new PubkeyBean(); + pubkey.setId(c.getLong(c.getColumnIndexOrThrow("_id"))); + pubkey.setNickname(c.getString(c.getColumnIndexOrThrow(FIELD_PUBKEY_NICKNAME))); + pubkey.setType(c.getString(c.getColumnIndexOrThrow(FIELD_PUBKEY_TYPE))); + pubkey.setPrivateKey(c.getBlob(c.getColumnIndexOrThrow(FIELD_PUBKEY_PRIVATE))); + pubkey.setPublicKey(c.getBlob(c.getColumnIndexOrThrow(FIELD_PUBKEY_PUBLIC))); + pubkey.setEncrypted(c.getInt(c.getColumnIndexOrThrow(FIELD_PUBKEY_ENCRYPTED)) > 0); + pubkey.setStartup(c.getInt(c.getColumnIndexOrThrow(FIELD_PUBKEY_STARTUP)) > 0); + pubkey.setConfirmUse(c.getInt(c.getColumnIndexOrThrow(FIELD_PUBKEY_CONFIRMUSE)) > 0); + pubkey.setLifetime(c.getInt(c.getColumnIndexOrThrow(FIELD_PUBKEY_LIFETIME))); + return pubkey; + } + + /** + * Pull all values for a given column as a list of Strings, probably for use + * in a ListPreference. Sorted by _id ascending. + */ + public List allValues(String column) { + List list = new LinkedList(); + SQLiteDatabase db = this.getReadableDatabase(); + Cursor c = db.query(TABLE_PUBKEYS, new String[] { "_id", column }, + null, null, null, null, "_id ASC"); + + if (c != null) { + int COL = c.getColumnIndexOrThrow(column); + + while (c.moveToNext()) + list.add(c.getString(COL)); + + c.close(); + } + + db.close(); + return list; + } + + public String getNickname(long id) { + String nickname = null; + SQLiteDatabase db = this.getReadableDatabase(); + Cursor c = db.query(TABLE_PUBKEYS, new String[] { "_id", + FIELD_PUBKEY_NICKNAME + }, "_id = ?", + new String[] { Long.toString(id) }, null, null, null); + + if (c != null) { + if (c.moveToFirst()) + nickname = c.getString(c.getColumnIndexOrThrow(FIELD_PUBKEY_NICKNAME)); + + c.close(); + } + + db.close(); + return nickname; + } + + /* + public void setOnStart(long id, boolean onStart) { + + SQLiteDatabase db = this.getWritableDatabase(); + + ContentValues values = new ContentValues(); + values.put(FIELD_PUBKEY_STARTUP, onStart ? 1 : 0); + + db.update(TABLE_PUBKEYS, values, "_id = ?", new String[] { Long.toString(id) }); + + } + + public boolean changePassword(long id, String oldPassword, String newPassword) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException { + SQLiteDatabase db = this.getWritableDatabase(); + + Cursor c = db.query(TABLE_PUBKEYS, new String[] { FIELD_PUBKEY_TYPE, + FIELD_PUBKEY_PRIVATE, FIELD_PUBKEY_ENCRYPTED }, + "_id = ?", new String[] { String.valueOf(id) }, + null, null, null); + + if (!c.moveToFirst()) + return false; + + String keyType = c.getString(0); + byte[] encPriv = c.getBlob(1); + c.close(); + + PrivateKey priv; + try { + priv = PubkeyUtils.decodePrivate(encPriv, keyType, oldPassword); + } catch (InvalidKeyException e) { + return false; + } catch (BadPaddingException e) { + return false; + } catch (InvalidKeySpecException e) { + return false; + } + + ContentValues values = new ContentValues(); + values.put(FIELD_PUBKEY_PRIVATE, PubkeyUtils.getEncodedPrivate(priv, newPassword)); + values.put(FIELD_PUBKEY_ENCRYPTED, newPassword.length() > 0 ? 1 : 0); + db.update(TABLE_PUBKEYS, values, "_id = ?", new String[] { String.valueOf(id) }); + + return true; + } + */ + + /** + * @param pubkey + */ + public PubkeyBean savePubkey(PubkeyBean pubkey) { + SQLiteDatabase db = this.getWritableDatabase(); + boolean success = false; + ContentValues values = pubkey.getValues(); + + if (pubkey.getId() > 0) { + values.remove("_id"); + + if (db.update(TABLE_PUBKEYS, values, "_id = ?", new String[] { String.valueOf(pubkey.getId()) }) > 0) + success = true; + } + + if (!success) { + long id = db.insert(TABLE_PUBKEYS, null, pubkey.getValues()); + pubkey.setId(id); + } + + db.close(); + return pubkey; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/util/PubkeyUtils.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/util/PubkeyUtils.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,392 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.util; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.DSAParams; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateCrtKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.DSAPublicKeySpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.InvalidParameterSpecException; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.EncryptedPrivateKeyInfo; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.keyczar.jce.EcCore; + +import com.five_ten_sg.connectbot.bean.PubkeyBean; +import android.util.Log; + +import ch.ethz.ssh2.crypto.Base64; +import ch.ethz.ssh2.crypto.SecureRandomFix; +import ch.ethz.ssh2.crypto.SimpleDERReader; +import ch.ethz.ssh2.signature.DSASHA1Verify; +import ch.ethz.ssh2.signature.ECDSASHA2Verify; +import ch.ethz.ssh2.signature.RSASHA1Verify; + +public class PubkeyUtils { + private static final String TAG = "PubkeyUtils"; + + public static final String PKCS8_START = "-----BEGIN PRIVATE KEY-----"; + public static final String PKCS8_END = "-----END PRIVATE KEY-----"; + + // Size in bytes of salt to use. + private static final int SALT_SIZE = 8; + + // Number of iterations for password hashing. PKCS#5 recommends 1000 + private static final int ITERATIONS = 1000; + + // Cannot be instantiated + private PubkeyUtils() { + } + + public static String formatKey(Key key) { + String algo = key.getAlgorithm(); + String fmt = key.getFormat(); + byte[] encoded = key.getEncoded(); + return "Key[algorithm=" + algo + ", format=" + fmt + + ", bytes=" + encoded.length + "]"; + } + + public static byte[] sha256(byte[] data) throws NoSuchAlgorithmException { + return MessageDigest.getInstance("SHA-256").digest(data); + } + + public static byte[] cipher(int mode, byte[] data, byte[] secret) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { + SecretKeySpec secretKeySpec = new SecretKeySpec(sha256(secret), "AES"); + Cipher c = Cipher.getInstance("AES"); + c.init(mode, secretKeySpec); + return c.doFinal(data); + } + + public static byte[] encrypt(byte[] cleartext, String secret) throws Exception { + byte[] salt = new byte[SALT_SIZE]; + byte[] ciphertext = Encryptor.encrypt(salt, ITERATIONS, secret, cleartext); + byte[] complete = new byte[salt.length + ciphertext.length]; + System.arraycopy(salt, 0, complete, 0, salt.length); + System.arraycopy(ciphertext, 0, complete, salt.length, ciphertext.length); + Arrays.fill(salt, (byte) 0x00); + Arrays.fill(ciphertext, (byte) 0x00); + return complete; + } + + public static byte[] decrypt(byte[] saltAndCiphertext, String secret) throws Exception { + try { + byte[] salt = new byte[SALT_SIZE]; + byte[] ciphertext = new byte[saltAndCiphertext.length - salt.length]; + System.arraycopy(saltAndCiphertext, 0, salt, 0, salt.length); + System.arraycopy(saltAndCiphertext, salt.length, ciphertext, 0, ciphertext.length); + return Encryptor.decrypt(salt, ITERATIONS, secret, ciphertext); + } + catch (Exception e) { + Log.d("decrypt", "Could not decrypt with new method", e); + // We might be using the old encryption method. + return cipher(Cipher.DECRYPT_MODE, saltAndCiphertext, secret.getBytes()); + } + } + + public static byte[] getEncodedPrivate(PrivateKey pk, String secret) throws Exception { + final byte[] encoded = pk.getEncoded(); + + if (secret == null || secret.length() == 0) { + return encoded; + } + + return encrypt(pk.getEncoded(), secret); + } + + public static PrivateKey decodePrivate(byte[] encoded, String keyType) throws NoSuchAlgorithmException, InvalidKeySpecException { + PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(encoded); + KeyFactory kf = KeyFactory.getInstance(keyType); + return kf.generatePrivate(privKeySpec); + } + + public static PrivateKey decodePrivate(byte[] encoded, String keyType, String secret) throws Exception { + if (secret != null && secret.length() > 0) + return decodePrivate(decrypt(encoded, secret), keyType); + else + return decodePrivate(encoded, keyType); + } + + public static PublicKey decodePublic(byte[] encoded, String keyType) throws NoSuchAlgorithmException, InvalidKeySpecException { + X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(encoded); + KeyFactory kf = KeyFactory.getInstance(keyType); + return kf.generatePublic(pubKeySpec); + } + + static String getAlgorithmForOid(String oid) throws NoSuchAlgorithmException { + if ("1.2.840.10045.2.1".equals(oid)) { + return "EC"; + } + else if ("1.2.840.113549.1.1.1".equals(oid)) { + return "RSA"; + } + else if ("1.2.840.10040.4.1".equals(oid)) { + return "DSA"; + } + else { + throw new NoSuchAlgorithmException("Unknown algorithm OID " + oid); + } + } + + static String getOidFromPkcs8Encoded(byte[] encoded) throws NoSuchAlgorithmException { + if (encoded == null) { + throw new NoSuchAlgorithmException("encoding is null"); + } + + try { + SimpleDERReader reader = new SimpleDERReader(encoded); + reader.resetInput(reader.readSequenceAsByteArray()); + reader.readInt(); + reader.resetInput(reader.readSequenceAsByteArray()); + return reader.readOid(); + } + catch (IOException e) { + Log.w(TAG, "Could not read OID", e); + throw new NoSuchAlgorithmException("Could not read key", e); + } + } + + public static KeyPair recoverKeyPair(byte[] encoded) throws NoSuchAlgorithmException, + InvalidKeySpecException { + final String algo = getAlgorithmForOid(getOidFromPkcs8Encoded(encoded)); + final KeySpec privKeySpec = new PKCS8EncodedKeySpec(encoded); + final KeyFactory kf = KeyFactory.getInstance(algo); + final PrivateKey priv = kf.generatePrivate(privKeySpec); + return new KeyPair(recoverPublicKey(kf, priv), priv); + } + + + static PublicKey recoverPublicKey(KeyFactory kf, PrivateKey priv) + throws NoSuchAlgorithmException, InvalidKeySpecException { + if (priv instanceof RSAPrivateCrtKey) { + RSAPrivateCrtKey rsaPriv = (RSAPrivateCrtKey) priv; + return kf.generatePublic(new RSAPublicKeySpec(rsaPriv.getModulus(), rsaPriv + .getPublicExponent())); + } + else if (priv instanceof DSAPrivateKey) { + DSAPrivateKey dsaPriv = (DSAPrivateKey) priv; + DSAParams params = dsaPriv.getParams(); + // Calculate public key Y + BigInteger y = params.getG().modPow(dsaPriv.getX(), params.getP()); + return kf.generatePublic(new DSAPublicKeySpec(y, params.getP(), params.getQ(), params + .getG())); + } + else if (priv instanceof ECPrivateKey) { + ECPrivateKey ecPriv = (ECPrivateKey) priv; + ECParameterSpec params = ecPriv.getParams(); + // Calculate public key Y + ECPoint generator = params.getGenerator(); + BigInteger[] wCoords = EcCore.multiplyPointA(new BigInteger[] { generator.getAffineX(), + generator.getAffineY() + }, ecPriv.getS(), params); + ECPoint w = new ECPoint(wCoords[0], wCoords[1]); + return kf.generatePublic(new ECPublicKeySpec(w, params)); + } + else { + throw new NoSuchAlgorithmException("Key type must be RSA, DSA, or EC"); + } + } + + /* + * OpenSSH compatibility methods + */ + + public static String convertToOpenSSHFormat(PublicKey pk, String origNickname) throws IOException, InvalidKeyException { + String nickname = origNickname; + + if (nickname == null) + nickname = "connectbot@android"; + + if (pk instanceof RSAPublicKey) { + String data = "ssh-rsa "; + data += String.valueOf(Base64.encode(RSASHA1Verify.encodeSSHRSAPublicKey((RSAPublicKey) pk))); + return data + " " + nickname; + } + else if (pk instanceof DSAPublicKey) { + String data = "ssh-dss "; + data += String.valueOf(Base64.encode(DSASHA1Verify.encodeSSHDSAPublicKey((DSAPublicKey) pk))); + return data + " " + nickname; + } + else if (pk instanceof ECPublicKey) { + ECPublicKey ecPub = (ECPublicKey) pk; + String keyType = ECDSASHA2Verify.getCurveName(ecPub.getParams().getCurve().getField().getFieldSize()); + String keyData = String.valueOf(Base64.encode(ECDSASHA2Verify.encodeSSHECDSAPublicKey(ecPub))); + return ECDSASHA2Verify.ECDSA_SHA2_PREFIX + keyType + " " + keyData + " " + nickname; + } + + throw new InvalidKeyException("Unknown key type"); + } + + /* + * OpenSSH compatibility methods + */ + + /** + * @param pair + * @return OpenSSH-encoded pubkey + */ + public static byte[] extractOpenSSHPublic(KeyPair pair) { + try { + PublicKey pubKey = pair.getPublic(); + + if (pubKey instanceof RSAPublicKey) { + return RSASHA1Verify.encodeSSHRSAPublicKey((RSAPublicKey) pair.getPublic()); + } + else if (pubKey instanceof DSAPublicKey) { + return DSASHA1Verify.encodeSSHDSAPublicKey((DSAPublicKey) pair.getPublic()); + } + else if (pubKey instanceof ECPublicKey) { + return ECDSASHA2Verify.encodeSSHECDSAPublicKey((ECPublicKey) pair.getPublic()); + } + else { + return null; + } + } + catch (IOException e) { + return null; + } + } + + public static String exportPEM(PrivateKey key, String secret) throws NoSuchAlgorithmException, InvalidParameterSpecException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, InvalidKeySpecException, IllegalBlockSizeException, IOException { + StringBuilder sb = new StringBuilder(); + byte[] data = key.getEncoded(); + sb.append(PKCS8_START); + sb.append('\n'); + + if (secret != null) { + byte[] salt = new byte[8]; + SecureRandomFix random = new SecureRandomFix(); + random.nextBytes(salt); + PBEParameterSpec defParams = new PBEParameterSpec(salt, 1); + AlgorithmParameters params = AlgorithmParameters.getInstance(key.getAlgorithm()); + params.init(defParams); + PBEKeySpec pbeSpec = new PBEKeySpec(secret.toCharArray()); + SecretKeyFactory keyFact = SecretKeyFactory.getInstance(key.getAlgorithm()); + Cipher cipher = Cipher.getInstance(key.getAlgorithm()); + cipher.init(Cipher.WRAP_MODE, keyFact.generateSecret(pbeSpec), params); + byte[] wrappedKey = cipher.wrap(key); + EncryptedPrivateKeyInfo pinfo = new EncryptedPrivateKeyInfo(params, wrappedKey); + data = pinfo.getEncoded(); + sb.append("Proc-Type: 4,ENCRYPTED\n"); + sb.append("DEK-Info: DES-EDE3-CBC,"); + sb.append(encodeHex(salt)); + sb.append("\n\n"); + } + + int i = sb.length(); + sb.append(Base64.encode(data)); + + for (i += 63; i < sb.length(); i += 64) { + sb.insert(i, "\n"); + } + + sb.append('\n'); + sb.append(PKCS8_END); + sb.append('\n'); + return sb.toString(); + } + + private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', + '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + protected static String encodeHex(byte[] bytes) { + final char[] hex = new char[bytes.length * 2]; + int i = 0; + + for (byte b : bytes) { + hex[i++] = HEX_DIGITS[(b >> 4) & 0x0f]; + hex[i++] = HEX_DIGITS[b & 0x0f]; + } + + return String.valueOf(hex); + } + + public static String getPubkeyString(PubkeyBean pubkey) { + try { + PublicKey pk = decodePublic(pubkey.getPublicKey(), pubkey.getType()); + return convertToOpenSSHFormat(pk, pubkey.getNickname()); + } + catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + public static String getPrivkeyString(PubkeyBean pubkey, String passphrase) { + String data = null; + boolean imported = PubkeyDatabase.KEY_TYPE_IMPORTED.equals(pubkey.getType()); + + if (imported) + try { + data = new String(pubkey.getPrivateKey()); + } + catch (Exception e) { + e.printStackTrace(); + } + else { + try { + PrivateKey pk = null; + + if (passphrase == null) + pk = decodePrivate(pubkey.getPrivateKey(), pubkey.getType()); + else + pk = decodePrivate(pubkey.getPrivateKey(), pubkey.getType(), passphrase); + + data = exportPEM(pk, passphrase); + } + catch (Exception e) { + e.printStackTrace(); + } + } + + return data; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/util/RobustSQLiteOpenHelper.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/util/RobustSQLiteOpenHelper.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,135 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.util; + +import java.util.LinkedList; +import java.util.List; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteDatabase.CursorFactory; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteOpenHelper; + +/** + * @author Kenny Root + * + */ +public abstract class RobustSQLiteOpenHelper extends SQLiteOpenHelper { + private static List mTableNames = new LinkedList(); + private static List mIndexNames = new LinkedList(); + + public RobustSQLiteOpenHelper(Context context, String name, + CursorFactory factory, int version) { + super(context, name, factory, version); + } + + protected static void addTableName(String tableName) { + mTableNames.add(tableName); + } + + protected static void addIndexName(String indexName) { + mIndexNames.add(indexName); + } + + @Override + public void onCreate(SQLiteDatabase db) { + dropAllTables(db); + } + + @Override + public final void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + try { + onRobustUpgrade(db, oldVersion, newVersion); + } + catch (SQLiteException e) { + // The database has entered an unknown state. Try to recover. + try { + regenerateTables(db); + } + catch (SQLiteException e2) { + dropAndCreateTables(db); + } + } + } + + public abstract void onRobustUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) throws SQLiteException; + + private void regenerateTables(SQLiteDatabase db) { + dropAllTablesWithPrefix(db, "OLD_"); + + for (String tableName : mTableNames) + db.execSQL("ALTER TABLE " + tableName + " RENAME TO OLD_" + + tableName); + + onCreate(db); + + for (String tableName : mTableNames) + repopulateTable(db, tableName); + + dropAllTablesWithPrefix(db, "OLD_"); + } + + private void repopulateTable(SQLiteDatabase db, String tableName) { + String columns = getTableColumnNames(db, tableName); + StringBuilder sb = new StringBuilder(); + sb.append("INSERT INTO ") + .append(tableName) + .append(" (") + .append(columns) + .append(") SELECT ") + .append(columns) + .append(" FROM OLD_") + .append(tableName); + String sql = sb.toString(); + db.execSQL(sql); + } + + private String getTableColumnNames(SQLiteDatabase db, String tableName) { + StringBuilder sb = new StringBuilder(); + Cursor fields = db.rawQuery("PRAGMA table_info(" + tableName + ")", null); + + while (fields.moveToNext()) { + if (!fields.isFirst()) + sb.append(", "); + + sb.append(fields.getString(1)); + } + + fields.close(); + return sb.toString(); + } + + private void dropAndCreateTables(SQLiteDatabase db) { + dropAllTables(db); + onCreate(db); + } + + private void dropAllTablesWithPrefix(SQLiteDatabase db, String prefix) { + for (String indexName : mIndexNames) + db.execSQL("DROP INDEX IF EXISTS " + prefix + indexName); + + for (String tableName : mTableNames) + db.execSQL("DROP TABLE IF EXISTS " + prefix + tableName); + } + + private void dropAllTables(SQLiteDatabase db) { + dropAllTablesWithPrefix(db, ""); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/util/StringPickerDialog.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/util/StringPickerDialog.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.util; + +import com.five_ten_sg.connectbot.R; + +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.text.*; +import android.view.LayoutInflater; +import android.view.View.OnClickListener; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.Button; +import android.widget.GridView; + +/** + * Dialog for choosing accented characters related to a base character. + */ +public class StringPickerDialog extends Dialog + implements OnItemClickListener, OnClickListener { + private View mView; + private Editable mText; + private String []mOptions; + private boolean mInsert; + private LayoutInflater mInflater; + private Button mCancelButton; + + /** + * Creates a new StringPickerDialog that presents the specified + * options for insertion or replacement (depending on + * the sense of insert) into text. + */ + public StringPickerDialog(Context context, View view, + Editable text, String []options, + boolean insert) { + //super(context, com.android.internal.R.style.Theme_Panel); + //Resources res = Resources.getSystem(); + //int id = res.getIdentifier("Theme_Panel", "style", "android"); + //int id = android.R.style.Theme_Panel; + super(context, android.R.style.Theme_Panel); + mView = view; + mText = text; + mOptions = options; + mInsert = insert; + mInflater = LayoutInflater.from(context); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + WindowManager.LayoutParams params = getWindow().getAttributes(); + params.token = mView.getApplicationWindowToken(); + params.type = params.TYPE_APPLICATION_ATTACHED_DIALOG; + params.flags = params.flags | Window.FEATURE_NO_TITLE; + setContentView(R.layout.string_picker); + GridView grid = (GridView) findViewById(R.id.stringPicker); + grid.setAdapter(new OptionsAdapter(getContext())); + grid.setOnItemClickListener(this); + mCancelButton = (Button) findViewById(R.id.cancel); + mCancelButton.setOnClickListener(this); + } + + /** + * Handles clicks on the character buttons. + */ + public void onItemClick(AdapterView parent, View view, int position, long id) { + String result = mOptions[position]; + replaceCharacterAndClose(result); + } + + private void replaceCharacterAndClose(CharSequence replace) { + int selEnd = Selection.getSelectionEnd(mText); + + if (mInsert || selEnd == 0) { + mText.insert(selEnd, replace); + } + else { + mText.replace(selEnd - 1, selEnd, replace); + } + + dismiss(); + } + + /** + * Handles clicks on the Cancel button. + */ + public void onClick(View v) { + if (v == mCancelButton) { + dismiss(); + } + else if (v instanceof Button) { + CharSequence result = ((Button) v).getText(); + replaceCharacterAndClose(result); + } + } + + private class OptionsAdapter extends BaseAdapter { + + public OptionsAdapter(Context context) { + super(); + } + + public View getView(int position, View convertView, ViewGroup parent) { + Button b = (Button) + mInflater.inflate(R.layout.string_picker_button, null); + b.setText(mOptions[position]); + b.setOnClickListener(StringPickerDialog.this); + return b; + } + + public final int getCount() { + return mOptions.length; + } + + public final Object getItem(int position) { + return mOptions[position]; + } + + public final long getItemId(int position) { + return position; + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/util/TransferThread.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/util/TransferThread.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,150 @@ +/* + * TransferThread Class for VX ConnectBot + * Copyright 2012 Martin Matuska + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.five_ten_sg.connectbot.util; + +import java.util.StringTokenizer; + +import com.five_ten_sg.connectbot.R; +import com.five_ten_sg.connectbot.service.TerminalBridge; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.os.Handler; +import android.preference.PreferenceManager; +import android.util.Log; +import android.widget.Toast; + +public class TransferThread extends Thread { + public final static String TAG = "ConnectBot.TransferThread"; + private final Activity activity; + private final SharedPreferences prefs; + private String dialogMessage = null; + private Handler handler = new Handler(); + private TerminalBridge bridge; + private String files, destName, destFolder; + private ProgressDialog progress = null; + private boolean upload; + private Toast progressToast = null; + + public TransferThread(Activity activity, Handler handler) { + this.activity = activity; +// this.handler = handler; + this.prefs = PreferenceManager.getDefaultSharedPreferences(this.activity); + } + + public void setProgressDialogMessage(String message) { + this.dialogMessage = message; + } + + public void download(TerminalBridge bridge, String files, String destName, String destFolder) { + this.bridge = bridge; + this.files = files; + this.destName = destName; + this.destFolder = destFolder; + this.upload = false; + this.configureProgressDialog(); + this.start(); + } + + public void upload(TerminalBridge bridge, String files, String destName, String destFolder) { + this.bridge = bridge; + this.files = files; + this.destName = destName; + this.destFolder = destFolder; + this.upload = true; + this.configureProgressDialog(); + this.start(); + } + + @Override + public void run() { + if (this.activity == null || this.handler == null || this.bridge == null) + return; + + Log.d(TAG, "Requested " + (upload ? "upload" : "download") + " of [" + files + "]"); + Resources res = activity.getResources(); + String fail = ""; + + try { + StringTokenizer fileSet = new StringTokenizer(files, "\n"); + + while (fileSet.hasMoreTokens()) { + String file = fileSet.nextToken(); + final String newMessage = res.getString(upload ? R.string.transfer_uploading_file : R.string.transfer_downloading_file, file); + handler.post(new Runnable() { + public void run() { + if (prefs.getBoolean(PreferenceConstants.BACKGROUND_FILE_TRANSFER, true)) { + if (progressToast == null) + progressToast = Toast.makeText(activity, newMessage, Toast.LENGTH_LONG); + else + progressToast.setText(newMessage); + + progressToast.show(); + } + else if (progress != null) { + progress.setMessage(newMessage); + } + } + }); + boolean success = (upload ? bridge.uploadFile(file, destName, destFolder, null) : bridge.downloadFile(file, destFolder)); + + if (! success) + fail += " " + file; + } + } + finally { + final String failMessage = (fail.length() == 0 ? null : res.getString(upload ? R.string.transfer_upload_failed : R.string.transfer_download_failed, fail)); + final String sucMessage = (res.getString(upload ? R.string.transfer_upload_complete : R.string.transfer_download_complete)); + handler.post(new Runnable() { + public void run() { + if (progress != null) + progress.dismiss(); + + if (prefs.getBoolean(PreferenceConstants.BACKGROUND_FILE_TRANSFER, true)) { + Toast.makeText(activity, + failMessage != null ? failMessage : sucMessage, + Toast.LENGTH_LONG) + .show(); + } + else if (failMessage != null) { + new AlertDialog.Builder(activity) + .setMessage(failMessage) + .setNegativeButton(android.R.string.ok, null).create().show(); + } + } + }); + } + } + + private void configureProgressDialog() { + if (dialogMessage != null) + progress = fileProgressDialog(activity, this.dialogMessage); + else + progress = null; + } + + private ProgressDialog fileProgressDialog(Activity activity, String message) { + ProgressDialog progress = new ProgressDialog(activity); + progress.setIndeterminate(true); + progress.setMessage(message); + progress.setCancelable(false); + progress.show(); + return progress; + } +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/util/UberColorPickerDialog.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/util/UberColorPickerDialog.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,947 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * 090408 + * Keith Wiley + * kwiley@keithwiley.com + * http://keithwiley.com + * + * UberColorPickerDialog v1.1 + * + * This color picker was implemented as a (significant) extension of the + * ColorPickerDialog class provided in the Android API Demos. You are free + * to drop it unchanged into your own projects or to modify it as you see + * fit. I would appreciate it if this comment block were let intact, + * merely for credit's sake. + * + * Enjoy! + */ + +package com.five_ten_sg.connectbot.util; + +import android.app.Dialog; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorMatrix; +import android.graphics.ComposeShader; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.RadialGradient; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.SweepGradient; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.GradientDrawable.Orientation; +import android.os.Bundle; +import android.util.DisplayMetrics; +import android.util.FloatMath; +import android.view.MotionEvent; +import android.view.View; + +/** + * UberColorPickerDialog is a seriously enhanced version of the UberColorPickerDialog + * class provided in the Android API Demos.

+ * + * NOTE (from Kenny Root): This is a VERY slimmed down version custom for ConnectBot. + * Visit Keith's site for the full version at the URL listed in the author line.

+ * + * @author Keith Wiley, kwiley@keithwiley.com, http://keithwiley.com + */ +public class UberColorPickerDialog extends Dialog { + private final OnColorChangedListener mListener; + private final int mInitialColor; + + /** + * Callback to the creator of the dialog, informing the creator of a new color and notifying that the dialog is about to dismiss. + */ + public interface OnColorChangedListener { + void colorChanged(int color); + } + + /** + * Ctor + * @param context + * @param listener + * @param initialColor + * @param showTitle If true, a title is shown across the top of the dialog. If false a toast is shown instead. + */ + public UberColorPickerDialog(Context context, + OnColorChangedListener listener, + int initialColor) { + super(context); + mListener = listener; + mInitialColor = initialColor; + } + + /** + * Activity entry point + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + OnColorChangedListener l = new OnColorChangedListener() { + public void colorChanged(int color) { + mListener.colorChanged(color); + dismiss(); + } + }; + DisplayMetrics dm = new DisplayMetrics(); + getWindow().getWindowManager().getDefaultDisplay().getMetrics(dm); + int screenWidth = dm.widthPixels; + int screenHeight = dm.heightPixels; + setTitle("Pick a color (try the trackball)"); + + try { + setContentView(new ColorPickerView(getContext(), l, screenWidth, screenHeight, mInitialColor)); + } + catch (Exception e) { + //There is currently only one kind of ctor exception, that where no methods are enabled. + dismiss(); //This doesn't work! The dialog is still shown (its title at least, the layout is empty from the exception being thrown). + } + } + + /** + * ColorPickerView is the meat of this color picker (as opposed to the enclosing class). + * All the heavy lifting is done directly by this View subclass. + *

+ * You can enable/disable whichever color chooser methods you want by modifying the ENABLED_METHODS switches. They *should* + * do all the work required to properly enable/disable methods without losing track of what goes with what and what maps to what. + *

+ * If you add a new color chooser method, do a text search for "NEW_METHOD_WORK_NEEDED_HERE". That tag indicates all + * the locations in the code that will have to be amended in order to properly add a new color chooser method. + * I highly recommend adding new methods to the end of the list. If you want to try to reorder the list, you're on your own. + */ + private static class ColorPickerView extends View { + private static int SWATCH_WIDTH = 95; + private static final int SWATCH_HEIGHT = 60; + + private static int PALETTE_POS_X = 0; + private static int PALETTE_POS_Y = SWATCH_HEIGHT; + private static final int PALETTE_DIM = SWATCH_WIDTH * 2; + private static final int PALETTE_RADIUS = PALETTE_DIM / 2; + private static final int PALETTE_CENTER_X = PALETTE_RADIUS; + private static final int PALETTE_CENTER_Y = PALETTE_RADIUS; + + private static final int SLIDER_THICKNESS = 40; + + private static int VIEW_DIM_X = PALETTE_DIM; + private static int VIEW_DIM_Y = SWATCH_HEIGHT; + + //NEW_METHOD_WORK_NEEDED_HERE + private static final int METHOD_HS_V_PALETTE = 0; + + //NEW_METHOD_WORK_NEEDED_HERE + //Add a new entry to the list for each controller in the new method + private static final int TRACKED_NONE = -1; //No object on screen is currently being tracked + private static final int TRACK_SWATCH_OLD = 10; + private static final int TRACK_SWATCH_NEW = 11; + private static final int TRACK_HS_PALETTE = 30; + private static final int TRACK_VER_VALUE_SLIDER = 31; + + private static final int TEXT_SIZE = 12; + private static int[] TEXT_HSV_POS = new int[2]; + private static int[] TEXT_RGB_POS = new int[2]; + private static int[] TEXT_YUV_POS = new int[2]; + private static int[] TEXT_HEX_POS = new int[2]; + + private static final float PI = 3.141592653589793f; + + private int mMethod = METHOD_HS_V_PALETTE; + private int mTracking = TRACKED_NONE; //What object on screen is currently being tracked for movement + + //Zillions of persistant Paint objecs for drawing the View + + private Paint mSwatchOld, mSwatchNew; + + //NEW_METHOD_WORK_NEEDED_HERE + //Add Paints to represent the palettes of the new method's UI controllers + private Paint mOvalHueSat; + + private Bitmap mVerSliderBM; + private Canvas mVerSliderCv; + + private Bitmap[] mHorSlidersBM = new Bitmap[3]; + private Canvas[] mHorSlidersCv = new Canvas[3]; + + private Paint mValDimmer; + + //NEW_METHOD_WORK_NEEDED_HERE + //Add Paints to represent the icon for the new method + private Paint mOvalHueSatSmall; + + private Paint mPosMarker; + private Paint mText; + + private Rect mOldSwatchRect = new Rect(); + private Rect mNewSwatchRect = new Rect(); + private Rect mPaletteRect = new Rect(); + private Rect mVerSliderRect = new Rect(); + + private int[] mSpectrumColorsRev; + private int mOriginalColor = 0; //The color passed in at the beginning, which can be reverted to at any time by tapping the old swatch. + private float[] mHSV = new float[3]; + private int[] mRGB = new int[3]; + private float[] mYUV = new float[3]; + private String mHexStr = ""; + private boolean mHSVenabled = true; //Only true if an HSV method is enabled + private boolean mRGBenabled = true; //Only true if an RGB method is enabled + private boolean mYUVenabled = true; //Only true if a YUV method is enabled + private boolean mHexenabled = true; //Only true if an RGB method is enabled + private int[] mCoord = new int[3]; //For drawing slider/palette markers + private int mFocusedControl = -1; //Which control receives trackball events. + private OnColorChangedListener mListener; + + /** + * Ctor. + * @param c + * @param l + * @param width Used to determine orientation and adjust layout accordingly + * @param height Used to determine orientation and adjust layout accordingly + * @param color The initial color + * @throws Exception + */ + ColorPickerView(Context c, OnColorChangedListener l, int width, int height, int color) + throws Exception { + super(c); + //We need to make the dialog focusable to retrieve trackball events. + setFocusable(true); + mListener = l; + mOriginalColor = color; + Color.colorToHSV(color, mHSV); + updateAllFromHSV(); + + //Setup the layout based on whether this is a portrait or landscape orientation. + if (width <= height) { //Portrait layout + SWATCH_WIDTH = (PALETTE_DIM + SLIDER_THICKNESS) / 2; + PALETTE_POS_X = 0; + PALETTE_POS_Y = TEXT_SIZE * 4 + SWATCH_HEIGHT; + //Set more rects, lots of rects + mOldSwatchRect.set(0, TEXT_SIZE * 4, SWATCH_WIDTH, TEXT_SIZE * 4 + SWATCH_HEIGHT); + mNewSwatchRect.set(SWATCH_WIDTH, TEXT_SIZE * 4, SWATCH_WIDTH * 2, TEXT_SIZE * 4 + SWATCH_HEIGHT); + mPaletteRect.set(0, PALETTE_POS_Y, PALETTE_DIM, PALETTE_POS_Y + PALETTE_DIM); + mVerSliderRect.set(PALETTE_DIM, PALETTE_POS_Y, PALETTE_DIM + SLIDER_THICKNESS, PALETTE_POS_Y + PALETTE_DIM); + TEXT_HSV_POS[0] = 3; + TEXT_HSV_POS[1] = 0; + TEXT_RGB_POS[0] = TEXT_HSV_POS[0] + 50; + TEXT_RGB_POS[1] = TEXT_HSV_POS[1]; + TEXT_YUV_POS[0] = TEXT_HSV_POS[0] + 100; + TEXT_YUV_POS[1] = TEXT_HSV_POS[1]; + TEXT_HEX_POS[0] = TEXT_HSV_POS[0] + 150; + TEXT_HEX_POS[1] = TEXT_HSV_POS[1]; + VIEW_DIM_X = PALETTE_DIM + SLIDER_THICKNESS; + VIEW_DIM_Y = SWATCH_HEIGHT + PALETTE_DIM + TEXT_SIZE * 4; + } + else { //Landscape layout + SWATCH_WIDTH = 110; + PALETTE_POS_X = SWATCH_WIDTH; + PALETTE_POS_Y = 0; + //Set more rects, lots of rects + mOldSwatchRect.set(0, TEXT_SIZE * 7, SWATCH_WIDTH, TEXT_SIZE * 7 + SWATCH_HEIGHT); + mNewSwatchRect.set(0, TEXT_SIZE * 7 + SWATCH_HEIGHT, SWATCH_WIDTH, TEXT_SIZE * 7 + SWATCH_HEIGHT * 2); + mPaletteRect.set(SWATCH_WIDTH, PALETTE_POS_Y, SWATCH_WIDTH + PALETTE_DIM, PALETTE_POS_Y + PALETTE_DIM); + mVerSliderRect.set(SWATCH_WIDTH + PALETTE_DIM, PALETTE_POS_Y, SWATCH_WIDTH + PALETTE_DIM + SLIDER_THICKNESS, PALETTE_POS_Y + PALETTE_DIM); + TEXT_HSV_POS[0] = 3; + TEXT_HSV_POS[1] = 0; + TEXT_RGB_POS[0] = TEXT_HSV_POS[0]; + TEXT_RGB_POS[1] = (int)(TEXT_HSV_POS[1] + TEXT_SIZE * 3.5); + TEXT_YUV_POS[0] = TEXT_HSV_POS[0] + 50; + TEXT_YUV_POS[1] = (int)(TEXT_HSV_POS[1] + TEXT_SIZE * 3.5); + TEXT_HEX_POS[0] = TEXT_HSV_POS[0] + 50; + TEXT_HEX_POS[1] = TEXT_HSV_POS[1]; + VIEW_DIM_X = PALETTE_POS_X + PALETTE_DIM + SLIDER_THICKNESS; + VIEW_DIM_Y = Math.max(mNewSwatchRect.bottom, PALETTE_DIM); + } + + //Rainbows make everybody happy! + mSpectrumColorsRev = new int[] { + 0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF, + 0xFF00FF00, 0xFFFFFF00, 0xFFFF0000, + }; + //Setup all the Paint and Shader objects. There are lots of them! + //NEW_METHOD_WORK_NEEDED_HERE + //Add Paints to represent the palettes of the new method's UI controllers + mSwatchOld = new Paint(Paint.ANTI_ALIAS_FLAG); + mSwatchOld.setStyle(Paint.Style.FILL); + mSwatchOld.setColor(Color.HSVToColor(mHSV)); + mSwatchNew = new Paint(Paint.ANTI_ALIAS_FLAG); + mSwatchNew.setStyle(Paint.Style.FILL); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + Shader shaderA = new SweepGradient(0, 0, mSpectrumColorsRev, null); + Shader shaderB = new RadialGradient(0, 0, PALETTE_CENTER_X, 0xFFFFFFFF, 0xFF000000, Shader.TileMode.CLAMP); + Shader shader = new ComposeShader(shaderA, shaderB, PorterDuff.Mode.SCREEN); + mOvalHueSat = new Paint(Paint.ANTI_ALIAS_FLAG); + mOvalHueSat.setShader(shader); + mOvalHueSat.setStyle(Paint.Style.FILL); + mOvalHueSat.setDither(true); + mVerSliderBM = Bitmap.createBitmap(SLIDER_THICKNESS, PALETTE_DIM, Bitmap.Config.RGB_565); + mVerSliderCv = new Canvas(mVerSliderBM); + + for (int i = 0; i < 3; i++) { + mHorSlidersBM[i] = Bitmap.createBitmap(PALETTE_DIM, SLIDER_THICKNESS, Bitmap.Config.RGB_565); + mHorSlidersCv[i] = new Canvas(mHorSlidersBM[i]); + } + + mValDimmer = new Paint(Paint.ANTI_ALIAS_FLAG); + mValDimmer.setStyle(Paint.Style.FILL); + mValDimmer.setDither(true); + mValDimmer.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)); + //Whew, we're done making the big Paints and Shaders for the swatches, palettes, and sliders. + //Now we need to make the Paints and Shaders that will draw the little method icons in the method selector list. + //NEW_METHOD_WORK_NEEDED_HERE + //Add Paints to represent the icon for the new method + shaderA = new SweepGradient(0, 0, mSpectrumColorsRev, null); + shaderB = new RadialGradient(0, 0, PALETTE_DIM / 2, 0xFFFFFFFF, 0xFF000000, Shader.TileMode.CLAMP); + shader = new ComposeShader(shaderA, shaderB, PorterDuff.Mode.SCREEN); + mOvalHueSatSmall = new Paint(Paint.ANTI_ALIAS_FLAG); + mOvalHueSatSmall.setShader(shader); + mOvalHueSatSmall.setStyle(Paint.Style.FILL); + //Make a simple stroking Paint for drawing markers and borders and stuff like that. + mPosMarker = new Paint(Paint.ANTI_ALIAS_FLAG); + mPosMarker.setStyle(Paint.Style.STROKE); + mPosMarker.setStrokeWidth(2); + //Make a basic text Paint. + mText = new Paint(Paint.ANTI_ALIAS_FLAG); + mText.setTextSize(TEXT_SIZE); + mText.setColor(Color.WHITE); + //Kickstart + initUI(); + } + + /** + * Draw the entire view (the entire dialog). + */ + @Override + protected void onDraw(Canvas canvas) { + //Draw the old and new swatches + drawSwatches(canvas); + //Write the text + writeColorParams(canvas); + + //Draw the palette and sliders (the UI) + if (mMethod == METHOD_HS_V_PALETTE) + drawHSV1Palette(canvas); + } + + /** + * Draw the old and new swatches. + * @param canvas + */ + private void drawSwatches(Canvas canvas) { + float[] hsv = new float[3]; + mText.setTextSize(16); + //Draw the original swatch + canvas.drawRect(mOldSwatchRect, mSwatchOld); + Color.colorToHSV(mOriginalColor, hsv); + + //if (UberColorPickerDialog.isGray(mColor)) //Don't need this right here, but imp't to note + // hsv[1] = 0; + if (hsv[2] > .5) + mText.setColor(Color.BLACK); + + canvas.drawText("Revert", mOldSwatchRect.left + SWATCH_WIDTH / 2 - mText.measureText("Revert") / 2, mOldSwatchRect.top + 16, mText); + mText.setColor(Color.WHITE); + //Draw the new swatch + canvas.drawRect(mNewSwatchRect, mSwatchNew); + + if (mHSV[2] > .5) + mText.setColor(Color.BLACK); + + canvas.drawText("Accept", mNewSwatchRect.left + SWATCH_WIDTH / 2 - mText.measureText("Accept") / 2, mNewSwatchRect.top + 16, mText); + mText.setColor(Color.WHITE); + mText.setTextSize(TEXT_SIZE); + } + + /** + * Write the color parametes (HSV, RGB, YUV, Hex, etc.). + * @param canvas + */ + private void writeColorParams(Canvas canvas) { + if (mHSVenabled) { + canvas.drawText("H: " + Integer.toString((int)(mHSV[0] / 360.0f * 255)), TEXT_HSV_POS[0], TEXT_HSV_POS[1] + TEXT_SIZE, mText); + canvas.drawText("S: " + Integer.toString((int)(mHSV[1] * 255)), TEXT_HSV_POS[0], TEXT_HSV_POS[1] + TEXT_SIZE * 2, mText); + canvas.drawText("V: " + Integer.toString((int)(mHSV[2] * 255)), TEXT_HSV_POS[0], TEXT_HSV_POS[1] + TEXT_SIZE * 3, mText); + } + + if (mRGBenabled) { + canvas.drawText("R: " + mRGB[0], TEXT_RGB_POS[0], TEXT_RGB_POS[1] + TEXT_SIZE, mText); + canvas.drawText("G: " + mRGB[1], TEXT_RGB_POS[0], TEXT_RGB_POS[1] + TEXT_SIZE * 2, mText); + canvas.drawText("B: " + mRGB[2], TEXT_RGB_POS[0], TEXT_RGB_POS[1] + TEXT_SIZE * 3, mText); + } + + if (mYUVenabled) { + canvas.drawText("Y: " + Integer.toString((int)(mYUV[0] * 255)), TEXT_YUV_POS[0], TEXT_YUV_POS[1] + TEXT_SIZE, mText); + canvas.drawText("U: " + Integer.toString((int)((mYUV[1] + .5f) * 255)), TEXT_YUV_POS[0], TEXT_YUV_POS[1] + TEXT_SIZE * 2, mText); + canvas.drawText("V: " + Integer.toString((int)((mYUV[2] + .5f) * 255)), TEXT_YUV_POS[0], TEXT_YUV_POS[1] + TEXT_SIZE * 3, mText); + } + + if (mHexenabled) + canvas.drawText("#" + mHexStr, TEXT_HEX_POS[0], TEXT_HEX_POS[1] + TEXT_SIZE, mText); + } + + /** + * Place a small circle on the 2D palette to indicate the current values. + * @param canvas + * @param markerPosX + * @param markerPosY + */ + private void mark2DPalette(Canvas canvas, int markerPosX, int markerPosY) { + mPosMarker.setColor(Color.BLACK); + canvas.drawOval(new RectF(markerPosX - 5, markerPosY - 5, markerPosX + 5, markerPosY + 5), mPosMarker); + mPosMarker.setColor(Color.WHITE); + canvas.drawOval(new RectF(markerPosX - 3, markerPosY - 3, markerPosX + 3, markerPosY + 3), mPosMarker); + } + + /** + * Draw a line across the slider to indicate its current value. + * @param canvas + * @param markerPos + */ + private void markVerSlider(Canvas canvas, int markerPos) { + mPosMarker.setColor(Color.BLACK); + canvas.drawRect(new Rect(0, markerPos - 2, SLIDER_THICKNESS, markerPos + 3), mPosMarker); + mPosMarker.setColor(Color.WHITE); + canvas.drawRect(new Rect(0, markerPos, SLIDER_THICKNESS, markerPos + 1), mPosMarker); + } + + /** + * Frame the slider to indicate that it has trackball focus. + * @param canvas + */ + private void hilightFocusedVerSlider(Canvas canvas) { + mPosMarker.setColor(Color.WHITE); + canvas.drawRect(new Rect(0, 0, SLIDER_THICKNESS, PALETTE_DIM), mPosMarker); + mPosMarker.setColor(Color.BLACK); + canvas.drawRect(new Rect(2, 2, SLIDER_THICKNESS - 2, PALETTE_DIM - 2), mPosMarker); + } + + /** + * Frame the 2D palette to indicate that it has trackball focus. + * @param canvas + */ + private void hilightFocusedOvalPalette(Canvas canvas) { + mPosMarker.setColor(Color.WHITE); + canvas.drawOval(new RectF(-PALETTE_RADIUS, -PALETTE_RADIUS, PALETTE_RADIUS, PALETTE_RADIUS), mPosMarker); + mPosMarker.setColor(Color.BLACK); + canvas.drawOval(new RectF(-PALETTE_RADIUS + 2, -PALETTE_RADIUS + 2, PALETTE_RADIUS - 2, PALETTE_RADIUS - 2), mPosMarker); + } + + //NEW_METHOD_WORK_NEEDED_HERE + //To add a new method, replicate the basic draw functions here. Use the 2D palette or 1D sliders as templates for the new method. + /** + * Draw the UI for HSV with angular H and radial S combined in 2D and a 1D V slider. + * @param canvas + */ + private void drawHSV1Palette(Canvas canvas) { + canvas.save(); + canvas.translate(PALETTE_POS_X, PALETTE_POS_Y); + //Draw the 2D palette + canvas.translate(PALETTE_CENTER_X, PALETTE_CENTER_Y); + canvas.drawOval(new RectF(-PALETTE_RADIUS, -PALETTE_RADIUS, PALETTE_RADIUS, PALETTE_RADIUS), mOvalHueSat); + canvas.drawOval(new RectF(-PALETTE_RADIUS, -PALETTE_RADIUS, PALETTE_RADIUS, PALETTE_RADIUS), mValDimmer); + + if (mFocusedControl == 0) + hilightFocusedOvalPalette(canvas); + + mark2DPalette(canvas, mCoord[0], mCoord[1]); + canvas.translate(-PALETTE_CENTER_X, -PALETTE_CENTER_Y); + //Draw the 1D slider + canvas.translate(PALETTE_DIM, 0); + canvas.drawBitmap(mVerSliderBM, 0, 0, null); + + if (mFocusedControl == 1) + hilightFocusedVerSlider(canvas); + + markVerSlider(canvas, mCoord[2]); + canvas.restore(); + } + + /** + * Initialize the current color chooser's UI (set its color parameters and set its palette and slider values accordingly). + */ + private void initUI() { + initHSV1Palette(); + //Focus on the first controller (arbitrary). + mFocusedControl = 0; + } + + //NEW_METHOD_WORK_NEEDED_HERE + //To add a new method, replicate and extend the last init function shown below + /** + * Initialize a color chooser. + */ + private void initHSV1Palette() { + setOvalValDimmer(); + setVerValSlider(); + float angle = 2 * PI - mHSV[0] / (180 / 3.1415927f); + float radius = mHSV[1] * PALETTE_RADIUS; + mCoord[0] = (int)(FloatMath.cos(angle) * radius); + mCoord[1] = (int)(FloatMath.sin(angle) * radius); + mCoord[2] = PALETTE_DIM - (int)(mHSV[2] * PALETTE_DIM); + } + + //NEW_METHOD_WORK_NEEDED_HERE + //To add a new method, replicate and extend the set functions below, one per UI controller in the new method + /** + * Adjust a Paint which, when painted, dims its underlying object to show the effects of varying value (brightness). + */ + private void setOvalValDimmer() { + float[] hsv = new float[3]; + hsv[0] = mHSV[0]; + hsv[1] = 0; + hsv[2] = mHSV[2]; + int gray = Color.HSVToColor(hsv); + mValDimmer.setColor(gray); + } + + /** + * Create a linear gradient shader to show variations in value. + */ + private void setVerValSlider() { + float[] hsv = new float[3]; + hsv[0] = mHSV[0]; + hsv[1] = mHSV[1]; + hsv[2] = 1; + int col = Color.HSVToColor(hsv); + int colors[] = new int[2]; + colors[0] = col; + colors[1] = 0xFF000000; + GradientDrawable gradDraw = new GradientDrawable(Orientation.TOP_BOTTOM, colors); + gradDraw.setDither(true); + gradDraw.setLevel(10000); + gradDraw.setBounds(0, 0, SLIDER_THICKNESS, PALETTE_DIM); + gradDraw.draw(mVerSliderCv); + } + + /** + * Report the correct tightly bounded dimensions of the view. + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(VIEW_DIM_X, VIEW_DIM_Y); + } + + /** + * Wrap Math.round(). I'm not a Java expert. Is this the only way to avoid writing "(int)Math.round" everywhere? + * @param x + * @return + */ + private int round(double x) { + return (int)Math.round(x); + } + + /** + * Limit a value to the range [0,1]. + * @param n + * @return + */ + private float pinToUnit(float n) { + if (n < 0) { + n = 0; + } + else if (n > 1) { + n = 1; + } + + return n; + } + + /** + * Limit a value to the range [0,max]. + * @param n + * @param max + * @return + */ + private float pin(float n, float max) { + if (n < 0) { + n = 0; + } + else if (n > max) { + n = max; + } + + return n; + } + + /** + * Limit a value to the range [min,max]. + * @param n + * @param min + * @param max + * @return + */ + private float pin(float n, float min, float max) { + if (n < min) { + n = min; + } + else if (n > max) { + n = max; + } + + return n; + } + + /** + * No clue what this does (some sort of average/mean I presume). It came with the original UberColorPickerDialog + * in the API Demos and wasn't documented. I don't feel like spending any time figuring it out, I haven't looked at it at all. + * @param s + * @param d + * @param p + * @return + */ + private int ave(int s, int d, float p) { + return s + round(p * (d - s)); + } + + /** + * Came with the original UberColorPickerDialog in the API Demos, wasn't documented. I believe it takes an array of + * colors and a value in the range [0,1] and interpolates a resulting color in a seemingly predictable manner. + * I haven't looked at it at all. + * @param colors + * @param unit + * @return + */ + private int interpColor(int colors[], float unit) { + if (unit <= 0) { + return colors[0]; + } + + if (unit >= 1) { + return colors[colors.length - 1]; + } + + float p = unit * (colors.length - 1); + int i = (int)p; + p -= i; + // now p is just the fractional part [0...1) and i is the index + int c0 = colors[i]; + int c1 = colors[i + 1]; + int a = ave(Color.alpha(c0), Color.alpha(c1), p); + int r = ave(Color.red(c0), Color.red(c1), p); + int g = ave(Color.green(c0), Color.green(c1), p); + int b = ave(Color.blue(c0), Color.blue(c1), p); + return Color.argb(a, r, g, b); + } + + /** + * A standard point-in-rect routine. + * @param x + * @param y + * @param r + * @return true if point x,y is in rect r + */ + public boolean ptInRect(int x, int y, Rect r) { + return x > r.left && x < r.right && y > r.top && y < r.bottom; + } + + /** + * Process trackball events. Used mainly for fine-tuned color adjustment, or alternatively to switch between slider controls. + */ + @Override + public boolean dispatchTrackballEvent(MotionEvent event) { + float x = event.getX(); + float y = event.getY(); + //A longer event history implies faster trackball movement. + //Use it to infer a larger jump and therefore faster palette/slider adjustment. + int jump = event.getHistorySize() + 1; + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: { + } + break; + + case MotionEvent.ACTION_MOVE: { + //NEW_METHOD_WORK_NEEDED_HERE + //To add a new method, replicate and extend the appropriate entry in this list, + //depending on whether you use 1D or 2D controllers + switch (mMethod) { + case METHOD_HS_V_PALETTE: + if (mFocusedControl == 0) { + changeHSPalette(x, y, jump); + } + else if (mFocusedControl == 1) { + if (y < 0) + changeSlider(mFocusedControl, true, jump); + else if (y > 0) + changeSlider(mFocusedControl, false, jump); + } + + break; + } + } + break; + + case MotionEvent.ACTION_UP: { + } + break; + } + + return true; + } + + //NEW_METHOD_WORK_NEEDED_HERE + //To add a new method, replicate and extend the appropriate functions below, + //one per UI controller in the new method + /** + * Effect a trackball change to a 2D palette. + * @param x -1: negative x change, 0: no x change, +1: positive x change. + * @param y -1: negative y change, 0, no y change, +1: positive y change. + * @param jump the amount by which to change. + */ + private void changeHSPalette(float x, float y, int jump) { + int x2 = 0, y2 = 0; + + if (x < 0) + x2 = -jump; + else if (x > 0) + x2 = jump; + + if (y < 0) + y2 = -jump; + else if (y > 0) + y2 = jump; + + mCoord[0] += x2; + mCoord[1] += y2; + + if (mCoord[0] < -PALETTE_RADIUS) + mCoord[0] = -PALETTE_RADIUS; + else if (mCoord[0] > PALETTE_RADIUS) + mCoord[0] = PALETTE_RADIUS; + + if (mCoord[1] < -PALETTE_RADIUS) + mCoord[1] = -PALETTE_RADIUS; + else if (mCoord[1] > PALETTE_RADIUS) + mCoord[1] = PALETTE_RADIUS; + + float radius = FloatMath.sqrt(mCoord[0] * mCoord[0] + mCoord[1] * mCoord[1]); + + if (radius > PALETTE_RADIUS) + radius = PALETTE_RADIUS; + + float angle = (float)Math.atan2(mCoord[1], mCoord[0]); + // need to turn angle [-PI ... PI] into unit [0....1] + float unit = angle / (2 * PI); + + if (unit < 0) { + unit += 1; + } + + mCoord[0] = round(FloatMath.cos(angle) * radius); + mCoord[1] = round(FloatMath.sin(angle) * radius); + int c = interpColor(mSpectrumColorsRev, unit); + float[] hsv = new float[3]; + Color.colorToHSV(c, hsv); + mHSV[0] = hsv[0]; + mHSV[1] = radius / PALETTE_RADIUS; + updateAllFromHSV(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + setVerValSlider(); + invalidate(); + } + + /** + * Effect a trackball change to a 1D slider. + * @param slider id of the slider to be effected + * @param increase true if the change is an increase, false if a decrease + * @param jump the amount by which to change in units of the range [0,255] + */ + private void changeSlider(int slider, boolean increase, int jump) { + //NEW_METHOD_WORK_NEEDED_HERE + //It is only necessary to add an entry here for a new method if the new method uses a 1D slider. + //Note, some sliders are horizontal and others are vertical. + //They differ a bit, especially in a sign flip on the vertical axis. + if (mMethod == METHOD_HS_V_PALETTE) { + //slider *must* equal 1 + mHSV[2] += (increase ? jump : -jump) / 256.0f; + mHSV[2] = pinToUnit(mHSV[2]); + updateAllFromHSV(); + mCoord[2] = PALETTE_DIM - (int)(mHSV[2] * PALETTE_DIM); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + setOvalValDimmer(); + invalidate(); + } + } + + /** + * Keep all colorspace representations in sync. + */ + private void updateRGBfromHSV() { + int color = Color.HSVToColor(mHSV); + mRGB[0] = Color.red(color); + mRGB[1] = Color.green(color); + mRGB[2] = Color.blue(color); + } + + /** + * Keep all colorspace representations in sync. + */ + private void updateYUVfromRGB() { + float r = mRGB[0] / 255.0f; + float g = mRGB[1] / 255.0f; + float b = mRGB[2] / 255.0f; + ColorMatrix cm = new ColorMatrix(); + cm.setRGB2YUV(); + final float[] a = cm.getArray(); + mYUV[0] = a[0] * r + a[1] * g + a[2] * b; + mYUV[0] = pinToUnit(mYUV[0]); + mYUV[1] = a[5] * r + a[6] * g + a[7] * b; + mYUV[1] = pin(mYUV[1], -.5f, .5f); + mYUV[2] = a[10] * r + a[11] * g + a[12] * b; + mYUV[2] = pin(mYUV[2], -.5f, .5f); + } + + /** + * Keep all colorspace representations in sync. + */ + private void updateHexFromHSV() { + //For now, assume 100% opacity + mHexStr = Integer.toHexString(Color.HSVToColor(mHSV)).toUpperCase(); + mHexStr = mHexStr.substring(2, mHexStr.length()); + } + + /** + * Keep all colorspace representations in sync. + */ + private void updateAllFromHSV() { + //Update mRGB + if (mRGBenabled || mYUVenabled) + updateRGBfromHSV(); + + //Update mYUV + if (mYUVenabled) + updateYUVfromRGB(); + + //Update mHexStr + if (mRGBenabled) + updateHexFromHSV(); + } + + /** + * Process touch events: down, move, and up + */ + @Override + public boolean onTouchEvent(MotionEvent event) { + float x = event.getX(); + float y = event.getY(); + //Generate coordinates which are palette=local with the origin at the upper left of the main 2D palette + int y2 = (int)(pin(round(y - PALETTE_POS_Y), PALETTE_DIM)); + //Generate coordinates which are palette-local with the origin at the center of the main 2D palette + float circlePinnedX = x - PALETTE_POS_X - PALETTE_CENTER_X; + float circlePinnedY = y - PALETTE_POS_Y - PALETTE_CENTER_Y; + //Is the event in a swatch? + boolean inSwatchOld = ptInRect(round(x), round(y), mOldSwatchRect); + boolean inSwatchNew = ptInRect(round(x), round(y), mNewSwatchRect); + //Get the event's distance from the center of the main 2D palette + float radius = FloatMath.sqrt(circlePinnedX * circlePinnedX + circlePinnedY * circlePinnedY); + //Is the event in a circle-pinned 2D palette? + boolean inOvalPalette = radius <= PALETTE_RADIUS; + + //Pin the radius + if (radius > PALETTE_RADIUS) + radius = PALETTE_RADIUS; + + //Is the event in a vertical slider to the right of the main 2D palette + boolean inVerSlider = ptInRect(round(x), round(y), mVerSliderRect); + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mTracking = TRACKED_NONE; + + if (inSwatchOld) + mTracking = TRACK_SWATCH_OLD; + else if (inSwatchNew) + mTracking = TRACK_SWATCH_NEW; + //NEW_METHOD_WORK_NEEDED_HERE + //To add a new method, replicate and extend the last entry in this list + else if (mMethod == METHOD_HS_V_PALETTE) { + if (inOvalPalette) { + mTracking = TRACK_HS_PALETTE; + mFocusedControl = 0; + } + else if (inVerSlider) { + mTracking = TRACK_VER_VALUE_SLIDER; + mFocusedControl = 1; + } + } + + case MotionEvent.ACTION_MOVE: + + //NEW_METHOD_WORK_NEEDED_HERE + //To add a new method, replicate and extend the entries in this list, + //one per UI controller the new method requires. + if (mTracking == TRACK_HS_PALETTE) { + float angle = (float)java.lang.Math.atan2(circlePinnedY, circlePinnedX); + // need to turn angle [-PI ... PI] into unit [0....1] + float unit = angle / (2 * PI); + + if (unit < 0) { + unit += 1; + } + + mCoord[0] = round(FloatMath.cos(angle) * radius); + mCoord[1] = round(FloatMath.sin(angle) * radius); + int c = interpColor(mSpectrumColorsRev, unit); + float[] hsv = new float[3]; + Color.colorToHSV(c, hsv); + mHSV[0] = hsv[0]; + mHSV[1] = radius / PALETTE_RADIUS; + updateAllFromHSV(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + setVerValSlider(); + invalidate(); + } + else if (mTracking == TRACK_VER_VALUE_SLIDER) { + if (mCoord[2] != y2) { + mCoord[2] = y2; + float value = 1.0f - (float)y2 / (float)PALETTE_DIM; + mHSV[2] = value; + updateAllFromHSV(); + mSwatchNew.setColor(Color.HSVToColor(mHSV)); + setOvalValDimmer(); + invalidate(); + } + } + + break; + + case MotionEvent.ACTION_UP: + + //NEW_METHOD_WORK_NEEDED_HERE + //To add a new method, replicate and extend the last entry in this list. + if (mTracking == TRACK_SWATCH_OLD && inSwatchOld) { + Color.colorToHSV(mOriginalColor, mHSV); + mSwatchNew.setColor(mOriginalColor); + initUI(); + invalidate(); + } + else if (mTracking == TRACK_SWATCH_NEW && inSwatchNew) { + mListener.colorChanged(mSwatchNew.getColor()); + invalidate(); + } + + mTracking = TRACKED_NONE; + break; + } + + return true; + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/util/VolumePreference.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/util/VolumePreference.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,68 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.util; + +import android.content.Context; +import android.preference.DialogPreference; +import android.util.AttributeSet; +import android.view.View; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; + +/** + * @author kenny + * + */ +public class VolumePreference extends DialogPreference implements OnSeekBarChangeListener { + /** + * @param context + * @param attrs + */ + public VolumePreference(Context context, AttributeSet attrs) { + super(context, attrs); + setupLayout(context, attrs); + } + + public VolumePreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + setupLayout(context, attrs); + } + + private void setupLayout(Context context, AttributeSet attrs) { + setPersistent(true); + } + + @Override + protected View onCreateDialogView() { + SeekBar sb = new SeekBar(getContext()); + sb.setMax(100); + sb.setProgress((int)(getPersistedFloat( + PreferenceConstants.DEFAULT_BELL_VOLUME) * 100)); + sb.setPadding(10, 10, 10, 10); + sb.setOnSeekBarChangeListener(this); + return sb; + } + + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + persistFloat(progress / 100f); + } + + public void onStartTrackingTouch(SeekBar seekBar) { } + + public void onStopTrackingTouch(SeekBar seekBar) { } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/five_ten_sg/connectbot/util/XmlBuilder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/five_ten_sg/connectbot/util/XmlBuilder.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,76 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.five_ten_sg.connectbot.util; + +import ch.ethz.ssh2.crypto.Base64; + +/** + * @author Kenny Root + * + */ +public class XmlBuilder { + private StringBuilder sb; + + public XmlBuilder() { + sb = new StringBuilder(); + } + + public XmlBuilder append(String data) { + sb.append(data); + return this; + } + + public XmlBuilder append(String field, Object data) { + if (data == null) { + sb.append(String.format("<%s/>", field)); + } + else if (data instanceof String) { + String input = (String) data; + boolean binary = false; + + for (byte b : input.getBytes()) { + if (b < 0x20 || b > 0x7e) { + binary = true; + break; + } + } + + sb.append(String.format("<%s>%s", field, + binary ? new String(Base64.encode(input.getBytes())) : input, field)); + } + else if (data instanceof Integer) { + sb.append(String.format("<%s>%d", field, data, field)); + } + else if (data instanceof Long) { + sb.append(String.format("<%s>%d", field, data, field)); + } + else if (data instanceof byte[]) { + sb.append(String.format("<%s>%s", field, new String(Base64.encode((byte[]) data)), field)); + } + else if (data instanceof Boolean) { + sb.append(String.format("<%s>%s", field, data, field)); + } + + return this; + } + + @Override + public String toString() { + return sb.toString(); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/google/ase/Exec.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/google/ase/Exec.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.ase; + +import java.io.FileDescriptor; + +/** + * Tools for executing commands. + */ +public class Exec { + /** + * @param cmd + * The command to execute + * @param arg0 + * The first argument to the command, may be null + * @param arg1 + * the second argument to the command, may be null + * @return the file descriptor of the started process. + * + */ + public static FileDescriptor createSubprocess(String cmd, String arg0, String arg1) { + return createSubprocess(cmd, arg0, arg1, null); + } + + /** + * @param cmd + * The command to execute + * @param arg0 + * The first argument to the command, may be null + * @param arg1 + * the second argument to the command, may be null + * @param processId + * A one-element array to which the process ID of the started process will be written. + * @return the file descriptor of the started process. + * + */ + public static native FileDescriptor createSubprocess(String cmd, String arg0, String arg1, + int[] processId); + + public static native void setPtyWindowSize(FileDescriptor fd, int row, int col, int xpixel, + int ypixel); + + /** + * Causes the calling thread to wait for the process associated with the receiver to finish + * executing. + * + * @return The exit value of the Process being waited on + * + */ + public static native int waitFor(int processId); + + static { + System.loadLibrary("com_google_ase_Exec"); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/jcraft/jzlib/Adler32.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/jcraft/jzlib/Adler32.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,139 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000-2011 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final public class Adler32 implements Checksum { + + // largest prime smaller than 65536 + static final private int BASE=65521; + // NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 + static final private int NMAX=5552; + + private long s1=1L; + private long s2=0L; + + public void reset(long init){ + s1=init&0xffff; + s2=(init>>16)&0xffff; + } + + public void reset(){ + s1=1L; + s2=0L; + } + + public long getValue(){ + return ((s2<<16)|s1); + } + + public void update(byte[] buf, int index, int len){ + + if(len==1){ + s1+=buf[index++]&0xff; s2+=s1; + s1%=BASE; + s2%=BASE; + return; + } + + int len1 = len/NMAX; + int len2 = len%NMAX; + while(len1-->0) { + int k=NMAX; + len-=k; + while(k-->0){ + s1+=buf[index++]&0xff; s2+=s1; + } + s1%=BASE; + s2%=BASE; + } + + int k=len2; + len-=k; + while(k-->0){ + s1+=buf[index++]&0xff; s2+=s1; + } + s1%=BASE; + s2%=BASE; + } + + public Adler32 copy(){ + Adler32 foo = new Adler32(); + foo.s1 = this.s1; + foo.s2 = this.s2; + return foo; + } + + // The following logic has come from zlib.1.2. + static long combine(long adler1, long adler2, long len2){ + long BASEL = (long)BASE; + long sum1; + long sum2; + long rem; // unsigned int + + rem = len2 % BASEL; + sum1 = adler1 & 0xffffL; + sum2 = rem * sum1; + sum2 %= BASEL; // MOD(sum2); + sum1 += (adler2 & 0xffffL) + BASEL - 1; + sum2 += ((adler1 >> 16) & 0xffffL) + ((adler2 >> 16) & 0xffffL) + BASEL - rem; + if (sum1 >= BASEL) sum1 -= BASEL; + if (sum1 >= BASEL) sum1 -= BASEL; + if (sum2 >= (BASEL << 1)) sum2 -= (BASEL << 1); + if (sum2 >= BASEL) sum2 -= BASEL; + return sum1 | (sum2 << 16); + } + +/* + private java.util.zip.Adler32 adler=new java.util.zip.Adler32(); + public void update(byte[] buf, int index, int len){ + if(buf==null) {adler.reset();} + else{adler.update(buf, index, len);} + } + public void reset(){ + adler.reset(); + } + public void reset(long init){ + if(init==1L){ + adler.reset(); + } + else{ + System.err.println("unsupported operation"); + } + } + public long getValue(){ + return adler.getValue(); + } +*/ +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/jcraft/jzlib/CRC32.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/jcraft/jzlib/CRC32.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,179 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final public class CRC32 implements Checksum { + + /* + * The following logic has come from RFC1952. + */ + private int v = 0; + private static int[] crc_table = null; + static { + crc_table = new int[256]; + for (int n = 0; n < 256; n++) { + int c = n; + for (int k = 8; --k >= 0; ) { + if ((c & 1) != 0) + c = 0xedb88320 ^ (c >>> 1); + else + c = c >>> 1; + } + crc_table[n] = c; + } + } + + public void update (byte[] buf, int index, int len) { + int c = ~v; + while (--len >= 0) + c = crc_table[(c^buf[index++])&0xff]^(c >>> 8); + v = ~c; + } + + public void reset(){ + v = 0; + } + + public void reset(long vv){ + v = (int)(vv&0xffffffffL); + } + + public long getValue(){ + return (long)(v&0xffffffffL); + } + + // The following logic has come from zlib.1.2. + private static final int GF2_DIM = 32; + static long combine(long crc1, long crc2, long len2){ + long row; + long[] even = new long[GF2_DIM]; + long[] odd = new long[GF2_DIM]; + + // degenerate case (also disallow negative lengths) + if (len2 <= 0) + return crc1; + + // put operator for one zero bit in odd + odd[0] = 0xedb88320L; // CRC-32 polynomial + row = 1; + for (int n = 1; n < GF2_DIM; n++) { + odd[n] = row; + row <<= 1; + } + + // put operator for two zero bits in even + gf2_matrix_square(even, odd); + + // put operator for four zero bits in odd + gf2_matrix_square(odd, even); + + // apply len2 zeros to crc1 (first square will put the operator for one + // zero byte, eight zero bits, in even) + do { + // apply zeros operator for this bit of len2 + gf2_matrix_square(even, odd); + if ((len2 & 1)!=0) + crc1 = gf2_matrix_times(even, crc1); + len2 >>= 1; + + // if no more bits set, then done + if (len2 == 0) + break; + + // another iteration of the loop with odd and even swapped + gf2_matrix_square(odd, even); + if ((len2 & 1)!=0) + crc1 = gf2_matrix_times(odd, crc1); + len2 >>= 1; + + // if no more bits set, then done + } while (len2 != 0); + + /* return combined crc */ + crc1 ^= crc2; + return crc1; + } + + private static long gf2_matrix_times(long[] mat, long vec){ + long sum = 0; + int index = 0; + while (vec!=0) { + if ((vec & 1)!=0) + sum ^= mat[index]; + vec >>= 1; + index++; + } + return sum; + } + + static final void gf2_matrix_square(long[] square, long[] mat) { + for (int n = 0; n < GF2_DIM; n++) + square[n] = gf2_matrix_times(mat, mat[n]); + } + + /* + private java.util.zip.CRC32 crc32 = new java.util.zip.CRC32(); + + public void update(byte[] buf, int index, int len){ + if(buf==null) {crc32.reset();} + else{crc32.update(buf, index, len);} + } + public void reset(){ + crc32.reset(); + } + public void reset(long init){ + if(init==0L){ + crc32.reset(); + } + else{ + System.err.println("unsupported operation"); + } + } + public long getValue(){ + return crc32.getValue(); + } +*/ + public CRC32 copy(){ + CRC32 foo = new CRC32(); + foo.v = this.v; + return foo; + } + + public static int[] getCRC32Table(){ + int[] tmp = new int[crc_table.length]; + System.arraycopy(crc_table, 0, tmp, 0, tmp.length); + return tmp; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/jcraft/jzlib/Checksum.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/jcraft/jzlib/Checksum.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,43 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +interface Checksum { + void update(byte[] buf, int index, int len); + void reset(); + void reset(long init); + long getValue(); + Checksum copy(); +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/jcraft/jzlib/Deflate.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/jcraft/jzlib/Deflate.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,1757 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000-2011 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +public +final class Deflate implements Cloneable { + + static final private int MAX_MEM_LEVEL=9; + + static final private int Z_DEFAULT_COMPRESSION=-1; + + static final private int MAX_WBITS=15; // 32K LZ77 window + static final private int DEF_MEM_LEVEL=8; + + static class Config{ + int good_length; // reduce lazy search above this match length + int max_lazy; // do not perform lazy search above this match length + int nice_length; // quit search above this match length + int max_chain; + int func; + Config(int good_length, int max_lazy, + int nice_length, int max_chain, int func){ + this.good_length=good_length; + this.max_lazy=max_lazy; + this.nice_length=nice_length; + this.max_chain=max_chain; + this.func=func; + } + } + + static final private int STORED=0; + static final private int FAST=1; + static final private int SLOW=2; + static final private Config[] config_table; + static{ + config_table=new Config[10]; + // good lazy nice chain + config_table[0]=new Config(0, 0, 0, 0, STORED); + config_table[1]=new Config(4, 4, 8, 4, FAST); + config_table[2]=new Config(4, 5, 16, 8, FAST); + config_table[3]=new Config(4, 6, 32, 32, FAST); + + config_table[4]=new Config(4, 4, 16, 16, SLOW); + config_table[5]=new Config(8, 16, 32, 32, SLOW); + config_table[6]=new Config(8, 16, 128, 128, SLOW); + config_table[7]=new Config(8, 32, 128, 256, SLOW); + config_table[8]=new Config(32, 128, 258, 1024, SLOW); + config_table[9]=new Config(32, 258, 258, 4096, SLOW); + } + + static final private String[] z_errmsg = { + "need dictionary", // Z_NEED_DICT 2 + "stream end", // Z_STREAM_END 1 + "", // Z_OK 0 + "file error", // Z_ERRNO (-1) + "stream error", // Z_STREAM_ERROR (-2) + "data error", // Z_DATA_ERROR (-3) + "insufficient memory", // Z_MEM_ERROR (-4) + "buffer error", // Z_BUF_ERROR (-5) + "incompatible version",// Z_VERSION_ERROR (-6) + "" + }; + + // block not completed, need more input or more output + static final private int NeedMore=0; + + // block flush performed + static final private int BlockDone=1; + + // finish started, need only more output at next deflate + static final private int FinishStarted=2; + + // finish done, accept no more input or output + static final private int FinishDone=3; + + // preset dictionary flag in zlib header + static final private int PRESET_DICT=0x20; + + static final private int Z_FILTERED=1; + static final private int Z_HUFFMAN_ONLY=2; + static final private int Z_DEFAULT_STRATEGY=0; + + static final private int Z_NO_FLUSH=0; + static final private int Z_PARTIAL_FLUSH=1; + static final private int Z_SYNC_FLUSH=2; + static final private int Z_FULL_FLUSH=3; + static final private int Z_FINISH=4; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + static final private int INIT_STATE=42; + static final private int BUSY_STATE=113; + static final private int FINISH_STATE=666; + + // The deflate compression method + static final private int Z_DEFLATED=8; + + static final private int STORED_BLOCK=0; + static final private int STATIC_TREES=1; + static final private int DYN_TREES=2; + + // The three kinds of block type + static final private int Z_BINARY=0; + static final private int Z_ASCII=1; + static final private int Z_UNKNOWN=2; + + static final private int Buf_size=8*2; + + // repeat previous bit length 3-6 times (2 bits of repeat count) + static final private int REP_3_6=16; + + // repeat a zero length 3-10 times (3 bits of repeat count) + static final private int REPZ_3_10=17; + + // repeat a zero length 11-138 times (7 bits of repeat count) + static final private int REPZ_11_138=18; + + static final private int MIN_MATCH=3; + static final private int MAX_MATCH=258; + static final private int MIN_LOOKAHEAD=(MAX_MATCH+MIN_MATCH+1); + + static final private int MAX_BITS=15; + static final private int D_CODES=30; + static final private int BL_CODES=19; + static final private int LENGTH_CODES=29; + static final private int LITERALS=256; + static final private int L_CODES=(LITERALS+1+LENGTH_CODES); + static final private int HEAP_SIZE=(2*L_CODES+1); + + static final private int END_BLOCK=256; + + ZStream strm; // pointer back to this zlib stream + int status; // as the name implies + byte[] pending_buf; // output still pending + int pending_buf_size; // size of pending_buf + int pending_out; // next pending byte to output to the stream + int pending; // nb of bytes in the pending buffer + int wrap = 1; + byte data_type; // UNKNOWN, BINARY or ASCII + byte method; // STORED (for zip only) or DEFLATED + int last_flush; // value of flush param for previous deflate call + + int w_size; // LZ77 window size (32K by default) + int w_bits; // log2(w_size) (8..16) + int w_mask; // w_size - 1 + + byte[] window; + // Sliding window. Input bytes are read into the second half of the window, + // and move to the first half later to keep a dictionary of at least wSize + // bytes. With this organization, matches are limited to a distance of + // wSize-MAX_MATCH bytes, but this ensures that IO is always + // performed with a length multiple of the block size. Also, it limits + // the window size to 64K, which is quite useful on MSDOS. + // To do: use the user input buffer as sliding window. + + int window_size; + // Actual size of window: 2*wSize, except when the user input buffer + // is directly used as sliding window. + + short[] prev; + // Link to older string with same hash index. To limit the size of this + // array to 64K, this link is maintained only for the last 32K strings. + // An index in this array is thus a window index modulo 32K. + + short[] head; // Heads of the hash chains or NIL. + + int ins_h; // hash index of string to be inserted + int hash_size; // number of elements in hash table + int hash_bits; // log2(hash_size) + int hash_mask; // hash_size-1 + + // Number of bits by which ins_h must be shifted at each input + // step. It must be such that after MIN_MATCH steps, the oldest + // byte no longer takes part in the hash key, that is: + // hash_shift * MIN_MATCH >= hash_bits + int hash_shift; + + // Window position at the beginning of the current output block. Gets + // negative when the window is moved backwards. + + int block_start; + + int match_length; // length of best match + int prev_match; // previous match + int match_available; // set if previous match exists + int strstart; // start of string to insert + int match_start; // start of matching string + int lookahead; // number of valid bytes ahead in window + + // Length of the best match at previous step. Matches not greater than this + // are discarded. This is used in the lazy match evaluation. + int prev_length; + + // To speed up deflation, hash chains are never searched beyond this + // length. A higher limit improves compression ratio but degrades the speed. + int max_chain_length; + + // Attempt to find a better match only when the current match is strictly + // smaller than this value. This mechanism is used only for compression + // levels >= 4. + int max_lazy_match; + + // Insert new strings in the hash table only if the match length is not + // greater than this length. This saves time but degrades compression. + // max_insert_length is used only for compression levels <= 3. + + int level; // compression level (1..9) + int strategy; // favor or force Huffman coding + + // Use a faster search when the previous match is longer than this + int good_match; + + // Stop searching when current match exceeds this + int nice_match; + + short[] dyn_ltree; // literal and length tree + short[] dyn_dtree; // distance tree + short[] bl_tree; // Huffman tree for bit lengths + + Tree l_desc=new Tree(); // desc for literal tree + Tree d_desc=new Tree(); // desc for distance tree + Tree bl_desc=new Tree(); // desc for bit length tree + + // number of codes at each bit length for an optimal tree + short[] bl_count=new short[MAX_BITS+1]; + // working area to be used in Tree#gen_codes() + short[] next_code=new short[MAX_BITS+1]; + + // heap used to build the Huffman trees + int[] heap=new int[2*L_CODES+1]; + + int heap_len; // number of elements in the heap + int heap_max; // element of largest frequency + // The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + // The same heap array is used to build all trees. + + // Depth of each subtree used as tie breaker for trees of equal frequency + byte[] depth=new byte[2*L_CODES+1]; + + byte[] l_buf; // index for literals or lengths */ + + // Size of match buffer for literals/lengths. There are 4 reasons for + // limiting lit_bufsize to 64K: + // - frequencies can be kept in 16 bit counters + // - if compression is not successful for the first block, all input + // data is still in the window so we can still emit a stored block even + // when input comes from standard input. (This can also be done for + // all blocks if lit_bufsize is not greater than 32K.) + // - if compression is not successful for a file smaller than 64K, we can + // even emit a stored file instead of a stored block (saving 5 bytes). + // This is applicable only for zip (not gzip or zlib). + // - creating new Huffman trees less frequently may not provide fast + // adaptation to changes in the input data statistics. (Take for + // example a binary file with poorly compressible code followed by + // a highly compressible string table.) Smaller buffer sizes give + // fast adaptation but have of course the overhead of transmitting + // trees more frequently. + // - I can't count above 4 + int lit_bufsize; + + int last_lit; // running index in l_buf + + // Buffer for distances. To simplify the code, d_buf and l_buf have + // the same number of elements. To use different lengths, an extra flag + // array would be necessary. + + int d_buf; // index of pendig_buf + + int opt_len; // bit length of current block with optimal trees + int static_len; // bit length of current block with static trees + int matches; // number of string matches in current block + int last_eob_len; // bit length of EOB code for last block + + // Output buffer. bits are inserted starting at the bottom (least + // significant bits). + short bi_buf; + + // Number of valid bits in bi_buf. All bits above the last valid bit + // are always zero. + int bi_valid; + + GZIPHeader gheader = null; + + Deflate(ZStream strm){ + this.strm=strm; + dyn_ltree=new short[HEAP_SIZE*2]; + dyn_dtree=new short[(2*D_CODES+1)*2]; // distance tree + bl_tree=new short[(2*BL_CODES+1)*2]; // Huffman tree for bit lengths + } + + void lm_init() { + window_size=2*w_size; + + head[hash_size-1]=0; + for(int i=0; i= 3; max_blindex--) { + if (bl_tree[Tree.bl_order[max_blindex]*2+1] != 0) break; + } + // Update opt_len to include the bit length tree and counts + opt_len += 3*(max_blindex+1) + 5+5+4; + + return max_blindex; + } + + + // Send the header for a block using dynamic Huffman trees: the counts, the + // lengths of the bit length codes, the literal tree and the distance tree. + // IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + void send_all_trees(int lcodes, int dcodes, int blcodes){ + int rank; // index in bl_order + + send_bits(lcodes-257, 5); // not +255 as stated in appnote.txt + send_bits(dcodes-1, 5); + send_bits(blcodes-4, 4); // not -3 as stated in appnote.txt + for (rank = 0; rank < blcodes; rank++) { + send_bits(bl_tree[Tree.bl_order[rank]*2+1], 3); + } + send_tree(dyn_ltree, lcodes-1); // literal tree + send_tree(dyn_dtree, dcodes-1); // distance tree + } + + // Send a literal or distance tree in compressed form, using the codes in + // bl_tree. + void send_tree (short[] tree,// the tree to be sent + int max_code // and its largest code of non zero frequency + ){ + int n; // iterates over all tree elements + int prevlen = -1; // last emitted length + int curlen; // length of current code + int nextlen = tree[0*2+1]; // length of next code + int count = 0; // repeat count of the current code + int max_count = 7; // max repeat count + int min_count = 4; // min repeat count + + if (nextlen == 0){ max_count = 138; min_count = 3; } + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; nextlen = tree[(n+1)*2+1]; + if(++count < max_count && curlen == nextlen) { + continue; + } + else if(count < min_count) { + do { send_code(curlen, bl_tree); } while (--count != 0); + } + else if(curlen != 0){ + if(curlen != prevlen){ + send_code(curlen, bl_tree); count--; + } + send_code(REP_3_6, bl_tree); + send_bits(count-3, 2); + } + else if(count <= 10){ + send_code(REPZ_3_10, bl_tree); + send_bits(count-3, 3); + } + else{ + send_code(REPZ_11_138, bl_tree); + send_bits(count-11, 7); + } + count = 0; prevlen = curlen; + if(nextlen == 0){ + max_count = 138; min_count = 3; + } + else if(curlen == nextlen){ + max_count = 6; min_count = 3; + } + else{ + max_count = 7; min_count = 4; + } + } + } + + // Output a byte on the stream. + // IN assertion: there is enough room in pending_buf. + final void put_byte(byte[] p, int start, int len){ + System.arraycopy(p, start, pending_buf, pending, len); + pending+=len; + } + + final void put_byte(byte c){ + pending_buf[pending++]=c; + } + final void put_short(int w) { + put_byte((byte)(w/*&0xff*/)); + put_byte((byte)(w>>>8)); + } + final void putShortMSB(int b){ + put_byte((byte)(b>>8)); + put_byte((byte)(b/*&0xff*/)); + } + + final void send_code(int c, short[] tree){ + int c2=c*2; + send_bits((tree[c2]&0xffff), (tree[c2+1]&0xffff)); + } + + void send_bits(int value, int length){ + int len = length; + if (bi_valid > (int)Buf_size - len) { + int val = value; +// bi_buf |= (val << bi_valid); + bi_buf |= ((val << bi_valid)&0xffff); + put_short(bi_buf); + bi_buf = (short)(val >>> (Buf_size - bi_valid)); + bi_valid += len - Buf_size; + } else { +// bi_buf |= (value) << bi_valid; + bi_buf |= (((value) << bi_valid)&0xffff); + bi_valid += len; + } + } + + // Send one empty static block to give enough lookahead for inflate. + // This takes 10 bits, of which 7 may remain in the bit buffer. + // The current inflate code requires 9 bits of lookahead. If the + // last two codes for the previous block (real code plus EOB) were coded + // on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode + // the last real code. In this case we send two empty static blocks instead + // of one. (There are no problems if the previous block is stored or fixed.) + // To simplify the code, we assume the worst case of last real code encoded + // on one bit only. + void _tr_align(){ + send_bits(STATIC_TREES<<1, 3); + send_code(END_BLOCK, StaticTree.static_ltree); + + bi_flush(); + + // Of the 10 bits for the empty block, we have already sent + // (10 - bi_valid) bits. The lookahead for the last real code (before + // the EOB of the previous block) was thus at least one plus the length + // of the EOB plus what we have just sent of the empty static block. + if (1 + last_eob_len + 10 - bi_valid < 9) { + send_bits(STATIC_TREES<<1, 3); + send_code(END_BLOCK, StaticTree.static_ltree); + bi_flush(); + } + last_eob_len = 7; + } + + + // Save the match info and tally the frequency counts. Return true if + // the current block must be flushed. + boolean _tr_tally (int dist, // distance of matched string + int lc // match length-MIN_MATCH or unmatched char (if dist==0) + ){ + + pending_buf[d_buf+last_lit*2] = (byte)(dist>>>8); + pending_buf[d_buf+last_lit*2+1] = (byte)dist; + + l_buf[last_lit] = (byte)lc; last_lit++; + + if (dist == 0) { + // lc is the unmatched char + dyn_ltree[lc*2]++; + } + else { + matches++; + // Here, lc is the match length - MIN_MATCH + dist--; // dist = match distance - 1 + dyn_ltree[(Tree._length_code[lc]+LITERALS+1)*2]++; + dyn_dtree[Tree.d_code(dist)*2]++; + } + + if ((last_lit & 0x1fff) == 0 && level > 2) { + // Compute an upper bound for the compressed length + int out_length = last_lit*8; + int in_length = strstart - block_start; + int dcode; + for (dcode = 0; dcode < D_CODES; dcode++) { + out_length += (int)dyn_dtree[dcode*2] * + (5L+Tree.extra_dbits[dcode]); + } + out_length >>>= 3; + if ((matches < (last_lit/2)) && out_length < in_length/2) return true; + } + + return (last_lit == lit_bufsize-1); + // We avoid equality with lit_bufsize because of wraparound at 64K + // on 16 bit machines and because stored blocks are restricted to + // 64K-1 bytes. + } + + // Send the block data compressed using the given Huffman trees + void compress_block(short[] ltree, short[] dtree){ + int dist; // distance of matched string + int lc; // match length or unmatched char (if dist == 0) + int lx = 0; // running index in l_buf + int code; // the code to send + int extra; // number of extra bits to send + + if (last_lit != 0){ + do{ + dist=((pending_buf[d_buf+lx*2]<<8)&0xff00)| + (pending_buf[d_buf+lx*2+1]&0xff); + lc=(l_buf[lx])&0xff; lx++; + + if(dist == 0){ + send_code(lc, ltree); // send a literal byte + } + else{ + // Here, lc is the match length - MIN_MATCH + code = Tree._length_code[lc]; + + send_code(code+LITERALS+1, ltree); // send the length code + extra = Tree.extra_lbits[code]; + if(extra != 0){ + lc -= Tree.base_length[code]; + send_bits(lc, extra); // send the extra length bits + } + dist--; // dist is now the match distance - 1 + code = Tree.d_code(dist); + + send_code(code, dtree); // send the distance code + extra = Tree.extra_dbits[code]; + if (extra != 0) { + dist -= Tree.base_dist[code]; + send_bits(dist, extra); // send the extra distance bits + } + } // literal or match pair ? + + // Check that the overlay between pending_buf and d_buf+l_buf is ok: + } + while (lx < last_lit); + } + + send_code(END_BLOCK, ltree); + last_eob_len = ltree[END_BLOCK*2+1]; + } + + // Set the data type to ASCII or BINARY, using a crude approximation: + // binary if more than 20% of the bytes are <= 6 or >= 128, ascii otherwise. + // IN assertion: the fields freq of dyn_ltree are set and the total of all + // frequencies does not exceed 64K (to fit in an int on 16 bit machines). + void set_data_type(){ + int n = 0; + int ascii_freq = 0; + int bin_freq = 0; + while(n<7){ bin_freq += dyn_ltree[n*2]; n++;} + while(n<128){ ascii_freq += dyn_ltree[n*2]; n++;} + while(n (ascii_freq >>> 2) ? Z_BINARY : Z_ASCII); + } + + // Flush the bit buffer, keeping at most 7 bits in it. + void bi_flush(){ + if (bi_valid == 16) { + put_short(bi_buf); + bi_buf=0; + bi_valid=0; + } + else if (bi_valid >= 8) { + put_byte((byte)bi_buf); + bi_buf>>>=8; + bi_valid-=8; + } + } + + // Flush the bit buffer and align the output on a byte boundary + void bi_windup(){ + if (bi_valid > 8) { + put_short(bi_buf); + } else if (bi_valid > 0) { + put_byte((byte)bi_buf); + } + bi_buf = 0; + bi_valid = 0; + } + + // Copy a stored block, storing first the length and its + // one's complement if requested. + void copy_block(int buf, // the input data + int len, // its length + boolean header // true if block header must be written + ){ + int index=0; + bi_windup(); // align on byte boundary + last_eob_len = 8; // enough lookahead for inflate + + if (header) { + put_short((short)len); + put_short((short)~len); + } + + // while(len--!=0) { + // put_byte(window[buf+index]); + // index++; + // } + put_byte(window, buf, len); + } + + void flush_block_only(boolean eof){ + _tr_flush_block(block_start>=0 ? block_start : -1, + strstart-block_start, + eof); + block_start=strstart; + strm.flush_pending(); + } + + // Copy without compression as much as possible from the input stream, return + // the current block state. + // This function does not insert new strings in the dictionary since + // uncompressible data is probably not useful. This function is used + // only for the level=0 compression option. + // NOTE: this function should be optimized to avoid extra copying from + // window to pending_buf. + int deflate_stored(int flush){ + // Stored blocks are limited to 0xffff bytes, pending_buf is limited + // to pending_buf_size, and each stored block has a 5 byte header: + + int max_block_size = 0xffff; + int max_start; + + if(max_block_size > pending_buf_size - 5) { + max_block_size = pending_buf_size - 5; + } + + // Copy as much as possible from input to output: + while(true){ + // Fill the window as much as possible: + if(lookahead<=1){ + fill_window(); + if(lookahead==0 && flush==Z_NO_FLUSH) return NeedMore; + if(lookahead==0) break; // flush the current block + } + + strstart+=lookahead; + lookahead=0; + + // Emit a stored block if pending_buf will be full: + max_start=block_start+max_block_size; + if(strstart==0|| strstart>=max_start) { + // strstart == 0 is possible when wraparound on 16-bit machine + lookahead = (int)(strstart-max_start); + strstart = (int)max_start; + + flush_block_only(false); + if(strm.avail_out==0) return NeedMore; + + } + + // Flush if we may have to slide, otherwise block_start may become + // negative and the data will be gone: + if(strstart-block_start >= w_size-MIN_LOOKAHEAD) { + flush_block_only(false); + if(strm.avail_out==0) return NeedMore; + } + } + + flush_block_only(flush == Z_FINISH); + if(strm.avail_out==0) + return (flush == Z_FINISH) ? FinishStarted : NeedMore; + + return flush == Z_FINISH ? FinishDone : BlockDone; + } + + // Send a stored block + void _tr_stored_block(int buf, // input block + int stored_len, // length of input block + boolean eof // true if this is the last block for a file + ){ + send_bits((STORED_BLOCK<<1)+(eof?1:0), 3); // send block type + copy_block(buf, stored_len, true); // with header + } + + // Determine the best encoding for the current block: dynamic trees, static + // trees or store, and output the encoded block to the zip file. + void _tr_flush_block(int buf, // input block, or NULL if too old + int stored_len, // length of input block + boolean eof // true if this is the last block for a file + ) { + int opt_lenb, static_lenb;// opt_len and static_len in bytes + int max_blindex = 0; // index of last bit length code of non zero freq + + // Build the Huffman trees unless a stored block is forced + if(level > 0) { + // Check if the file is ascii or binary + if(data_type == Z_UNKNOWN) set_data_type(); + + // Construct the literal and distance trees + l_desc.build_tree(this); + + d_desc.build_tree(this); + + // At this point, opt_len and static_len are the total bit lengths of + // the compressed block data, excluding the tree representations. + + // Build the bit length tree for the above two trees, and get the index + // in bl_order of the last bit length code to send. + max_blindex=build_bl_tree(); + + // Determine the best encoding. Compute first the block length in bytes + opt_lenb=(opt_len+3+7)>>>3; + static_lenb=(static_len+3+7)>>>3; + + if(static_lenb<=opt_lenb) opt_lenb=static_lenb; + } + else { + opt_lenb=static_lenb=stored_len+5; // force a stored block + } + + if(stored_len+4<=opt_lenb && buf != -1){ + // 4: two words for the lengths + // The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + // Otherwise we can't have processed more than WSIZE input bytes since + // the last block flush, because compression would have been + // successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + // transform a block into a stored block. + _tr_stored_block(buf, stored_len, eof); + } + else if(static_lenb == opt_lenb){ + send_bits((STATIC_TREES<<1)+(eof?1:0), 3); + compress_block(StaticTree.static_ltree, StaticTree.static_dtree); + } + else{ + send_bits((DYN_TREES<<1)+(eof?1:0), 3); + send_all_trees(l_desc.max_code+1, d_desc.max_code+1, max_blindex+1); + compress_block(dyn_ltree, dyn_dtree); + } + + // The above check is made mod 2^32, for files larger than 512 MB + // and uLong implemented on 32 bits. + + init_block(); + + if(eof){ + bi_windup(); + } + } + + // Fill the window when the lookahead becomes insufficient. + // Updates strstart and lookahead. + // + // IN assertion: lookahead < MIN_LOOKAHEAD + // OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + // At least one byte has been read, or avail_in == 0; reads are + // performed for at least two bytes (required for the zip translate_eol + // option -- not supported here). + void fill_window(){ + int n, m; + int p; + int more; // Amount of free space at the end of the window. + + do{ + more = (window_size-lookahead-strstart); + + // Deal with !@#$% 64K limit: + if(more==0 && strstart==0 && lookahead==0){ + more = w_size; + } + else if(more==-1) { + // Very unlikely, but possible on 16 bit machine if strstart == 0 + // and lookahead == 1 (input done one byte at time) + more--; + + // If the window is almost full and there is insufficient lookahead, + // move the upper half to the lower one to make room in the upper half. + } + else if(strstart >= w_size+ w_size-MIN_LOOKAHEAD) { + System.arraycopy(window, w_size, window, 0, w_size); + match_start-=w_size; + strstart-=w_size; // we now have strstart >= MAX_DIST + block_start-=w_size; + + // Slide the hash table (could be avoided with 32 bit values + // at the expense of memory usage). We slide even when level == 0 + // to keep the hash table consistent if we switch back to level > 0 + // later. (Using level 0 permanently is not an optimal usage of + // zlib, so we don't care about this pathological case.) + + n = hash_size; + p=n; + do { + m = (head[--p]&0xffff); + head[p]=(m>=w_size ? (short)(m-w_size) : 0); + } + while (--n != 0); + + n = w_size; + p = n; + do { + m = (prev[--p]&0xffff); + prev[p] = (m >= w_size ? (short)(m-w_size) : 0); + // If n is not on any hash chain, prev[n] is garbage but + // its value will never be used. + } + while (--n!=0); + more += w_size; + } + + if (strm.avail_in == 0) return; + + // If there was no sliding: + // strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + // more == window_size - lookahead - strstart + // => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + // => more >= window_size - 2*WSIZE + 2 + // In the BIG_MEM or MMAP case (not yet supported), + // window_size == input_size + MIN_LOOKAHEAD && + // strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + // Otherwise, window_size == 2*WSIZE so more >= 2. + // If there was sliding, more >= WSIZE. So in all cases, more >= 2. + + n = strm.read_buf(window, strstart + lookahead, more); + lookahead += n; + + // Initialize the hash value now that we have some input: + if(lookahead >= MIN_MATCH) { + ins_h = window[strstart]&0xff; + ins_h=(((ins_h)<= MIN_MATCH){ + ins_h=(((ins_h)<=MIN_MATCH){ + // check_match(strstart, match_start, match_length); + + bflush=_tr_tally(strstart-match_start, match_length-MIN_MATCH); + + lookahead -= match_length; + + // Insert new strings in the hash table only if the match length + // is not too large. This saves time but degrades compression. + if(match_length <= max_lazy_match && + lookahead >= MIN_MATCH) { + match_length--; // string at strstart already in hash table + do{ + strstart++; + + ins_h=((ins_h<= MIN_MATCH) { + ins_h=(((ins_h)< 4096))) { + + // If prev_match is also MIN_MATCH, match_start is garbage + // but we will ignore the current match anyway. + match_length = MIN_MATCH-1; + } + } + + // If there was a match at the previous step and the current + // match is not better, output the previous match: + if(prev_length >= MIN_MATCH && match_length <= prev_length) { + int max_insert = strstart + lookahead - MIN_MATCH; + // Do not insert strings in hash table beyond this. + + // check_match(strstart-1, prev_match, prev_length); + + bflush=_tr_tally(strstart-1-prev_match, prev_length - MIN_MATCH); + + // Insert in hash table all strings up to the end of the match. + // strstart-1 and strstart are already inserted. If there is not + // enough lookahead, the last two strings are not inserted in + // the hash table. + lookahead -= prev_length-1; + prev_length -= 2; + do{ + if(++strstart <= max_insert) { + ins_h=(((ins_h)<(w_size-MIN_LOOKAHEAD) ? + strstart-(w_size-MIN_LOOKAHEAD) : 0; + int nice_match=this.nice_match; + + // Stop when cur_match becomes <= limit. To simplify the code, + // we prevent matches with the string of window index 0. + + int wmask = w_mask; + + int strend = strstart + MAX_MATCH; + byte scan_end1 = window[scan+best_len-1]; + byte scan_end = window[scan+best_len]; + + // The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + // It is easy to get rid of this optimization if necessary. + + // Do not waste too much time if we already have a good match: + if (prev_length >= good_match) { + chain_length >>= 2; + } + + // Do not look for matches beyond the end of the input. This is necessary + // to make deflate deterministic. + if (nice_match > lookahead) nice_match = lookahead; + + do { + match = cur_match; + + // Skip to next match if the match length cannot increase + // or if the match length is less than 2: + if (window[match+best_len] != scan_end || + window[match+best_len-1] != scan_end1 || + window[match] != window[scan] || + window[++match] != window[scan+1]) continue; + + // The check at best_len-1 can be removed because it will be made + // again later. (This heuristic is not always a win.) + // It is not necessary to compare scan[2] and match[2] since they + // are always equal when the other bytes match, given that + // the hash keys are equal and that HASH_BITS >= 8. + scan += 2; match++; + + // We check for insufficient lookahead only every 8th comparison; + // the 256th check will be made at strstart+258. + do { + } while (window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + window[++scan] == window[++match] && + scan < strend); + + len = MAX_MATCH - (int)(strend - scan); + scan = strend - MAX_MATCH; + + if(len>best_len) { + match_start = cur_match; + best_len = len; + if (len >= nice_match) break; + scan_end1 = window[scan+best_len-1]; + scan_end = window[scan+best_len]; + } + + } while ((cur_match = (prev[cur_match & wmask]&0xffff)) > limit + && --chain_length != 0); + + if (best_len <= lookahead) return best_len; + return lookahead; + } + + int deflateInit(int level, int bits, int memlevel){ + return deflateInit(level, Z_DEFLATED, bits, memlevel, + Z_DEFAULT_STRATEGY); + } + + int deflateInit(int level, int bits){ + return deflateInit(level, Z_DEFLATED, bits, DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY); + } + int deflateInit(int level){ + return deflateInit(level, MAX_WBITS); + } + private int deflateInit(int level, int method, int windowBits, + int memLevel, int strategy){ + int wrap = 1; + // byte[] my_version=ZLIB_VERSION; + + // + // if (version == null || version[0] != my_version[0] + // || stream_size != sizeof(z_stream)) { + // return Z_VERSION_ERROR; + // } + + strm.msg = null; + + if (level == Z_DEFAULT_COMPRESSION) level = 6; + + if (windowBits < 0) { // undocumented feature: suppress zlib header + wrap = 0; + windowBits = -windowBits; + } + else if(windowBits > 15){ + wrap = 2; + windowBits -= 16; + strm.adler=new CRC32(); + } + + if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || + method != Z_DEFLATED || + windowBits < 9 || windowBits > 15 || level < 0 || level > 9 || + strategy < 0 || strategy > Z_HUFFMAN_ONLY) { + return Z_STREAM_ERROR; + } + + strm.dstate = (Deflate)this; + + this.wrap = wrap; + w_bits = windowBits; + w_size = 1 << w_bits; + w_mask = w_size - 1; + + hash_bits = memLevel + 7; + hash_size = 1 << hash_bits; + hash_mask = hash_size - 1; + hash_shift = ((hash_bits+MIN_MATCH-1)/MIN_MATCH); + + window = new byte[w_size*2]; + prev = new short[w_size]; + head = new short[hash_size]; + + lit_bufsize = 1 << (memLevel + 6); // 16K elements by default + + // We overlay pending_buf and d_buf+l_buf. This works since the average + // output size for (length,distance) codes is <= 24 bits. + pending_buf = new byte[lit_bufsize*3]; + pending_buf_size = lit_bufsize*3; + + d_buf = lit_bufsize; + l_buf = new byte[lit_bufsize]; + + this.level = level; + + this.strategy = strategy; + this.method = (byte)method; + + return deflateReset(); + } + + int deflateReset(){ + strm.total_in = strm.total_out = 0; + strm.msg = null; // + strm.data_type = Z_UNKNOWN; + + pending = 0; + pending_out = 0; + + if(wrap < 0){ + wrap = -wrap; + } + status = (wrap==0) ? BUSY_STATE : INIT_STATE; + strm.adler.reset(); + + last_flush = Z_NO_FLUSH; + + tr_init(); + lm_init(); + return Z_OK; + } + + int deflateEnd(){ + if(status!=INIT_STATE && status!=BUSY_STATE && status!=FINISH_STATE){ + return Z_STREAM_ERROR; + } + // Deallocate in reverse order of allocations: + pending_buf=null; + l_buf=null; + head=null; + prev=null; + window=null; + // free + // dstate=null; + return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK; + } + + int deflateParams(int _level, int _strategy){ + int err=Z_OK; + + if(_level == Z_DEFAULT_COMPRESSION){ + _level = 6; + } + if(_level < 0 || _level > 9 || + _strategy < 0 || _strategy > Z_HUFFMAN_ONLY) { + return Z_STREAM_ERROR; + } + + if(config_table[level].func!=config_table[_level].func && + strm.total_in != 0) { + // Flush the last buffer: + err = strm.deflate(Z_PARTIAL_FLUSH); + } + + if(level != _level) { + level = _level; + max_lazy_match = config_table[level].max_lazy; + good_match = config_table[level].good_length; + nice_match = config_table[level].nice_length; + max_chain_length = config_table[level].max_chain; + } + strategy = _strategy; + return err; + } + + int deflateSetDictionary (byte[] dictionary, int dictLength){ + int length = dictLength; + int index=0; + + if(dictionary == null || status != INIT_STATE) + return Z_STREAM_ERROR; + + strm.adler.update(dictionary, 0, dictLength); + + if(length < MIN_MATCH) return Z_OK; + if(length > w_size-MIN_LOOKAHEAD){ + length = w_size-MIN_LOOKAHEAD; + index=dictLength-length; // use the tail of the dictionary + } + System.arraycopy(dictionary, index, window, 0, length); + strstart = length; + block_start = length; + + // Insert all strings in the hash table (except for the last two bytes). + // s->lookahead stays null, so s->ins_h will be recomputed at the next + // call of fill_window. + + ins_h = window[0]&0xff; + ins_h=(((ins_h)<Z_FINISH || flush<0){ + return Z_STREAM_ERROR; + } + + if(strm.next_out == null || + (strm.next_in == null && strm.avail_in != 0) || + (status == FINISH_STATE && flush != Z_FINISH)) { + strm.msg=z_errmsg[Z_NEED_DICT-(Z_STREAM_ERROR)]; + return Z_STREAM_ERROR; + } + if(strm.avail_out == 0){ + strm.msg=z_errmsg[Z_NEED_DICT-(Z_BUF_ERROR)]; + return Z_BUF_ERROR; + } + + old_flush = last_flush; + last_flush = flush; + + // Write the zlib header + if(status == INIT_STATE) { + if(wrap == 2){ + getGZIPHeader().put(this); + status=BUSY_STATE; + strm.adler.reset(); + } + else{ + int header = (Z_DEFLATED+((w_bits-8)<<4))<<8; + int level_flags=((level-1)&0xff)>>1; + + if(level_flags>3) level_flags=3; + header |= (level_flags<<6); + if(strstart!=0) header |= PRESET_DICT; + header+=31-(header % 31); + + status=BUSY_STATE; + putShortMSB(header); + + + // Save the adler32 of the preset dictionary: + if(strstart!=0){ + long adler=strm.adler.getValue(); + putShortMSB((int)(adler>>>16)); + putShortMSB((int)(adler&0xffff)); + } + strm.adler.reset(); + } + } + + // Flush as much pending output as possible + if(pending != 0) { + strm.flush_pending(); + if(strm.avail_out == 0) { + // Since avail_out is 0, deflate will be called again with + // more output space, but possibly with both pending and + // avail_in equal to zero. There won't be anything to do, + // but this is not an error situation so make sure we + // return OK instead of BUF_ERROR at next call of deflate: + last_flush = -1; + return Z_OK; + } + + // Make sure there is something to do and avoid duplicate consecutive + // flushes. For repeated and useless calls with Z_FINISH, we keep + // returning Z_STREAM_END instead of Z_BUFF_ERROR. + } + else if(strm.avail_in==0 && flush <= old_flush && + flush != Z_FINISH) { + strm.msg=z_errmsg[Z_NEED_DICT-(Z_BUF_ERROR)]; + return Z_BUF_ERROR; + } + + // User must not provide more input after the first FINISH: + if(status == FINISH_STATE && strm.avail_in != 0) { + strm.msg=z_errmsg[Z_NEED_DICT-(Z_BUF_ERROR)]; + return Z_BUF_ERROR; + } + + // Start a new block or continue the current one. + if(strm.avail_in!=0 || lookahead!=0 || + (flush != Z_NO_FLUSH && status != FINISH_STATE)) { + int bstate=-1; + switch(config_table[level].func){ + case STORED: + bstate = deflate_stored(flush); + break; + case FAST: + bstate = deflate_fast(flush); + break; + case SLOW: + bstate = deflate_slow(flush); + break; + default: + } + + if (bstate==FinishStarted || bstate==FinishDone) { + status = FINISH_STATE; + } + if (bstate==NeedMore || bstate==FinishStarted) { + if(strm.avail_out == 0) { + last_flush = -1; // avoid BUF_ERROR next call, see above + } + return Z_OK; + // If flush != Z_NO_FLUSH && avail_out == 0, the next call + // of deflate should use the same flush parameter to make sure + // that the flush is complete. So we don't have to output an + // empty block here, this will be done at next call. This also + // ensures that for a very small output buffer, we emit at most + // one empty block. + } + + if (bstate==BlockDone) { + if(flush == Z_PARTIAL_FLUSH) { + _tr_align(); + } + else { // FULL_FLUSH or SYNC_FLUSH + _tr_stored_block(0, 0, false); + // For a full flush, this empty block will be recognized + // as a special marker by inflate_sync(). + if(flush == Z_FULL_FLUSH) { + //state.head[s.hash_size-1]=0; + for(int i=0; i>8)&0xff)); + put_byte((byte)((adler>>16)&0xff)); + put_byte((byte)((adler>>24)&0xff)); + put_byte((byte)(strm.total_in&0xff)); + put_byte((byte)((strm.total_in>>8)&0xff)); + put_byte((byte)((strm.total_in>>16)&0xff)); + put_byte((byte)((strm.total_in>>24)&0xff)); + + getGZIPHeader().setCRC(adler); + } + else{ + // Write the zlib trailer (adler32) + long adler=strm.adler.getValue(); + putShortMSB((int)(adler>>>16)); + putShortMSB((int)(adler&0xffff)); + } + + strm.flush_pending(); + + // If avail_out is zero, the application will call deflate again + // to flush the rest. + + if(wrap > 0) wrap = -wrap; // write the trailer only once! + return pending != 0 ? Z_OK : Z_STREAM_END; + } + + static int deflateCopy(ZStream dest, ZStream src){ + + if(src.dstate == null){ + return Z_STREAM_ERROR; + } + + if(src.next_in!=null){ + dest.next_in = new byte[src.next_in.length]; + System.arraycopy(src.next_in, 0, dest.next_in, 0, src.next_in.length); + } + dest.next_in_index = src.next_in_index; + dest.avail_in = src.avail_in; + dest.total_in = src.total_in; + + if(src.next_out!=null){ + dest.next_out = new byte[src.next_out.length]; + System.arraycopy(src.next_out, 0, dest.next_out ,0 , src.next_out.length); + } + + dest.next_out_index = src.next_out_index; + dest.avail_out = src.avail_out; + dest.total_out = src.total_out; + + dest.msg = src.msg; + dest.data_type = src.data_type; + dest.adler = src.adler.copy(); + + try{ + dest.dstate = (Deflate)src.dstate.clone(); + dest.dstate.strm = dest; + } + catch(CloneNotSupportedException e){ + // + } + return Z_OK; + } + + public Object clone() throws CloneNotSupportedException { + Deflate dest = (Deflate)super.clone(); + + dest.pending_buf = dup(dest.pending_buf); + dest.d_buf = dest.d_buf; + dest.l_buf = dup(dest.l_buf); + dest.window = dup(dest.window); + + dest.prev = dup(dest.prev); + dest.head = dup(dest.head); + dest.dyn_ltree = dup(dest.dyn_ltree); + dest.dyn_dtree = dup(dest.dyn_dtree); + dest.bl_tree = dup(dest.bl_tree); + + dest.bl_count = dup(dest.bl_count); + dest.next_code = dup(dest.next_code); + dest.heap = dup(dest.heap); + dest.depth = dup(dest.depth); + + dest.l_desc.dyn_tree = dest.dyn_ltree; + dest.d_desc.dyn_tree = dest.dyn_dtree; + dest.bl_desc.dyn_tree = dest.bl_tree; + + /* + dest.l_desc.stat_desc = StaticTree.static_l_desc; + dest.d_desc.stat_desc = StaticTree.static_d_desc; + dest.bl_desc.stat_desc = StaticTree.static_bl_desc; + */ + + if(dest.gheader!=null){ + dest.gheader = (GZIPHeader)dest.gheader.clone(); + } + + return dest; + } + + private byte[] dup(byte[] buf){ + byte[] foo = new byte[buf.length]; + System.arraycopy(buf, 0, foo, 0, foo.length); + return foo; + } + private short[] dup(short[] buf){ + short[] foo = new short[buf.length]; + System.arraycopy(buf, 0, foo, 0, foo.length); + return foo; + } + private int[] dup(int[] buf){ + int[] foo = new int[buf.length]; + System.arraycopy(buf, 0, foo, 0, foo.length); + return foo; + } + + synchronized GZIPHeader getGZIPHeader(){ + if(gheader==null){ + gheader = new GZIPHeader(); + } + return gheader; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/jcraft/jzlib/Deflater.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/jcraft/jzlib/Deflater.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,171 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final public class Deflater extends ZStream{ + + static final private int MAX_WBITS=15; // 32K LZ77 window + static final private int DEF_WBITS=MAX_WBITS; + + static final private int Z_NO_FLUSH=0; + static final private int Z_PARTIAL_FLUSH=1; + static final private int Z_SYNC_FLUSH=2; + static final private int Z_FULL_FLUSH=3; + static final private int Z_FINISH=4; + + static final private int MAX_MEM_LEVEL=9; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + private boolean finished = false; + + public Deflater(){ + super(); + } + + public Deflater(int level) throws GZIPException { + this(level, MAX_WBITS); + } + + public Deflater(int level, boolean nowrap) throws GZIPException { + this(level, MAX_WBITS, nowrap); + } + + public Deflater(int level, int bits) throws GZIPException { + this(level, bits, false); + } + + public Deflater(int level, int bits, boolean nowrap) throws GZIPException { + super(); + int ret = init(level, bits, nowrap); + if(ret!=Z_OK) + throw new GZIPException(ret+": "+msg); + } + + public Deflater(int level, int bits, int memlevel, JZlib.WrapperType wrapperType) throws GZIPException { + super(); + int ret = init(level, bits, memlevel, wrapperType); + if(ret!=Z_OK) + throw new GZIPException(ret+": "+msg); + } + + public Deflater(int level, int bits, int memlevel) throws GZIPException { + super(); + int ret = init(level, bits, memlevel); + if(ret!=Z_OK) + throw new GZIPException(ret+": "+msg); + } + + public int init(int level){ + return init(level, MAX_WBITS); + } + public int init(int level, boolean nowrap){ + return init(level, MAX_WBITS, nowrap); + } + public int init(int level, int bits){ + return init(level, bits, false); + } + public int init(int level, int bits, int memlevel, JZlib.WrapperType wrapperType){ + if(bits < 9 || bits > 15){ + return Z_STREAM_ERROR; + } + if(wrapperType == JZlib.W_NONE) { + bits *= -1; + } + else if(wrapperType == JZlib.W_GZIP) { + bits += 16; + } + else if(wrapperType == JZlib.W_ANY) { + return Z_STREAM_ERROR; + } + else if(wrapperType == JZlib.W_ZLIB) { + } + return init(level, bits, memlevel); + } + public int init(int level, int bits, int memlevel){ + finished = false; + dstate=new Deflate(this); + return dstate.deflateInit(level, bits, memlevel); + } + public int init(int level, int bits, boolean nowrap){ + finished = false; + dstate=new Deflate(this); + return dstate.deflateInit(level, nowrap?-bits:bits); + } + + public int deflate(int flush){ + if(dstate==null){ + return Z_STREAM_ERROR; + } + int ret = dstate.deflate(flush); + if(ret == Z_STREAM_END) + finished = true; + return ret; + } + public int end(){ + finished = true; + if(dstate==null) return Z_STREAM_ERROR; + int ret=dstate.deflateEnd(); + dstate=null; + free(); + return ret; + } + public int params(int level, int strategy){ + if(dstate==null) return Z_STREAM_ERROR; + return dstate.deflateParams(level, strategy); + } + public int setDictionary (byte[] dictionary, int dictLength){ + if(dstate == null) + return Z_STREAM_ERROR; + return dstate.deflateSetDictionary(dictionary, dictLength); + } + + public boolean finished(){ + return finished; + } + + public int copy(Deflater src){ + this.finished = src.finished; + return Deflate.deflateCopy(this, src); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/jcraft/jzlib/DeflaterOutputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/jcraft/jzlib/DeflaterOutputStream.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,181 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jcraft.jzlib; +import java.io.*; + +public class DeflaterOutputStream extends FilterOutputStream { + + protected final Deflater deflater; + + protected byte[] buffer; + + private boolean closed = false; + + private boolean syncFlush = false; + + private final byte[] buf1 = new byte[1]; + + protected boolean mydeflater = false; + + private boolean close_out = true; + + protected static final int DEFAULT_BUFSIZE = 512; + + public DeflaterOutputStream(OutputStream out) throws IOException { + this(out, + new Deflater(JZlib.Z_DEFAULT_COMPRESSION), + DEFAULT_BUFSIZE, true); + mydeflater = true; + } + + public DeflaterOutputStream(OutputStream out, Deflater def) throws IOException { + this(out, def, DEFAULT_BUFSIZE, true); + } + + public DeflaterOutputStream(OutputStream out, + Deflater deflater, + int size) throws IOException { + this(out, deflater, size, true); + } + public DeflaterOutputStream(OutputStream out, + Deflater deflater, + int size, + boolean close_out) throws IOException { + super(out); + if (out == null || deflater == null) { + throw new NullPointerException(); + } + else if (size <= 0) { + throw new IllegalArgumentException("buffer size must be greater than 0"); + } + this.deflater = deflater; + buffer = new byte[size]; + this.close_out = close_out; + } + + public void write(int b) throws IOException { + buf1[0] = (byte)(b & 0xff); + write(buf1, 0, 1); + } + + public void write(byte[] b, int off, int len) throws IOException { + if (deflater.finished()) { + throw new IOException("finished"); + } + else if (off<0 | len<0 | off+len>b.length) { + throw new IndexOutOfBoundsException(); + } + else if (len == 0) { + return; + } + else { + int flush = syncFlush ? JZlib.Z_SYNC_FLUSH : JZlib.Z_NO_FLUSH; + deflater.setInput(b, off, len, true); + while (deflater.avail_in>0) { + int err = deflate(flush); + if (err == JZlib.Z_STREAM_END) + break; + } + } + } + + public void finish() throws IOException { + while (!deflater.finished()) { + deflate(JZlib.Z_FINISH); + } + } + + public void close() throws IOException { + if (!closed) { + finish(); + if (mydeflater){ + deflater.end(); + } + if(close_out) + out.close(); + closed = true; + } + } + + protected int deflate(int flush) throws IOException { + deflater.setOutput(buffer, 0, buffer.length); + int err = deflater.deflate(flush); + switch(err) { + case JZlib.Z_OK: + case JZlib.Z_STREAM_END: + break; + case JZlib.Z_BUF_ERROR: + if(deflater.avail_in<=0 && flush!=JZlib.Z_FINISH){ + // flush() without any data + break; + } + default: + throw new IOException("failed to deflate: error="+err+" avail_out="+deflater.avail_out); + } + int len = deflater.next_out_index; + if (len > 0) { + out.write(buffer, 0, len); + } + return err; + } + + public void flush() throws IOException { + if (syncFlush && !deflater.finished()) { + while (true) { + int err = deflate(JZlib.Z_SYNC_FLUSH); + if (deflater.next_out_index < buffer.length) + break; + if (err == JZlib.Z_STREAM_END) + break; + } + } + out.flush(); + } + + public long getTotalIn() { + return deflater.getTotalIn(); + } + + public long getTotalOut() { + return deflater.getTotalOut(); + } + + public void setSyncFlush(boolean syncFlush){ + this.syncFlush = syncFlush; + } + + public boolean getSyncFlush(){ + return this.syncFlush; + } + + public Deflater getDeflater(){ + return deflater; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/jcraft/jzlib/GZIPException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/jcraft/jzlib/GZIPException.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,44 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +public class GZIPException extends java.io.IOException { + public GZIPException() { + super(); + } + public GZIPException(String s) { + super(s); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/jcraft/jzlib/GZIPHeader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/jcraft/jzlib/GZIPHeader.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,214 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +import java.io.UnsupportedEncodingException; + +/** + * @see "http://www.ietf.org/rfc/rfc1952.txt" + */ +public class GZIPHeader implements Cloneable { + + public static final byte OS_MSDOS = (byte) 0x00; + public static final byte OS_AMIGA = (byte) 0x01; + public static final byte OS_VMS = (byte) 0x02; + public static final byte OS_UNIX = (byte) 0x03; + public static final byte OS_ATARI = (byte) 0x05; + public static final byte OS_OS2 = (byte) 0x06; + public static final byte OS_MACOS = (byte) 0x07; + public static final byte OS_TOPS20 = (byte) 0x0a; + public static final byte OS_WIN32 = (byte) 0x0b; + public static final byte OS_VMCMS = (byte) 0x04; + public static final byte OS_ZSYSTEM = (byte) 0x08; + public static final byte OS_CPM = (byte) 0x09; + public static final byte OS_QDOS = (byte) 0x0c; + public static final byte OS_RISCOS = (byte) 0x0d; + public static final byte OS_UNKNOWN = (byte) 0xff; + + boolean text = false; + private boolean fhcrc = false; + long time; + int xflags; + int os = 255; + byte[] extra; + byte[] name; + byte[] comment; + int hcrc; + long crc; + boolean done = false; + long mtime = 0; + + public void setModifiedTime(long mtime) { + this.mtime = mtime; + } + + public long getModifiedTime() { + return mtime; + } + + public void setOS(int os) { + if((0<=os && os <=13) || os==255) + this.os=os; + else + throw new IllegalArgumentException("os: "+os); + } + + public int getOS(){ + return os; + } + + public void setName(String name) { + try{ + this.name=name.getBytes("ISO-8859-1"); + } + catch(UnsupportedEncodingException e){ + throw new IllegalArgumentException("name must be in ISO-8859-1 "+name); + } + } + + public String getName(){ + if(name==null) return ""; + try { + return new String(name, "ISO-8859-1"); + } + catch (UnsupportedEncodingException e) { + throw new InternalError(e.toString()); + } + } + + public void setComment(String comment) { + try{ + this.comment=comment.getBytes("ISO-8859-1"); + } + catch(UnsupportedEncodingException e){ + throw new IllegalArgumentException("comment must be in ISO-8859-1 "+name); + } + } + + public String getComment(){ + if(comment==null) return ""; + try { + return new String(comment, "ISO-8859-1"); + } + catch (UnsupportedEncodingException e) { + throw new InternalError(e.toString()); + } + } + + public void setCRC(long crc){ + this.crc = crc; + } + + public long getCRC(){ + return crc; + } + + void put(Deflate d){ + int flag = 0; + if(text){ + flag |= 1; // FTEXT + } + if(fhcrc){ + flag |= 2; // FHCRC + } + if(extra!=null){ + flag |= 4; // FEXTRA + } + if(name!=null){ + flag |= 8; // FNAME + } + if(comment!=null){ + flag |= 16; // FCOMMENT + } + int xfl = 0; + if(d.level == JZlib.Z_BEST_SPEED){ + xfl |= 4; + } + else if (d.level == JZlib.Z_BEST_COMPRESSION){ + xfl |= 2; + } + + d.put_short((short)0x8b1f); // ID1 ID2 + d.put_byte((byte)8); // CM(Compression Method) + d.put_byte((byte)flag); + d.put_byte((byte)mtime); + d.put_byte((byte)(mtime>>8)); + d.put_byte((byte)(mtime>>16)); + d.put_byte((byte)(mtime>>24)); + d.put_byte((byte)xfl); + d.put_byte((byte)os); + + if(extra!=null){ + d.put_byte((byte)extra.length); + d.put_byte((byte)(extra.length>>8)); + d.put_byte(extra, 0, extra.length); + } + + if(name!=null){ + d.put_byte(name, 0, name.length); + d.put_byte((byte)0); + } + + if(comment!=null){ + d.put_byte(comment, 0, comment.length); + d.put_byte((byte)0); + } + } + + @Override + public Object clone() throws CloneNotSupportedException { + GZIPHeader gheader = (GZIPHeader)super.clone(); + byte[] tmp; + if(gheader.extra!=null){ + tmp=new byte[gheader.extra.length]; + System.arraycopy(gheader.extra, 0, tmp, 0, tmp.length); + gheader.extra = tmp; + } + + if(gheader.name!=null){ + tmp=new byte[gheader.name.length]; + System.arraycopy(gheader.name, 0, tmp, 0, tmp.length); + gheader.name = tmp; + } + + if(gheader.comment!=null){ + tmp=new byte[gheader.comment.length]; + System.arraycopy(gheader.comment, 0, tmp, 0, tmp.length); + gheader.comment = tmp; + } + + return gheader; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/jcraft/jzlib/GZIPInputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/jcraft/jzlib/GZIPInputStream.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,145 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jcraft.jzlib; +import java.io.*; + +public class GZIPInputStream extends InflaterInputStream { + + public GZIPInputStream(InputStream in) throws IOException { + this(in, DEFAULT_BUFSIZE, true); + } + + public GZIPInputStream(InputStream in, + int size, + boolean close_in) throws IOException { + this(in, new Inflater(15+16), size, close_in); + myinflater = true; + } + + public GZIPInputStream(InputStream in, + Inflater inflater, + int size, + boolean close_in) throws IOException { + super(in, inflater, size, close_in); + } + + public long getModifiedtime() { + return inflater.istate.getGZIPHeader().getModifiedTime(); + } + + public int getOS() { + return inflater.istate.getGZIPHeader().getOS(); + } + + public String getName() { + return inflater.istate.getGZIPHeader().getName(); + } + + public String getComment() { + return inflater.istate.getGZIPHeader().getComment(); + } + + public long getCRC() throws GZIPException { + if(inflater.istate.mode != 12 /*DONE*/) + throw new GZIPException("checksum is not calculated yet."); + return inflater.istate.getGZIPHeader().getCRC(); + } + + public void readHeader() throws IOException { + + byte[] empty = "".getBytes(); + inflater.setOutput(empty, 0, 0); + inflater.setInput(empty, 0, 0, false); + + byte[] b = new byte[10]; + + int n = fill(b); + if(n!=10){ + if(n>0){ + inflater.setInput(b, 0, n, false); + //inflater.next_in_index = n; + inflater.next_in_index = 0; + inflater.avail_in = n; + } + throw new IOException("no input"); + } + + inflater.setInput(b, 0, n, false); + + byte[] b1 = new byte[1]; + do{ + if(inflater.avail_in<=0){ + int i = in.read(b1); + if(i<=0) + throw new IOException("no input"); + inflater.setInput(b1, 0, 1, true); + } + + int err = inflater.inflate(JZlib.Z_NO_FLUSH); + + if(err!=0/*Z_OK*/){ + int len = 2048-inflater.next_in.length; + if(len>0){ + byte[] tmp = new byte[len]; + n = fill(tmp); + if(n>0){ + inflater.avail_in += inflater.next_in_index; + inflater.next_in_index = 0; + inflater.setInput(tmp, 0, n, true); + } + } + //inflater.next_in_index = inflater.next_in.length; + inflater.avail_in += inflater.next_in_index; + inflater.next_in_index = 0; + throw new IOException(inflater.msg); + } + } + while(inflater.istate.inParsingHeader()); + } + + private int fill(byte[] buf) { + int len = buf.length; + int n = 0; + do{ + int i = -1; + try { + i = in.read(buf, n, buf.length - n); + } + catch(IOException e){ + } + if(i == -1){ + break; + } + n+=i; + } + while(n>> 1){ + case 0: // stored + {b>>>=(3);k-=(3);} + t = k & 7; // go to byte boundary + + {b>>>=(t);k-=(t);} + mode = LENS; // get length of stored block + break; + case 1: // fixed + InfTree.inflate_trees_fixed(bl, bd, tl, td, z); + codes.init(bl[0], bd[0], tl[0], 0, td[0], 0); + + {b>>>=(3);k-=(3);} + + mode = CODES; + break; + case 2: // dynamic + + {b>>>=(3);k-=(3);} + + mode = TABLE; + break; + case 3: // illegal + + {b>>>=(3);k-=(3);} + mode = BAD; + z.msg = "invalid block type"; + r = Z_DATA_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(r); + } + break; + case LENS: + + while(k<(32)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(r); + }; + n--; + b|=(z.next_in[p++]&0xff)<>> 16) & 0xffff) != (b & 0xffff)){ + mode = BAD; + z.msg = "invalid stored block lengths"; + r = Z_DATA_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(r); + } + left = (b & 0xffff); + b = k = 0; // dump bits + mode = left!=0 ? STORED : (last!=0 ? DRY : TYPE); + break; + case STORED: + if (n == 0){ + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(r); + } + + if(m==0){ + if(q==end&&read!=0){ + q=0; m=(int)(qn) t = n; + if(t>m) t = m; + System.arraycopy(z.next_in, p, window, q, t); + p += t; n -= t; + q += t; m -= t; + if ((left -= t) != 0) + break; + mode = last!=0 ? DRY : TYPE; + break; + case TABLE: + + while(k<(14)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(r); + }; + n--; + b|=(z.next_in[p++]&0xff)< 29 || ((t >> 5) & 0x1f) > 29) + { + mode = BAD; + z.msg = "too many length or distance symbols"; + r = Z_DATA_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(r); + } + t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f); + if(blens==null || blens.length>>=(14);k-=(14);} + + index = 0; + mode = BTREE; + case BTREE: + while (index < 4 + (table >>> 10)){ + while(k<(3)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(r); + }; + n--; + b|=(z.next_in[p++]&0xff)<>>=(3);k-=(3);} + } + + while(index < 19){ + blens[border[index++]] = 0; + } + + bb[0] = 7; + t = inftree.inflate_trees_bits(blens, bb, tb, hufts, z); + if (t != Z_OK){ + r = t; + if (r == Z_DATA_ERROR){ + blens=null; + mode = BAD; + } + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(r); + } + + index = 0; + mode = DTREE; + case DTREE: + while (true){ + t = table; + if(!(index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f))){ + break; + } + + int[] h; + int i, j, c; + + t = bb[0]; + + while(k<(t)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(r); + }; + n--; + b|=(z.next_in[p++]&0xff)<>>=(t);k-=(t); + blens[index++] = c; + } + else { // c == 16..18 + i = c == 18 ? 7 : c - 14; + j = c == 18 ? 11 : 3; + + while(k<(t+i)){ + if(n!=0){ + r=Z_OK; + } + else{ + bitb=b; bitk=k; + z.avail_in=n; + z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(r); + }; + n--; + b|=(z.next_in[p++]&0xff)<>>=(t);k-=(t); + + j += (b & inflate_mask[i]); + + b>>>=(i);k-=(i); + + i = index; + t = table; + if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || + (c == 16 && i < 1)){ + blens=null; + mode = BAD; + z.msg = "invalid bit length repeat"; + r = Z_DATA_ERROR; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(r); + } + + c = c == 16 ? blens[i-1] : 0; + do{ + blens[i++] = c; + } + while (--j!=0); + index = i; + } + } + + tb[0]=-1; + { + bl[0] = 9; // must be <= 9 for lookahead assumptions + bd[0] = 6; // must be <= 9 for lookahead assumptions + t = table; + t = inftree.inflate_trees_dynamic(257 + (t & 0x1f), + 1 + ((t >> 5) & 0x1f), + blens, bl, bd, tli, tdi, hufts, z); + + if (t != Z_OK){ + if (t == Z_DATA_ERROR){ + blens=null; + mode = BAD; + } + r = t; + + bitb=b; bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + return inflate_flush(r); + } + codes.init(bl[0], bd[0], hufts, tli[0], hufts, tdi[0]); + } + mode = CODES; + case CODES: + bitb=b; bitk=k; + z.avail_in=n; z.total_in+=p-z.next_in_index;z.next_in_index=p; + write=q; + + if ((r = codes.proc(r)) != Z_STREAM_END){ + return inflate_flush(r); + } + r = Z_OK; + codes.free(z); + + p=z.next_in_index; n=z.avail_in;b=bitb;k=bitk; + q=write;m=(int)(q z.avail_out) n = z.avail_out; + if(n!=0 && r == Z_BUF_ERROR) r = Z_OK; + + // update counters + z.avail_out -= n; + z.total_out += n; + + // update check information + if(check && n>0){ + z.adler.update(window, q, n); + } + + // copy as far as end of window + System.arraycopy(window, q, z.next_out, p, n); + p += n; + q += n; + + // see if more to copy at beginning of window + if (q == end){ + // wrap pointers + q = 0; + if (write == end) + write = 0; + + // compute bytes to copy + n = write - q; + if (n > z.avail_out) n = z.avail_out; + if (n!=0 && r == Z_BUF_ERROR) r = Z_OK; + + // update counters + z.avail_out -= n; + z.total_out += n; + + // update check information + if(check && n>0){ + z.adler.update(window, q, n); + } + + // copy + System.arraycopy(window, q, z.next_out, p, n); + p += n; + q += n; + } + + // update pointers + z.next_out_index = p; + read = q; + + // done + return r; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/jcraft/jzlib/InfCodes.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/jcraft/jzlib/InfCodes.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,610 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final class InfCodes{ + + static final private int[] inflate_mask = { + 0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, + 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, 0x000001ff, + 0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, + 0x00007fff, 0x0000ffff + }; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + // waiting for "i:"=input, + // "o:"=output, + // "x:"=nothing + static final private int START=0; // x: set up for LEN + static final private int LEN=1; // i: get length/literal/eob next + static final private int LENEXT=2; // i: getting length extra (have base) + static final private int DIST=3; // i: get distance next + static final private int DISTEXT=4;// i: getting distance extra + static final private int COPY=5; // o: copying bytes in window, waiting for space + static final private int LIT=6; // o: got literal, waiting for output space + static final private int WASH=7; // o: got eob, possibly still output waiting + static final private int END=8; // x: got eob and all data flushed + static final private int BADCODE=9;// x: got error + + int mode; // current inflate_codes mode + + // mode dependent information + int len; + + int[] tree; // pointer into tree + int tree_index=0; + int need; // bits needed + + int lit; + + // if EXT or COPY, where and how much + int get; // bits to get for extra + int dist; // distance back to copy from + + byte lbits; // ltree bits decoded per branch + byte dbits; // dtree bits decoder per branch + int[] ltree; // literal/length/eob tree + int ltree_index; // literal/length/eob tree + int[] dtree; // distance tree + int dtree_index; // distance tree + + private final ZStream z; + private final InfBlocks s; + InfCodes(ZStream z, InfBlocks s){ + this.z=z; + this.s=s; + } + + void init(int bl, int bd, + int[] tl, int tl_index, + int[] td, int td_index){ + mode=START; + lbits=(byte)bl; + dbits=(byte)bd; + ltree=tl; + ltree_index=tl_index; + dtree = td; + dtree_index=td_index; + tree=null; + } + + int proc(int r){ + int j; // temporary storage + int[] t; // temporary pointer + int tindex; // temporary pointer + int e; // extra bits or operation + int b=0; // bit buffer + int k=0; // bits in bit buffer + int p=0; // input data pointer + int n; // bytes available there + int q; // output window write pointer + int m; // bytes to end of window or read pointer + int f; // pointer to copy strings from + + // copy input/output information to locals (UPDATE macro restores) + p=z.next_in_index;n=z.avail_in;b=s.bitb;k=s.bitk; + q=s.write;m=q= 258 && n >= 10){ + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + r = inflate_fast(lbits, dbits, + ltree, ltree_index, + dtree, dtree_index, + s, z); + + p=z.next_in_index;n=z.avail_in;b=s.bitb;k=s.bitk; + q=s.write;m=q>>=(tree[tindex+1]); + k-=(tree[tindex+1]); + + e=tree[tindex]; + + if(e == 0){ // literal + lit = tree[tindex+2]; + mode = LIT; + break; + } + if((e & 16)!=0 ){ // length + get = e & 15; + len = tree[tindex+2]; + mode = LENEXT; + break; + } + if ((e & 64) == 0){ // next table + need = e; + tree_index = tindex/3+tree[tindex+2]; + break; + } + if ((e & 32)!=0){ // end of block + mode = WASH; + break; + } + mode = BADCODE; // invalid code + z.msg = "invalid literal/length code"; + r = Z_DATA_ERROR; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(r); + + case LENEXT: // i: getting length extra (have base) + j = get; + + while(k<(j)){ + if(n!=0)r=Z_OK; + else{ + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(r); + } + n--; b|=(z.next_in[p++]&0xff)<>=j; + k-=j; + + need = dbits; + tree = dtree; + tree_index=dtree_index; + mode = DIST; + case DIST: // i: get distance next + j = need; + + while(k<(j)){ + if(n!=0)r=Z_OK; + else{ + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(r); + } + n--; b|=(z.next_in[p++]&0xff)<>=tree[tindex+1]; + k-=tree[tindex+1]; + + e = (tree[tindex]); + if((e & 16)!=0){ // distance + get = e & 15; + dist = tree[tindex+2]; + mode = DISTEXT; + break; + } + if ((e & 64) == 0){ // next table + need = e; + tree_index = tindex/3 + tree[tindex+2]; + break; + } + mode = BADCODE; // invalid code + z.msg = "invalid distance code"; + r = Z_DATA_ERROR; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(r); + + case DISTEXT: // i: getting distance extra + j = get; + + while(k<(j)){ + if(n!=0)r=Z_OK; + else{ + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + return s.inflate_flush(r); + } + n--; b|=(z.next_in[p++]&0xff)<>=j; + k-=j; + + mode = COPY; + case COPY: // o: copying bytes in window, waiting for space + f = q - dist; + while(f < 0){ // modulo window size-"while" instead + f += s.end; // of "if" handles invalid distances + } + while (len!=0){ + + if(m==0){ + if(q==s.end&&s.read!=0){q=0;m=q 7){ // return unused byte, if any + k -= 8; + n++; + p--; // can always return one + } + + s.write=q; r=s.inflate_flush(r); + q=s.write;m=q= 258 && n >= 10 + // get literal/length code + while(k<(20)){ // max bits for literal/length code + n--; + b|=(z.next_in[p++]&0xff)<>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); + + s.window[q++] = (byte)tp[tp_index_t_3+2]; + m--; + continue; + } + do { + + b>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); + + if((e&16)!=0){ + e &= 15; + c = tp[tp_index_t_3+2] + ((int)b & inflate_mask[e]); + + b>>=e; k-=e; + + // decode distance base of block to copy + while(k<(15)){ // max bits for distance code + n--; + b|=(z.next_in[p++]&0xff)<>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); + + if((e&16)!=0){ + // get extra bits to add to distance base + e &= 15; + while(k<(e)){ // get extra bits (up to 13) + n--; + b|=(z.next_in[p++]&0xff)<>=(e); k-=(e); + + // do the copy + m -= c; + if (q >= d){ // offset before dest + // just copy + r=q-d; + if(q-r>0 && 2>(q-r)){ + s.window[q++]=s.window[r++]; // minimum count is three, + s.window[q++]=s.window[r++]; // so unroll loop a little + c-=2; + } + else{ + System.arraycopy(s.window, r, s.window, q, 2); + q+=2; r+=2; c-=2; + } + } + else{ // else offset after destination + r=q-d; + do{ + r+=s.end; // force pointer in window + }while(r<0); // covers invalid distances + e=s.end-r; + if(c>e){ // if source crosses, + c-=e; // wrapped copy + if(q-r>0 && e>(q-r)){ + do{s.window[q++] = s.window[r++];} + while(--e!=0); + } + else{ + System.arraycopy(s.window, r, s.window, q, e); + q+=e; r+=e; e=0; + } + r = 0; // copy rest from start of window + } + + } + + // copy all or what's left + if(q-r>0 && c>(q-r)){ + do{s.window[q++] = s.window[r++];} + while(--c!=0); + } + else{ + System.arraycopy(s.window, r, s.window, q, c); + q+=c; r+=c; c=0; + } + break; + } + else if((e&64)==0){ + t+=tp[tp_index_t_3+2]; + t+=(b&inflate_mask[e]); + tp_index_t_3=(tp_index+t)*3; + e=tp[tp_index_t_3]; + } + else{ + z.msg = "invalid distance code"; + + c=z.avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + + return Z_DATA_ERROR; + } + } + while(true); + break; + } + + if((e&64)==0){ + t+=tp[tp_index_t_3+2]; + t+=(b&inflate_mask[e]); + tp_index_t_3=(tp_index+t)*3; + if((e=tp[tp_index_t_3])==0){ + + b>>=(tp[tp_index_t_3+1]); k-=(tp[tp_index_t_3+1]); + + s.window[q++]=(byte)tp[tp_index_t_3+2]; + m--; + break; + } + } + else if((e&32)!=0){ + + c=z.avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + + return Z_STREAM_END; + } + else{ + z.msg="invalid literal/length code"; + + c=z.avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + + return Z_DATA_ERROR; + } + } + while(true); + } + while(m>=258 && n>= 10); + + // not enough input or output--restore pointers and return + c=z.avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3; + + s.bitb=b;s.bitk=k; + z.avail_in=n;z.total_in+=p-z.next_in_index;z.next_in_index=p; + s.write=q; + + return Z_OK; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/jcraft/jzlib/InfTree.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/jcraft/jzlib/InfTree.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,518 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final class InfTree{ + + static final private int MANY=1440; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + static final int fixed_bl = 9; + static final int fixed_bd = 5; + + static final int[] fixed_tl = { + 96,7,256, 0,8,80, 0,8,16, 84,8,115, + 82,7,31, 0,8,112, 0,8,48, 0,9,192, + 80,7,10, 0,8,96, 0,8,32, 0,9,160, + 0,8,0, 0,8,128, 0,8,64, 0,9,224, + 80,7,6, 0,8,88, 0,8,24, 0,9,144, + 83,7,59, 0,8,120, 0,8,56, 0,9,208, + 81,7,17, 0,8,104, 0,8,40, 0,9,176, + 0,8,8, 0,8,136, 0,8,72, 0,9,240, + 80,7,4, 0,8,84, 0,8,20, 85,8,227, + 83,7,43, 0,8,116, 0,8,52, 0,9,200, + 81,7,13, 0,8,100, 0,8,36, 0,9,168, + 0,8,4, 0,8,132, 0,8,68, 0,9,232, + 80,7,8, 0,8,92, 0,8,28, 0,9,152, + 84,7,83, 0,8,124, 0,8,60, 0,9,216, + 82,7,23, 0,8,108, 0,8,44, 0,9,184, + 0,8,12, 0,8,140, 0,8,76, 0,9,248, + 80,7,3, 0,8,82, 0,8,18, 85,8,163, + 83,7,35, 0,8,114, 0,8,50, 0,9,196, + 81,7,11, 0,8,98, 0,8,34, 0,9,164, + 0,8,2, 0,8,130, 0,8,66, 0,9,228, + 80,7,7, 0,8,90, 0,8,26, 0,9,148, + 84,7,67, 0,8,122, 0,8,58, 0,9,212, + 82,7,19, 0,8,106, 0,8,42, 0,9,180, + 0,8,10, 0,8,138, 0,8,74, 0,9,244, + 80,7,5, 0,8,86, 0,8,22, 192,8,0, + 83,7,51, 0,8,118, 0,8,54, 0,9,204, + 81,7,15, 0,8,102, 0,8,38, 0,9,172, + 0,8,6, 0,8,134, 0,8,70, 0,9,236, + 80,7,9, 0,8,94, 0,8,30, 0,9,156, + 84,7,99, 0,8,126, 0,8,62, 0,9,220, + 82,7,27, 0,8,110, 0,8,46, 0,9,188, + 0,8,14, 0,8,142, 0,8,78, 0,9,252, + 96,7,256, 0,8,81, 0,8,17, 85,8,131, + 82,7,31, 0,8,113, 0,8,49, 0,9,194, + 80,7,10, 0,8,97, 0,8,33, 0,9,162, + 0,8,1, 0,8,129, 0,8,65, 0,9,226, + 80,7,6, 0,8,89, 0,8,25, 0,9,146, + 83,7,59, 0,8,121, 0,8,57, 0,9,210, + 81,7,17, 0,8,105, 0,8,41, 0,9,178, + 0,8,9, 0,8,137, 0,8,73, 0,9,242, + 80,7,4, 0,8,85, 0,8,21, 80,8,258, + 83,7,43, 0,8,117, 0,8,53, 0,9,202, + 81,7,13, 0,8,101, 0,8,37, 0,9,170, + 0,8,5, 0,8,133, 0,8,69, 0,9,234, + 80,7,8, 0,8,93, 0,8,29, 0,9,154, + 84,7,83, 0,8,125, 0,8,61, 0,9,218, + 82,7,23, 0,8,109, 0,8,45, 0,9,186, + 0,8,13, 0,8,141, 0,8,77, 0,9,250, + 80,7,3, 0,8,83, 0,8,19, 85,8,195, + 83,7,35, 0,8,115, 0,8,51, 0,9,198, + 81,7,11, 0,8,99, 0,8,35, 0,9,166, + 0,8,3, 0,8,131, 0,8,67, 0,9,230, + 80,7,7, 0,8,91, 0,8,27, 0,9,150, + 84,7,67, 0,8,123, 0,8,59, 0,9,214, + 82,7,19, 0,8,107, 0,8,43, 0,9,182, + 0,8,11, 0,8,139, 0,8,75, 0,9,246, + 80,7,5, 0,8,87, 0,8,23, 192,8,0, + 83,7,51, 0,8,119, 0,8,55, 0,9,206, + 81,7,15, 0,8,103, 0,8,39, 0,9,174, + 0,8,7, 0,8,135, 0,8,71, 0,9,238, + 80,7,9, 0,8,95, 0,8,31, 0,9,158, + 84,7,99, 0,8,127, 0,8,63, 0,9,222, + 82,7,27, 0,8,111, 0,8,47, 0,9,190, + 0,8,15, 0,8,143, 0,8,79, 0,9,254, + 96,7,256, 0,8,80, 0,8,16, 84,8,115, + 82,7,31, 0,8,112, 0,8,48, 0,9,193, + + 80,7,10, 0,8,96, 0,8,32, 0,9,161, + 0,8,0, 0,8,128, 0,8,64, 0,9,225, + 80,7,6, 0,8,88, 0,8,24, 0,9,145, + 83,7,59, 0,8,120, 0,8,56, 0,9,209, + 81,7,17, 0,8,104, 0,8,40, 0,9,177, + 0,8,8, 0,8,136, 0,8,72, 0,9,241, + 80,7,4, 0,8,84, 0,8,20, 85,8,227, + 83,7,43, 0,8,116, 0,8,52, 0,9,201, + 81,7,13, 0,8,100, 0,8,36, 0,9,169, + 0,8,4, 0,8,132, 0,8,68, 0,9,233, + 80,7,8, 0,8,92, 0,8,28, 0,9,153, + 84,7,83, 0,8,124, 0,8,60, 0,9,217, + 82,7,23, 0,8,108, 0,8,44, 0,9,185, + 0,8,12, 0,8,140, 0,8,76, 0,9,249, + 80,7,3, 0,8,82, 0,8,18, 85,8,163, + 83,7,35, 0,8,114, 0,8,50, 0,9,197, + 81,7,11, 0,8,98, 0,8,34, 0,9,165, + 0,8,2, 0,8,130, 0,8,66, 0,9,229, + 80,7,7, 0,8,90, 0,8,26, 0,9,149, + 84,7,67, 0,8,122, 0,8,58, 0,9,213, + 82,7,19, 0,8,106, 0,8,42, 0,9,181, + 0,8,10, 0,8,138, 0,8,74, 0,9,245, + 80,7,5, 0,8,86, 0,8,22, 192,8,0, + 83,7,51, 0,8,118, 0,8,54, 0,9,205, + 81,7,15, 0,8,102, 0,8,38, 0,9,173, + 0,8,6, 0,8,134, 0,8,70, 0,9,237, + 80,7,9, 0,8,94, 0,8,30, 0,9,157, + 84,7,99, 0,8,126, 0,8,62, 0,9,221, + 82,7,27, 0,8,110, 0,8,46, 0,9,189, + 0,8,14, 0,8,142, 0,8,78, 0,9,253, + 96,7,256, 0,8,81, 0,8,17, 85,8,131, + 82,7,31, 0,8,113, 0,8,49, 0,9,195, + 80,7,10, 0,8,97, 0,8,33, 0,9,163, + 0,8,1, 0,8,129, 0,8,65, 0,9,227, + 80,7,6, 0,8,89, 0,8,25, 0,9,147, + 83,7,59, 0,8,121, 0,8,57, 0,9,211, + 81,7,17, 0,8,105, 0,8,41, 0,9,179, + 0,8,9, 0,8,137, 0,8,73, 0,9,243, + 80,7,4, 0,8,85, 0,8,21, 80,8,258, + 83,7,43, 0,8,117, 0,8,53, 0,9,203, + 81,7,13, 0,8,101, 0,8,37, 0,9,171, + 0,8,5, 0,8,133, 0,8,69, 0,9,235, + 80,7,8, 0,8,93, 0,8,29, 0,9,155, + 84,7,83, 0,8,125, 0,8,61, 0,9,219, + 82,7,23, 0,8,109, 0,8,45, 0,9,187, + 0,8,13, 0,8,141, 0,8,77, 0,9,251, + 80,7,3, 0,8,83, 0,8,19, 85,8,195, + 83,7,35, 0,8,115, 0,8,51, 0,9,199, + 81,7,11, 0,8,99, 0,8,35, 0,9,167, + 0,8,3, 0,8,131, 0,8,67, 0,9,231, + 80,7,7, 0,8,91, 0,8,27, 0,9,151, + 84,7,67, 0,8,123, 0,8,59, 0,9,215, + 82,7,19, 0,8,107, 0,8,43, 0,9,183, + 0,8,11, 0,8,139, 0,8,75, 0,9,247, + 80,7,5, 0,8,87, 0,8,23, 192,8,0, + 83,7,51, 0,8,119, 0,8,55, 0,9,207, + 81,7,15, 0,8,103, 0,8,39, 0,9,175, + 0,8,7, 0,8,135, 0,8,71, 0,9,239, + 80,7,9, 0,8,95, 0,8,31, 0,9,159, + 84,7,99, 0,8,127, 0,8,63, 0,9,223, + 82,7,27, 0,8,111, 0,8,47, 0,9,191, + 0,8,15, 0,8,143, 0,8,79, 0,9,255 + }; + static final int[] fixed_td = { + 80,5,1, 87,5,257, 83,5,17, 91,5,4097, + 81,5,5, 89,5,1025, 85,5,65, 93,5,16385, + 80,5,3, 88,5,513, 84,5,33, 92,5,8193, + 82,5,9, 90,5,2049, 86,5,129, 192,5,24577, + 80,5,2, 87,5,385, 83,5,25, 91,5,6145, + 81,5,7, 89,5,1537, 85,5,97, 93,5,24577, + 80,5,4, 88,5,769, 84,5,49, 92,5,12289, + 82,5,13, 90,5,3073, 86,5,193, 192,5,24577 + }; + + // Tables for deflate from PKZIP's appnote.txt. + static final int[] cplens = { // Copy lengths for literal codes 257..285 + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 + }; + + // see note #13 above about 258 + static final int[] cplext = { // Extra bits for literal codes 257..285 + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112 // 112==invalid + }; + + static final int[] cpdist = { // Copy offsets for distance codes 0..29 + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577 + }; + + static final int[] cpdext = { // Extra bits for distance codes + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13}; + + // If BMAX needs to be larger than 16, then h and x[] should be uLong. + static final int BMAX=15; // maximum bit length of any code + + int[] hn = null; // hufts used in space + int[] v = null; // work area for huft_build + int[] c = null; // bit length count table + int[] r = null; // table entry for structure assignment + int[] u = null; // table stack + int[] x = null; // bit offsets, then code stack + + private int huft_build(int[] b, // code lengths in bits (all assumed <= BMAX) + int bindex, + int n, // number of codes (assumed <= 288) + int s, // number of simple-valued codes (0..s-1) + int[] d, // list of base values for non-simple codes + int[] e, // list of extra bits for non-simple codes + int[] t, // result: starting table + int[] m, // maximum lookup bits, returns actual + int[] hp,// space for trees + int[] hn,// hufts used in space + int[] v // working area: values in order of bit length + ){ + // Given a list of code lengths and a maximum table size, make a set of + // tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR + // if the given code set is incomplete (the tables are still built in this + // case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of + // lengths), or Z_MEM_ERROR if not enough memory. + + int a; // counter for codes of length k + int f; // i repeats in table every f entries + int g; // maximum code length + int h; // table level + int i; // counter, current code + int j; // counter + int k; // number of bits in current code + int l; // bits per table (returned in m) + int mask; // (1 << w) - 1, to avoid cc -O bug on HP + int p; // pointer into c[], b[], or v[] + int q; // points to current table + int w; // bits before this table == (l * h) + int xp; // pointer into x + int y; // number of dummy codes added + int z; // number of entries in current table + + // Generate counts for each bit length + + p = 0; i = n; + do { + c[b[bindex+p]]++; p++; i--; // assume all entries <= BMAX + }while(i!=0); + + if(c[0] == n){ // null input--all zero length codes + t[0] = -1; + m[0] = 0; + return Z_OK; + } + + // Find minimum and maximum length, bound *m by those + l = m[0]; + for (j = 1; j <= BMAX; j++) + if(c[j]!=0) break; + k = j; // minimum code length + if(l < j){ + l = j; + } + for (i = BMAX; i!=0; i--){ + if(c[i]!=0) break; + } + g = i; // maximum code length + if(l > i){ + l = i; + } + m[0] = l; + + // Adjust last length count to fill out codes, if needed + for (y = 1 << j; j < i; j++, y <<= 1){ + if ((y -= c[j]) < 0){ + return Z_DATA_ERROR; + } + } + if ((y -= c[i]) < 0){ + return Z_DATA_ERROR; + } + c[i] += y; + + // Generate starting offsets into the value table for each length + x[1] = j = 0; + p = 1; xp = 2; + while (--i!=0) { // note that i == g from above + x[xp] = (j += c[p]); + xp++; + p++; + } + + // Make a table of values in order of bit lengths + i = 0; p = 0; + do { + if ((j = b[bindex+p]) != 0){ + v[x[j]++] = i; + } + p++; + } + while (++i < n); + n = x[g]; // set n to length of v + + // Generate the Huffman codes and for each, make the table entries + x[0] = i = 0; // first Huffman code is zero + p = 0; // grab values in bit order + h = -1; // no tables yet--level -1 + w = -l; // bits decoded == (l * h) + u[0] = 0; // just to keep compilers happy + q = 0; // ditto + z = 0; // ditto + + // go through the bit lengths (k already is bits in shortest code) + for (; k <= g; k++){ + a = c[k]; + while (a--!=0){ + // here i is the Huffman code of length k bits for value *p + // make tables up to required level + while (k > w + l){ + h++; + w += l; // previous table always l bits + // compute minimum size table less than or equal to l bits + z = g - w; + z = (z > l) ? l : z; // table size upper limit + if((f=1<<(j=k-w))>a+1){ // try a k-w bit table + // too few codes for k-w bit table + f -= a + 1; // deduct codes from patterns left + xp = k; + if(j < z){ + while (++j < z){ // try smaller tables up to z bits + if((f <<= 1) <= c[++xp]) + break; // enough codes to use up j bits + f -= c[xp]; // else deduct codes from patterns + } + } + } + z = 1 << j; // table entries for j-bit table + + // allocate new table + if (hn[0] + z > MANY){ // (note: doesn't matter for fixed) + return Z_DATA_ERROR; // overflow of MANY + } + u[h] = q = /*hp+*/ hn[0]; // DEBUG + hn[0] += z; + + // connect to last table, if there is one + if(h!=0){ + x[h]=i; // save pattern for backing up + r[0]=(byte)j; // bits in this table + r[1]=(byte)l; // bits to dump before this table + j=i>>>(w - l); + r[2] = (int)(q - u[h-1] - j); // offset to this table + System.arraycopy(r, 0, hp, (u[h-1]+j)*3, 3); // connect to last table + } + else{ + t[0] = q; // first table is returned result + } + } + + // set up table entry in r + r[1] = (byte)(k - w); + if (p >= n){ + r[0] = 128 + 64; // out of values--invalid code + } + else if (v[p] < s){ + r[0] = (byte)(v[p] < 256 ? 0 : 32 + 64); // 256 is end-of-block + r[2] = v[p++]; // simple code is just the value + } + else{ + r[0]=(byte)(e[v[p]-s]+16+64); // non-simple--look up in lists + r[2]=d[v[p++] - s]; + } + + // fill code-like entries with r + f=1<<(k-w); + for (j=i>>>w;j>>= 1){ + i ^= j; + } + i ^= j; + + // backup over finished tables + mask = (1 << w) - 1; // needed on HP, cc -O bug + while ((i & mask) != x[h]){ + h--; // don't need to update q + w -= l; + mask = (1 << w) - 1; + } + } + } + // Return Z_BUF_ERROR if we were given an incomplete table + return y != 0 && g != 1 ? Z_BUF_ERROR : Z_OK; + } + + int inflate_trees_bits(int[] c, // 19 code lengths + int[] bb, // bits tree desired/actual depth + int[] tb, // bits tree result + int[] hp, // space for trees + ZStream z // for messages + ){ + int result; + initWorkArea(19); + hn[0]=0; + result = huft_build(c, 0, 19, 19, null, null, tb, bb, hp, hn, v); + + if(result == Z_DATA_ERROR){ + z.msg = "oversubscribed dynamic bit lengths tree"; + } + else if(result == Z_BUF_ERROR || bb[0] == 0){ + z.msg = "incomplete dynamic bit lengths tree"; + result = Z_DATA_ERROR; + } + return result; + } + + int inflate_trees_dynamic(int nl, // number of literal/length codes + int nd, // number of distance codes + int[] c, // that many (total) code lengths + int[] bl, // literal desired/actual bit depth + int[] bd, // distance desired/actual bit depth + int[] tl, // literal/length tree result + int[] td, // distance tree result + int[] hp, // space for trees + ZStream z // for messages + ){ + int result; + + // build literal/length tree + initWorkArea(288); + hn[0]=0; + result = huft_build(c, 0, nl, 257, cplens, cplext, tl, bl, hp, hn, v); + if (result != Z_OK || bl[0] == 0){ + if(result == Z_DATA_ERROR){ + z.msg = "oversubscribed literal/length tree"; + } + else if (result != Z_MEM_ERROR){ + z.msg = "incomplete literal/length tree"; + result = Z_DATA_ERROR; + } + return result; + } + + // build distance tree + initWorkArea(288); + result = huft_build(c, nl, nd, 0, cpdist, cpdext, td, bd, hp, hn, v); + + if (result != Z_OK || (bd[0] == 0 && nl > 257)){ + if (result == Z_DATA_ERROR){ + z.msg = "oversubscribed distance tree"; + } + else if (result == Z_BUF_ERROR) { + z.msg = "incomplete distance tree"; + result = Z_DATA_ERROR; + } + else if (result != Z_MEM_ERROR){ + z.msg = "empty distance tree with lengths"; + result = Z_DATA_ERROR; + } + return result; + } + + return Z_OK; + } + + static int inflate_trees_fixed(int[] bl, //literal desired/actual bit depth + int[] bd, //distance desired/actual bit depth + int[][] tl,//literal/length tree result + int[][] td,//distance tree result + ZStream z //for memory allocation + ){ + bl[0]=fixed_bl; + bd[0]=fixed_bd; + tl[0]=fixed_tl; + td[0]=fixed_td; + return Z_OK; + } + + private void initWorkArea(int vsize){ + if(hn==null){ + hn=new int[1]; + v=new int[vsize]; + c=new int[BMAX+1]; + r=new int[3]; + u=new int[BMAX]; + x=new int[BMAX+1]; + } + if(v.length> 4) + 1; + if(w < 48) + w &= 15; + } + + if(w<8 ||w>15){ + inflateEnd(); + return Z_STREAM_ERROR; + } + if(blocks != null && wbits != w){ + blocks.free(); + blocks=null; + } + + // set window size + wbits=w; + + this.blocks=new InfBlocks(z, 1<>8))&0xff; + + if(((wrap&1)==0 || // check if zlib header allowed + (((this.method << 8)+b) % 31)!=0) && + (this.method&0xf)!=Z_DEFLATED){ + if(wrap == 4){ + z.next_in_index -= 2; + z.avail_in += 2; + z.total_in -= 2; + wrap = 0; + this.mode = BLOCKS; + break; + } + this.mode = BAD; + z.msg = "incorrect header check"; + // since zlib 1.2, it is allowted to inflateSync for this case. + /* + this.marker = 5; // can't try inflateSync + */ + break; + } + + if((this.method&0xf)!=Z_DEFLATED){ + this.mode = BAD; + z.msg="unknown compression method"; + // since zlib 1.2, it is allowted to inflateSync for this case. + /* + this.marker = 5; // can't try inflateSync + */ + break; + } + + if(wrap == 4){ + wrap = 1; + } + + if((this.method>>4)+8>this.wbits){ + this.mode = BAD; + z.msg="invalid window size"; + // since zlib 1.2, it is allowted to inflateSync for this case. + /* + this.marker = 5; // can't try inflateSync + */ + break; + } + + z.adler=new Adler32(); + + if((b&PRESET_DICT)==0){ + this.mode = BLOCKS; + break; + } + this.mode = DICT4; + case DICT4: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + this.need=((z.next_in[z.next_in_index++]&0xff)<<24)&0xff000000L; + this.mode=DICT3; + case DICT3: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + this.need+=((z.next_in[z.next_in_index++]&0xff)<<16)&0xff0000L; + this.mode=DICT2; + case DICT2: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + this.need+=((z.next_in[z.next_in_index++]&0xff)<<8)&0xff00L; + this.mode=DICT1; + case DICT1: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + this.need += (z.next_in[z.next_in_index++]&0xffL); + z.adler.reset(this.need); + this.mode = DICT0; + return Z_NEED_DICT; + case DICT0: + this.mode = BAD; + z.msg = "need dictionary"; + this.marker = 0; // can try inflateSync + return Z_STREAM_ERROR; + case BLOCKS: + r = this.blocks.proc(r); + if(r == Z_DATA_ERROR){ + this.mode = BAD; + this.marker = 0; // can try inflateSync + break; + } + if(r == Z_OK){ + r = f; + } + if(r != Z_STREAM_END){ + return r; + } + r = f; + this.was=z.adler.getValue(); + this.blocks.reset(); + if(this.wrap==0){ + this.mode=DONE; + break; + } + this.mode=CHECK4; + case CHECK4: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + this.need=((z.next_in[z.next_in_index++]&0xff)<<24)&0xff000000L; + this.mode=CHECK3; + case CHECK3: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + this.need+=((z.next_in[z.next_in_index++]&0xff)<<16)&0xff0000L; + this.mode = CHECK2; + case CHECK2: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + this.need+=((z.next_in[z.next_in_index++]&0xff)<<8)&0xff00L; + this.mode = CHECK1; + case CHECK1: + + if(z.avail_in==0)return r;r=f; + + z.avail_in--; z.total_in++; + this.need+=(z.next_in[z.next_in_index++]&0xffL); + + if(flags!=0){ // gzip + this.need = ((this.need&0xff000000)>>24 | + (this.need&0x00ff0000)>>8 | + (this.need&0x0000ff00)<<8 | + (this.need&0x0000ffff)<<24)&0xffffffffL; + } + + if(((int)(this.was)) != ((int)(this.need))){ + z.msg = "incorrect data check"; + // chack is delayed + /* + this.mode = BAD; + this.marker = 5; // can't try inflateSync + break; + */ + } + else if(flags!=0 && gheader!=null){ + gheader.crc = this.need; + } + + this.mode = LENGTH; + case LENGTH: + if (wrap!=0 && flags!=0) { + + try { r=readBytes(4, r, f); } + catch(Return e){ return e.r; } + + if(z.msg!=null && z.msg.equals("incorrect data check")){ + this.mode = BAD; + this.marker = 5; // can't try inflateSync + break; + } + + if (this.need != (z.total_out & 0xffffffffL)) { + z.msg = "incorrect length check"; + this.mode = BAD; + break; + } + z.msg = null; + } + else { + if(z.msg!=null && z.msg.equals("incorrect data check")){ + this.mode = BAD; + this.marker = 5; // can't try inflateSync + break; + } + } + + this.mode = DONE; + case DONE: + return Z_STREAM_END; + case BAD: + return Z_DATA_ERROR; + + case FLAGS: + + try { r=readBytes(2, r, f); } + catch(Return e){ return e.r; } + + flags = ((int)this.need)&0xffff; + + if ((flags & 0xff) != Z_DEFLATED) { + z.msg = "unknown compression method"; + this.mode = BAD; + break; + } + if ((flags & 0xe000)!=0) { + z.msg = "unknown header flags set"; + this.mode = BAD; + break; + } + + if ((flags & 0x0200)!=0){ + checksum(2, this.need); + } + + this.mode = TIME; + + case TIME: + try { r=readBytes(4, r, f); } + catch(Return e){ return e.r; } + if(gheader!=null) + gheader.time = this.need; + if ((flags & 0x0200)!=0){ + checksum(4, this.need); + } + this.mode = OS; + case OS: + try { r=readBytes(2, r, f); } + catch(Return e){ return e.r; } + if(gheader!=null){ + gheader.xflags = ((int)this.need)&0xff; + gheader.os = (((int)this.need)>>8)&0xff; + } + if ((flags & 0x0200)!=0){ + checksum(2, this.need); + } + this.mode = EXLEN; + case EXLEN: + if ((flags & 0x0400)!=0) { + try { r=readBytes(2, r, f); } + catch(Return e){ return e.r; } + if(gheader!=null){ + gheader.extra = new byte[((int)this.need)&0xffff]; + } + if ((flags & 0x0200)!=0){ + checksum(2, this.need); + } + } + else if(gheader!=null){ + gheader.extra=null; + } + this.mode = EXTRA; + + case EXTRA: + if ((flags & 0x0400)!=0) { + try { + r=readBytes(r, f); + if(gheader!=null){ + byte[] foo = tmp_string.toByteArray(); + tmp_string=null; + if(foo.length == gheader.extra.length){ + System.arraycopy(foo, 0, gheader.extra, 0, foo.length); + } + else{ + z.msg = "bad extra field length"; + this.mode = BAD; + break; + } + } + } + catch(Return e){ return e.r; } + } + else if(gheader!=null){ + gheader.extra=null; + } + this.mode = NAME; + case NAME: + if ((flags & 0x0800)!=0) { + try { + r=readString(r, f); + if(gheader!=null){ + gheader.name=tmp_string.toByteArray(); + } + tmp_string=null; + } + catch(Return e){ return e.r; } + } + else if(gheader!=null){ + gheader.name=null; + } + this.mode = COMMENT; + case COMMENT: + if ((flags & 0x1000)!=0) { + try { + r=readString(r, f); + if(gheader!=null){ + gheader.comment=tmp_string.toByteArray(); + } + tmp_string=null; + } + catch(Return e){ return e.r; } + } + else if(gheader!=null){ + gheader.comment=null; + } + this.mode = HCRC; + case HCRC: + if ((flags & 0x0200)!=0) { + try { r=readBytes(2, r, f); } + catch(Return e){ return e.r; } + if(gheader!=null){ + gheader.hcrc=(int)(this.need&0xffff); + } + if(this.need != (z.adler.getValue()&0xffffL)){ + this.mode = BAD; + z.msg = "header crc mismatch"; + this.marker = 5; // can't try inflateSync + break; + } + } + z.adler = new CRC32(); + + this.mode = BLOCKS; + break; + default: + return Z_STREAM_ERROR; + } + } + } + + int inflateSetDictionary(byte[] dictionary, int dictLength){ + if(z==null || (this.mode != DICT0 && this.wrap != 0)){ + return Z_STREAM_ERROR; + } + + int index=0; + int length = dictLength; + + if(this.mode==DICT0){ + long adler_need=z.adler.getValue(); + z.adler.reset(); + z.adler.update(dictionary, 0, dictLength); + if(z.adler.getValue()!=adler_need){ + return Z_DATA_ERROR; + } + } + + z.adler.reset(); + + if(length >= (1<0){ + if(z.avail_in==0){ throw new Return(r); }; r=f; + z.avail_in--; z.total_in++; + this.need = this.need | + ((z.next_in[z.next_in_index++]&0xff)<<((n-need_bytes)*8)); + need_bytes--; + } + if(n==2){ + this.need&=0xffffL; + } + else if(n==4) { + this.need&=0xffffffffL; + } + need_bytes=-1; + return r; + } + class Return extends Exception{ + int r; + Return(int r){this.r=r; } + } + + private java.io.ByteArrayOutputStream tmp_string = null; + private int readString(int r, int f) throws Return{ + if(tmp_string == null){ + tmp_string=new java.io.ByteArrayOutputStream(); + } + int b=0; + do { + if(z.avail_in==0){ throw new Return(r); }; r=f; + z.avail_in--; z.total_in++; + b = z.next_in[z.next_in_index]; + if(b!=0) tmp_string.write(z.next_in, z.next_in_index, 1); + z.adler.update(z.next_in, z.next_in_index, 1); + z.next_in_index++; + }while(b!=0); + return r; + } + + private int readBytes(int r, int f) throws Return{ + if(tmp_string == null){ + tmp_string=new java.io.ByteArrayOutputStream(); + } + int b=0; + while(this.need>0){ + if(z.avail_in==0){ throw new Return(r); }; r=f; + z.avail_in--; z.total_in++; + b = z.next_in[z.next_in_index]; + tmp_string.write(z.next_in, z.next_in_index, 1); + z.adler.update(z.next_in, z.next_in_index, 1); + z.next_in_index++; + this.need--; + } + return r; + } + + private void checksum(int n, long v){ + for(int i=0; i>=8; + } + z.adler.update(crcbuf, 0, n); + } + + public GZIPHeader getGZIPHeader(){ + return gheader; + } + + boolean inParsingHeader(){ + switch(mode){ + case HEAD: + case DICT4: + case DICT3: + case DICT2: + case DICT1: + case FLAGS: + case TIME: + case OS: + case EXLEN: + case EXTRA: + case NAME: + case COMMENT: + case HCRC: + return true; + default: + return false; + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/jcraft/jzlib/Inflater.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/jcraft/jzlib/Inflater.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,168 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final public class Inflater extends ZStream{ + + static final private int MAX_WBITS=15; // 32K LZ77 window + static final private int DEF_WBITS=MAX_WBITS; + + static final private int Z_NO_FLUSH=0; + static final private int Z_PARTIAL_FLUSH=1; + static final private int Z_SYNC_FLUSH=2; + static final private int Z_FULL_FLUSH=3; + static final private int Z_FINISH=4; + + static final private int MAX_MEM_LEVEL=9; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + public Inflater() { + super(); + init(); + } + + public Inflater(JZlib.WrapperType wrapperType) throws GZIPException { + this(DEF_WBITS, wrapperType); + } + + public Inflater(int w, JZlib.WrapperType wrapperType) throws GZIPException { + super(); + int ret = init(w, wrapperType); + if(ret!=Z_OK) + throw new GZIPException(ret+": "+msg); + } + + public Inflater(int w) throws GZIPException { + this(w, false); + } + + public Inflater(boolean nowrap) throws GZIPException { + this(DEF_WBITS, nowrap); + } + + public Inflater(int w, boolean nowrap) throws GZIPException { + super(); + int ret = init(w, nowrap); + if(ret!=Z_OK) + throw new GZIPException(ret+": "+msg); + } + + private boolean finished = false; + + public int init(){ + return init(DEF_WBITS); + } + + public int init(JZlib.WrapperType wrapperType){ + return init(DEF_WBITS, wrapperType); + } + + public int init(int w, JZlib.WrapperType wrapperType) { + boolean nowrap = false; + if(wrapperType == JZlib.W_NONE){ + nowrap = true; + } + else if(wrapperType == JZlib.W_GZIP) { + w += 16; + } + else if(wrapperType == JZlib.W_ANY) { + w |= Inflate.INFLATE_ANY; + } + else if(wrapperType == JZlib.W_ZLIB) { + } + return init(w, nowrap); + } + + public int init(boolean nowrap){ + return init(DEF_WBITS, nowrap); + } + + public int init(int w){ + return init(w, false); + } + + public int init(int w, boolean nowrap){ + finished = false; + istate=new Inflate(this); + return istate.inflateInit(nowrap?-w:w); + } + + public int inflate(int f){ + if(istate==null) return Z_STREAM_ERROR; + int ret = istate.inflate(f); + if(ret == Z_STREAM_END) + finished = true; + return ret; + } + + public int end(){ + finished = true; + if(istate==null) return Z_STREAM_ERROR; + int ret=istate.inflateEnd(); +// istate = null; + return ret; + } + + public int sync(){ + if(istate == null) + return Z_STREAM_ERROR; + return istate.inflateSync(); + } + + public int syncPoint(){ + if(istate == null) + return Z_STREAM_ERROR; + return istate.inflateSyncPoint(); + } + + public int setDictionary(byte[] dictionary, int dictLength){ + if(istate == null) + return Z_STREAM_ERROR; + return istate.inflateSetDictionary(dictionary, dictLength); + } + + public boolean finished(){ + return istate.mode==12 /*DONE*/; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/jcraft/jzlib/InflaterInputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/jcraft/jzlib/InflaterInputStream.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,247 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jcraft.jzlib; +import java.io.*; + +public class InflaterInputStream extends FilterInputStream { + protected final Inflater inflater; + protected byte[] buf; + + private boolean closed = false; + + private boolean eof = false; + + private boolean close_in = true; + + protected static final int DEFAULT_BUFSIZE = 512; + + public InflaterInputStream(InputStream in) throws IOException { + this(in, false); + } + + public InflaterInputStream(InputStream in, boolean nowrap) throws IOException { + this(in, new Inflater(nowrap)); + myinflater = true; + } + + public InflaterInputStream(InputStream in, Inflater inflater) throws IOException { + this(in, inflater, DEFAULT_BUFSIZE); + } + + public InflaterInputStream(InputStream in, + Inflater inflater, int size) throws IOException { + this(in, inflater, size, true); + } + + public InflaterInputStream(InputStream in, + Inflater inflater, + int size, boolean close_in) throws IOException { + super(in); + if (in == null || inflater == null) { + throw new NullPointerException(); + } + else if (size <= 0) { + throw new IllegalArgumentException("buffer size must be greater than 0"); + } + this.inflater = inflater; + buf = new byte[size]; + this.close_in = close_in; + } + + protected boolean myinflater = false; + + private byte[] byte1 = new byte[1]; + + public int read() throws IOException { + if (closed) { throw new IOException("Stream closed"); } + return read(byte1, 0, 1) == -1 ? -1 : byte1[0] & 0xff; + } + + public int read(byte[] b, int off, int len) throws IOException { + if (closed) { throw new IOException("Stream closed"); } + if (b == null) { + throw new NullPointerException(); + } + else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } + else if (len == 0) { + return 0; + } + else if (eof) { + return -1; + } + + int n = 0; + inflater.setOutput(b, off, len); + while(!eof) { + if(inflater.avail_in==0) + fill(); + int err = inflater.inflate(JZlib.Z_NO_FLUSH); + n += inflater.next_out_index - off; + off = inflater.next_out_index; + switch(err) { + case JZlib.Z_DATA_ERROR: + throw new IOException(inflater.msg); + case JZlib.Z_STREAM_END: + case JZlib.Z_NEED_DICT: + eof = true; + if(err == JZlib.Z_NEED_DICT) + return -1; + break; + default: + } + if(inflater.avail_out==0) + break; + } + return n; + } + + public int available() throws IOException { + if (closed) { throw new IOException("Stream closed"); } + if (eof) { + return 0; + } + else { + return 1; + } + } + + private byte[] b = new byte[512]; + + public long skip(long n) throws IOException { + if (n < 0) { + throw new IllegalArgumentException("negative skip length"); + } + + if (closed) { throw new IOException("Stream closed"); } + + int max = (int)Math.min(n, Integer.MAX_VALUE); + int total = 0; + while (total < max) { + int len = max - total; + if (len > b.length) { + len = b.length; + } + len = read(b, 0, len); + if (len == -1) { + eof = true; + break; + } + total += len; + } + return total; + } + + public void close() throws IOException { + if (!closed) { + if (myinflater) + inflater.end(); + if(close_in) + in.close(); + closed = true; + } + } + + protected void fill() throws IOException { + if (closed) { throw new IOException("Stream closed"); } + int len = in.read(buf, 0, buf.length); + if (len == -1) { + if(inflater.istate.wrap == 0 && + !inflater.finished()){ + buf[0]=0; + len=1; + } + else if(inflater.istate.was != -1){ // in reading trailer + throw new IOException("footer is not found"); + } + else{ + throw new EOFException("Unexpected end of ZLIB input stream"); + } + } + inflater.setInput(buf, 0, len, true); + } + + public boolean markSupported() { + return false; + } + + public synchronized void mark(int readlimit) { + } + + public synchronized void reset() throws IOException { + throw new IOException("mark/reset not supported"); + } + + public long getTotalIn() { + return inflater.getTotalIn(); + } + + public long getTotalOut() { + return inflater.getTotalOut(); + } + + public byte[] getAvailIn() { + if(inflater.avail_in<=0) + return null; + byte[] tmp = new byte[inflater.avail_in]; + System.arraycopy(inflater.next_in, inflater.next_in_index, + tmp, 0, inflater.avail_in); + return tmp; + } + + public void readHeader() throws IOException { + + byte[] empty = "".getBytes(); + inflater.setInput(empty, 0, 0, false); + inflater.setOutput(empty, 0, 0); + + int err = inflater.inflate(JZlib.Z_NO_FLUSH); + if(!inflater.istate.inParsingHeader()){ + return; + } + + byte[] b1 = new byte[1]; + do{ + int i = in.read(b1); + if(i<=0) + throw new IOException("no input"); + inflater.setInput(b1); + err = inflater.inflate(JZlib.Z_NO_FLUSH); + if(err!=0/*Z_OK*/) + throw new IOException(inflater.msg); + } + while(inflater.istate.inParsingHeader()); + } + + public Inflater getInflater(){ + return inflater; + } +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/jcraft/jzlib/JZlib.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/jcraft/jzlib/JZlib.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,92 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final public class JZlib{ + private static final String version="1.1.0"; + public static String version(){return version;} + + static final public int MAX_WBITS=15; // 32K LZ77 window + static final public int DEF_WBITS=MAX_WBITS; + + public enum WrapperType { + NONE, ZLIB, GZIP, ANY + } + + public static final WrapperType W_NONE = WrapperType.NONE; + public static final WrapperType W_ZLIB = WrapperType.ZLIB; + public static final WrapperType W_GZIP = WrapperType.GZIP; + public static final WrapperType W_ANY = WrapperType.ANY; + + // compression levels + static final public int Z_NO_COMPRESSION=0; + static final public int Z_BEST_SPEED=1; + static final public int Z_BEST_COMPRESSION=9; + static final public int Z_DEFAULT_COMPRESSION=(-1); + + // compression strategy + static final public int Z_FILTERED=1; + static final public int Z_HUFFMAN_ONLY=2; + static final public int Z_DEFAULT_STRATEGY=0; + + static final public int Z_NO_FLUSH=0; + static final public int Z_PARTIAL_FLUSH=1; + static final public int Z_SYNC_FLUSH=2; + static final public int Z_FULL_FLUSH=3; + static final public int Z_FINISH=4; + + static final public int Z_OK=0; + static final public int Z_STREAM_END=1; + static final public int Z_NEED_DICT=2; + static final public int Z_ERRNO=-1; + static final public int Z_STREAM_ERROR=-2; + static final public int Z_DATA_ERROR=-3; + static final public int Z_MEM_ERROR=-4; + static final public int Z_BUF_ERROR=-5; + static final public int Z_VERSION_ERROR=-6; + + // The three kinds of block type + static final public byte Z_BINARY = 0; + static final public byte Z_ASCII = 1; + static final public byte Z_UNKNOWN = 2; + + public static long adler32_combine(long adler1, long adler2, long len2){ + return Adler32.combine(adler1, adler2, len2); + } + + public static long crc32_combine(long crc1, long crc2, long len2){ + return CRC32.combine(crc1, crc2, len2); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/jcraft/jzlib/StaticTree.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/jcraft/jzlib/StaticTree.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,148 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final class StaticTree{ + static final private int MAX_BITS=15; + + static final private int BL_CODES=19; + static final private int D_CODES=30; + static final private int LITERALS=256; + static final private int LENGTH_CODES=29; + static final private int L_CODES=(LITERALS+1+LENGTH_CODES); + + // Bit length codes must not exceed MAX_BL_BITS bits + static final int MAX_BL_BITS=7; + + static final short[] static_ltree = { + 12, 8, 140, 8, 76, 8, 204, 8, 44, 8, + 172, 8, 108, 8, 236, 8, 28, 8, 156, 8, + 92, 8, 220, 8, 60, 8, 188, 8, 124, 8, + 252, 8, 2, 8, 130, 8, 66, 8, 194, 8, + 34, 8, 162, 8, 98, 8, 226, 8, 18, 8, + 146, 8, 82, 8, 210, 8, 50, 8, 178, 8, + 114, 8, 242, 8, 10, 8, 138, 8, 74, 8, + 202, 8, 42, 8, 170, 8, 106, 8, 234, 8, + 26, 8, 154, 8, 90, 8, 218, 8, 58, 8, + 186, 8, 122, 8, 250, 8, 6, 8, 134, 8, + 70, 8, 198, 8, 38, 8, 166, 8, 102, 8, + 230, 8, 22, 8, 150, 8, 86, 8, 214, 8, + 54, 8, 182, 8, 118, 8, 246, 8, 14, 8, + 142, 8, 78, 8, 206, 8, 46, 8, 174, 8, + 110, 8, 238, 8, 30, 8, 158, 8, 94, 8, + 222, 8, 62, 8, 190, 8, 126, 8, 254, 8, + 1, 8, 129, 8, 65, 8, 193, 8, 33, 8, + 161, 8, 97, 8, 225, 8, 17, 8, 145, 8, + 81, 8, 209, 8, 49, 8, 177, 8, 113, 8, + 241, 8, 9, 8, 137, 8, 73, 8, 201, 8, + 41, 8, 169, 8, 105, 8, 233, 8, 25, 8, + 153, 8, 89, 8, 217, 8, 57, 8, 185, 8, + 121, 8, 249, 8, 5, 8, 133, 8, 69, 8, + 197, 8, 37, 8, 165, 8, 101, 8, 229, 8, + 21, 8, 149, 8, 85, 8, 213, 8, 53, 8, + 181, 8, 117, 8, 245, 8, 13, 8, 141, 8, + 77, 8, 205, 8, 45, 8, 173, 8, 109, 8, + 237, 8, 29, 8, 157, 8, 93, 8, 221, 8, + 61, 8, 189, 8, 125, 8, 253, 8, 19, 9, + 275, 9, 147, 9, 403, 9, 83, 9, 339, 9, + 211, 9, 467, 9, 51, 9, 307, 9, 179, 9, + 435, 9, 115, 9, 371, 9, 243, 9, 499, 9, + 11, 9, 267, 9, 139, 9, 395, 9, 75, 9, + 331, 9, 203, 9, 459, 9, 43, 9, 299, 9, + 171, 9, 427, 9, 107, 9, 363, 9, 235, 9, + 491, 9, 27, 9, 283, 9, 155, 9, 411, 9, + 91, 9, 347, 9, 219, 9, 475, 9, 59, 9, + 315, 9, 187, 9, 443, 9, 123, 9, 379, 9, + 251, 9, 507, 9, 7, 9, 263, 9, 135, 9, + 391, 9, 71, 9, 327, 9, 199, 9, 455, 9, + 39, 9, 295, 9, 167, 9, 423, 9, 103, 9, + 359, 9, 231, 9, 487, 9, 23, 9, 279, 9, + 151, 9, 407, 9, 87, 9, 343, 9, 215, 9, + 471, 9, 55, 9, 311, 9, 183, 9, 439, 9, + 119, 9, 375, 9, 247, 9, 503, 9, 15, 9, + 271, 9, 143, 9, 399, 9, 79, 9, 335, 9, + 207, 9, 463, 9, 47, 9, 303, 9, 175, 9, + 431, 9, 111, 9, 367, 9, 239, 9, 495, 9, + 31, 9, 287, 9, 159, 9, 415, 9, 95, 9, + 351, 9, 223, 9, 479, 9, 63, 9, 319, 9, + 191, 9, 447, 9, 127, 9, 383, 9, 255, 9, + 511, 9, 0, 7, 64, 7, 32, 7, 96, 7, + 16, 7, 80, 7, 48, 7, 112, 7, 8, 7, + 72, 7, 40, 7, 104, 7, 24, 7, 88, 7, + 56, 7, 120, 7, 4, 7, 68, 7, 36, 7, + 100, 7, 20, 7, 84, 7, 52, 7, 116, 7, + 3, 8, 131, 8, 67, 8, 195, 8, 35, 8, + 163, 8, 99, 8, 227, 8 + }; + + static final short[] static_dtree = { + 0, 5, 16, 5, 8, 5, 24, 5, 4, 5, + 20, 5, 12, 5, 28, 5, 2, 5, 18, 5, + 10, 5, 26, 5, 6, 5, 22, 5, 14, 5, + 30, 5, 1, 5, 17, 5, 9, 5, 25, 5, + 5, 5, 21, 5, 13, 5, 29, 5, 3, 5, + 19, 5, 11, 5, 27, 5, 7, 5, 23, 5 + }; + + static StaticTree static_l_desc = + new StaticTree(static_ltree, Tree.extra_lbits, + LITERALS+1, L_CODES, MAX_BITS); + + static StaticTree static_d_desc = + new StaticTree(static_dtree, Tree.extra_dbits, + 0, D_CODES, MAX_BITS); + + static StaticTree static_bl_desc = + new StaticTree(null, Tree.extra_blbits, + 0, BL_CODES, MAX_BL_BITS); + + short[] static_tree; // static tree or null + int[] extra_bits; // extra bits for each code or null + int extra_base; // base index for extra_bits + int elems; // max number of elements in the tree + int max_length; // max bit length for the codes + + private StaticTree(short[] static_tree, + int[] extra_bits, + int extra_base, + int elems, + int max_length){ + this.static_tree=static_tree; + this.extra_bits=extra_bits; + this.extra_base=extra_base; + this.elems=elems; + this.max_length=max_length; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/jcraft/jzlib/Tree.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/jcraft/jzlib/Tree.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,367 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +final class Tree{ + static final private int MAX_BITS=15; + static final private int BL_CODES=19; + static final private int D_CODES=30; + static final private int LITERALS=256; + static final private int LENGTH_CODES=29; + static final private int L_CODES=(LITERALS+1+LENGTH_CODES); + static final private int HEAP_SIZE=(2*L_CODES+1); + + // Bit length codes must not exceed MAX_BL_BITS bits + static final int MAX_BL_BITS=7; + + // end of block literal code + static final int END_BLOCK=256; + + // repeat previous bit length 3-6 times (2 bits of repeat count) + static final int REP_3_6=16; + + // repeat a zero length 3-10 times (3 bits of repeat count) + static final int REPZ_3_10=17; + + // repeat a zero length 11-138 times (7 bits of repeat count) + static final int REPZ_11_138=18; + + // extra bits for each length code + static final int[] extra_lbits={ + 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0 + }; + + // extra bits for each distance code + static final int[] extra_dbits={ + 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 + }; + + // extra bits for each bit length code + static final int[] extra_blbits={ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7 + }; + + static final byte[] bl_order={ + 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15}; + + + // The lengths of the bit length codes are sent in order of decreasing + // probability, to avoid transmitting the lengths for unused bit + // length codes. + + static final int Buf_size=8*2; + + // see definition of array dist_code below + static final int DIST_CODE_LEN=512; + + static final byte[] _dist_code = { + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, + 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17, + 18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 + }; + + static final byte[] _length_code={ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, + 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, + 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28 + }; + + static final int[] base_length = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, + 64, 80, 96, 112, 128, 160, 192, 224, 0 + }; + + static final int[] base_dist = { + 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, + 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, + 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576 + }; + + // Mapping from a distance to a distance code. dist is the distance - 1 and + // must not have side effects. _dist_code[256] and _dist_code[257] are never + // used. + static int d_code(int dist){ + return ((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>>7)]); + } + + short[] dyn_tree; // the dynamic tree + int max_code; // largest code with non zero frequency + StaticTree stat_desc; // the corresponding static tree + + // Compute the optimal bit lengths for a tree and update the total bit length + // for the current block. + // IN assertion: the fields freq and dad are set, heap[heap_max] and + // above are the tree nodes sorted by increasing frequency. + // OUT assertions: the field len is set to the optimal bit length, the + // array bl_count contains the frequencies for each bit length. + // The length opt_len is updated; static_len is also updated if stree is + // not null. + void gen_bitlen(Deflate s){ + short[] tree = dyn_tree; + short[] stree = stat_desc.static_tree; + int[] extra = stat_desc.extra_bits; + int base = stat_desc.extra_base; + int max_length = stat_desc.max_length; + int h; // heap index + int n, m; // iterate over the tree elements + int bits; // bit length + int xbits; // extra bits + short f; // frequency + int overflow = 0; // number of elements with bit length too large + + for (bits = 0; bits <= MAX_BITS; bits++) s.bl_count[bits] = 0; + + // In a first pass, compute the optimal bit lengths (which may + // overflow in the case of the bit length tree). + tree[s.heap[s.heap_max]*2+1] = 0; // root of the heap + + for(h=s.heap_max+1; h max_length){ bits = max_length; overflow++; } + tree[n*2+1] = (short)bits; + // We overwrite tree[n*2+1] which is no longer needed + + if (n > max_code) continue; // not a leaf node + + s.bl_count[bits]++; + xbits = 0; + if (n >= base) xbits = extra[n-base]; + f = tree[n*2]; + s.opt_len += f * (bits + xbits); + if (stree!=null) s.static_len += f * (stree[n*2+1] + xbits); + } + if (overflow == 0) return; + + // This happens for example on obj2 and pic of the Calgary corpus + // Find the first bit length which could increase: + do { + bits = max_length-1; + while(s.bl_count[bits]==0) bits--; + s.bl_count[bits]--; // move one leaf down the tree + s.bl_count[bits+1]+=2; // move one overflow item as its brother + s.bl_count[max_length]--; + // The brother of the overflow item also moves one step up, + // but this does not affect bl_count[max_length] + overflow -= 2; + } + while (overflow > 0); + + for (bits = max_length; bits != 0; bits--) { + n = s.bl_count[bits]; + while (n != 0) { + m = s.heap[--h]; + if (m > max_code) continue; + if (tree[m*2+1] != bits) { + s.opt_len += ((long)bits - (long)tree[m*2+1])*(long)tree[m*2]; + tree[m*2+1] = (short)bits; + } + n--; + } + } + } + + // Construct one Huffman tree and assigns the code bit strings and lengths. + // Update the total bit length for the current block. + // IN assertion: the field freq is set for all tree elements. + // OUT assertions: the fields len and code are set to the optimal bit length + // and corresponding code. The length opt_len is updated; static_len is + // also updated if stree is not null. The field max_code is set. + void build_tree(Deflate s){ + short[] tree=dyn_tree; + short[] stree=stat_desc.static_tree; + int elems=stat_desc.elems; + int n, m; // iterate over heap elements + int max_code=-1; // largest code with non zero frequency + int node; // new node being created + + // Construct the initial heap, with least frequent element in + // heap[1]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + // heap[0] is not used. + s.heap_len = 0; + s.heap_max = HEAP_SIZE; + + for(n=0; n=1; n--) + s.pqdownheap(tree, n); + + // Construct the Huffman tree by repeatedly combining the least two + // frequent nodes. + + node=elems; // next internal node of the tree + do{ + // n = node of least frequency + n=s.heap[1]; + s.heap[1]=s.heap[s.heap_len--]; + s.pqdownheap(tree, 1); + m=s.heap[1]; // m = node of next least frequency + + s.heap[--s.heap_max] = n; // keep the nodes sorted by frequency + s.heap[--s.heap_max] = m; + + // Create a new node father of n and m + tree[node*2] = (short)(tree[n*2] + tree[m*2]); + s.depth[node] = (byte)(Math.max(s.depth[n],s.depth[m])+1); + tree[n*2+1] = tree[m*2+1] = (short)node; + + // and insert the new node in the heap + s.heap[1] = node++; + s.pqdownheap(tree, 1); + } + while(s.heap_len>=2); + + s.heap[--s.heap_max] = s.heap[1]; + + // At this point, the fields freq and dad are set. We can now + // generate the bit lengths. + + gen_bitlen(s); + + // The field len is now set, we can generate the bit codes + gen_codes(tree, max_code, s.bl_count, s.next_code); + } + + // Generate the codes for a given tree and bit counts (which need not be + // optimal). + // IN assertion: the array bl_count contains the bit length statistics for + // the given tree and the field len is set for all tree elements. + // OUT assertion: the field code is set for all tree elements of non + // zero code length. + private final static void gen_codes( + short[] tree, // the tree to decorate + int max_code, // largest code with non zero frequency + short[] bl_count, // number of codes at each bit length + short[] next_code){ + short code = 0; // running code value + int bits; // bit index + int n; // code index + + // The distribution counts are first used to generate the code values + // without bit reversal. + next_code[0]=0; + for (bits = 1; bits <= MAX_BITS; bits++) { + next_code[bits] = code = (short)((code + bl_count[bits-1]) << 1); + } + + // Check that the bit counts in bl_count are consistent. The last code + // must be all ones. + //Assert (code + bl_count[MAX_BITS]-1 == (1<>>=1; + res<<=1; + } + while(--len>0); + return res>>>1; + } +} + diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/jcraft/jzlib/ZInputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/jcraft/jzlib/ZInputStream.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,126 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jcraft.jzlib; +import java.io.*; + +/** + * ZInputStream + * + * @deprecated use DeflaterOutputStream or InflaterInputStream + */ +@Deprecated +public class ZInputStream extends FilterInputStream { + + protected int flush=JZlib.Z_NO_FLUSH; + protected boolean compress; + protected InputStream in=null; + + protected Deflater deflater; + protected InflaterInputStream iis; + + public ZInputStream(InputStream in) throws IOException { + this(in, false); + } + public ZInputStream(InputStream in, boolean nowrap) throws IOException { + super(in); + iis = new InflaterInputStream(in, nowrap); + compress=false; + } + + public ZInputStream(InputStream in, int level) throws IOException { + super(in); + this.in=in; + deflater = new Deflater(); + deflater.init(level); + compress=true; + } + + private byte[] buf1 = new byte[1]; + public int read() throws IOException { + if(read(buf1, 0, 1)==-1) return -1; + return(buf1[0]&0xFF); + } + + private byte[] buf = new byte[512]; + + public int read(byte[] b, int off, int len) throws IOException { + if(compress){ + deflater.setOutput(b, off, len); + while(true){ + int datalen = in.read(buf, 0, buf.length); + if(datalen == -1) return -1; + deflater.setInput(buf, 0, datalen, true); + int err = deflater.deflate(flush); + if(deflater.next_out_index>0) + return deflater.next_out_index; + if(err == JZlib.Z_STREAM_END) + return 0; + if(err == JZlib.Z_STREAM_ERROR || + err == JZlib.Z_DATA_ERROR){ + throw new ZStreamException("deflating: "+deflater.msg); + } + } + } + else{ + return iis.read(b, off, len); + } + } + + public long skip(long n) throws IOException { + int len=512; + if(n0){ + inflater.setOutput(buf, 0, buf.length); + err = inflater.inflate(flush); + if(inflater.next_out_index>0) + out.write(buf, 0, inflater.next_out_index); + if(err != JZlib.Z_OK) + break; + } + if(err != JZlib.Z_OK) + throw new ZStreamException("inflating: "+inflater.msg); + return; + } + } + + public int getFlushMode() { + return flush; + } + + public void setFlushMode(int flush) { + this.flush=flush; + } + + public void finish() throws IOException { + int err; + if(compress){ + int tmp = flush; + int flush = JZlib.Z_FINISH; + try{ + write("".getBytes(), 0, 0); + } + finally { flush = tmp; } + } + else{ + dos.finish(); + } + flush(); + } + public synchronized void end() { + if(end) return; + if(compress){ + try { dos.finish(); } catch(Exception e){} + } + else{ + inflater.end(); + } + end=true; + } + public void close() throws IOException { + try{ + try{finish();} + catch (IOException ignored) {} + } + finally{ + end(); + out.close(); + out=null; + } + } + + public long getTotalIn() { + if(compress) return dos.getTotalIn(); + else return inflater.total_in; + } + + public long getTotalOut() { + if(compress) return dos.getTotalOut(); + else return inflater.total_out; + } + + public void flush() throws IOException { + out.flush(); + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/jcraft/jzlib/ZStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/jcraft/jzlib/ZStream.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,377 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2000-2011 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +/** + * ZStream + * + * @deprecated Not for public use in the future. + */ +@Deprecated +public class ZStream{ + + static final private int MAX_WBITS=15; // 32K LZ77 window + static final private int DEF_WBITS=MAX_WBITS; + + static final private int Z_NO_FLUSH=0; + static final private int Z_PARTIAL_FLUSH=1; + static final private int Z_SYNC_FLUSH=2; + static final private int Z_FULL_FLUSH=3; + static final private int Z_FINISH=4; + + static final private int MAX_MEM_LEVEL=9; + + static final private int Z_OK=0; + static final private int Z_STREAM_END=1; + static final private int Z_NEED_DICT=2; + static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR=-2; + static final private int Z_DATA_ERROR=-3; + static final private int Z_MEM_ERROR=-4; + static final private int Z_BUF_ERROR=-5; + static final private int Z_VERSION_ERROR=-6; + + public byte[] next_in; // next input byte + public int next_in_index; + public int avail_in; // number of bytes available at next_in + public long total_in; // total nb of input bytes read so far + + public byte[] next_out; // next output byte should be put there + public int next_out_index; + public int avail_out; // remaining free space at next_out + public long total_out; // total nb of bytes output so far + + public String msg; + + Deflate dstate; + Inflate istate; + + int data_type; // best guess about the data type: ascii or binary + + Checksum adler; + + public ZStream(){ + this(new Adler32()); + } + + public ZStream(Checksum adler){ + this.adler=adler; + } + + public int inflateInit(){ + return inflateInit(DEF_WBITS); + } + public int inflateInit(boolean nowrap){ + return inflateInit(DEF_WBITS, nowrap); + } + public int inflateInit(int w){ + return inflateInit(w, false); + } + public int inflateInit(JZlib.WrapperType wrapperType) { + return inflateInit(DEF_WBITS, wrapperType); + } + public int inflateInit(int w, JZlib.WrapperType wrapperType) { + boolean nowrap = false; + if(wrapperType == JZlib.W_NONE){ + nowrap = true; + } + else if(wrapperType == JZlib.W_GZIP) { + w += 16; + } + else if(wrapperType == JZlib.W_ANY) { + w |= Inflate.INFLATE_ANY; + } + else if(wrapperType == JZlib.W_ZLIB) { + } + return inflateInit(w, nowrap); + } + public int inflateInit(int w, boolean nowrap){ + istate=new Inflate(this); + return istate.inflateInit(nowrap?-w:w); + } + + public int inflate(int f){ + if(istate==null) return Z_STREAM_ERROR; + return istate.inflate(f); + } + public int inflateEnd(){ + if(istate==null) return Z_STREAM_ERROR; + int ret=istate.inflateEnd(); +// istate = null; + return ret; + } + public int inflateSync(){ + if(istate == null) + return Z_STREAM_ERROR; + return istate.inflateSync(); + } + public int inflateSyncPoint(){ + if(istate == null) + return Z_STREAM_ERROR; + return istate.inflateSyncPoint(); + } + public int inflateSetDictionary(byte[] dictionary, int dictLength){ + if(istate == null) + return Z_STREAM_ERROR; + return istate.inflateSetDictionary(dictionary, dictLength); + } + public boolean inflateFinished(){ + return istate.mode==12 /*DONE*/; + } + + public int deflateInit(int level){ + return deflateInit(level, MAX_WBITS); + } + public int deflateInit(int level, boolean nowrap){ + return deflateInit(level, MAX_WBITS, nowrap); + } + public int deflateInit(int level, int bits){ + return deflateInit(level, bits, false); + } + public int deflateInit(int level, int bits, int memlevel, JZlib.WrapperType wrapperType){ + if(bits < 9 || bits > 15){ + return Z_STREAM_ERROR; + } + if(wrapperType == JZlib.W_NONE) { + bits *= -1; + } + else if(wrapperType == JZlib.W_GZIP) { + bits += 16; + } + else if(wrapperType == JZlib.W_ANY) { + return Z_STREAM_ERROR; + } + else if(wrapperType == JZlib.W_ZLIB) { + } + return this.deflateInit(level, bits, memlevel); + } + public int deflateInit(int level, int bits, int memlevel){ + dstate=new Deflate(this); + return dstate.deflateInit(level, bits, memlevel); + } + public int deflateInit(int level, int bits, boolean nowrap){ + dstate=new Deflate(this); + return dstate.deflateInit(level, nowrap?-bits:bits); + } + public int deflate(int flush){ + if(dstate==null){ + return Z_STREAM_ERROR; + } + return dstate.deflate(flush); + } + public int deflateEnd(){ + if(dstate==null) return Z_STREAM_ERROR; + int ret=dstate.deflateEnd(); + dstate=null; + return ret; + } + public int deflateParams(int level, int strategy){ + if(dstate==null) return Z_STREAM_ERROR; + return dstate.deflateParams(level, strategy); + } + public int deflateSetDictionary (byte[] dictionary, int dictLength){ + if(dstate == null) + return Z_STREAM_ERROR; + return dstate.deflateSetDictionary(dictionary, dictLength); + } + + // Flush as much pending output as possible. All deflate() output goes + // through this function so some applications may wish to modify it + // to avoid allocating a large strm->next_out buffer and copying into it. + // (See also read_buf()). + void flush_pending(){ + int len=dstate.pending; + + if(len>avail_out) len=avail_out; + if(len==0) return; + + if(dstate.pending_buf.length<=dstate.pending_out || + next_out.length<=next_out_index || + dstate.pending_buf.length<(dstate.pending_out+len) || + next_out.length<(next_out_index+len)){ + //System.out.println(dstate.pending_buf.length+", "+dstate.pending_out+ + // ", "+next_out.length+", "+next_out_index+", "+len); + //System.out.println("avail_out="+avail_out); + } + + System.arraycopy(dstate.pending_buf, dstate.pending_out, + next_out, next_out_index, len); + + next_out_index+=len; + dstate.pending_out+=len; + total_out+=len; + avail_out-=len; + dstate.pending-=len; + if(dstate.pending==0){ + dstate.pending_out=0; + } + } + + // Read a new buffer from the current input stream, update the adler32 + // and total number of bytes read. All deflate() input goes through + // this function so some applications may wish to modify it to avoid + // allocating a large strm->next_in buffer and copying from it. + // (See also flush_pending()). + int read_buf(byte[] buf, int start, int size) { + int len=avail_in; + + if(len>size) len=size; + if(len==0) return 0; + + avail_in-=len; + + if(dstate.wrap!=0) { + adler.update(next_in, next_in_index, len); + } + System.arraycopy(next_in, next_in_index, buf, start, len); + next_in_index += len; + total_in += len; + return len; + } + + public long getAdler(){ + return adler.getValue(); + } + + public void free(){ + next_in=null; + next_out=null; + msg=null; + } + + public void setOutput(byte[] buf){ + setOutput(buf, 0, buf.length); + } + + public void setOutput(byte[] buf, int off, int len){ + next_out = buf; + next_out_index = off; + avail_out = len; + } + + public void setInput(byte[] buf){ + setInput(buf, 0, buf.length, false); + } + + public void setInput(byte[] buf, boolean append){ + setInput(buf, 0, buf.length, append); + } + + public void setInput(byte[] buf, int off, int len, boolean append){ + if(len<=0 && append && next_in!=null) return; + + if(avail_in>0 && append){ + byte[] tmp = new byte[avail_in+len]; + System.arraycopy(next_in, next_in_index, tmp, 0, avail_in); + System.arraycopy(buf, off, tmp, avail_in, len); + next_in=tmp; + next_in_index=0; + avail_in+=len; + } + else{ + next_in=buf; + next_in_index=off; + avail_in=len; + } + } + + public byte[] getNextIn(){ + return next_in; + } + + public void setNextIn(byte[] next_in){ + this.next_in = next_in; + } + + public int getNextInIndex(){ + return next_in_index; + } + + public void setNextInIndex(int next_in_index){ + this.next_in_index = next_in_index; + } + + public int getAvailIn(){ + return avail_in; + } + + public void setAvailIn(int avail_in){ + this.avail_in = avail_in; + } + + public byte[] getNextOut(){ + return next_out; + } + + public void setNextOut(byte[] next_out){ + this.next_out = next_out; + } + + public int getNextOutIndex(){ + return next_out_index; + } + + public void setNextOutIndex(int next_out_index){ + this.next_out_index = next_out_index; + } + + public int getAvailOut(){ + return avail_out; + + } + + public void setAvailOut(int avail_out){ + this.avail_out = avail_out; + } + + public long getTotalOut(){ + return total_out; + } + + public long getTotalIn(){ + return total_in; + } + + public String getMessage(){ + return msg; + } + + /** + * Those methods are expected to be override by Inflater and Deflater. + * In the future, they will become abstract methods. + */ + public int end(){ return Z_OK; } + public boolean finished(){ return false; } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/jcraft/jzlib/ZStreamException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/jcraft/jzlib/ZStreamException.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,44 @@ +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +public class ZStreamException extends java.io.IOException { + public ZStreamException() { + super(); + } + public ZStreamException(String s) { + super(s); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/lamerman/FileDialog.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/lamerman/FileDialog.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,292 @@ +package com.lamerman; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.TreeMap; + +import com.five_ten_sg.connectbot.R; +import android.app.AlertDialog; +import android.app.ListActivity; +import android.content.DialogInterface; +import android.net.Uri; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.SimpleAdapter; +import android.widget.TextView; + +public class FileDialog extends ListActivity { + + private static final String ITEM_KEY = "key"; + private static final String ITEM_IMAGE = "image"; + private static final String ROOT = "/"; + + public static final String START_PATH = "START_PATH"; + public static final String RESULT_PATH = "RESULT_PATH"; + public static final String SELECTION_MODE = "SELECTION_MODE"; + public static final String TITLE = "TITLE"; + + private List path = null; + private TextView myPath; + private EditText mFileName; + private ArrayList> mList; + + private Button selectButton; + + private LinearLayout layoutSelect; + private LinearLayout layoutCreate; + private InputMethodManager inputManager; + private String parentPath; + private String currentPath = ROOT; + + private int selectionMode = SelectionMode.MODE_CREATE; + + private File selectedFile; + private HashMap lastPositions = new HashMap(); + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setResult(RESULT_CANCELED, getIntent()); + setContentView(R.layout.file_dialog_main); + this.setTitle(String.format("%s: %s", + getResources().getText(R.string.app_name), + getIntent().getStringExtra(TITLE))); + myPath = (TextView) findViewById(R.id.path); + mFileName = (EditText) findViewById(R.id.fdEditTextFile); + inputManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); + selectButton = (Button) findViewById(R.id.fdButtonSelect); + selectButton.setEnabled(false); + selectButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + if (selectedFile != null) { + getIntent().setData(Uri.fromFile(selectedFile)); + setResult(RESULT_OK, getIntent()); + finish(); + } + } + }); + final Button newButton = (Button) findViewById(R.id.fdButtonNew); + newButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + setCreateVisible(v); + mFileName.setText(""); + mFileName.requestFocus(); + } + }); + final Button cancelButton = (Button) findViewById(R.id.fdButtonCancel); + cancelButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + setResult(RESULT_CANCELED, getIntent()); + finish(); + } + }); + selectionMode = getIntent().getIntExtra(SELECTION_MODE, + SelectionMode.MODE_CREATE); + + if (selectionMode == SelectionMode.MODE_OPEN) { + newButton.setEnabled(false); + } + + layoutSelect = (LinearLayout) findViewById(R.id.fdLinearLayoutSelect); + layoutCreate = (LinearLayout) findViewById(R.id.fdLinearLayoutCreate); + layoutCreate.setVisibility(View.GONE); + final Button cancelCreateButton = (Button) findViewById(R.id.fdButtonCancelCreate); + cancelCreateButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + setSelectVisible(v); + } + }); + final Button createButton = (Button) findViewById(R.id.fdButtonCreate); + createButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + if (mFileName.getText().length() > 0) { + getIntent().putExtra(RESULT_PATH, + currentPath + "/" + mFileName.getText()); + setResult(RESULT_OK, getIntent()); + finish(); + } + } + }); + String startPath = getIntent().getStringExtra(START_PATH); + + if (startPath != null) { + getDir(startPath); + } + else { + getDir(ROOT); + } + } + + @Override + protected void onPause() { + String myPathStr = myPath.getText().toString(); + int idx = myPathStr.lastIndexOf(':'); + String currPath = myPathStr.substring(idx + 2); + getIntent().putExtra(START_PATH, currPath); + super.onPause(); + } + + private void getDir(String dirPath) { + boolean useAutoSelection = dirPath.length() < currentPath.length(); + Integer position = lastPositions.get(parentPath); + getDirImpl(dirPath); + + if (position != null && useAutoSelection) { + getListView().setSelection(position); + } + } + + private void getDirImpl(final String dirPath) { + currentPath = dirPath; + final List item = new ArrayList(); + path = new ArrayList(); + mList = new ArrayList>(); + File f = new File(currentPath); + File[] files = f.listFiles(); + + if (files == null) { + currentPath = ROOT; + f = new File(currentPath); + files = f.listFiles(); + } + + myPath.setText(getText(R.string.location) + ": " + currentPath); + + if (!currentPath.equals(ROOT)) { + item.add(ROOT); + addItem(ROOT, R.drawable.folder); + path.add(ROOT); + item.add("../"); + addItem("../", R.drawable.folder); + path.add(f.getParent()); + parentPath = f.getParent(); + } + + TreeMap dirsMap = new TreeMap(); + TreeMap dirsPathMap = new TreeMap(); + TreeMap filesMap = new TreeMap(); + TreeMap filesPathMap = new TreeMap(); + + for (File file : files) { + if (file.isDirectory()) { + String dirName = file.getName(); + dirsMap.put(dirName, dirName); + dirsPathMap.put(dirName, file.getPath()); + } + else { + filesMap.put(file.getName(), file.getName()); + filesPathMap.put(file.getName(), file.getPath()); + } + } + + item.addAll(dirsMap.tailMap("").values()); + item.addAll(filesMap.tailMap("").values()); + path.addAll(dirsPathMap.tailMap("").values()); + path.addAll(filesPathMap.tailMap("").values()); + SimpleAdapter fileList = new SimpleAdapter(this, mList, + R.layout.file_dialog_row, + new String[] { ITEM_KEY, ITEM_IMAGE }, new int[] { + R.id.fdrowtext, R.id.fdrowimage + }); + + for (String dir : dirsMap.tailMap("").values()) { + addItem(dir, R.drawable.folder); + } + + for (String file : filesMap.tailMap("").values()) { + addItem(file, R.drawable.file); + } + + fileList.notifyDataSetChanged(); + setListAdapter(fileList); + } + + private void addItem(String fileName, int imageId) { + HashMap item = new HashMap(); + item.put(ITEM_KEY, fileName); + item.put(ITEM_IMAGE, imageId); + mList.add(item); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + File file = new File(path.get(position)); + setSelectVisible(v); + + if (file.isDirectory()) { + selectButton.setEnabled(false); + + if (file.canRead()) { + lastPositions.put(currentPath, position); + getDir(path.get(position)); + } + else { + new AlertDialog.Builder(this) + .setIcon(R.drawable.icon) + .setTitle( + "[" + file.getName() + "] " + + getText(R.string.cant_read_folder)) + .setPositiveButton("OK", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + } + }).show(); + } + } + else { + selectedFile = file; + v.setSelected(true); + selectButton.setEnabled(true); + } + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if ((keyCode == KeyEvent.KEYCODE_BACK)) { + selectButton.setEnabled(false); + + if (layoutCreate.getVisibility() == View.VISIBLE) { + layoutCreate.setVisibility(View.GONE); + layoutSelect.setVisibility(View.VISIBLE); + } + else { + if (!currentPath.equals(ROOT)) { + getDir(parentPath); + } + else { + return super.onKeyDown(keyCode, event); + } + } + + return true; + } + else { + return super.onKeyDown(keyCode, event); + } + } + + private void setCreateVisible(View v) { + layoutCreate.setVisibility(View.VISIBLE); + layoutSelect.setVisibility(View.GONE); + inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0); + selectButton.setEnabled(false); + } + + private void setSelectVisible(View v) { + layoutCreate.setVisibility(View.GONE); + layoutSelect.setVisibility(View.VISIBLE); + inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0); + selectButton.setEnabled(false); + } +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/lamerman/SelectionMode.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/lamerman/SelectionMode.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,7 @@ +package com.lamerman; + +public class SelectionMode { + public static final int MODE_CREATE = 0; + + public static final int MODE_OPEN = 1; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/com/madgag/ssh/android/authagent/AndroidAuthAgent.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/madgag/ssh/android/authagent/AndroidAuthAgent.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,120 @@ +/* + * This file is auto-generated. DO NOT MODIFY. + * Original file: /home/roberto/development/madgag-ssh/ssh-android/src/main/java/com/madgag/ssh/android/authagent/AndroidAuthAgent.aidl + */ +package com.madgag.ssh.android.authagent; +public interface AndroidAuthAgent extends android.os.IInterface { + /** Local-side IPC implementation stub class. */ + public static abstract class Stub extends android.os.Binder implements com.madgag.ssh.android.authagent.AndroidAuthAgent { + private static final java.lang.String DESCRIPTOR = "com.madgag.ssh.android.authagent.AndroidAuthAgent"; + /** Construct the stub at attach it to the interface. */ + public Stub() { + this.attachInterface(this, DESCRIPTOR); + } + /** + * Cast an IBinder object into an com.madgag.ssh.android.authagent.AndroidAuthAgent interface, + * generating a proxy if needed. + */ + public static com.madgag.ssh.android.authagent.AndroidAuthAgent asInterface(android.os.IBinder obj) { + if ((obj == null)) { + return null; + } + + android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR); + + if (((iin != null) && (iin instanceof com.madgag.ssh.android.authagent.AndroidAuthAgent))) { + return ((com.madgag.ssh.android.authagent.AndroidAuthAgent)iin); + } + + return new com.madgag.ssh.android.authagent.AndroidAuthAgent.Stub.Proxy(obj); + } + public android.os.IBinder asBinder() { + return this; + } + @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { + switch (code) { + case INTERFACE_TRANSACTION: { + reply.writeString(DESCRIPTOR); + return true; + } + + case TRANSACTION_getIdentities: { + data.enforceInterface(DESCRIPTOR); + java.util.Map _result = this.getIdentities(); + reply.writeNoException(); + reply.writeMap(_result); + return true; + } + + case TRANSACTION_sign: { + data.enforceInterface(DESCRIPTOR); + byte[] _arg0; + _arg0 = data.createByteArray(); + byte[] _arg1; + _arg1 = data.createByteArray(); + byte[] _result = this.sign(_arg0, _arg1); + reply.writeNoException(); + reply.writeByteArray(_result); + return true; + } + } + + return super.onTransact(code, data, reply, flags); + } + private static class Proxy implements com.madgag.ssh.android.authagent.AndroidAuthAgent { + private android.os.IBinder mRemote; + Proxy(android.os.IBinder remote) { + mRemote = remote; + } + public android.os.IBinder asBinder() { + return mRemote; + } + public java.lang.String getInterfaceDescriptor() { + return DESCRIPTOR; + } + public java.util.Map getIdentities() throws android.os.RemoteException { + android.os.Parcel _data = android.os.Parcel.obtain(); + android.os.Parcel _reply = android.os.Parcel.obtain(); + java.util.Map _result; + + try { + _data.writeInterfaceToken(DESCRIPTOR); + mRemote.transact(Stub.TRANSACTION_getIdentities, _data, _reply, 0); + _reply.readException(); + java.lang.ClassLoader cl = (java.lang.ClassLoader)this.getClass().getClassLoader(); + _result = _reply.readHashMap(cl); + } + finally { + _reply.recycle(); + _data.recycle(); + } + + return _result; + } + public byte[] sign(byte[] publicKey, byte[] data) throws android.os.RemoteException { + android.os.Parcel _data = android.os.Parcel.obtain(); + android.os.Parcel _reply = android.os.Parcel.obtain(); + byte[] _result; + + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeByteArray(publicKey); + _data.writeByteArray(data); + mRemote.transact(Stub.TRANSACTION_sign, _data, _reply, 0); + _reply.readException(); + _result = _reply.createByteArray(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + + return _result; + } + } + static final int TRANSACTION_getIdentities = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); + static final int TRANSACTION_sign = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); + } + public java.util.Map getIdentities() throws android.os.RemoteException; + public byte[] sign(byte[] publicKey, byte[] data) throws android.os.RemoteException; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/de/mud/telnet/TelnetProtocolHandler.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/de/mud/telnet/TelnetProtocolHandler.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,789 @@ +/* + * This file is part of "JTA - Telnet/SSH for the JAVA(tm) platform". + * + * (c) Matthias L. Jugel, Marcus Meißner 1996-2005. All Rights Reserved. + * + * Please visit http://javatelnet.org/ for updates and contact. + * + * --LICENSE NOTICE-- + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * --LICENSE NOTICE-- + * + */ + +package de.mud.telnet; + +import java.io.IOException; +/** + * This is a telnet protocol handler. The handler needs implementations + * for several methods to handle the telnet options and to be able to + * read and write the buffer. + *

+ * Maintainer: Marcus Meissner + * + * @version $Id: TelnetProtocolHandler.java 503 2005-10-24 07:34:13Z marcus $ + * @author Matthias L. Jugel, Marcus Meissner + */ +public abstract class TelnetProtocolHandler { + /** contains the current revision id */ + public final static String ID = "$Id: TelnetProtocolHandler.java 503 2005-10-24 07:34:13Z marcus $"; + + /** debug level */ + private final static int debug = 0; + + /** temporary buffer for data-telnetstuff-data transformation */ + private byte[] tempbuf = new byte[0]; + + /** the data sent on pressing \n */ + private byte[] crlf = new byte[2]; + /** the data sent on pressing \r */ + private byte[] cr = new byte[2]; + + /** + * Create a new telnet protocol handler. + */ + public TelnetProtocolHandler() { + reset(); + crlf[0] = 13; crlf[1] = 10; + cr[0] = 13; cr[1] = 0; + } + + /** + * Get the current terminal type for TTYPE telnet option. + * @return the string id of the terminal + */ + protected abstract String getTerminalType(); + + /** + * Get the current window size of the terminal for the + * NAWS telnet option. + * @return the size of the terminal as Dimension + */ + protected abstract int[] getWindowSize(); + + /** + * Set the local echo option of telnet. + * @param echo true for local echo, false for no local echo + */ + protected abstract void setLocalEcho(boolean echo); + + /** + * Generate an EOR (end of record) request. For use by prompt displaying. + */ + protected abstract void notifyEndOfRecord(); + + /** + * Send data to the remote host. + * @param b array of bytes to send + */ + protected abstract void write(byte[] b) throws IOException; + + /** + * Read the charset name from terminal. + */ + protected abstract String getCharsetName(); + + /** + * Send one byte to the remote host. + * @param b the byte to be sent + * @see #write(byte[] b) + */ + private static byte[] one = new byte[1]; + private void write(byte b) throws IOException { + one[0] = b; + write(one); + } + + /** + * Reset the protocol handler. This may be necessary after the + * connection was closed or some other problem occured. + */ + public void reset() { + neg_state = 0; + receivedDX = new byte[256]; + sentDX = new byte[256]; + receivedWX = new byte[256]; + sentWX = new byte[256]; + } + + // =================================================================== + // the actual negotiation handling for the telnet protocol follows: + // =================================================================== + + /** state variable for telnet negotiation reader */ + private byte neg_state = 0; + + /** constants for the negotiation state */ + private final static byte STATE_DATA = 0; + private final static byte STATE_IAC = 1; + private final static byte STATE_IACSB = 2; + private final static byte STATE_IACWILL = 3; + private final static byte STATE_IACDO = 4; + private final static byte STATE_IACWONT = 5; + private final static byte STATE_IACDONT = 6; + private final static byte STATE_IACSBIAC = 7; + private final static byte STATE_IACSBDATA = 8; + private final static byte STATE_IACSBDATAIAC = 9; + + /** What IAC SB we are handling right now */ + private byte current_sb; + + /** current SB negotiation buffer */ + private byte[] sbbuf; + + /** IAC - init sequence for telnet negotiation. */ + private final static byte IAC = (byte)255; + /** [IAC] End Of Record */ + private final static byte EOR = (byte)239; + /** [IAC] WILL */ + private final static byte WILL = (byte)251; + /** [IAC] WONT */ + private final static byte WONT = (byte)252; + /** [IAC] DO */ + private final static byte DO = (byte)253; + /** [IAC] DONT */ + private final static byte DONT = (byte)254; + /** [IAC] Sub Begin */ + private final static byte SB = (byte)250; + /** [IAC] Sub End */ + private final static byte SE = (byte)240; + /** Telnet option: binary mode */ + private final static byte TELOPT_BINARY = (byte)0; /* binary mode */ + /** Telnet option: echo text */ + private final static byte TELOPT_ECHO = (byte)1; /* echo on/off */ + /** Telnet option: sga */ + private final static byte TELOPT_SGA = (byte)3; /* supress go ahead */ + /** Telnet option: End Of Record */ + private final static byte TELOPT_EOR = (byte)25; /* end of record */ + /** Telnet option: Negotiate About Window Size */ + private final static byte TELOPT_NAWS = (byte)31; /* NA-WindowSize*/ + /** Telnet option: Terminal Type */ + private final static byte TELOPT_TTYPE = (byte)24; /* terminal type */ + /** Telnet option: CHARSET */ + private final static byte TELOPT_CHARSET = (byte)42; /* charset */ + + private final static byte[] IACWILL = { IAC, WILL }; + private final static byte[] IACWONT = { IAC, WONT }; + private final static byte[] IACDO = { IAC, DO }; + private final static byte[] IACDONT = { IAC, DONT }; + private final static byte[] IACSB = { IAC, SB }; + private final static byte[] IACSE = { IAC, SE }; + + private final static byte CHARSET_ACCEPTED = (byte)2; + private final static byte CHARSET_REJECTED = (byte)3; + + /** Telnet option qualifier 'IS' */ + private final static byte TELQUAL_IS = (byte)0; + /** Telnet option qualifier 'SEND' */ + private final static byte TELQUAL_SEND = (byte)1; + + /** What IAC DO(NT) request do we have received already ? */ + private byte[] receivedDX; + /** What IAC WILL/WONT request do we have received already ? */ + private byte[] receivedWX; + /** What IAC DO/DONT request do we have sent already ? */ + private byte[] sentDX; + /** What IAC WILL/WONT request do we have sent already ? */ + private byte[] sentWX; + + /** + * Send a Telnet Escape character (IAC ) + */ + public void sendTelnetControl(byte code) + throws IOException { + byte[] b = new byte[2]; + b[0] = IAC; + b[1] = code; + write(b); + } + + /** + * Send the new Window Size (via NAWS) + */ + public void setWindowSize(int columns, int rows) + throws IOException { + if (debug > 2) System.err.println("sending NAWS"); + + if (receivedDX[TELOPT_NAWS] != DO) { + System.err.println("not allowed to send NAWS? (DONT NAWS)"); + return; + } + + write(IAC); write(SB); write(TELOPT_NAWS); + write((byte)(columns >> 8)); + write((byte)(columns & 0xff)); + write((byte)(rows >> 8)); + write((byte)(rows & 0xff)); + write(IAC); write(SE); + } + + + /** + * Handle an incoming IAC SB <type> <bytes> IAC SE + * @param type type of SB + * @param sbata byte array as <bytes> + */ + private void handle_sb(byte type, byte[] sbdata) + throws IOException { + if (debug > 1) + System.err.println("TelnetIO.handle_sb(" + type + ")"); + + switch (type) { + case TELOPT_TTYPE: + if (sbdata.length > 0 && sbdata[0] == TELQUAL_SEND) { + write(IACSB); write(TELOPT_TTYPE); write(TELQUAL_IS); + /* FIXME: need more logic here if we use + * more than one terminal type + */ + String ttype = getTerminalType(); + + if (ttype == null) ttype = "dumb"; + + write(ttype.getBytes()); + write(IACSE); + } + + break; + + case TELOPT_CHARSET: + System.out.println("Got SB CHARSET"); + String charsetStr = new String(sbdata, "US-ASCII"); + + if (charsetStr.startsWith("TTABLE ")) { + charsetStr = charsetStr.substring(7); + } + + String[] charsets = charsetStr.split(charsetStr.substring(0, 0)); + String myCharset = getCharsetName(); + + for (String charset : charsets) { + if (charset.equals(myCharset)) { + write(IACSB); write(TELOPT_CHARSET); write(CHARSET_ACCEPTED); + write(charset.getBytes()); + write(IACSE); + System.out.println("Sent our charset!"); + return; + } + } + + write(IACSB); write(TELOPT_CHARSET); write(CHARSET_REJECTED); + write(IACSE); + break; + } + } + + /** + * Do not send any notifications at startup. We do not know, + * whether the remote client understands telnet protocol handling, + * so we are silent. + * (This used to send IAC WILL SGA, but this is false for a compliant + * client.) + */ + public void startup() throws IOException { + } + /** + * Transpose special telnet codes like 0xff or newlines to values + * that are compliant to the protocol. This method will also send + * the buffer immediately after transposing the data. + * @param buf the data buffer to be sent + */ + public void transpose(byte[] buf) throws IOException { + int i; + byte[] nbuf, xbuf; + int nbufptr = 0; + nbuf = new byte[buf.length * 2]; // FIXME: buffer overflows possible + + for (i = 0; i < buf.length ; i++) { + switch (buf[i]) { + // Escape IAC twice in stream ... to be telnet protocol compliant + // this is there in binary and non-binary mode. + case IAC: + nbuf[nbufptr++] = IAC; + nbuf[nbufptr++] = IAC; + break; + + // We need to heed RFC 854. LF (\n) is 10, CR (\r) is 13 + // we assume that the Terminal sends \n for lf+cr and \r for just cr + // linefeed+carriage return is CR LF */ + case 10: // \n + if (receivedDX[TELOPT_BINARY + 128 ] != DO) { + while (nbuf.length - nbufptr < crlf.length) { + xbuf = new byte[nbuf.length * 2]; + System.arraycopy(nbuf, 0, xbuf, 0, nbufptr); + nbuf = xbuf; + } + + for (int j = 0; j < crlf.length; j++) + nbuf[nbufptr++] = crlf[j]; + + break; + } + else { + // copy verbatim in binary mode. + nbuf[nbufptr++] = buf[i]; + } + + break; + + // carriage return is CR NUL */ + case 13: // \r + if (receivedDX[TELOPT_BINARY + 128 ] != DO) { + while (nbuf.length - nbufptr < cr.length) { + xbuf = new byte[nbuf.length * 2]; + System.arraycopy(nbuf, 0, xbuf, 0, nbufptr); + nbuf = xbuf; + } + + for (int j = 0; j < cr.length; j++) + nbuf[nbufptr++] = cr[j]; + } + else { + // copy verbatim in binary mode. + nbuf[nbufptr++] = buf[i]; + } + + break; + + // all other characters are just copied + default: + nbuf[nbufptr++] = buf[i]; + break; + } + } + + xbuf = new byte[nbufptr]; + System.arraycopy(nbuf, 0, xbuf, 0, nbufptr); + write(xbuf); + } + + public void setCRLF(String xcrlf) { crlf = xcrlf.getBytes(); } + public void setCR(String xcr) { cr = xcr.getBytes(); } + + /** + * Handle telnet protocol negotiation. The buffer will be parsed + * and necessary actions are taken according to the telnet protocol. + * See RFC-Telnet + * @param nbuf the byte buffer put out after negotiation + * @return number of bytes processed, 0 for none, and -1 for end of buffer. + */ + public int negotiate(byte nbuf[], int offset) + throws IOException { + int count = tempbuf.length; + byte[] buf = tempbuf; + byte sendbuf[] = new byte[3]; + byte b, reply; + int boffset = 0, noffset = offset; + boolean dobreak = false; + + if (count == 0) // buffer is empty. + return -1; + + while (!dobreak && (boffset < count) && (noffset < nbuf.length)) { + b = buf[boffset++]; + + // of course, byte is a signed entity (-128 -> 127) + // but apparently the SGI Netscape 3.0 doesn't seem + // to care and provides happily values up to 255 + if (b >= 128) + b = (byte)(b - 256); + + if (debug > 2) { + Byte B = new Byte(b); + System.err.print("byte: " + B.intValue() + " "); + } + + switch (neg_state) { + case STATE_DATA: + if (b == IAC) { + neg_state = STATE_IAC; + dobreak = true; // leave the loop so we can sync. + } + else + nbuf[noffset++] = b; + + break; + + case STATE_IAC: + switch (b) { + case IAC: + if (debug > 2) System.err.print("IAC "); + + neg_state = STATE_DATA; + nbuf[noffset++] = IAC; + break; + + case WILL: + if (debug > 2) System.err.print("WILL "); + + neg_state = STATE_IACWILL; + break; + + case WONT: + if (debug > 2) System.err.print("WONT "); + + neg_state = STATE_IACWONT; + break; + + case DONT: + if (debug > 2) System.err.print("DONT "); + + neg_state = STATE_IACDONT; + break; + + case DO: + if (debug > 2) System.err.print("DO "); + + neg_state = STATE_IACDO; + break; + + case EOR: + if (debug > 1) System.err.print("EOR "); + + notifyEndOfRecord(); + dobreak = true; // leave the loop so we can sync. + neg_state = STATE_DATA; + break; + + case SB: + if (debug > 2) System.err.print("SB "); + + neg_state = STATE_IACSB; + break; + + default: + if (debug > 2) System.err.print(" "); + + neg_state = STATE_DATA; + break; + } + + break; + + case STATE_IACWILL: + switch (b) { + case TELOPT_ECHO: + if (debug > 2) System.err.println("ECHO"); + + reply = DO; + setLocalEcho(false); + break; + + case TELOPT_SGA: + if (debug > 2) System.err.println("SGA"); + + reply = DO; + break; + + case TELOPT_EOR: + if (debug > 2) System.err.println("EOR"); + + reply = DO; + break; + + case TELOPT_BINARY: + if (debug > 2) System.err.println("BINARY"); + + reply = DO; + break; + + default: + if (debug > 2) System.err.println(""); + + reply = DONT; + break; + } + + if (debug > 1) System.err.println("<" + b + ", WILL =" + WILL + ">"); + + if (reply != sentDX[b + 128] || WILL != receivedWX[b + 128]) { + sendbuf[0] = IAC; + sendbuf[1] = reply; + sendbuf[2] = b; + write(sendbuf); + sentDX[b + 128] = reply; + receivedWX[b + 128] = WILL; + } + + neg_state = STATE_DATA; + break; + + case STATE_IACWONT: + switch (b) { + case TELOPT_ECHO: + if (debug > 2) System.err.println("ECHO"); + + setLocalEcho(true); + reply = DONT; + break; + + case TELOPT_SGA: + if (debug > 2) System.err.println("SGA"); + + reply = DONT; + break; + + case TELOPT_EOR: + if (debug > 2) System.err.println("EOR"); + + reply = DONT; + break; + + case TELOPT_BINARY: + if (debug > 2) System.err.println("BINARY"); + + reply = DONT; + break; + + default: + if (debug > 2) System.err.println(""); + + reply = DONT; + break; + } + + if (reply != sentDX[b + 128] || WONT != receivedWX[b + 128]) { + sendbuf[0] = IAC; + sendbuf[1] = reply; + sendbuf[2] = b; + write(sendbuf); + sentDX[b + 128] = reply; + receivedWX[b + 128] = WILL; + } + + neg_state = STATE_DATA; + break; + + case STATE_IACDO: + switch (b) { + case TELOPT_ECHO: + if (debug > 2) System.err.println("ECHO"); + + reply = WILL; + setLocalEcho(true); + break; + + case TELOPT_SGA: + if (debug > 2) System.err.println("SGA"); + + reply = WILL; + break; + + case TELOPT_TTYPE: + if (debug > 2) System.err.println("TTYPE"); + + reply = WILL; + break; + + case TELOPT_BINARY: + if (debug > 2) System.err.println("BINARY"); + + reply = WILL; + break; + + case TELOPT_NAWS: + if (debug > 2) System.err.println("NAWS"); + + int[] size = getWindowSize(); + receivedDX[b] = DO; + + if (size == null) { + // this shouldn't happen + write(IAC); + write(WONT); + write(TELOPT_NAWS); + reply = WONT; + sentWX[b] = WONT; + break; + } + + reply = WILL; + sentWX[b] = WILL; + sendbuf[0] = IAC; + sendbuf[1] = WILL; + sendbuf[2] = TELOPT_NAWS; + write(sendbuf); + write(IAC); write(SB); write(TELOPT_NAWS); + write((byte)(size[0] >> 8)); + write((byte)(size[0] & 0xff)); + write((byte)(size[1] >> 8)); + write((byte)(size[1] & 0xff)); + write(IAC); write(SE); + break; + + default: + if (debug > 2) System.err.println(""); + + reply = WONT; + break; + } + + if (reply != sentWX[128 + b] || DO != receivedDX[128 + b]) { + sendbuf[0] = IAC; + sendbuf[1] = reply; + sendbuf[2] = b; + write(sendbuf); + sentWX[b + 128] = reply; + receivedDX[b + 128] = DO; + } + + neg_state = STATE_DATA; + break; + + case STATE_IACDONT: + switch (b) { + case TELOPT_ECHO: + if (debug > 2) System.err.println("ECHO"); + + reply = WONT; + setLocalEcho(false); + break; + + case TELOPT_SGA: + if (debug > 2) System.err.println("SGA"); + + reply = WONT; + break; + + case TELOPT_NAWS: + if (debug > 2) System.err.println("NAWS"); + + reply = WONT; + break; + + case TELOPT_BINARY: + if (debug > 2) System.err.println("BINARY"); + + reply = WONT; + break; + + default: + if (debug > 2) System.err.println(""); + + reply = WONT; + break; + } + + if (reply != sentWX[b + 128] || DONT != receivedDX[b + 128]) { + write(IAC); write(reply); write(b); + sentWX[b + 128] = reply; + receivedDX[b + 128] = DONT; + } + + neg_state = STATE_DATA; + break; + + case STATE_IACSBIAC: + if (debug > 2) System.err.println("" + b + " "); + + if (b == IAC) { + sbbuf = new byte[0]; + current_sb = b; + neg_state = STATE_IACSBDATA; + } + else { + System.err.println("(bad) " + b + " "); + neg_state = STATE_DATA; + } + + break; + + case STATE_IACSB: + if (debug > 2) System.err.println("" + b + " "); + + switch (b) { + case IAC: + neg_state = STATE_IACSBIAC; + break; + + default: + current_sb = b; + sbbuf = new byte[0]; + neg_state = STATE_IACSBDATA; + break; + } + + break; + + case STATE_IACSBDATA: + if (debug > 2) System.err.println("" + b + " "); + + switch (b) { + case IAC: + neg_state = STATE_IACSBDATAIAC; + break; + + default: + byte[] xsb = new byte[sbbuf.length + 1]; + System.arraycopy(sbbuf, 0, xsb, 0, sbbuf.length); + sbbuf = xsb; + sbbuf[sbbuf.length - 1] = b; + break; + } + + break; + + case STATE_IACSBDATAIAC: + if (debug > 2) System.err.println("" + b + " "); + + switch (b) { + case IAC: + neg_state = STATE_IACSBDATA; + byte[] xsb = new byte[sbbuf.length + 1]; + System.arraycopy(sbbuf, 0, xsb, 0, sbbuf.length); + sbbuf = xsb; + sbbuf[sbbuf.length - 1] = IAC; + break; + + case SE: + handle_sb(current_sb, sbbuf); + current_sb = 0; + neg_state = STATE_DATA; + break; + + case SB: + handle_sb(current_sb, sbbuf); + neg_state = STATE_IACSB; + break; + + default: + neg_state = STATE_DATA; + break; + } + + break; + + default: + if (debug > 1) + System.err.println("This should not happen: " + neg_state + " "); + + neg_state = STATE_DATA; + break; + } + } + + // shrink tempbuf to new processed size. + byte[] xb = new byte[count - boffset]; + System.arraycopy(tempbuf, boffset, xb, 0, count - boffset); + tempbuf = xb; + return noffset - offset; + } + + public void inputfeed(byte[] b, int offset, int len) { + byte[] xb = new byte[tempbuf.length + len]; + System.arraycopy(tempbuf, 0, xb, 0, tempbuf.length); + System.arraycopy(b, offset, xb, tempbuf.length, len); + tempbuf = xb; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/de/mud/terminal/Precomposer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/de/mud/terminal/Precomposer.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,1052 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2007 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.mud.terminal; + +/** + * @author Kenny Root + * This data was taken from xterm's precompose.c + */ +public class Precomposer { + public final static char precompositions[][] = { + { 0x226E, 0x003C, 0x0338}, + { 0x2260, 0x003D, 0x0338}, + { 0x226F, 0x003E, 0x0338}, + { 0x00C0, 0x0041, 0x0300}, + { 0x00C1, 0x0041, 0x0301}, + { 0x00C2, 0x0041, 0x0302}, + { 0x00C3, 0x0041, 0x0303}, + { 0x0100, 0x0041, 0x0304}, + { 0x0102, 0x0041, 0x0306}, + { 0x0226, 0x0041, 0x0307}, + { 0x00C4, 0x0041, 0x0308}, + { 0x1EA2, 0x0041, 0x0309}, + { 0x00C5, 0x0041, 0x030A}, + { 0x01CD, 0x0041, 0x030C}, + { 0x0200, 0x0041, 0x030F}, + { 0x0202, 0x0041, 0x0311}, + { 0x1EA0, 0x0041, 0x0323}, + { 0x1E00, 0x0041, 0x0325}, + { 0x0104, 0x0041, 0x0328}, + { 0x1E02, 0x0042, 0x0307}, + { 0x1E04, 0x0042, 0x0323}, + { 0x1E06, 0x0042, 0x0331}, + { 0x0106, 0x0043, 0x0301}, + { 0x0108, 0x0043, 0x0302}, + { 0x010A, 0x0043, 0x0307}, + { 0x010C, 0x0043, 0x030C}, + { 0x00C7, 0x0043, 0x0327}, + { 0x1E0A, 0x0044, 0x0307}, + { 0x010E, 0x0044, 0x030C}, + { 0x1E0C, 0x0044, 0x0323}, + { 0x1E10, 0x0044, 0x0327}, + { 0x1E12, 0x0044, 0x032D}, + { 0x1E0E, 0x0044, 0x0331}, + { 0x00C8, 0x0045, 0x0300}, + { 0x00C9, 0x0045, 0x0301}, + { 0x00CA, 0x0045, 0x0302}, + { 0x1EBC, 0x0045, 0x0303}, + { 0x0112, 0x0045, 0x0304}, + { 0x0114, 0x0045, 0x0306}, + { 0x0116, 0x0045, 0x0307}, + { 0x00CB, 0x0045, 0x0308}, + { 0x1EBA, 0x0045, 0x0309}, + { 0x011A, 0x0045, 0x030C}, + { 0x0204, 0x0045, 0x030F}, + { 0x0206, 0x0045, 0x0311}, + { 0x1EB8, 0x0045, 0x0323}, + { 0x0228, 0x0045, 0x0327}, + { 0x0118, 0x0045, 0x0328}, + { 0x1E18, 0x0045, 0x032D}, + { 0x1E1A, 0x0045, 0x0330}, + { 0x1E1E, 0x0046, 0x0307}, + { 0x01F4, 0x0047, 0x0301}, + { 0x011C, 0x0047, 0x0302}, + { 0x1E20, 0x0047, 0x0304}, + { 0x011E, 0x0047, 0x0306}, + { 0x0120, 0x0047, 0x0307}, + { 0x01E6, 0x0047, 0x030C}, + { 0x0122, 0x0047, 0x0327}, + { 0x0124, 0x0048, 0x0302}, + { 0x1E22, 0x0048, 0x0307}, + { 0x1E26, 0x0048, 0x0308}, + { 0x021E, 0x0048, 0x030C}, + { 0x1E24, 0x0048, 0x0323}, + { 0x1E28, 0x0048, 0x0327}, + { 0x1E2A, 0x0048, 0x032E}, + { 0x00CC, 0x0049, 0x0300}, + { 0x00CD, 0x0049, 0x0301}, + { 0x00CE, 0x0049, 0x0302}, + { 0x0128, 0x0049, 0x0303}, + { 0x012A, 0x0049, 0x0304}, + { 0x012C, 0x0049, 0x0306}, + { 0x0130, 0x0049, 0x0307}, + { 0x00CF, 0x0049, 0x0308}, + { 0x1EC8, 0x0049, 0x0309}, + { 0x01CF, 0x0049, 0x030C}, + { 0x0208, 0x0049, 0x030F}, + { 0x020A, 0x0049, 0x0311}, + { 0x1ECA, 0x0049, 0x0323}, + { 0x012E, 0x0049, 0x0328}, + { 0x1E2C, 0x0049, 0x0330}, + { 0x0134, 0x004A, 0x0302}, + { 0x1E30, 0x004B, 0x0301}, + { 0x01E8, 0x004B, 0x030C}, + { 0x1E32, 0x004B, 0x0323}, + { 0x0136, 0x004B, 0x0327}, + { 0x1E34, 0x004B, 0x0331}, + { 0x0139, 0x004C, 0x0301}, + { 0x013D, 0x004C, 0x030C}, + { 0x1E36, 0x004C, 0x0323}, + { 0x013B, 0x004C, 0x0327}, + { 0x1E3C, 0x004C, 0x032D}, + { 0x1E3A, 0x004C, 0x0331}, + { 0x1E3E, 0x004D, 0x0301}, + { 0x1E40, 0x004D, 0x0307}, + { 0x1E42, 0x004D, 0x0323}, + { 0x01F8, 0x004E, 0x0300}, + { 0x0143, 0x004E, 0x0301}, + { 0x00D1, 0x004E, 0x0303}, + { 0x1E44, 0x004E, 0x0307}, + { 0x0147, 0x004E, 0x030C}, + { 0x1E46, 0x004E, 0x0323}, + { 0x0145, 0x004E, 0x0327}, + { 0x1E4A, 0x004E, 0x032D}, + { 0x1E48, 0x004E, 0x0331}, + { 0x00D2, 0x004F, 0x0300}, + { 0x00D3, 0x004F, 0x0301}, + { 0x00D4, 0x004F, 0x0302}, + { 0x00D5, 0x004F, 0x0303}, + { 0x014C, 0x004F, 0x0304}, + { 0x014E, 0x004F, 0x0306}, + { 0x022E, 0x004F, 0x0307}, + { 0x00D6, 0x004F, 0x0308}, + { 0x1ECE, 0x004F, 0x0309}, + { 0x0150, 0x004F, 0x030B}, + { 0x01D1, 0x004F, 0x030C}, + { 0x020C, 0x004F, 0x030F}, + { 0x020E, 0x004F, 0x0311}, + { 0x01A0, 0x004F, 0x031B}, + { 0x1ECC, 0x004F, 0x0323}, + { 0x01EA, 0x004F, 0x0328}, + { 0x1E54, 0x0050, 0x0301}, + { 0x1E56, 0x0050, 0x0307}, + { 0x0154, 0x0052, 0x0301}, + { 0x1E58, 0x0052, 0x0307}, + { 0x0158, 0x0052, 0x030C}, + { 0x0210, 0x0052, 0x030F}, + { 0x0212, 0x0052, 0x0311}, + { 0x1E5A, 0x0052, 0x0323}, + { 0x0156, 0x0052, 0x0327}, + { 0x1E5E, 0x0052, 0x0331}, + { 0x015A, 0x0053, 0x0301}, + { 0x015C, 0x0053, 0x0302}, + { 0x1E60, 0x0053, 0x0307}, + { 0x0160, 0x0053, 0x030C}, + { 0x1E62, 0x0053, 0x0323}, + { 0x0218, 0x0053, 0x0326}, + { 0x015E, 0x0053, 0x0327}, + { 0x1E6A, 0x0054, 0x0307}, + { 0x0164, 0x0054, 0x030C}, + { 0x1E6C, 0x0054, 0x0323}, + { 0x021A, 0x0054, 0x0326}, + { 0x0162, 0x0054, 0x0327}, + { 0x1E70, 0x0054, 0x032D}, + { 0x1E6E, 0x0054, 0x0331}, + { 0x00D9, 0x0055, 0x0300}, + { 0x00DA, 0x0055, 0x0301}, + { 0x00DB, 0x0055, 0x0302}, + { 0x0168, 0x0055, 0x0303}, + { 0x016A, 0x0055, 0x0304}, + { 0x016C, 0x0055, 0x0306}, + { 0x00DC, 0x0055, 0x0308}, + { 0x1EE6, 0x0055, 0x0309}, + { 0x016E, 0x0055, 0x030A}, + { 0x0170, 0x0055, 0x030B}, + { 0x01D3, 0x0055, 0x030C}, + { 0x0214, 0x0055, 0x030F}, + { 0x0216, 0x0055, 0x0311}, + { 0x01AF, 0x0055, 0x031B}, + { 0x1EE4, 0x0055, 0x0323}, + { 0x1E72, 0x0055, 0x0324}, + { 0x0172, 0x0055, 0x0328}, + { 0x1E76, 0x0055, 0x032D}, + { 0x1E74, 0x0055, 0x0330}, + { 0x1E7C, 0x0056, 0x0303}, + { 0x1E7E, 0x0056, 0x0323}, + { 0x1E80, 0x0057, 0x0300}, + { 0x1E82, 0x0057, 0x0301}, + { 0x0174, 0x0057, 0x0302}, + { 0x1E86, 0x0057, 0x0307}, + { 0x1E84, 0x0057, 0x0308}, + { 0x1E88, 0x0057, 0x0323}, + { 0x1E8A, 0x0058, 0x0307}, + { 0x1E8C, 0x0058, 0x0308}, + { 0x1EF2, 0x0059, 0x0300}, + { 0x00DD, 0x0059, 0x0301}, + { 0x0176, 0x0059, 0x0302}, + { 0x1EF8, 0x0059, 0x0303}, + { 0x0232, 0x0059, 0x0304}, + { 0x1E8E, 0x0059, 0x0307}, + { 0x0178, 0x0059, 0x0308}, + { 0x1EF6, 0x0059, 0x0309}, + { 0x1EF4, 0x0059, 0x0323}, + { 0x0179, 0x005A, 0x0301}, + { 0x1E90, 0x005A, 0x0302}, + { 0x017B, 0x005A, 0x0307}, + { 0x017D, 0x005A, 0x030C}, + { 0x1E92, 0x005A, 0x0323}, + { 0x1E94, 0x005A, 0x0331}, + { 0x00E0, 0x0061, 0x0300}, + { 0x00E1, 0x0061, 0x0301}, + { 0x00E2, 0x0061, 0x0302}, + { 0x00E3, 0x0061, 0x0303}, + { 0x0101, 0x0061, 0x0304}, + { 0x0103, 0x0061, 0x0306}, + { 0x0227, 0x0061, 0x0307}, + { 0x00E4, 0x0061, 0x0308}, + { 0x1EA3, 0x0061, 0x0309}, + { 0x00E5, 0x0061, 0x030A}, + { 0x01CE, 0x0061, 0x030C}, + { 0x0201, 0x0061, 0x030F}, + { 0x0203, 0x0061, 0x0311}, + { 0x1EA1, 0x0061, 0x0323}, + { 0x1E01, 0x0061, 0x0325}, + { 0x0105, 0x0061, 0x0328}, + { 0x1E03, 0x0062, 0x0307}, + { 0x1E05, 0x0062, 0x0323}, + { 0x1E07, 0x0062, 0x0331}, + { 0x0107, 0x0063, 0x0301}, + { 0x0109, 0x0063, 0x0302}, + { 0x010B, 0x0063, 0x0307}, + { 0x010D, 0x0063, 0x030C}, + { 0x00E7, 0x0063, 0x0327}, + { 0x1E0B, 0x0064, 0x0307}, + { 0x010F, 0x0064, 0x030C}, + { 0x1E0D, 0x0064, 0x0323}, + { 0x1E11, 0x0064, 0x0327}, + { 0x1E13, 0x0064, 0x032D}, + { 0x1E0F, 0x0064, 0x0331}, + { 0x00E8, 0x0065, 0x0300}, + { 0x00E9, 0x0065, 0x0301}, + { 0x00EA, 0x0065, 0x0302}, + { 0x1EBD, 0x0065, 0x0303}, + { 0x0113, 0x0065, 0x0304}, + { 0x0115, 0x0065, 0x0306}, + { 0x0117, 0x0065, 0x0307}, + { 0x00EB, 0x0065, 0x0308}, + { 0x1EBB, 0x0065, 0x0309}, + { 0x011B, 0x0065, 0x030C}, + { 0x0205, 0x0065, 0x030F}, + { 0x0207, 0x0065, 0x0311}, + { 0x1EB9, 0x0065, 0x0323}, + { 0x0229, 0x0065, 0x0327}, + { 0x0119, 0x0065, 0x0328}, + { 0x1E19, 0x0065, 0x032D}, + { 0x1E1B, 0x0065, 0x0330}, + { 0x1E1F, 0x0066, 0x0307}, + { 0x01F5, 0x0067, 0x0301}, + { 0x011D, 0x0067, 0x0302}, + { 0x1E21, 0x0067, 0x0304}, + { 0x011F, 0x0067, 0x0306}, + { 0x0121, 0x0067, 0x0307}, + { 0x01E7, 0x0067, 0x030C}, + { 0x0123, 0x0067, 0x0327}, + { 0x0125, 0x0068, 0x0302}, + { 0x1E23, 0x0068, 0x0307}, + { 0x1E27, 0x0068, 0x0308}, + { 0x021F, 0x0068, 0x030C}, + { 0x1E25, 0x0068, 0x0323}, + { 0x1E29, 0x0068, 0x0327}, + { 0x1E2B, 0x0068, 0x032E}, + { 0x1E96, 0x0068, 0x0331}, + { 0x00EC, 0x0069, 0x0300}, + { 0x00ED, 0x0069, 0x0301}, + { 0x00EE, 0x0069, 0x0302}, + { 0x0129, 0x0069, 0x0303}, + { 0x012B, 0x0069, 0x0304}, + { 0x012D, 0x0069, 0x0306}, + { 0x00EF, 0x0069, 0x0308}, + { 0x1EC9, 0x0069, 0x0309}, + { 0x01D0, 0x0069, 0x030C}, + { 0x0209, 0x0069, 0x030F}, + { 0x020B, 0x0069, 0x0311}, + { 0x1ECB, 0x0069, 0x0323}, + { 0x012F, 0x0069, 0x0328}, + { 0x1E2D, 0x0069, 0x0330}, + { 0x0135, 0x006A, 0x0302}, + { 0x01F0, 0x006A, 0x030C}, + { 0x1E31, 0x006B, 0x0301}, + { 0x01E9, 0x006B, 0x030C}, + { 0x1E33, 0x006B, 0x0323}, + { 0x0137, 0x006B, 0x0327}, + { 0x1E35, 0x006B, 0x0331}, + { 0x013A, 0x006C, 0x0301}, + { 0x013E, 0x006C, 0x030C}, + { 0x1E37, 0x006C, 0x0323}, + { 0x013C, 0x006C, 0x0327}, + { 0x1E3D, 0x006C, 0x032D}, + { 0x1E3B, 0x006C, 0x0331}, + { 0x1E3F, 0x006D, 0x0301}, + { 0x1E41, 0x006D, 0x0307}, + { 0x1E43, 0x006D, 0x0323}, + { 0x01F9, 0x006E, 0x0300}, + { 0x0144, 0x006E, 0x0301}, + { 0x00F1, 0x006E, 0x0303}, + { 0x1E45, 0x006E, 0x0307}, + { 0x0148, 0x006E, 0x030C}, + { 0x1E47, 0x006E, 0x0323}, + { 0x0146, 0x006E, 0x0327}, + { 0x1E4B, 0x006E, 0x032D}, + { 0x1E49, 0x006E, 0x0331}, + { 0x00F2, 0x006F, 0x0300}, + { 0x00F3, 0x006F, 0x0301}, + { 0x00F4, 0x006F, 0x0302}, + { 0x00F5, 0x006F, 0x0303}, + { 0x014D, 0x006F, 0x0304}, + { 0x014F, 0x006F, 0x0306}, + { 0x022F, 0x006F, 0x0307}, + { 0x00F6, 0x006F, 0x0308}, + { 0x1ECF, 0x006F, 0x0309}, + { 0x0151, 0x006F, 0x030B}, + { 0x01D2, 0x006F, 0x030C}, + { 0x020D, 0x006F, 0x030F}, + { 0x020F, 0x006F, 0x0311}, + { 0x01A1, 0x006F, 0x031B}, + { 0x1ECD, 0x006F, 0x0323}, + { 0x01EB, 0x006F, 0x0328}, + { 0x1E55, 0x0070, 0x0301}, + { 0x1E57, 0x0070, 0x0307}, + { 0x0155, 0x0072, 0x0301}, + { 0x1E59, 0x0072, 0x0307}, + { 0x0159, 0x0072, 0x030C}, + { 0x0211, 0x0072, 0x030F}, + { 0x0213, 0x0072, 0x0311}, + { 0x1E5B, 0x0072, 0x0323}, + { 0x0157, 0x0072, 0x0327}, + { 0x1E5F, 0x0072, 0x0331}, + { 0x015B, 0x0073, 0x0301}, + { 0x015D, 0x0073, 0x0302}, + { 0x1E61, 0x0073, 0x0307}, + { 0x0161, 0x0073, 0x030C}, + { 0x1E63, 0x0073, 0x0323}, + { 0x0219, 0x0073, 0x0326}, + { 0x015F, 0x0073, 0x0327}, + { 0x1E6B, 0x0074, 0x0307}, + { 0x1E97, 0x0074, 0x0308}, + { 0x0165, 0x0074, 0x030C}, + { 0x1E6D, 0x0074, 0x0323}, + { 0x021B, 0x0074, 0x0326}, + { 0x0163, 0x0074, 0x0327}, + { 0x1E71, 0x0074, 0x032D}, + { 0x1E6F, 0x0074, 0x0331}, + { 0x00F9, 0x0075, 0x0300}, + { 0x00FA, 0x0075, 0x0301}, + { 0x00FB, 0x0075, 0x0302}, + { 0x0169, 0x0075, 0x0303}, + { 0x016B, 0x0075, 0x0304}, + { 0x016D, 0x0075, 0x0306}, + { 0x00FC, 0x0075, 0x0308}, + { 0x1EE7, 0x0075, 0x0309}, + { 0x016F, 0x0075, 0x030A}, + { 0x0171, 0x0075, 0x030B}, + { 0x01D4, 0x0075, 0x030C}, + { 0x0215, 0x0075, 0x030F}, + { 0x0217, 0x0075, 0x0311}, + { 0x01B0, 0x0075, 0x031B}, + { 0x1EE5, 0x0075, 0x0323}, + { 0x1E73, 0x0075, 0x0324}, + { 0x0173, 0x0075, 0x0328}, + { 0x1E77, 0x0075, 0x032D}, + { 0x1E75, 0x0075, 0x0330}, + { 0x1E7D, 0x0076, 0x0303}, + { 0x1E7F, 0x0076, 0x0323}, + { 0x1E81, 0x0077, 0x0300}, + { 0x1E83, 0x0077, 0x0301}, + { 0x0175, 0x0077, 0x0302}, + { 0x1E87, 0x0077, 0x0307}, + { 0x1E85, 0x0077, 0x0308}, + { 0x1E98, 0x0077, 0x030A}, + { 0x1E89, 0x0077, 0x0323}, + { 0x1E8B, 0x0078, 0x0307}, + { 0x1E8D, 0x0078, 0x0308}, + { 0x1EF3, 0x0079, 0x0300}, + { 0x00FD, 0x0079, 0x0301}, + { 0x0177, 0x0079, 0x0302}, + { 0x1EF9, 0x0079, 0x0303}, + { 0x0233, 0x0079, 0x0304}, + { 0x1E8F, 0x0079, 0x0307}, + { 0x00FF, 0x0079, 0x0308}, + { 0x1EF7, 0x0079, 0x0309}, + { 0x1E99, 0x0079, 0x030A}, + { 0x1EF5, 0x0079, 0x0323}, + { 0x017A, 0x007A, 0x0301}, + { 0x1E91, 0x007A, 0x0302}, + { 0x017C, 0x007A, 0x0307}, + { 0x017E, 0x007A, 0x030C}, + { 0x1E93, 0x007A, 0x0323}, + { 0x1E95, 0x007A, 0x0331}, + { 0x1FED, 0x00A8, 0x0300}, + { 0x0385, 0x00A8, 0x0301}, + { 0x1FC1, 0x00A8, 0x0342}, + { 0x1EA6, 0x00C2, 0x0300}, + { 0x1EA4, 0x00C2, 0x0301}, + { 0x1EAA, 0x00C2, 0x0303}, + { 0x1EA8, 0x00C2, 0x0309}, + { 0x01DE, 0x00C4, 0x0304}, + { 0x01FA, 0x00C5, 0x0301}, + { 0x01FC, 0x00C6, 0x0301}, + { 0x01E2, 0x00C6, 0x0304}, + { 0x1E08, 0x00C7, 0x0301}, + { 0x1EC0, 0x00CA, 0x0300}, + { 0x1EBE, 0x00CA, 0x0301}, + { 0x1EC4, 0x00CA, 0x0303}, + { 0x1EC2, 0x00CA, 0x0309}, + { 0x1E2E, 0x00CF, 0x0301}, + { 0x1ED2, 0x00D4, 0x0300}, + { 0x1ED0, 0x00D4, 0x0301}, + { 0x1ED6, 0x00D4, 0x0303}, + { 0x1ED4, 0x00D4, 0x0309}, + { 0x1E4C, 0x00D5, 0x0301}, + { 0x022C, 0x00D5, 0x0304}, + { 0x1E4E, 0x00D5, 0x0308}, + { 0x022A, 0x00D6, 0x0304}, + { 0x01FE, 0x00D8, 0x0301}, + { 0x01DB, 0x00DC, 0x0300}, + { 0x01D7, 0x00DC, 0x0301}, + { 0x01D5, 0x00DC, 0x0304}, + { 0x01D9, 0x00DC, 0x030C}, + { 0x1EA7, 0x00E2, 0x0300}, + { 0x1EA5, 0x00E2, 0x0301}, + { 0x1EAB, 0x00E2, 0x0303}, + { 0x1EA9, 0x00E2, 0x0309}, + { 0x01DF, 0x00E4, 0x0304}, + { 0x01FB, 0x00E5, 0x0301}, + { 0x01FD, 0x00E6, 0x0301}, + { 0x01E3, 0x00E6, 0x0304}, + { 0x1E09, 0x00E7, 0x0301}, + { 0x1EC1, 0x00EA, 0x0300}, + { 0x1EBF, 0x00EA, 0x0301}, + { 0x1EC5, 0x00EA, 0x0303}, + { 0x1EC3, 0x00EA, 0x0309}, + { 0x1E2F, 0x00EF, 0x0301}, + { 0x1ED3, 0x00F4, 0x0300}, + { 0x1ED1, 0x00F4, 0x0301}, + { 0x1ED7, 0x00F4, 0x0303}, + { 0x1ED5, 0x00F4, 0x0309}, + { 0x1E4D, 0x00F5, 0x0301}, + { 0x022D, 0x00F5, 0x0304}, + { 0x1E4F, 0x00F5, 0x0308}, + { 0x022B, 0x00F6, 0x0304}, + { 0x01FF, 0x00F8, 0x0301}, + { 0x01DC, 0x00FC, 0x0300}, + { 0x01D8, 0x00FC, 0x0301}, + { 0x01D6, 0x00FC, 0x0304}, + { 0x01DA, 0x00FC, 0x030C}, + { 0x1EB0, 0x0102, 0x0300}, + { 0x1EAE, 0x0102, 0x0301}, + { 0x1EB4, 0x0102, 0x0303}, + { 0x1EB2, 0x0102, 0x0309}, + { 0x1EB1, 0x0103, 0x0300}, + { 0x1EAF, 0x0103, 0x0301}, + { 0x1EB5, 0x0103, 0x0303}, + { 0x1EB3, 0x0103, 0x0309}, + { 0x1E14, 0x0112, 0x0300}, + { 0x1E16, 0x0112, 0x0301}, + { 0x1E15, 0x0113, 0x0300}, + { 0x1E17, 0x0113, 0x0301}, + { 0x1E50, 0x014C, 0x0300}, + { 0x1E52, 0x014C, 0x0301}, + { 0x1E51, 0x014D, 0x0300}, + { 0x1E53, 0x014D, 0x0301}, + { 0x1E64, 0x015A, 0x0307}, + { 0x1E65, 0x015B, 0x0307}, + { 0x1E66, 0x0160, 0x0307}, + { 0x1E67, 0x0161, 0x0307}, + { 0x1E78, 0x0168, 0x0301}, + { 0x1E79, 0x0169, 0x0301}, + { 0x1E7A, 0x016A, 0x0308}, + { 0x1E7B, 0x016B, 0x0308}, + { 0x1E9B, 0x017F, 0x0307}, + { 0x1EDC, 0x01A0, 0x0300}, + { 0x1EDA, 0x01A0, 0x0301}, + { 0x1EE0, 0x01A0, 0x0303}, + { 0x1EDE, 0x01A0, 0x0309}, + { 0x1EE2, 0x01A0, 0x0323}, + { 0x1EDD, 0x01A1, 0x0300}, + { 0x1EDB, 0x01A1, 0x0301}, + { 0x1EE1, 0x01A1, 0x0303}, + { 0x1EDF, 0x01A1, 0x0309}, + { 0x1EE3, 0x01A1, 0x0323}, + { 0x1EEA, 0x01AF, 0x0300}, + { 0x1EE8, 0x01AF, 0x0301}, + { 0x1EEE, 0x01AF, 0x0303}, + { 0x1EEC, 0x01AF, 0x0309}, + { 0x1EF0, 0x01AF, 0x0323}, + { 0x1EEB, 0x01B0, 0x0300}, + { 0x1EE9, 0x01B0, 0x0301}, + { 0x1EEF, 0x01B0, 0x0303}, + { 0x1EED, 0x01B0, 0x0309}, + { 0x1EF1, 0x01B0, 0x0323}, + { 0x01EE, 0x01B7, 0x030C}, + { 0x01EC, 0x01EA, 0x0304}, + { 0x01ED, 0x01EB, 0x0304}, + { 0x01E0, 0x0226, 0x0304}, + { 0x01E1, 0x0227, 0x0304}, + { 0x1E1C, 0x0228, 0x0306}, + { 0x1E1D, 0x0229, 0x0306}, + { 0x0230, 0x022E, 0x0304}, + { 0x0231, 0x022F, 0x0304}, + { 0x01EF, 0x0292, 0x030C}, + { 0x0344, 0x0308, 0x0301}, + { 0x1FBA, 0x0391, 0x0300}, + { 0x0386, 0x0391, 0x0301}, + { 0x1FB9, 0x0391, 0x0304}, + { 0x1FB8, 0x0391, 0x0306}, + { 0x1F08, 0x0391, 0x0313}, + { 0x1F09, 0x0391, 0x0314}, + { 0x1FBC, 0x0391, 0x0345}, + { 0x1FC8, 0x0395, 0x0300}, + { 0x0388, 0x0395, 0x0301}, + { 0x1F18, 0x0395, 0x0313}, + { 0x1F19, 0x0395, 0x0314}, + { 0x1FCA, 0x0397, 0x0300}, + { 0x0389, 0x0397, 0x0301}, + { 0x1F28, 0x0397, 0x0313}, + { 0x1F29, 0x0397, 0x0314}, + { 0x1FCC, 0x0397, 0x0345}, + { 0x1FDA, 0x0399, 0x0300}, + { 0x038A, 0x0399, 0x0301}, + { 0x1FD9, 0x0399, 0x0304}, + { 0x1FD8, 0x0399, 0x0306}, + { 0x03AA, 0x0399, 0x0308}, + { 0x1F38, 0x0399, 0x0313}, + { 0x1F39, 0x0399, 0x0314}, + { 0x1FF8, 0x039F, 0x0300}, + { 0x038C, 0x039F, 0x0301}, + { 0x1F48, 0x039F, 0x0313}, + { 0x1F49, 0x039F, 0x0314}, + { 0x1FEC, 0x03A1, 0x0314}, + { 0x1FEA, 0x03A5, 0x0300}, + { 0x038E, 0x03A5, 0x0301}, + { 0x1FE9, 0x03A5, 0x0304}, + { 0x1FE8, 0x03A5, 0x0306}, + { 0x03AB, 0x03A5, 0x0308}, + { 0x1F59, 0x03A5, 0x0314}, + { 0x1FFA, 0x03A9, 0x0300}, + { 0x038F, 0x03A9, 0x0301}, + { 0x1F68, 0x03A9, 0x0313}, + { 0x1F69, 0x03A9, 0x0314}, + { 0x1FFC, 0x03A9, 0x0345}, + { 0x1FB4, 0x03AC, 0x0345}, + { 0x1FC4, 0x03AE, 0x0345}, + { 0x1F70, 0x03B1, 0x0300}, + { 0x03AC, 0x03B1, 0x0301}, + { 0x1FB1, 0x03B1, 0x0304}, + { 0x1FB0, 0x03B1, 0x0306}, + { 0x1F00, 0x03B1, 0x0313}, + { 0x1F01, 0x03B1, 0x0314}, + { 0x1FB6, 0x03B1, 0x0342}, + { 0x1FB3, 0x03B1, 0x0345}, + { 0x1F72, 0x03B5, 0x0300}, + { 0x03AD, 0x03B5, 0x0301}, + { 0x1F10, 0x03B5, 0x0313}, + { 0x1F11, 0x03B5, 0x0314}, + { 0x1F74, 0x03B7, 0x0300}, + { 0x03AE, 0x03B7, 0x0301}, + { 0x1F20, 0x03B7, 0x0313}, + { 0x1F21, 0x03B7, 0x0314}, + { 0x1FC6, 0x03B7, 0x0342}, + { 0x1FC3, 0x03B7, 0x0345}, + { 0x1F76, 0x03B9, 0x0300}, + { 0x03AF, 0x03B9, 0x0301}, + { 0x1FD1, 0x03B9, 0x0304}, + { 0x1FD0, 0x03B9, 0x0306}, + { 0x03CA, 0x03B9, 0x0308}, + { 0x1F30, 0x03B9, 0x0313}, + { 0x1F31, 0x03B9, 0x0314}, + { 0x1FD6, 0x03B9, 0x0342}, + { 0x1F78, 0x03BF, 0x0300}, + { 0x03CC, 0x03BF, 0x0301}, + { 0x1F40, 0x03BF, 0x0313}, + { 0x1F41, 0x03BF, 0x0314}, + { 0x1FE4, 0x03C1, 0x0313}, + { 0x1FE5, 0x03C1, 0x0314}, + { 0x1F7A, 0x03C5, 0x0300}, + { 0x03CD, 0x03C5, 0x0301}, + { 0x1FE1, 0x03C5, 0x0304}, + { 0x1FE0, 0x03C5, 0x0306}, + { 0x03CB, 0x03C5, 0x0308}, + { 0x1F50, 0x03C5, 0x0313}, + { 0x1F51, 0x03C5, 0x0314}, + { 0x1FE6, 0x03C5, 0x0342}, + { 0x1F7C, 0x03C9, 0x0300}, + { 0x03CE, 0x03C9, 0x0301}, + { 0x1F60, 0x03C9, 0x0313}, + { 0x1F61, 0x03C9, 0x0314}, + { 0x1FF6, 0x03C9, 0x0342}, + { 0x1FF3, 0x03C9, 0x0345}, + { 0x1FD2, 0x03CA, 0x0300}, + { 0x0390, 0x03CA, 0x0301}, + { 0x1FD7, 0x03CA, 0x0342}, + { 0x1FE2, 0x03CB, 0x0300}, + { 0x03B0, 0x03CB, 0x0301}, + { 0x1FE7, 0x03CB, 0x0342}, + { 0x1FF4, 0x03CE, 0x0345}, + { 0x03D3, 0x03D2, 0x0301}, + { 0x03D4, 0x03D2, 0x0308}, + { 0x0407, 0x0406, 0x0308}, + { 0x04D0, 0x0410, 0x0306}, + { 0x04D2, 0x0410, 0x0308}, + { 0x0403, 0x0413, 0x0301}, + { 0x0400, 0x0415, 0x0300}, + { 0x04D6, 0x0415, 0x0306}, + { 0x0401, 0x0415, 0x0308}, + { 0x04C1, 0x0416, 0x0306}, + { 0x04DC, 0x0416, 0x0308}, + { 0x04DE, 0x0417, 0x0308}, + { 0x040D, 0x0418, 0x0300}, + { 0x04E2, 0x0418, 0x0304}, + { 0x0419, 0x0418, 0x0306}, + { 0x04E4, 0x0418, 0x0308}, + { 0x040C, 0x041A, 0x0301}, + { 0x04E6, 0x041E, 0x0308}, + { 0x04EE, 0x0423, 0x0304}, + { 0x040E, 0x0423, 0x0306}, + { 0x04F0, 0x0423, 0x0308}, + { 0x04F2, 0x0423, 0x030B}, + { 0x04F4, 0x0427, 0x0308}, + { 0x04F8, 0x042B, 0x0308}, + { 0x04EC, 0x042D, 0x0308}, + { 0x04D1, 0x0430, 0x0306}, + { 0x04D3, 0x0430, 0x0308}, + { 0x0453, 0x0433, 0x0301}, + { 0x0450, 0x0435, 0x0300}, + { 0x04D7, 0x0435, 0x0306}, + { 0x0451, 0x0435, 0x0308}, + { 0x04C2, 0x0436, 0x0306}, + { 0x04DD, 0x0436, 0x0308}, + { 0x04DF, 0x0437, 0x0308}, + { 0x045D, 0x0438, 0x0300}, + { 0x04E3, 0x0438, 0x0304}, + { 0x0439, 0x0438, 0x0306}, + { 0x04E5, 0x0438, 0x0308}, + { 0x045C, 0x043A, 0x0301}, + { 0x04E7, 0x043E, 0x0308}, + { 0x04EF, 0x0443, 0x0304}, + { 0x045E, 0x0443, 0x0306}, + { 0x04F1, 0x0443, 0x0308}, + { 0x04F3, 0x0443, 0x030B}, + { 0x04F5, 0x0447, 0x0308}, + { 0x04F9, 0x044B, 0x0308}, + { 0x04ED, 0x044D, 0x0308}, + { 0x0457, 0x0456, 0x0308}, + { 0x0476, 0x0474, 0x030F}, + { 0x0477, 0x0475, 0x030F}, + { 0x04DA, 0x04D8, 0x0308}, + { 0x04DB, 0x04D9, 0x0308}, + { 0x04EA, 0x04E8, 0x0308}, + { 0x04EB, 0x04E9, 0x0308}, + { 0xFB2E, 0x05D0, 0x05B7}, + { 0xFB2F, 0x05D0, 0x05B8}, + { 0xFB30, 0x05D0, 0x05BC}, + { 0xFB31, 0x05D1, 0x05BC}, + { 0xFB4C, 0x05D1, 0x05BF}, + { 0xFB32, 0x05D2, 0x05BC}, + { 0xFB33, 0x05D3, 0x05BC}, + { 0xFB34, 0x05D4, 0x05BC}, + { 0xFB4B, 0x05D5, 0x05B9}, + { 0xFB35, 0x05D5, 0x05BC}, + { 0xFB36, 0x05D6, 0x05BC}, + { 0xFB38, 0x05D8, 0x05BC}, + { 0xFB1D, 0x05D9, 0x05B4}, + { 0xFB39, 0x05D9, 0x05BC}, + { 0xFB3A, 0x05DA, 0x05BC}, + { 0xFB3B, 0x05DB, 0x05BC}, + { 0xFB4D, 0x05DB, 0x05BF}, + { 0xFB3C, 0x05DC, 0x05BC}, + { 0xFB3E, 0x05DE, 0x05BC}, + { 0xFB40, 0x05E0, 0x05BC}, + { 0xFB41, 0x05E1, 0x05BC}, + { 0xFB43, 0x05E3, 0x05BC}, + { 0xFB44, 0x05E4, 0x05BC}, + { 0xFB4E, 0x05E4, 0x05BF}, + { 0xFB46, 0x05E6, 0x05BC}, + { 0xFB47, 0x05E7, 0x05BC}, + { 0xFB48, 0x05E8, 0x05BC}, + { 0xFB49, 0x05E9, 0x05BC}, + { 0xFB2A, 0x05E9, 0x05C1}, + { 0xFB2B, 0x05E9, 0x05C2}, + { 0xFB4A, 0x05EA, 0x05BC}, + { 0xFB1F, 0x05F2, 0x05B7}, + { 0x0622, 0x0627, 0x0653}, + { 0x0623, 0x0627, 0x0654}, + { 0x0625, 0x0627, 0x0655}, + { 0x0624, 0x0648, 0x0654}, + { 0x0626, 0x064A, 0x0654}, + { 0x06C2, 0x06C1, 0x0654}, + { 0x06D3, 0x06D2, 0x0654}, + { 0x06C0, 0x06D5, 0x0654}, + { 0x0958, 0x0915, 0x093C}, + { 0x0959, 0x0916, 0x093C}, + { 0x095A, 0x0917, 0x093C}, + { 0x095B, 0x091C, 0x093C}, + { 0x095C, 0x0921, 0x093C}, + { 0x095D, 0x0922, 0x093C}, + { 0x0929, 0x0928, 0x093C}, + { 0x095E, 0x092B, 0x093C}, + { 0x095F, 0x092F, 0x093C}, + { 0x0931, 0x0930, 0x093C}, + { 0x0934, 0x0933, 0x093C}, + { 0x09DC, 0x09A1, 0x09BC}, + { 0x09DD, 0x09A2, 0x09BC}, + { 0x09DF, 0x09AF, 0x09BC}, + { 0x09CB, 0x09C7, 0x09BE}, + { 0x09CC, 0x09C7, 0x09D7}, + { 0x0A59, 0x0A16, 0x0A3C}, + { 0x0A5A, 0x0A17, 0x0A3C}, + { 0x0A5B, 0x0A1C, 0x0A3C}, + { 0x0A5E, 0x0A2B, 0x0A3C}, + { 0x0A33, 0x0A32, 0x0A3C}, + { 0x0A36, 0x0A38, 0x0A3C}, + { 0x0B5C, 0x0B21, 0x0B3C}, + { 0x0B5D, 0x0B22, 0x0B3C}, + { 0x0B4B, 0x0B47, 0x0B3E}, + { 0x0B48, 0x0B47, 0x0B56}, + { 0x0B4C, 0x0B47, 0x0B57}, + { 0x0B94, 0x0B92, 0x0BD7}, + { 0x0BCA, 0x0BC6, 0x0BBE}, + { 0x0BCC, 0x0BC6, 0x0BD7}, + { 0x0BCB, 0x0BC7, 0x0BBE}, + { 0x0C48, 0x0C46, 0x0C56}, + { 0x0CC0, 0x0CBF, 0x0CD5}, + { 0x0CCA, 0x0CC6, 0x0CC2}, + { 0x0CC7, 0x0CC6, 0x0CD5}, + { 0x0CC8, 0x0CC6, 0x0CD6}, + { 0x0CCB, 0x0CCA, 0x0CD5}, + { 0x0D4A, 0x0D46, 0x0D3E}, + { 0x0D4C, 0x0D46, 0x0D57}, + { 0x0D4B, 0x0D47, 0x0D3E}, + { 0x0DDA, 0x0DD9, 0x0DCA}, + { 0x0DDC, 0x0DD9, 0x0DCF}, + { 0x0DDE, 0x0DD9, 0x0DDF}, + { 0x0DDD, 0x0DDC, 0x0DCA}, + { 0x0F69, 0x0F40, 0x0FB5}, + { 0x0F43, 0x0F42, 0x0FB7}, + { 0x0F4D, 0x0F4C, 0x0FB7}, + { 0x0F52, 0x0F51, 0x0FB7}, + { 0x0F57, 0x0F56, 0x0FB7}, + { 0x0F5C, 0x0F5B, 0x0FB7}, + { 0x0F73, 0x0F71, 0x0F72}, + { 0x0F75, 0x0F71, 0x0F74}, + { 0x0F81, 0x0F71, 0x0F80}, + { 0x0FB9, 0x0F90, 0x0FB5}, + { 0x0F93, 0x0F92, 0x0FB7}, + { 0x0F9D, 0x0F9C, 0x0FB7}, + { 0x0FA2, 0x0FA1, 0x0FB7}, + { 0x0FA7, 0x0FA6, 0x0FB7}, + { 0x0FAC, 0x0FAB, 0x0FB7}, + { 0x0F76, 0x0FB2, 0x0F80}, + { 0x0F78, 0x0FB3, 0x0F80}, + { 0x1026, 0x1025, 0x102E}, + { 0x1B06, 0x1B05, 0x1B35}, + { 0x1B08, 0x1B07, 0x1B35}, + { 0x1B0A, 0x1B09, 0x1B35}, + { 0x1B0C, 0x1B0B, 0x1B35}, + { 0x1B0E, 0x1B0D, 0x1B35}, + { 0x1B12, 0x1B11, 0x1B35}, + { 0x1B3B, 0x1B3A, 0x1B35}, + { 0x1B3D, 0x1B3C, 0x1B35}, + { 0x1B40, 0x1B3E, 0x1B35}, + { 0x1B41, 0x1B3F, 0x1B35}, + { 0x1B43, 0x1B42, 0x1B35}, + { 0x1E38, 0x1E36, 0x0304}, + { 0x1E39, 0x1E37, 0x0304}, + { 0x1E5C, 0x1E5A, 0x0304}, + { 0x1E5D, 0x1E5B, 0x0304}, + { 0x1E68, 0x1E62, 0x0307}, + { 0x1E69, 0x1E63, 0x0307}, + { 0x1EAC, 0x1EA0, 0x0302}, + { 0x1EB6, 0x1EA0, 0x0306}, + { 0x1EAD, 0x1EA1, 0x0302}, + { 0x1EB7, 0x1EA1, 0x0306}, + { 0x1EC6, 0x1EB8, 0x0302}, + { 0x1EC7, 0x1EB9, 0x0302}, + { 0x1ED8, 0x1ECC, 0x0302}, + { 0x1ED9, 0x1ECD, 0x0302}, + { 0x1F02, 0x1F00, 0x0300}, + { 0x1F04, 0x1F00, 0x0301}, + { 0x1F06, 0x1F00, 0x0342}, + { 0x1F80, 0x1F00, 0x0345}, + { 0x1F03, 0x1F01, 0x0300}, + { 0x1F05, 0x1F01, 0x0301}, + { 0x1F07, 0x1F01, 0x0342}, + { 0x1F81, 0x1F01, 0x0345}, + { 0x1F82, 0x1F02, 0x0345}, + { 0x1F83, 0x1F03, 0x0345}, + { 0x1F84, 0x1F04, 0x0345}, + { 0x1F85, 0x1F05, 0x0345}, + { 0x1F86, 0x1F06, 0x0345}, + { 0x1F87, 0x1F07, 0x0345}, + { 0x1F0A, 0x1F08, 0x0300}, + { 0x1F0C, 0x1F08, 0x0301}, + { 0x1F0E, 0x1F08, 0x0342}, + { 0x1F88, 0x1F08, 0x0345}, + { 0x1F0B, 0x1F09, 0x0300}, + { 0x1F0D, 0x1F09, 0x0301}, + { 0x1F0F, 0x1F09, 0x0342}, + { 0x1F89, 0x1F09, 0x0345}, + { 0x1F8A, 0x1F0A, 0x0345}, + { 0x1F8B, 0x1F0B, 0x0345}, + { 0x1F8C, 0x1F0C, 0x0345}, + { 0x1F8D, 0x1F0D, 0x0345}, + { 0x1F8E, 0x1F0E, 0x0345}, + { 0x1F8F, 0x1F0F, 0x0345}, + { 0x1F12, 0x1F10, 0x0300}, + { 0x1F14, 0x1F10, 0x0301}, + { 0x1F13, 0x1F11, 0x0300}, + { 0x1F15, 0x1F11, 0x0301}, + { 0x1F1A, 0x1F18, 0x0300}, + { 0x1F1C, 0x1F18, 0x0301}, + { 0x1F1B, 0x1F19, 0x0300}, + { 0x1F1D, 0x1F19, 0x0301}, + { 0x1F22, 0x1F20, 0x0300}, + { 0x1F24, 0x1F20, 0x0301}, + { 0x1F26, 0x1F20, 0x0342}, + { 0x1F90, 0x1F20, 0x0345}, + { 0x1F23, 0x1F21, 0x0300}, + { 0x1F25, 0x1F21, 0x0301}, + { 0x1F27, 0x1F21, 0x0342}, + { 0x1F91, 0x1F21, 0x0345}, + { 0x1F92, 0x1F22, 0x0345}, + { 0x1F93, 0x1F23, 0x0345}, + { 0x1F94, 0x1F24, 0x0345}, + { 0x1F95, 0x1F25, 0x0345}, + { 0x1F96, 0x1F26, 0x0345}, + { 0x1F97, 0x1F27, 0x0345}, + { 0x1F2A, 0x1F28, 0x0300}, + { 0x1F2C, 0x1F28, 0x0301}, + { 0x1F2E, 0x1F28, 0x0342}, + { 0x1F98, 0x1F28, 0x0345}, + { 0x1F2B, 0x1F29, 0x0300}, + { 0x1F2D, 0x1F29, 0x0301}, + { 0x1F2F, 0x1F29, 0x0342}, + { 0x1F99, 0x1F29, 0x0345}, + { 0x1F9A, 0x1F2A, 0x0345}, + { 0x1F9B, 0x1F2B, 0x0345}, + { 0x1F9C, 0x1F2C, 0x0345}, + { 0x1F9D, 0x1F2D, 0x0345}, + { 0x1F9E, 0x1F2E, 0x0345}, + { 0x1F9F, 0x1F2F, 0x0345}, + { 0x1F32, 0x1F30, 0x0300}, + { 0x1F34, 0x1F30, 0x0301}, + { 0x1F36, 0x1F30, 0x0342}, + { 0x1F33, 0x1F31, 0x0300}, + { 0x1F35, 0x1F31, 0x0301}, + { 0x1F37, 0x1F31, 0x0342}, + { 0x1F3A, 0x1F38, 0x0300}, + { 0x1F3C, 0x1F38, 0x0301}, + { 0x1F3E, 0x1F38, 0x0342}, + { 0x1F3B, 0x1F39, 0x0300}, + { 0x1F3D, 0x1F39, 0x0301}, + { 0x1F3F, 0x1F39, 0x0342}, + { 0x1F42, 0x1F40, 0x0300}, + { 0x1F44, 0x1F40, 0x0301}, + { 0x1F43, 0x1F41, 0x0300}, + { 0x1F45, 0x1F41, 0x0301}, + { 0x1F4A, 0x1F48, 0x0300}, + { 0x1F4C, 0x1F48, 0x0301}, + { 0x1F4B, 0x1F49, 0x0300}, + { 0x1F4D, 0x1F49, 0x0301}, + { 0x1F52, 0x1F50, 0x0300}, + { 0x1F54, 0x1F50, 0x0301}, + { 0x1F56, 0x1F50, 0x0342}, + { 0x1F53, 0x1F51, 0x0300}, + { 0x1F55, 0x1F51, 0x0301}, + { 0x1F57, 0x1F51, 0x0342}, + { 0x1F5B, 0x1F59, 0x0300}, + { 0x1F5D, 0x1F59, 0x0301}, + { 0x1F5F, 0x1F59, 0x0342}, + { 0x1F62, 0x1F60, 0x0300}, + { 0x1F64, 0x1F60, 0x0301}, + { 0x1F66, 0x1F60, 0x0342}, + { 0x1FA0, 0x1F60, 0x0345}, + { 0x1F63, 0x1F61, 0x0300}, + { 0x1F65, 0x1F61, 0x0301}, + { 0x1F67, 0x1F61, 0x0342}, + { 0x1FA1, 0x1F61, 0x0345}, + { 0x1FA2, 0x1F62, 0x0345}, + { 0x1FA3, 0x1F63, 0x0345}, + { 0x1FA4, 0x1F64, 0x0345}, + { 0x1FA5, 0x1F65, 0x0345}, + { 0x1FA6, 0x1F66, 0x0345}, + { 0x1FA7, 0x1F67, 0x0345}, + { 0x1F6A, 0x1F68, 0x0300}, + { 0x1F6C, 0x1F68, 0x0301}, + { 0x1F6E, 0x1F68, 0x0342}, + { 0x1FA8, 0x1F68, 0x0345}, + { 0x1F6B, 0x1F69, 0x0300}, + { 0x1F6D, 0x1F69, 0x0301}, + { 0x1F6F, 0x1F69, 0x0342}, + { 0x1FA9, 0x1F69, 0x0345}, + { 0x1FAA, 0x1F6A, 0x0345}, + { 0x1FAB, 0x1F6B, 0x0345}, + { 0x1FAC, 0x1F6C, 0x0345}, + { 0x1FAD, 0x1F6D, 0x0345}, + { 0x1FAE, 0x1F6E, 0x0345}, + { 0x1FAF, 0x1F6F, 0x0345}, + { 0x1FB2, 0x1F70, 0x0345}, + { 0x1FC2, 0x1F74, 0x0345}, + { 0x1FF2, 0x1F7C, 0x0345}, + { 0x1FB7, 0x1FB6, 0x0345}, + { 0x1FCD, 0x1FBF, 0x0300}, + { 0x1FCE, 0x1FBF, 0x0301}, + { 0x1FCF, 0x1FBF, 0x0342}, + { 0x1FC7, 0x1FC6, 0x0345}, + { 0x1FF7, 0x1FF6, 0x0345}, + { 0x1FDD, 0x1FFE, 0x0300}, + { 0x1FDE, 0x1FFE, 0x0301}, + { 0x1FDF, 0x1FFE, 0x0342}, + { 0x219A, 0x2190, 0x0338}, + { 0x219B, 0x2192, 0x0338}, + { 0x21AE, 0x2194, 0x0338}, + { 0x21CD, 0x21D0, 0x0338}, + { 0x21CF, 0x21D2, 0x0338}, + { 0x21CE, 0x21D4, 0x0338}, + { 0x2204, 0x2203, 0x0338}, + { 0x2209, 0x2208, 0x0338}, + { 0x220C, 0x220B, 0x0338}, + { 0x2224, 0x2223, 0x0338}, + { 0x2226, 0x2225, 0x0338}, + { 0x2241, 0x223C, 0x0338}, + { 0x2244, 0x2243, 0x0338}, + { 0x2247, 0x2245, 0x0338}, + { 0x2249, 0x2248, 0x0338}, + { 0x226D, 0x224D, 0x0338}, + { 0x2262, 0x2261, 0x0338}, + { 0x2270, 0x2264, 0x0338}, + { 0x2271, 0x2265, 0x0338}, + { 0x2274, 0x2272, 0x0338}, + { 0x2275, 0x2273, 0x0338}, + { 0x2278, 0x2276, 0x0338}, + { 0x2279, 0x2277, 0x0338}, + { 0x2280, 0x227A, 0x0338}, + { 0x2281, 0x227B, 0x0338}, + { 0x22E0, 0x227C, 0x0338}, + { 0x22E1, 0x227D, 0x0338}, + { 0x2284, 0x2282, 0x0338}, + { 0x2285, 0x2283, 0x0338}, + { 0x2288, 0x2286, 0x0338}, + { 0x2289, 0x2287, 0x0338}, + { 0x22E2, 0x2291, 0x0338}, + { 0x22E3, 0x2292, 0x0338}, + { 0x22AC, 0x22A2, 0x0338}, + { 0x22AD, 0x22A8, 0x0338}, + { 0x22AE, 0x22A9, 0x0338}, + { 0x22AF, 0x22AB, 0x0338}, + { 0x22EA, 0x22B2, 0x0338}, + { 0x22EB, 0x22B3, 0x0338}, + { 0x22EC, 0x22B4, 0x0338}, + { 0x22ED, 0x22B5, 0x0338}, + { 0x2ADC, 0x2ADD, 0x0338}, + { 0x3094, 0x3046, 0x3099}, + { 0x304C, 0x304B, 0x3099}, + { 0x304E, 0x304D, 0x3099}, + { 0x3050, 0x304F, 0x3099}, + { 0x3052, 0x3051, 0x3099}, + { 0x3054, 0x3053, 0x3099}, + { 0x3056, 0x3055, 0x3099}, + { 0x3058, 0x3057, 0x3099}, + { 0x305A, 0x3059, 0x3099}, + { 0x305C, 0x305B, 0x3099}, + { 0x305E, 0x305D, 0x3099}, + { 0x3060, 0x305F, 0x3099}, + { 0x3062, 0x3061, 0x3099}, + { 0x3065, 0x3064, 0x3099}, + { 0x3067, 0x3066, 0x3099}, + { 0x3069, 0x3068, 0x3099}, + { 0x3070, 0x306F, 0x3099}, + { 0x3071, 0x306F, 0x309A}, + { 0x3073, 0x3072, 0x3099}, + { 0x3074, 0x3072, 0x309A}, + { 0x3076, 0x3075, 0x3099}, + { 0x3077, 0x3075, 0x309A}, + { 0x3079, 0x3078, 0x3099}, + { 0x307A, 0x3078, 0x309A}, + { 0x307C, 0x307B, 0x3099}, + { 0x307D, 0x307B, 0x309A}, + { 0x309E, 0x309D, 0x3099}, + { 0x30F4, 0x30A6, 0x3099}, + { 0x30AC, 0x30AB, 0x3099}, + { 0x30AE, 0x30AD, 0x3099}, + { 0x30B0, 0x30AF, 0x3099}, + { 0x30B2, 0x30B1, 0x3099}, + { 0x30B4, 0x30B3, 0x3099}, + { 0x30B6, 0x30B5, 0x3099}, + { 0x30B8, 0x30B7, 0x3099}, + { 0x30BA, 0x30B9, 0x3099}, + { 0x30BC, 0x30BB, 0x3099}, + { 0x30BE, 0x30BD, 0x3099}, + { 0x30C0, 0x30BF, 0x3099}, + { 0x30C2, 0x30C1, 0x3099}, + { 0x30C5, 0x30C4, 0x3099}, + { 0x30C7, 0x30C6, 0x3099}, + { 0x30C9, 0x30C8, 0x3099}, + { 0x30D0, 0x30CF, 0x3099}, + { 0x30D1, 0x30CF, 0x309A}, + { 0x30D3, 0x30D2, 0x3099}, + { 0x30D4, 0x30D2, 0x309A}, + { 0x30D6, 0x30D5, 0x3099}, + { 0x30D7, 0x30D5, 0x309A}, + { 0x30D9, 0x30D8, 0x3099}, + { 0x30DA, 0x30D8, 0x309A}, + { 0x30DC, 0x30DB, 0x3099}, + { 0x30DD, 0x30DB, 0x309A}, + { 0x30F7, 0x30EF, 0x3099}, + { 0x30F8, 0x30F0, 0x3099}, + { 0x30F9, 0x30F1, 0x3099}, + { 0x30FA, 0x30F2, 0x3099}, + { 0x30FE, 0x30FD, 0x3099}, + { 0xFB2C, 0xFB49, 0x05C1}, + { 0xFB2D, 0xFB49, 0x05C2}, + }; + + private static final int UNICODE_SHIFT = 21; + + public static char precompose(char base, char comb) { + int min = 0; + int max = precompositions.length - 1; + int mid; + long sought = base << UNICODE_SHIFT | comb; + long that; + + while (max >= min) { + mid = (min + max) / 2; + that = precompositions[mid][1] << UNICODE_SHIFT | precompositions[mid][2]; + + if (that < sought) + min = mid + 1; + else if (that > sought) + max = mid - 1; + else + return precompositions[mid][0]; + } + + // No match; return character without combiner + return base; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/de/mud/terminal/VDUBuffer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/de/mud/terminal/VDUBuffer.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,880 @@ +/* + * This file is part of "JTA - Telnet/SSH for the JAVA(tm) platform". + * + * (c) Matthias L. Jugel, Marcus Meißner 1996-2005. All Rights Reserved. + * + * Please visit http://javatelnet.org/ for updates and contact. + * + * --LICENSE NOTICE-- + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * --LICENSE NOTICE-- + * + */ + +package de.mud.terminal; + +import java.util.Arrays; + +/** + * Implementation of a Video Display Unit (VDU) buffer. This class contains + * all methods to manipulate the buffer that stores characters and their + * attributes as well as the regions displayed. + * + * @author Matthias L. Jugel, Marcus Meißner + * @version $Id: VDUBuffer.java 503 2005-10-24 07:34:13Z marcus $ + */ +public class VDUBuffer { + + /** a generic display that should redraw on demand */ + protected VDUDisplay display; + + /** Enable debug messages. */ + public final static int debug = 0; + + public int height, width; /* rows and columns */ + public boolean[] update; /* contains the lines that need update */ + public char[][] charArray; /* contains the characters */ + public int[][] charAttributes; /* contains character attrs */ + public int bufSize; + public int maxBufSize; /* buffer sizes */ + public int screenBase; /* the actual screen start */ + public int windowBase; /* where the start displaying */ + public int scrollMarker; /* marks the last line inserted */ + + private int topMargin; /* top scroll margin */ + private int bottomMargin; /* bottom scroll margin */ + + // cursor variables + protected boolean showcursor = true; + protected int cursorX, cursorY; + + /** Scroll up when inserting a line. */ + public final static boolean SCROLL_UP = false; + /** Scroll down when inserting a line. */ + public final static boolean SCROLL_DOWN = true; + + /* Attributes bit-field usage: + * + * 8421 8421 8421 8421 8421 8421 8421 8421 + * |||| |||| |||| |||| |||| |||| |||| |||`- Bold + * |||| |||| |||| |||| |||| |||| |||| ||`-- Underline + * |||| |||| |||| |||| |||| |||| |||| |`--- Invert + * |||| |||| |||| |||| |||| |||| |||| `---- Low + * |||| |||| |||| |||| |||| |||| |||`------ Invisible + * |||| |||| |||| |||| ||`+-++++-+++------- Foreground Color + * |||| |||| |`++-++++-++------------------ Background Color + * |||| |||| `----------------------------- Fullwidth character + * `+++-++++------------------------------- Reserved for future use + */ + + /** Make character normal. */ + public final static int NORMAL = 0x00; + /** Make character bold. */ + public final static int BOLD = 0x01; + /** Underline character. */ + public final static int UNDERLINE = 0x02; + /** Invert character. */ + public final static int INVERT = 0x04; + /** Lower intensity character. */ + public final static int LOW = 0x08; + /** Invisible character. */ + public final static int INVISIBLE = 0x10; + /** Unicode full-width character (CJK, et al.) */ + public final static int FULLWIDTH = 0x8000000; + + /** how much to left shift the foreground color */ + public final static int COLOR_FG_SHIFT = 5; + /** how much to left shift the background color */ + public final static int COLOR_BG_SHIFT = 14; + /** color mask */ + public final static int COLOR = 0x7fffe0; /* 0000 0000 0111 1111 1111 1111 1110 0000 */ + /** foreground color mask */ + public final static int COLOR_FG = 0x003fe0; /* 0000 0000 0000 0000 0011 1111 1110 0000 */ + /** background color mask */ + public final static int COLOR_BG = 0x7fc000; /* 0000 0000 0111 1111 1100 0000 0000 0000 */ + + /** + * Create a new video display buffer with the passed width and height in + * characters. + * @param width the length of the character lines + * @param height the amount of lines on the screen + */ + public VDUBuffer(int width, int height) { + // set the display screen size + setScreenSize(width, height, false); + } + + /** + * Create a standard video display buffer with 80 columns and 24 lines. + */ + public VDUBuffer() { + this(80, 24); + } + + /** + * Put a character on the screen with normal font and outline. + * The character previously on that position will be overwritten. + * You need to call redraw() to update the screen. + * @param c x-coordinate (column) + * @param l y-coordinate (line) + * @param ch the character to show on the screen + * @see #insertChar + * @see #deleteChar + * @see #redraw + */ + public void putChar(int c, int l, char ch) { + putChar(c, l, ch, NORMAL); + } + + /** + * Put a character on the screen with specific font and outline. + * The character previously on that position will be overwritten. + * You need to call redraw() to update the screen. + * @param c x-coordinate (column) + * @param l y-coordinate (line) + * @param ch the character to show on the screen + * @param attributes the character attributes + * @see #BOLD + * @see #UNDERLINE + * @see #INVERT + * @see #INVISIBLE + * @see #NORMAL + * @see #LOW + * @see #insertChar + * @see #deleteChar + * @see #redraw + */ + + public void putChar(int c, int l, char ch, int attributes) { + int ll = screenBase + l; + + if ((ll >= bufSize) || (c >= width)) return; // ignore characters outside our buffer + + charArray[ll][c] = ch; + charAttributes[ll][c] = attributes; + + if (l < height) + update[l + 1] = true; + } + + /** + * Get the character at the specified position. + * @param c x-coordinate (column) + * @param l y-coordinate (line) + * @see #putChar + */ + public char getChar(int c, int l) { + return charArray[screenBase + l][c]; + } + + /** + * Get the attributes for the specified position. + * @param c x-coordinate (column) + * @param l y-coordinate (line) + * @see #putChar + */ + public int getAttributes(int c, int l) { + return charAttributes[screenBase + l][c]; + } + + /** + * Insert a character at a specific position on the screen. + * All character right to from this position will be moved one to the right. + * You need to call redraw() to update the screen. + * @param c x-coordinate (column) + * @param l y-coordinate (line) + * @param ch the character to insert + * @param attributes the character attributes + * @see #BOLD + * @see #UNDERLINE + * @see #INVERT + * @see #INVISIBLE + * @see #NORMAL + * @see #LOW + * @see #putChar + * @see #deleteChar + * @see #redraw + */ + public void insertChar(int c, int l, char ch, int attributes) { + System.arraycopy(charArray[screenBase + l], c, + charArray[screenBase + l], c + 1, width - c - 1); + System.arraycopy(charAttributes[screenBase + l], c, + charAttributes[screenBase + l], c + 1, width - c - 1); + putChar(c, l, ch, attributes); + } + + /** + * Delete a character at a given position on the screen. + * All characters right to the position will be moved one to the left. + * You need to call redraw() to update the screen. + * @param c x-coordinate (column) + * @param l y-coordinate (line) + * @see #putChar + * @see #insertChar + * @see #redraw + */ + public void deleteChar(int c, int l) { + if (c < width - 1) { + System.arraycopy(charArray[screenBase + l], c + 1, + charArray[screenBase + l], c, width - c - 1); + System.arraycopy(charAttributes[screenBase + l], c + 1, + charAttributes[screenBase + l], c, width - c - 1); + } + + putChar(width - 1, l, (char) 0); + } + + /** + * Put a String at a specific position. Any characters previously on that + * position will be overwritten. You need to call redraw() for screen update. + * @param c x-coordinate (column) + * @param l y-coordinate (line) + * @param s the string to be shown on the screen + * @see #BOLD + * @see #UNDERLINE + * @see #INVERT + * @see #INVISIBLE + * @see #NORMAL + * @see #LOW + * @see #putChar + * @see #insertLine + * @see #deleteLine + * @see #redraw + */ + public void putString(int c, int l, String s) { + putString(c, l, s, NORMAL); + } + + /** + * Put a String at a specific position giving all characters the same + * attributes. Any characters previously on that position will be + * overwritten. You need to call redraw() to update the screen. + * @param c x-coordinate (column) + * @param l y-coordinate (line) + * @param s the string to be shown on the screen + * @param attributes character attributes + * @see #BOLD + * @see #UNDERLINE + * @see #INVERT + * @see #INVISIBLE + * @see #NORMAL + * @see #LOW + * @see #putChar + * @see #insertLine + * @see #deleteLine + * @see #redraw + */ + public void putString(int c, int l, String s, int attributes) { + for (int i = 0; i < s.length() && c + i < width; i++) + putChar(c + i, l, s.charAt(i), attributes); + } + + /** + * Insert a blank line at a specific position. + * The current line and all previous lines are scrolled one line up. The + * top line is lost. You need to call redraw() to update the screen. + * @param l the y-coordinate to insert the line + * @see #deleteLine + * @see #redraw + */ + public void insertLine(int l) { + insertLine(l, 1, SCROLL_UP); + } + + /** + * Insert blank lines at a specific position. + * You need to call redraw() to update the screen + * @param l the y-coordinate to insert the line + * @param n amount of lines to be inserted + * @see #deleteLine + * @see #redraw + */ + public void insertLine(int l, int n) { + insertLine(l, n, SCROLL_UP); + } + + /** + * Insert a blank line at a specific position. Scroll text according to + * the argument. + * You need to call redraw() to update the screen + * @param l the y-coordinate to insert the line + * @param scrollDown scroll down + * @see #deleteLine + * @see #SCROLL_UP + * @see #SCROLL_DOWN + * @see #redraw + */ + public void insertLine(int l, boolean scrollDown) { + insertLine(l, 1, scrollDown); + } + + /** + * Insert blank lines at a specific position. + * The current line and all previous lines are scrolled one line up. The + * top line is lost. You need to call redraw() to update the screen. + * @param l the y-coordinate to insert the line + * @param n number of lines to be inserted + * @param scrollDown scroll down + * @see #deleteLine + * @see #SCROLL_UP + * @see #SCROLL_DOWN + * @see #redraw + */ + + public synchronized void insertLine(int l, int n, boolean scrollDown) { + char cbuf[][] = null; + int abuf[][] = null; + int offset = 0; + int oldBase = screenBase; + int newScreenBase = screenBase; + int newWindowBase = windowBase; + int newBufSize = bufSize; + + if (l > bottomMargin) /* We do not scroll below bottom margin (below the scrolling region). */ + return; + + int top = (l < topMargin ? + 0 : (l > bottomMargin ? + (bottomMargin + 1 < height ? + bottomMargin + 1 : height - 1) : topMargin)); + int bottom = (l > bottomMargin ? + height - 1 : (l < topMargin ? + (topMargin > 0 ? + topMargin - 1 : 0) : bottomMargin)); + + // System.out.println("l is "+l+", top is "+top+", bottom is "+bottom+", bottomargin is "+bottomMargin+", topMargin is "+topMargin); + if (scrollDown) { + if (n > (bottom - top)) n = (bottom - top); + + int size = bottom - l - (n - 1); + + if (size < 0) size = 0; + + cbuf = new char[size][]; + abuf = new int[size][]; + System.arraycopy(charArray, oldBase + l, cbuf, 0, bottom - l - (n - 1)); + System.arraycopy(charAttributes, oldBase + l, + abuf, 0, bottom - l - (n - 1)); + System.arraycopy(cbuf, 0, charArray, oldBase + l + n, + bottom - l - (n - 1)); + System.arraycopy(abuf, 0, charAttributes, oldBase + l + n, + bottom - l - (n - 1)); + cbuf = charArray; + abuf = charAttributes; + } + else { + try { + if (n > (bottom - top) + 1) n = (bottom - top) + 1; + + if (bufSize < maxBufSize) { + if (bufSize + n > maxBufSize) { + offset = n - (maxBufSize - bufSize); + scrollMarker += offset; + newBufSize = maxBufSize; + newScreenBase = maxBufSize - height - 1; + newWindowBase = screenBase; + } + else { + scrollMarker += n; + newScreenBase += n; + newWindowBase += n; + newBufSize += n; + } + + cbuf = new char[newBufSize][]; + abuf = new int[newBufSize][]; + } + else { + offset = n; + cbuf = charArray; + abuf = charAttributes; + } + + // copy anything from the top of the buffer (+offset) to the new top + // up to the screenBase. + if (oldBase > 0) { + System.arraycopy(charArray, offset, + cbuf, 0, + oldBase - offset); + System.arraycopy(charAttributes, offset, + abuf, 0, + oldBase - offset); + } + + // copy anything from the top of the screen (screenBase) up to the + // topMargin to the new screen + if (top > 0) { + System.arraycopy(charArray, oldBase, + cbuf, newScreenBase, + top); + System.arraycopy(charAttributes, oldBase, + abuf, newScreenBase, + top); + } + + // copy anything from the topMargin up to the amount of lines inserted + // to the gap left over between scrollback buffer and screenBase + if (oldBase >= 0) { + System.arraycopy(charArray, oldBase + top, + cbuf, oldBase - offset, + n); + System.arraycopy(charAttributes, oldBase + top, + abuf, oldBase - offset, + n); + } + + // copy anything from topMargin + n up to the line linserted to the + // topMargin + System.arraycopy(charArray, oldBase + top + n, + cbuf, newScreenBase + top, + l - top - (n - 1)); + System.arraycopy(charAttributes, oldBase + top + n, + abuf, newScreenBase + top, + l - top - (n - 1)); + + // + // copy the all lines next to the inserted to the new buffer + if (l < height - 1) { + System.arraycopy(charArray, oldBase + l + 1, + cbuf, newScreenBase + l + 1, + (height - 1) - l); + System.arraycopy(charAttributes, oldBase + l + 1, + abuf, newScreenBase + l + 1, + (height - 1) - l); + } + } + catch (ArrayIndexOutOfBoundsException e) { + // this should not happen anymore, but I will leave the code + // here in case something happens anyway. That code above is + // so complex I always have a hard time understanding what + // I did, even though there are comments + System.err.println("*** Error while scrolling up:"); + System.err.println("--- BEGIN STACK TRACE ---"); + e.printStackTrace(); + System.err.println("--- END STACK TRACE ---"); + System.err.println("bufSize=" + bufSize + ", maxBufSize=" + maxBufSize); + System.err.println("top=" + top + ", bottom=" + bottom); + System.err.println("n=" + n + ", l=" + l); + System.err.println("screenBase=" + screenBase + ", windowBase=" + windowBase); + System.err.println("newScreenBase=" + newScreenBase + ", newWindowBase=" + newWindowBase); + System.err.println("oldBase=" + oldBase); + System.err.println("size.width=" + width + ", size.height=" + height); + System.err.println("abuf.length=" + abuf.length + ", cbuf.length=" + cbuf.length); + System.err.println("*** done dumping debug information"); + } + } + + // this is a little helper to mark the scrolling + scrollMarker -= n; + + for (int i = 0; i < n; i++) { + cbuf[(newScreenBase + l) + (scrollDown ? i : -i)] = new char[width]; + Arrays.fill(cbuf[(newScreenBase + l) + (scrollDown ? i : -i)], ' '); + abuf[(newScreenBase + l) + (scrollDown ? i : -i)] = new int[width]; + } + + charArray = cbuf; + charAttributes = abuf; + screenBase = newScreenBase; + windowBase = newWindowBase; + bufSize = newBufSize; + + if (scrollDown) + markLine(l, bottom - l + 1); + else + markLine(top, l - top + 1); + + display.updateScrollBar(); + } + + /** + * Delete a line at a specific position. Subsequent lines will be scrolled + * up to fill the space and a blank line is inserted at the end of the + * screen. + * @param l the y-coordinate to insert the line + * @see #deleteLine + */ + public void deleteLine(int l) { + int bottom = (l > bottomMargin ? height - 1 : + (l < topMargin ? topMargin : bottomMargin + 1)); + int numRows = bottom - l - 1; + char[] discardedChars = charArray[screenBase + l]; + int[] discardedAttributes = charAttributes[screenBase + l]; + + if (numRows > 0) { + System.arraycopy(charArray, screenBase + l + 1, + charArray, screenBase + l, numRows); + System.arraycopy(charAttributes, screenBase + l + 1, + charAttributes, screenBase + l, numRows); + } + + int newBottomRow = screenBase + bottom - 1; + charArray[newBottomRow] = discardedChars; + charAttributes[newBottomRow] = discardedAttributes; + Arrays.fill(charArray[newBottomRow], ' '); + Arrays.fill(charAttributes[newBottomRow], 0); + markLine(l, bottom - l); + } + + /** + * Delete a rectangular portion of the screen. + * You need to call redraw() to update the screen. + * @param c x-coordinate (column) + * @param l y-coordinate (row) + * @param w with of the area in characters + * @param h height of the area in characters + * @param curAttr attribute to fill + * @see #deleteChar + * @see #deleteLine + * @see #redraw + */ + public void deleteArea(int c, int l, int w, int h, int curAttr) { + int endColumn = c + w; + int targetRow = screenBase + l; + + for (int i = 0; i < h && l + i < height; i++) { + Arrays.fill(charAttributes[targetRow], c, endColumn, curAttr); + Arrays.fill(charArray[targetRow], c, endColumn, ' '); + targetRow++; + } + + markLine(l, h); + } + + /** + * Delete a rectangular portion of the screen. + * You need to call redraw() to update the screen. + * @param c x-coordinate (column) + * @param l y-coordinate (row) + * @param w with of the area in characters + * @param h height of the area in characters + * @see #deleteChar + * @see #deleteLine + * @see #redraw + */ + public void deleteArea(int c, int l, int w, int h) { + deleteArea(c, l, w, h, 0); + } + + /** + * Sets whether the cursor is visible or not. + * @param doshow + */ + public void showCursor(boolean doshow) { + showcursor = doshow; + } + + /** + * Check whether the cursor is currently visible. + * @return visibility + */ + public boolean isCursorVisible() { + return showcursor; + } + + /** + * Puts the cursor at the specified position. + * @param c column + * @param l line + */ + public void setCursorPosition(int c, int l) { + cursorX = (c > width - 1) ? width - 1 : c; + cursorY = (l > height - 1) ? height - 1 : l; + } + + /** + * Get the current column of the cursor position. + */ + public int getCursorColumn() { + return cursorX; + } + + /** + * Get the current line of the cursor position. + */ + public int getCursorRow() { + return cursorY; + } + + /** + * Set the current window base. This allows to view the scrollback buffer. + * @param line the line where the screen window starts + * @see #setBufferSize + * @see #getBufferSize + */ + public void setWindowBase(int line) { + if (line > screenBase) + line = screenBase; + else if (line < 0) line = 0; + + windowBase = line; + update[0] = true; + redraw(); + } + + /** + * Get the current window base. + * @see #setWindowBase + */ + public int getWindowBase() { + return windowBase; + } + + /** + * Set the scroll margins simultaneously. If they're out of bounds, trim them. + * @param l1 line that is the top + * @param l2 line that is the bottom + */ + public void setMargins(int l1, int l2) { + if (l1 > l2) + return; + + if (l1 < 0) + l1 = 0; + + if (l2 >= height) + l2 = height - 1; + + topMargin = l1; + bottomMargin = l2; + } + + /** + * Set the top scroll margin for the screen. If the current bottom margin + * is smaller it will become the top margin and the line will become the + * bottom margin. + * @param l line that is the margin + */ + public void setTopMargin(int l) { + if (l > bottomMargin) { + topMargin = bottomMargin; + bottomMargin = l; + } + else + topMargin = l; + + if (topMargin < 0) topMargin = 0; + + if (bottomMargin >= height) bottomMargin = height - 1; + } + + /** + * Get the top scroll margin. + */ + public int getTopMargin() { + return topMargin; + } + + /** + * Set the bottom scroll margin for the screen. If the current top margin + * is bigger it will become the bottom margin and the line will become the + * top margin. + * @param l line that is the margin + */ + public void setBottomMargin(int l) { + if (l < topMargin) { + bottomMargin = topMargin; + topMargin = l; + } + else + bottomMargin = l; + + if (topMargin < 0) topMargin = 0; + + if (bottomMargin >= height) bottomMargin = height - 1; + } + + /** + * Get the bottom scroll margin. + */ + public int getBottomMargin() { + return bottomMargin; + } + + /** + * Set scrollback buffer size. + * @param amount new size of the buffer + */ + public void setBufferSize(int amount) { + if (amount < height) amount = height; + + if (amount < maxBufSize) { + char cbuf[][] = new char[amount][width]; + int abuf[][] = new int[amount][width]; + int copyStart = bufSize - amount < 0 ? 0 : bufSize - amount; + int copyCount = bufSize - amount < 0 ? bufSize : amount; + + if (charArray != null) + System.arraycopy(charArray, copyStart, cbuf, 0, copyCount); + + if (charAttributes != null) + System.arraycopy(charAttributes, copyStart, abuf, 0, copyCount); + + charArray = cbuf; + charAttributes = abuf; + bufSize = copyCount; + screenBase = bufSize - height; + windowBase = screenBase; + } + + maxBufSize = amount; + update[0] = true; + redraw(); + } + + /** + * Retrieve current scrollback buffer size. + * @see #setBufferSize + */ + public int getBufferSize() { + return bufSize; + } + + /** + * Retrieve maximum buffer Size. + * @see #getBufferSize + */ + public int getMaxBufferSize() { + return maxBufSize; + } + + /** + * Change the size of the screen. This will include adjustment of the + * scrollback buffer. + * @param w of the screen + * @param h of the screen + */ + public void setScreenSize(int w, int h, boolean broadcast) { + char cbuf[][]; + int abuf[][]; + int maxSize = bufSize; + + if (w < 1 || h < 1) return; + + if (debug > 0) + System.err.println("VDU: screen size [" + w + "," + h + "]"); + + if (h > maxBufSize) + maxBufSize = h; + + if (h > bufSize) { + bufSize = h; + screenBase = 0; + windowBase = 0; + } + + if (windowBase + h >= bufSize) + windowBase = bufSize - h; + + if (screenBase + h >= bufSize) + screenBase = bufSize - h; + + cbuf = new char[bufSize][w]; + abuf = new int[bufSize][w]; + + for (int i = 0; i < bufSize; i++) { + Arrays.fill(cbuf[i], ' '); + } + + if (bufSize < maxSize) + maxSize = bufSize; + + int rowLength; + + if (charArray != null && charAttributes != null) { + for (int i = 0; i < maxSize && charArray[i] != null; i++) { + rowLength = charArray[i].length; + System.arraycopy(charArray[i], 0, cbuf[i], 0, + w < rowLength ? w : rowLength); + System.arraycopy(charAttributes[i], 0, abuf[i], 0, + w < rowLength ? w : rowLength); + } + } + + int C = getCursorColumn(); + + if (C < 0) + C = 0; + else if (C >= width) + C = width - 1; + + int R = getCursorRow(); + + if (R < 0) + R = 0; + else if (R >= height) + R = height - 1; + + setCursorPosition(C, R); + charArray = cbuf; + charAttributes = abuf; + width = w; + height = h; + topMargin = 0; + bottomMargin = h - 1; + update = new boolean[h + 1]; + update[0] = true; + /* FIXME: ??? + if(resizeStrategy == RESIZE_FONT) + setBounds(getBounds()); + */ + } + + /** + * Get amount of rows on the screen. + */ + public int getRows() { + return height; + } + + /** + * Get amount of columns on the screen. + */ + public int getColumns() { + return width; + } + + /** + * Mark lines to be updated with redraw(). + * @param l starting line + * @param n amount of lines to be updated + * @see #redraw + */ + public void markLine(int l, int n) { + for (int i = 0; (i < n) && (l + i < height); i++) + update[l + i + 1] = true; + } + +// private static int checkBounds(int value, int lower, int upper) { +// if (value < lower) +// return lower; +// else if (value > upper) +// return upper; +// else +// return value; +// } + + public void setDisplay(VDUDisplay display) { + this.display = display; + } + + /** + * Trigger a redraw on the display. + */ + protected void redraw() { + if (display != null) + display.redraw(); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/de/mud/terminal/VDUDisplay.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/de/mud/terminal/VDUDisplay.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,37 @@ +/* + * This file is part of "JTA - Telnet/SSH for the JAVA(tm) platform". + * + * (c) Matthias L. Jugel, Marcus Meißner 1996-2005. All Rights Reserved. + * + * Please visit http://javatelnet.org/ for updates and contact. + * + * --LICENSE NOTICE-- + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * --LICENSE NOTICE-- + * + */ + +package de.mud.terminal; + +/** + * Generic display + */ +public interface VDUDisplay { + public void redraw(); + public void updateScrollBar(); + + public void setColor(int index, int red, int green, int blue); + public void resetColors(); +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/de/mud/terminal/VDUInput.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/de/mud/terminal/VDUInput.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,82 @@ +/* + * This file is part of "JTA - Telnet/SSH for the JAVA(tm) platform". + * + * (c) Matthias L. Jugel, Marcus Meißner 1996-2005. All Rights Reserved. + * + * Please visit http://javatelnet.org/ for updates and contact. + * + * --LICENSE NOTICE-- + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * --LICENSE NOTICE-- + * + */ +package de.mud.terminal; + +import java.util.Properties; + +/** + * An interface for a terminal that accepts input from keyboard and mouse. + * + * @author Matthias L. Jugel, Marcus Meißner + * @version $Id: VDUInput.java 499 2005-09-29 08:24:54Z leo $ + */ +public interface VDUInput { + + public final static int KEY_CONTROL = 0x01; + public final static int KEY_SHIFT = 0x02; + public final static int KEY_ALT = 0x04; + public final static int KEY_ACTION = 0x08; + + + + /** + * Direct access to writing data ... + * @param b + */ + void write(byte b[]); + + /** + * Terminal is mouse-aware and requires (x,y) coordinates of + * on the terminal (character coordinates) and the button clicked. + * @param x + * @param y + * @param modifiers + */ + void mousePressed(int x, int y, int modifiers); + + /** + * Terminal is mouse-aware and requires the coordinates and button + * of the release. + * @param x + * @param y + * @param modifiers + */ + void mouseReleased(int x, int y, int modifiers); + + /** + * Override the standard key codes used by the terminal emulation. + * @param codes a properties object containing key code definitions + */ + void setKeyCodes(Properties codes); + + /** + * keytyping event handler for all the special function and modifier keys + * @param keyCode the key code + * @param keyChar the character represented by the key + * @param modifiers shift/alt/control modifiers + */ + void keyPressed(int keyCode, char keyChar, int modifiers); + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/de/mud/terminal/vt320.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/de/mud/terminal/vt320.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,3423 @@ +/* + * This file is part of "JTA - Telnet/SSH for the JAVA(tm) platform". + * + * (c) Matthias L. Jugel, Marcus Meiner 1996-2005. All Rights Reserved. + * + * Please visit http://javatelnet.org/ for updates and contact. + * + * --LICENSE NOTICE-- + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * --LICENSE NOTICE-- + * + */ + +package de.mud.terminal; + +import android.text.AndroidCharacter; + +import java.util.Properties; + +/** + * Implementation of a VT terminal emulation plus ANSI compatible. + *

+ * Maintainer: Marcus Meißner + * + * @version $Id: vt320.java 507 2005-10-25 10:14:52Z marcus $ + * @author Matthias L. Jugel, Marcus Meißner + */ +public abstract class vt320 extends VDUBuffer implements VDUInput { + + /** the debug level */ + private final static int debug = 0; + private StringBuilder debugStr; + public abstract void debug(String notice); + + /** + * Write an answer back to the remote host. This is needed to be able to + * send terminal answers requests like status and type information. + * @param b the array of bytes to be sent + */ + public abstract void write(byte[] b); + + /** + * Write an answer back to the remote host. This is needed to be able to + * send terminal answers requests like status and type information. + * @param b the byte to be sent + */ + public abstract void write(int b); + + /** + * No more bytes to read from the transport, hook here to test screen changes + */ + public void testChanged() { + /* do nothing by default */ + } + + /** + * inject field contents as if typed + */ + public void setField(int l, int c, char [] d) { + // ignore line and column, just send the bytes to the host. + int n = d.length; + byte [] b = new byte [n]; + + for (int i = 0; i < n; i++) b[i] = (byte)(d[i] & 0x00ff); + + write(b); + } + + public void monitorKey(boolean down) { + // do nothing + } + + public void keyDepressed(int keyCode, char keyChar, int modifiers) { + keyPressed(keyCode, keyChar, modifiers); + } + + /** + * Play the beep sound ... + */ + public void beep() { + /* do nothing by default */ + } + + public void redrawPassthru() { + redraw(); // VDUBuffer.redraw is protected + } + + /** + * Convenience function for putString(char[], int, int) + */ + public void putString(String s) { + int len = s.length(); + char[] tmp = new char[len]; + s.getChars(0, len, tmp, 0); + putString(tmp, null, 0, len); + } + + /** + * Put string at current cursor position. Moves cursor + * according to the String. Does NOT wrap. + * @param s character array + * @param start place to start in array + * @param len number of characters to process + */ + public void putString(char[] s, byte[] fullwidths, int start, int len) { + if (len > 0) { + //markLine(R, 1); + int lastChar = -1; + char c; + boolean isWide = false; + + for (int i = 0; i < len; i++) { + c = s[start + i]; + + // Shortcut for my favorite ASCII + if (c <= 0x7F) { + if (lastChar != -1) + putChar((char) lastChar, isWide, false); + + lastChar = c; + isWide = false; + } + else if (!Character.isLowSurrogate(c) && !Character.isHighSurrogate(c)) { + if (Character.getType(c) == Character.NON_SPACING_MARK) { + if (lastChar != -1) { + char nc = Precomposer.precompose((char) lastChar, c); + putChar(nc, isWide, false); + lastChar = -1; + } + } + else { + if (lastChar != -1) + putChar((char) lastChar, isWide, false); + + lastChar = c; + + if (fullwidths != null) { + final byte width = fullwidths[i]; + isWide = (width == AndroidCharacter.EAST_ASIAN_WIDTH_WIDE) + || (width == AndroidCharacter.EAST_ASIAN_WIDTH_FULL_WIDTH); + } + } + } + } + + if (lastChar != -1) + putChar((char) lastChar, isWide, false); + + setCursorPosition(C, R); + redraw(); + } + } + + protected void sendTelnetCommand(byte cmd) { + /* do nothing by default */ + } + + /** + * Sent the changed window size from the terminal to all listeners. + */ + protected void setWindowSize(int c, int r) { + /* To be overridden by Terminal.java */ + } + + @Override + public void setScreenSize(int c, int r, boolean broadcast) { + int oldrows = height; + + if (debug > 2) { + if (debugStr == null) + debugStr = new StringBuilder(); + + debugStr.append("setscreensize (") + .append(c) + .append(',') + .append(r) + .append(',') + .append(broadcast) + .append(')'); + debug(debugStr.toString()); + debugStr.setLength(0); + } + + super.setScreenSize(c, r, false); + boolean cursorChanged = false; + + // Don't let the cursor go off the screen. + if (C >= c) { + C = c - 1; + cursorChanged = true; + } + + if (R >= r) { + R = r - 1; + cursorChanged = true; + } + + if (cursorChanged) { + setCursorPosition(C, R); + redraw(); + } + + if (broadcast) { + setWindowSize(c, r); /* broadcast up */ + } + } + + + /** + * Create a new vt320 terminal and intialize it with useful settings. + */ + public vt320(int width, int height) { + super(width, height); + debugStr = new StringBuilder(); + setVMS(false); + setIBMCharset(false); + setTerminalID("vt320"); + setBufferSize(100); + //setBorder(2, false); + gx = new char[4]; + reset(); + /* top row of numpad */ + PF1 = "\u001bOP"; + PF2 = "\u001bOQ"; + PF3 = "\u001bOR"; + PF4 = "\u001bOS"; + /* the 3x2 keyblock on PC keyboards */ + Insert = new String[4]; + Remove = new String[4]; + KeyHome = new String[4]; + KeyEnd = new String[4]; + NextScn = new String[4]; + PrevScn = new String[4]; + Escape = new String[4]; + BackSpace = new String[4]; + TabKey = new String[4]; + Insert[0] = Insert[1] = Insert[2] = Insert[3] = "\u001b[2~"; + Remove[0] = Remove[1] = Remove[2] = Remove[3] = "\u001b[3~"; + PrevScn[0] = PrevScn[1] = PrevScn[2] = PrevScn[3] = "\u001b[5~"; + NextScn[0] = NextScn[1] = NextScn[2] = NextScn[3] = "\u001b[6~"; + KeyHome[0] = KeyHome[1] = KeyHome[2] = KeyHome[3] = "\u001b[H"; + KeyEnd[0] = KeyEnd[1] = KeyEnd[2] = KeyEnd[3] = "\u001b[F"; + Escape[0] = Escape[1] = Escape[2] = Escape[3] = "\u001b"; + + if (vms) { + BackSpace[1] = "" + (char) 10; // VMS shift deletes word back + BackSpace[2] = "\u0018"; // VMS control deletes line back + BackSpace[0] = BackSpace[3] = "\u007f"; // VMS other is delete + } + else { + //BackSpace[0] = BackSpace[1] = BackSpace[2] = BackSpace[3] = "\b"; + // ConnectBot modifications. + BackSpace[0] = "\b"; + BackSpace[1] = "\u007f"; + BackSpace[2] = "\u001b[3~"; + BackSpace[3] = "\u001b[2~"; + } + + /* some more VT100 keys */ + Find = "\u001b[1~"; + Select = "\u001b[4~"; + Help = "\u001b[28~"; + Do = "\u001b[29~"; + FunctionKey = new String[21]; + FunctionKey[0] = ""; + FunctionKey[1] = PF1; + FunctionKey[2] = PF2; + FunctionKey[3] = PF3; + FunctionKey[4] = PF4; + /* following are defined differently for vt220 / vt132 ... */ + FunctionKey[5] = "\u001b[15~"; + FunctionKey[6] = "\u001b[17~"; + FunctionKey[7] = "\u001b[18~"; + FunctionKey[8] = "\u001b[19~"; + FunctionKey[9] = "\u001b[20~"; + FunctionKey[10] = "\u001b[21~"; + FunctionKey[11] = "\u001b[23~"; + FunctionKey[12] = "\u001b[24~"; + FunctionKey[13] = "\u001b[25~"; + FunctionKey[14] = "\u001b[26~"; + FunctionKey[15] = Help; + FunctionKey[16] = Do; + FunctionKey[17] = "\u001b[31~"; + FunctionKey[18] = "\u001b[32~"; + FunctionKey[19] = "\u001b[33~"; + FunctionKey[20] = "\u001b[34~"; + FunctionKeyShift = new String[21]; + FunctionKeyAlt = new String[21]; + FunctionKeyCtrl = new String[21]; + + for (int i = 0; i < 20; i++) { + FunctionKeyShift[i] = ""; + FunctionKeyAlt[i] = ""; + FunctionKeyCtrl[i] = ""; + } + + FunctionKeyShift[15] = Find; + FunctionKeyShift[16] = Select; + TabKey[0] = "\u0009"; + TabKey[1] = "\u001bOP\u0009"; + TabKey[2] = TabKey[3] = ""; + KeyUp = new String[4]; + KeyUp[0] = "\u001b[A"; + KeyDown = new String[4]; + KeyDown[0] = "\u001b[B"; + KeyRight = new String[4]; + KeyRight[0] = "\u001b[C"; + KeyLeft = new String[4]; + KeyLeft[0] = "\u001b[D"; + Numpad = new String[10]; + Numpad[0] = "\u001bOp"; + Numpad[1] = "\u001bOq"; + Numpad[2] = "\u001bOr"; + Numpad[3] = "\u001bOs"; + Numpad[4] = "\u001bOt"; + Numpad[5] = "\u001bOu"; + Numpad[6] = "\u001bOv"; + Numpad[7] = "\u001bOw"; + Numpad[8] = "\u001bOx"; + Numpad[9] = "\u001bOy"; + KPMinus = PF4; + KPComma = "\u001bOl"; + KPPeriod = "\u001bOn"; + KPEnter = "\u001bOM"; + NUMPlus = new String[4]; + NUMPlus[0] = "+"; + NUMDot = new String[4]; + NUMDot[0] = "."; + } + + public void setBackspace(int type) { + switch (type) { + case DELETE_IS_DEL: + BackSpace[0] = "\u007f"; + BackSpace[1] = "\b"; + break; + + case DELETE_IS_BACKSPACE: + BackSpace[0] = "\b"; + BackSpace[1] = "\u007f"; + break; + } + } + + /** + * Create a default vt320 terminal with 80 columns and 24 lines. + */ + public vt320() { + this(80, 24); + } + + /** + * Terminal is mouse-aware and requires (x,y) coordinates of + * on the terminal (character coordinates) and the button clicked. + * @param x + * @param y + * @param modifiers + */ + public void mousePressed(int x, int y, int modifiers) { + if (mouserpt == 0) + return; + + int mods = modifiers; + mousebut = 3; + + if ((mods & 16) == 16) mousebut = 0; + + if ((mods & 8) == 8) mousebut = 1; + + if ((mods & 4) == 4) mousebut = 2; + + int mousecode; + + if (mouserpt == 9) /* X10 Mouse */ + mousecode = 0x20 | mousebut; + else /* normal xterm mouse reporting */ + mousecode = mousebut | 0x20 | ((mods & 7) << 2); + + byte b[] = new byte[6]; + b[0] = 27; + b[1] = (byte) '['; + b[2] = (byte) 'M'; + b[3] = (byte) mousecode; + b[4] = (byte)(0x20 + x + 1); + b[5] = (byte)(0x20 + y + 1); + write(b); // FIXME: writeSpecial here + } + + /** + * Terminal is mouse-aware and requires the coordinates and button + * of the release. + * @param x + * @param y + * @param modifiers + */ + public void mouseReleased(int x, int y, int modifiers) { + if (mouserpt == 0) + return; + + /* problem is tht modifiers still have the released button set in them. + int mods = modifiers; + mousebut = 3; + if ((mods & 16)==16) mousebut=0; + if ((mods & 8)==8 ) mousebut=1; + if ((mods & 4)==4 ) mousebut=2; + */ + int mousecode; + + if (mouserpt == 9) + mousecode = 0x20 + mousebut; /* same as press? appears so. */ + else + mousecode = '#'; + + byte b[] = new byte[6]; + b[0] = 27; + b[1] = (byte) '['; + b[2] = (byte) 'M'; + b[3] = (byte) mousecode; + b[4] = (byte)(0x20 + x + 1); + b[5] = (byte)(0x20 + y + 1); + write(b); // FIXME: writeSpecial here + mousebut = 0; + } + + + /** we should do localecho (passed from other modules). false is default */ + private boolean localecho = false; + + /** + * Enable or disable the local echo property of the terminal. + * @param echo true if the terminal should echo locally + */ + public void setLocalEcho(boolean echo) { + localecho = echo; + } + + /** + * Enable the VMS mode of the terminal to handle some things differently + * for VMS hosts. + * @param vms true for vms mode, false for normal mode + */ + public void setVMS(boolean vms) { + this.vms = vms; + } + + /** + * Enable the usage of the IBM character set used by some BBS's. Special + * graphical character are available in this mode. + * @param ibm true to use the ibm character set + */ + public void setIBMCharset(boolean ibm) { + useibmcharset = ibm; + } + + /** + * Override the standard key codes used by the terminal emulation. + * @param codes a properties object containing key code definitions + */ + public void setKeyCodes(Properties codes) { + String res, prefixes[] = {"", "S", "C", "A"}; + int i; + + for (i = 0; i < 10; i++) { + res = codes.getProperty("NUMPAD" + i); + + if (res != null) Numpad[i] = unEscape(res); + } + + for (i = 1; i < 20; i++) { + res = codes.getProperty("F" + i); + + if (res != null) FunctionKey[i] = unEscape(res); + + res = codes.getProperty("SF" + i); + + if (res != null) FunctionKeyShift[i] = unEscape(res); + + res = codes.getProperty("CF" + i); + + if (res != null) FunctionKeyCtrl[i] = unEscape(res); + + res = codes.getProperty("AF" + i); + + if (res != null) FunctionKeyAlt[i] = unEscape(res); + } + + for (i = 0; i < 4; i++) { + res = codes.getProperty(prefixes[i] + "PGUP"); + + if (res != null) PrevScn[i] = unEscape(res); + + res = codes.getProperty(prefixes[i] + "PGDOWN"); + + if (res != null) NextScn[i] = unEscape(res); + + res = codes.getProperty(prefixes[i] + "END"); + + if (res != null) KeyEnd[i] = unEscape(res); + + res = codes.getProperty(prefixes[i] + "HOME"); + + if (res != null) KeyHome[i] = unEscape(res); + + res = codes.getProperty(prefixes[i] + "INSERT"); + + if (res != null) Insert[i] = unEscape(res); + + res = codes.getProperty(prefixes[i] + "REMOVE"); + + if (res != null) Remove[i] = unEscape(res); + + res = codes.getProperty(prefixes[i] + "UP"); + + if (res != null) KeyUp[i] = unEscape(res); + + res = codes.getProperty(prefixes[i] + "DOWN"); + + if (res != null) KeyDown[i] = unEscape(res); + + res = codes.getProperty(prefixes[i] + "LEFT"); + + if (res != null) KeyLeft[i] = unEscape(res); + + res = codes.getProperty(prefixes[i] + "RIGHT"); + + if (res != null) KeyRight[i] = unEscape(res); + + res = codes.getProperty(prefixes[i] + "ESCAPE"); + + if (res != null) Escape[i] = unEscape(res); + + res = codes.getProperty(prefixes[i] + "BACKSPACE"); + + if (res != null) BackSpace[i] = unEscape(res); + + res = codes.getProperty(prefixes[i] + "TAB"); + + if (res != null) TabKey[i] = unEscape(res); + + res = codes.getProperty(prefixes[i] + "NUMPLUS"); + + if (res != null) NUMPlus[i] = unEscape(res); + + res = codes.getProperty(prefixes[i] + "NUMDECIMAL"); + + if (res != null) NUMDot[i] = unEscape(res); + } + } + + /** + * Set the terminal id used to identify this terminal. + * @param terminalID the id string + */ + public void setTerminalID(String terminalID) { + this.terminalID = terminalID; + + if (terminalID.equals("scoansi")) { + FunctionKey[1] = "\u001b[M"; FunctionKey[2] = "\u001b[N"; + FunctionKey[3] = "\u001b[O"; FunctionKey[4] = "\u001b[P"; + FunctionKey[5] = "\u001b[Q"; FunctionKey[6] = "\u001b[R"; + FunctionKey[7] = "\u001b[S"; FunctionKey[8] = "\u001b[T"; + FunctionKey[9] = "\u001b[U"; FunctionKey[10] = "\u001b[V"; + FunctionKey[11] = "\u001b[W"; FunctionKey[12] = "\u001b[X"; + FunctionKey[13] = "\u001b[Y"; FunctionKey[14] = "?"; + FunctionKey[15] = "\u001b[a"; FunctionKey[16] = "\u001b[b"; + FunctionKey[17] = "\u001b[c"; FunctionKey[18] = "\u001b[d"; + FunctionKey[19] = "\u001b[e"; FunctionKey[20] = "\u001b[f"; + PrevScn[0] = PrevScn[1] = PrevScn[2] = PrevScn[3] = "\u001b[I"; + NextScn[0] = NextScn[1] = NextScn[2] = NextScn[3] = "\u001b[G"; + // more theoretically. + } + } + + public void setAnswerBack(String ab) { + this.answerBack = unEscape(ab); + } + + /** + * Get the terminal id used to identify this terminal. + */ + public String getTerminalID() { + return terminalID; + } + + /** + * A small conveniance method thar converts the string to a byte array + * for sending. + * @param s the string to be sent + */ + private boolean write(String s, boolean doecho) { + if (debug > 2) { + debugStr.append("write(|") + .append(s) + .append("|,") + .append(doecho); + debug(debugStr.toString()); + debugStr.setLength(0); + } + + if (s == null) // aka the empty string. + return true; + + /* NOTE: getBytes() honours some locale, it *CONVERTS* the string. + * However, we output only 7bit stuff towards the target, and *some* + * 8 bit control codes. We must not mess up the latter, so we do hand + * by hand copy. + */ + byte arr[] = new byte[s.length()]; + + for (int i = 0; i < s.length(); i++) { + arr[i] = (byte) s.charAt(i); + } + + write(arr); + + if (doecho) + putString(s); + + return true; + } + + private boolean write(int s, boolean doecho) { + if (debug > 2) { + debugStr.append("write(|") + .append(s) + .append("|,") + .append(doecho); + debug(debugStr.toString()); + debugStr.setLength(0); + } + + write(s); + + // TODO check if character is wide + if (doecho) + putChar((char)s, false, false); + + return true; + } + + private boolean write(String s) { + return write(s, localecho); + } + + // =================================================================== + // the actual terminal emulation code comes here: + // =================================================================== + + private String terminalID = "vt320"; + private String answerBack = "Use Terminal.answerback to set ...\n"; + + // X - COLUMNS, Y - ROWS + int R, C; + int attributes = 0; + + int Sc, Sr, Sa, Stm, Sbm; + char Sgr, Sgl; + char Sgx[]; + + int insertmode = 0; + int statusmode = 0; + boolean vt52mode = false; + boolean keypadmode = false; /* false - numeric, true - application */ + boolean output8bit = false; + int normalcursor = 0; + boolean moveoutsidemargins = true; + boolean wraparound = true; + boolean sendcrlf = true; + boolean capslock = false; + boolean numlock = false; + int mouserpt = 0; + byte mousebut = 0; + + boolean useibmcharset = false; + + int lastwaslf = 0; + boolean usedcharsets = false; + + private final static char ESC = 27; + private final static char IND = 132; + private final static char NEL = 133; + private final static char RI = 141; + private final static char SS2 = 142; + private final static char SS3 = 143; + private final static char DCS = 144; + private final static char HTS = 136; + private final static char CSI = 155; + private final static char OSC = 157; + private final static int TSTATE_DATA = 0; + private final static int TSTATE_ESC = 1; /* ESC */ + private final static int TSTATE_CSI = 2; /* ESC [ */ + private final static int TSTATE_DCS = 3; /* ESC P */ + private final static int TSTATE_DCEQ = 4; /* ESC [? */ + private final static int TSTATE_ESCSQUARE = 5; /* ESC # */ + private final static int TSTATE_OSC = 6; /* ESC ] */ + private final static int TSTATE_SETG0 = 7; /* ESC (? */ + private final static int TSTATE_SETG1 = 8; /* ESC )? */ + private final static int TSTATE_SETG2 = 9; /* ESC *? */ + private final static int TSTATE_SETG3 = 10; /* ESC +? */ + private final static int TSTATE_CSI_DOLLAR = 11; /* ESC [ Pn $ */ + private final static int TSTATE_CSI_EX = 12; /* ESC [ ! */ + private final static int TSTATE_ESCSPACE = 13; /* ESC */ + private final static int TSTATE_VT52X = 14; + private final static int TSTATE_VT52Y = 15; + private final static int TSTATE_CSI_TICKS = 16; + private final static int TSTATE_CSI_EQUAL = 17; /* ESC [ = */ + private final static int TSTATE_TITLE = 18; /* xterm title */ + + /* Keys we support */ + public final static int KEY_PAUSE = 1; + public final static int KEY_F1 = 2; + public final static int KEY_F2 = 3; + public final static int KEY_F3 = 4; + public final static int KEY_F4 = 5; + public final static int KEY_F5 = 6; + public final static int KEY_F6 = 7; + public final static int KEY_F7 = 8; + public final static int KEY_F8 = 9; + public final static int KEY_F9 = 10; + public final static int KEY_F10 = 11; + public final static int KEY_F11 = 12; + public final static int KEY_F12 = 13; + public final static int KEY_F13 = 14; // only used by tn5250 + public final static int KEY_F14 = 15; // only used by tn5250 + public final static int KEY_F15 = 16; // only used by tn5250 + public final static int KEY_F16 = 17; // only used by tn5250 + public final static int KEY_F17 = 18; // only used by tn5250 + public final static int KEY_F18 = 19; // only used by tn5250 + public final static int KEY_F19 = 20; // only used by tn5250 + public final static int KEY_F20 = 21; // only used by tn5250 + public final static int KEY_F21 = 22; // only used by tn5250 + public final static int KEY_F22 = 23; // only used by tn5250 + public final static int KEY_F23 = 24; // only used by tn5250 + public final static int KEY_F24 = 25; // only used by tn5250 + public final static int KEY_UP = 26; + public final static int KEY_DOWN = 27; + public final static int KEY_LEFT = 28; + public final static int KEY_RIGHT = 29; + public final static int KEY_PAGE_DOWN = 30; + public final static int KEY_PAGE_UP = 31; + public final static int KEY_INSERT = 32; + public final static int KEY_DELETE = 33; + public final static int KEY_BACK_SPACE = 34; + public final static int KEY_HOME = 35; + public final static int KEY_END = 36; + public final static int KEY_NUM_LOCK = 37; + public final static int KEY_CAPS_LOCK = 38; + public final static int KEY_SHIFT = 39; + public final static int KEY_CONTROL = 40; + public final static int KEY_ALT = 41; + public final static int KEY_ENTER = 42; + public final static int KEY_NUMPAD0 = 43; + public final static int KEY_NUMPAD1 = 44; + public final static int KEY_NUMPAD2 = 45; + public final static int KEY_NUMPAD3 = 46; + public final static int KEY_NUMPAD4 = 47; + public final static int KEY_NUMPAD5 = 48; + public final static int KEY_NUMPAD6 = 49; + public final static int KEY_NUMPAD7 = 50; + public final static int KEY_NUMPAD8 = 51; + public final static int KEY_NUMPAD9 = 52; + public final static int KEY_DECIMAL = 53; + public final static int KEY_ADD = 54; + public final static int KEY_ESCAPE = 55; + public final static int KEY_TAB = 56; + public final static int KEY_SYSREQ = 57; // only used by tn5250 + + public final static int DELETE_IS_DEL = 0; + public final static int DELETE_IS_BACKSPACE = 1; + + /* The graphics charsets + * B - default ASCII + * A - ISO Latin 1 + * 0 - DEC SPECIAL + * < - User defined + * .... + */ + char gx[]; + char gl; // GL (left charset) + char gr; // GR (right charset) + int onegl; // single shift override for GL. + + // Map from scoansi linedrawing to DEC _and_ unicode (for the stuff which + // is not in linedrawing). Got from experimenting with scoadmin. + private final static String scoansi_acs = "Tm7k3x4u?kZl@mYjEnB\u2566DqCtAvM\u2550:\u2551N\u2557I\u2554;\u2557H\u255a0a<\u255d"; + // array to store DEC Special -> Unicode mapping + // Unicode DEC Unicode name (DEC name) + private static char DECSPECIAL[] = { + '\u0040', //5f blank + '\u2666', //60 black diamond + '\u2592', //61 grey square + '\u2409', //62 Horizontal tab (ht) pict. for control + '\u240c', //63 Form Feed (ff) pict. for control + '\u240d', //64 Carriage Return (cr) pict. for control + '\u240a', //65 Line Feed (lf) pict. for control + '\u00ba', //66 Masculine ordinal indicator + '\u00b1', //67 Plus or minus sign + '\u2424', //68 New Line (nl) pict. for control + '\u240b', //69 Vertical Tab (vt) pict. for control + '\u2518', //6a Forms light up and left + '\u2510', //6b Forms light down and left + '\u250c', //6c Forms light down and right + '\u2514', //6d Forms light up and right + '\u253c', //6e Forms light vertical and horizontal + '\u2594', //6f Upper 1/8 block (Scan 1) + '\u2580', //70 Upper 1/2 block (Scan 3) + '\u2500', //71 Forms light horizontal or ?em dash? (Scan 5) + '\u25ac', //72 \u25ac black rect. or \u2582 lower 1/4 (Scan 7) + '\u005f', //73 \u005f underscore or \u2581 lower 1/8 (Scan 9) + '\u251c', //74 Forms light vertical and right + '\u2524', //75 Forms light vertical and left + '\u2534', //76 Forms light up and horizontal + '\u252c', //77 Forms light down and horizontal + '\u2502', //78 vertical bar + '\u2264', //79 less than or equal + '\u2265', //7a greater than or equal + '\u00b6', //7b paragraph + '\u2260', //7c not equal + '\u00a3', //7d Pound Sign (british) + '\u00b7' //7e Middle Dot + }; + + /** Strings to send on function key pressing */ + private String Numpad[]; + private String FunctionKey[]; + private String FunctionKeyShift[]; + private String FunctionKeyCtrl[]; + private String FunctionKeyAlt[]; + private String TabKey[]; + private String KeyUp[], KeyDown[], KeyLeft[], KeyRight[]; + private String KPMinus, KPComma, KPPeriod, KPEnter; + private String PF1, PF2, PF3, PF4; + private String Help, Do, Find, Select; + + private String KeyHome[], KeyEnd[], Insert[], Remove[], PrevScn[], NextScn[]; + private String Escape[], BackSpace[], NUMDot[], NUMPlus[]; + + private String osc, dcs; /* to memorize OSC & DCS control sequence */ + + /** vt320 state variable (internal) */ + private int term_state = TSTATE_DATA; + /** in vms mode, set by Terminal.VMS property */ + private boolean vms = false; + /** Tabulators */ + private byte[] Tabs; + /** The list of integers as used by CSI */ + private int[] DCEvars = new int[30]; + private int DCEvar; + + /** + * Replace escape code characters (backslash + identifier) with their + * respective codes. + * @param tmp the string to be parsed + * @return a unescaped string + */ + static String unEscape(String tmp) { + int idx = 0, oldidx = 0; + String cmd; + // f.println("unescape("+tmp+")"); + cmd = ""; + + while ((idx = tmp.indexOf('\\', oldidx)) >= 0 && + ++idx <= tmp.length()) { + cmd += tmp.substring(oldidx, idx - 1); + + if (idx == tmp.length()) return cmd; + + switch (tmp.charAt(idx)) { + case 'b': + cmd += "\b"; + break; + + case 'e': + cmd += "\u001b"; + break; + + case 'n': + cmd += "\n"; + break; + + case 'r': + cmd += "\r"; + break; + + case 't': + cmd += "\t"; + break; + + case 'v': + cmd += "\u000b"; + break; + + case 'a': + cmd += "\u0012"; + break; + + default : + if ((tmp.charAt(idx) >= '0') && (tmp.charAt(idx) <= '9')) { + int i; + + for (i = idx; i < tmp.length(); i++) + if ((tmp.charAt(i) < '0') || (tmp.charAt(i) > '9')) + break; + + cmd += (char) Integer.parseInt(tmp.substring(idx, i)); + idx = i - 1; + } + else + cmd += tmp.substring(idx, ++idx); + + break; + } + + oldidx = ++idx; + } + + if (oldidx <= tmp.length()) cmd += tmp.substring(oldidx); + + return cmd; + } + + /** + * A small conveniance method thar converts a 7bit string to the 8bit + * version depending on VT52/Output8Bit mode. + * + * @param s the string to be sent + */ + private boolean writeSpecial(String s) { + if (s == null) + return true; + + if (((s.length() >= 3) && (s.charAt(0) == 27) && (s.charAt(1) == 'O'))) { + if (vt52mode) { + if ((s.charAt(2) >= 'P') && (s.charAt(2) <= 'S')) { + s = "\u001b" + s.substring(2); /* ESC x */ + } + else { + s = "\u001b?" + s.substring(2); /* ESC ? x */ + } + } + else { + if (output8bit) { + s = "\u008f" + s.substring(2); /* SS3 x */ + } /* else keep string as it is */ + } + } + + if (((s.length() >= 3) && (s.charAt(0) == 27) && (s.charAt(1) == '['))) { + if (output8bit) { + s = "\u009b" + s.substring(2); /* CSI ... */ + } /* else keep */ + } + + return write(s, false); + } + + /** + * main keytyping event handler for all the special function and modifier keys + * the normal keys are processed by write(byte b); + */ + public void keyPressed(int keyCode, char keyChar, int modifiers) { + boolean control = (modifiers & VDUInput.KEY_CONTROL) != 0; + boolean shift = (modifiers & VDUInput.KEY_SHIFT) != 0; + boolean alt = (modifiers & VDUInput.KEY_ALT) != 0; + + if (debug > 1) { + debugStr.append("keyPressed(") + .append(keyCode) + .append(", ") + .append((int)keyChar) + .append(", ") + .append(modifiers) + .append(')'); + debug(debugStr.toString()); + debugStr.setLength(0); + } + + int xind; + String fmap[]; + xind = 0; + fmap = FunctionKey; + + if (shift) { + fmap = FunctionKeyShift; + xind = 1; + } + + if (control) { + fmap = FunctionKeyCtrl; + xind = 2; + } + + if (alt) { + fmap = FunctionKeyAlt; + xind = 3; + } + + switch (keyCode) { + case KEY_PAUSE: + if (shift || control) + sendTelnetCommand((byte) 243); // BREAK + + break; + + case KEY_F1: + writeSpecial(fmap[1]); + break; + + case KEY_F2: + writeSpecial(fmap[2]); + break; + + case KEY_F3: + writeSpecial(fmap[3]); + break; + + case KEY_F4: + writeSpecial(fmap[4]); + break; + + case KEY_F5: + writeSpecial(fmap[5]); + break; + + case KEY_F6: + writeSpecial(fmap[6]); + break; + + case KEY_F7: + writeSpecial(fmap[7]); + break; + + case KEY_F8: + writeSpecial(fmap[8]); + break; + + case KEY_F9: + writeSpecial(fmap[9]); + break; + + case KEY_F10: + writeSpecial(fmap[10]); + break; + + case KEY_F11: + writeSpecial(fmap[11]); + break; + + case KEY_F12: + writeSpecial(fmap[12]); + break; + + case KEY_F13: + case KEY_F14: + case KEY_F15: + case KEY_F16: + case KEY_F17: + case KEY_F18: + case KEY_F19: + case KEY_F20: + case KEY_F21: + case KEY_F22: + case KEY_F23: + case KEY_F24: + break; + + case KEY_UP: + writeSpecial(KeyUp[xind]); + break; + + case KEY_DOWN: + writeSpecial(KeyDown[xind]); + break; + + case KEY_LEFT: + writeSpecial(KeyLeft[xind]); + break; + + case KEY_RIGHT: + writeSpecial(KeyRight[xind]); + break; + + case KEY_PAGE_DOWN: + writeSpecial(NextScn[xind]); + break; + + case KEY_PAGE_UP: + writeSpecial(PrevScn[xind]); + break; + + case KEY_INSERT: + writeSpecial(Insert[xind]); + break; + + case KEY_DELETE: + writeSpecial(Remove[xind]); + break; + + case KEY_BACK_SPACE: + writeSpecial(BackSpace[xind]); + + if (localecho) { + if (BackSpace[xind] == "\b") { + putString("\b \b"); // make the last char 'deleted' + } + else { + putString(BackSpace[xind]); // echo it + } + } + + break; + + case KEY_HOME: + writeSpecial(KeyHome[xind]); + break; + + case KEY_END: + writeSpecial(KeyEnd[xind]); + break; + + case KEY_NUM_LOCK: + if (vms && control) { + writeSpecial(PF1); + } + + if (!control) + numlock = !numlock; + + break; + + case KEY_CAPS_LOCK: + capslock = !capslock; + break; + + case KEY_SHIFT: + case KEY_CONTROL: + case KEY_ALT: + break; + + case KEY_ESCAPE: + writeSpecial(Escape[xind]); + break; + + case KEY_ENTER: + write(0x0d); + break; + + case KEY_TAB: + writeSpecial(TabKey[xind]); + break; + + default: + break; + } + } + + private void handle_dcs(String dcs) { + debugStr.append("DCS: ") + .append(dcs); + debug(debugStr.toString()); + debugStr.setLength(0); + } + + private void handle_osc(String osc) { + if (osc.length() > 2 && osc.substring(0, 2).equals("4;")) { + // Define color palette + String[] colorData = osc.split(";"); + + try { + int colorIndex = Integer.parseInt(colorData[1]); + + if ("rgb:".equals(colorData[2].substring(0, 4))) { + String[] rgb = colorData[2].substring(4).split("/"); + int red = Integer.parseInt(rgb[0].substring(0, 2), 16) & 0xFF; + int green = Integer.parseInt(rgb[1].substring(0, 2), 16) & 0xFF; + int blue = Integer.parseInt(rgb[2].substring(0, 2), 16) & 0xFF; + display.setColor(colorIndex, red, green, blue); + } + } + catch (Exception e) { + debugStr.append("OSC: invalid color sequence encountered: ") + .append(osc); + debug(debugStr.toString()); + debugStr.setLength(0); + } + } + else + debug("OSC: " + osc); + } + + private final static char unimap[] = { + //# + //# Name: cp437_DOSLatinUS to Unicode table + //# Unicode version: 1.1 + //# Table version: 1.1 + //# Table format: Format A + //# Date: 03/31/95 + //# Authors: Michel Suignard + //# Lori Hoerth + //# General notes: none + //# + //# Format: Three tab-separated columns + //# Column #1 is the cp1255_WinHebrew code (in hex) + //# Column #2 is the Unicode (in hex as 0xXXXX) + //# Column #3 is the Unicode name (follows a comment sign, '#') + //# + //# The entries are in cp437_DOSLatinUS order + //# + + 0x0000, // #NULL + 0x0001, // #START OF HEADING + 0x0002, // #START OF TEXT + 0x0003, // #END OF TEXT + 0x0004, // #END OF TRANSMISSION + 0x0005, // #ENQUIRY + 0x0006, // #ACKNOWLEDGE + 0x0007, // #BELL + 0x0008, // #BACKSPACE + 0x0009, // #HORIZONTAL TABULATION + 0x000a, // #LINE FEED + 0x000b, // #VERTICAL TABULATION + 0x000c, // #FORM FEED + 0x000d, // #CARRIAGE RETURN + 0x000e, // #SHIFT OUT + 0x000f, // #SHIFT IN + 0x0010, // #DATA LINK ESCAPE + 0x0011, // #DEVICE CONTROL ONE + 0x0012, // #DEVICE CONTROL TWO + 0x0013, // #DEVICE CONTROL THREE + 0x0014, // #DEVICE CONTROL FOUR + 0x0015, // #NEGATIVE ACKNOWLEDGE + 0x0016, // #SYNCHRONOUS IDLE + 0x0017, // #END OF TRANSMISSION BLOCK + 0x0018, // #CANCEL + 0x0019, // #END OF MEDIUM + 0x001a, // #SUBSTITUTE + 0x001b, // #ESCAPE + 0x001c, // #FILE SEPARATOR + 0x001d, // #GROUP SEPARATOR + 0x001e, // #RECORD SEPARATOR + 0x001f, // #UNIT SEPARATOR + 0x0020, // #SPACE + 0x0021, // #EXCLAMATION MARK + 0x0022, // #QUOTATION MARK + 0x0023, // #NUMBER SIGN + 0x0024, // #DOLLAR SIGN + 0x0025, // #PERCENT SIGN + 0x0026, // #AMPERSAND + 0x0027, // #APOSTROPHE + 0x0028, // #LEFT PARENTHESIS + 0x0029, // #RIGHT PARENTHESIS + 0x002a, // #ASTERISK + 0x002b, // #PLUS SIGN + 0x002c, // #COMMA + 0x002d, // #HYPHEN-MINUS + 0x002e, // #FULL STOP + 0x002f, // #SOLIDUS + 0x0030, // #DIGIT ZERO + 0x0031, // #DIGIT ONE + 0x0032, // #DIGIT TWO + 0x0033, // #DIGIT THREE + 0x0034, // #DIGIT FOUR + 0x0035, // #DIGIT FIVE + 0x0036, // #DIGIT SIX + 0x0037, // #DIGIT SEVEN + 0x0038, // #DIGIT EIGHT + 0x0039, // #DIGIT NINE + 0x003a, // #COLON + 0x003b, // #SEMICOLON + 0x003c, // #LESS-THAN SIGN + 0x003d, // #EQUALS SIGN + 0x003e, // #GREATER-THAN SIGN + 0x003f, // #QUESTION MARK + 0x0040, // #COMMERCIAL AT + 0x0041, // #LATIN CAPITAL LETTER A + 0x0042, // #LATIN CAPITAL LETTER B + 0x0043, // #LATIN CAPITAL LETTER C + 0x0044, // #LATIN CAPITAL LETTER D + 0x0045, // #LATIN CAPITAL LETTER E + 0x0046, // #LATIN CAPITAL LETTER F + 0x0047, // #LATIN CAPITAL LETTER G + 0x0048, // #LATIN CAPITAL LETTER H + 0x0049, // #LATIN CAPITAL LETTER I + 0x004a, // #LATIN CAPITAL LETTER J + 0x004b, // #LATIN CAPITAL LETTER K + 0x004c, // #LATIN CAPITAL LETTER L + 0x004d, // #LATIN CAPITAL LETTER M + 0x004e, // #LATIN CAPITAL LETTER N + 0x004f, // #LATIN CAPITAL LETTER O + 0x0050, // #LATIN CAPITAL LETTER P + 0x0051, // #LATIN CAPITAL LETTER Q + 0x0052, // #LATIN CAPITAL LETTER R + 0x0053, // #LATIN CAPITAL LETTER S + 0x0054, // #LATIN CAPITAL LETTER T + 0x0055, // #LATIN CAPITAL LETTER U + 0x0056, // #LATIN CAPITAL LETTER V + 0x0057, // #LATIN CAPITAL LETTER W + 0x0058, // #LATIN CAPITAL LETTER X + 0x0059, // #LATIN CAPITAL LETTER Y + 0x005a, // #LATIN CAPITAL LETTER Z + 0x005b, // #LEFT SQUARE BRACKET + 0x005c, // #REVERSE SOLIDUS + 0x005d, // #RIGHT SQUARE BRACKET + 0x005e, // #CIRCUMFLEX ACCENT + 0x005f, // #LOW LINE + 0x0060, // #GRAVE ACCENT + 0x0061, // #LATIN SMALL LETTER A + 0x0062, // #LATIN SMALL LETTER B + 0x0063, // #LATIN SMALL LETTER C + 0x0064, // #LATIN SMALL LETTER D + 0x0065, // #LATIN SMALL LETTER E + 0x0066, // #LATIN SMALL LETTER F + 0x0067, // #LATIN SMALL LETTER G + 0x0068, // #LATIN SMALL LETTER H + 0x0069, // #LATIN SMALL LETTER I + 0x006a, // #LATIN SMALL LETTER J + 0x006b, // #LATIN SMALL LETTER K + 0x006c, // #LATIN SMALL LETTER L + 0x006d, // #LATIN SMALL LETTER M + 0x006e, // #LATIN SMALL LETTER N + 0x006f, // #LATIN SMALL LETTER O + 0x0070, // #LATIN SMALL LETTER P + 0x0071, // #LATIN SMALL LETTER Q + 0x0072, // #LATIN SMALL LETTER R + 0x0073, // #LATIN SMALL LETTER S + 0x0074, // #LATIN SMALL LETTER T + 0x0075, // #LATIN SMALL LETTER U + 0x0076, // #LATIN SMALL LETTER V + 0x0077, // #LATIN SMALL LETTER W + 0x0078, // #LATIN SMALL LETTER X + 0x0079, // #LATIN SMALL LETTER Y + 0x007a, // #LATIN SMALL LETTER Z + 0x007b, // #LEFT CURLY BRACKET + 0x007c, // #VERTICAL LINE + 0x007d, // #RIGHT CURLY BRACKET + 0x007e, // #TILDE + 0x007f, // #DELETE + 0x00c7, // #LATIN CAPITAL LETTER C WITH CEDILLA + 0x00fc, // #LATIN SMALL LETTER U WITH DIAERESIS + 0x00e9, // #LATIN SMALL LETTER E WITH ACUTE + 0x00e2, // #LATIN SMALL LETTER A WITH CIRCUMFLEX + 0x00e4, // #LATIN SMALL LETTER A WITH DIAERESIS + 0x00e0, // #LATIN SMALL LETTER A WITH GRAVE + 0x00e5, // #LATIN SMALL LETTER A WITH RING ABOVE + 0x00e7, // #LATIN SMALL LETTER C WITH CEDILLA + 0x00ea, // #LATIN SMALL LETTER E WITH CIRCUMFLEX + 0x00eb, // #LATIN SMALL LETTER E WITH DIAERESIS + 0x00e8, // #LATIN SMALL LETTER E WITH GRAVE + 0x00ef, // #LATIN SMALL LETTER I WITH DIAERESIS + 0x00ee, // #LATIN SMALL LETTER I WITH CIRCUMFLEX + 0x00ec, // #LATIN SMALL LETTER I WITH GRAVE + 0x00c4, // #LATIN CAPITAL LETTER A WITH DIAERESIS + 0x00c5, // #LATIN CAPITAL LETTER A WITH RING ABOVE + 0x00c9, // #LATIN CAPITAL LETTER E WITH ACUTE + 0x00e6, // #LATIN SMALL LIGATURE AE + 0x00c6, // #LATIN CAPITAL LIGATURE AE + 0x00f4, // #LATIN SMALL LETTER O WITH CIRCUMFLEX + 0x00f6, // #LATIN SMALL LETTER O WITH DIAERESIS + 0x00f2, // #LATIN SMALL LETTER O WITH GRAVE + 0x00fb, // #LATIN SMALL LETTER U WITH CIRCUMFLEX + 0x00f9, // #LATIN SMALL LETTER U WITH GRAVE + 0x00ff, // #LATIN SMALL LETTER Y WITH DIAERESIS + 0x00d6, // #LATIN CAPITAL LETTER O WITH DIAERESIS + 0x00dc, // #LATIN CAPITAL LETTER U WITH DIAERESIS + 0x00a2, // #CENT SIGN + 0x00a3, // #POUND SIGN + 0x00a5, // #YEN SIGN + 0x20a7, // #PESETA SIGN + 0x0192, // #LATIN SMALL LETTER F WITH HOOK + 0x00e1, // #LATIN SMALL LETTER A WITH ACUTE + 0x00ed, // #LATIN SMALL LETTER I WITH ACUTE + 0x00f3, // #LATIN SMALL LETTER O WITH ACUTE + 0x00fa, // #LATIN SMALL LETTER U WITH ACUTE + 0x00f1, // #LATIN SMALL LETTER N WITH TILDE + 0x00d1, // #LATIN CAPITAL LETTER N WITH TILDE + 0x00aa, // #FEMININE ORDINAL INDICATOR + 0x00ba, // #MASCULINE ORDINAL INDICATOR + 0x00bf, // #INVERTED QUESTION MARK + 0x2310, // #REVERSED NOT SIGN + 0x00ac, // #NOT SIGN + 0x00bd, // #VULGAR FRACTION ONE HALF + 0x00bc, // #VULGAR FRACTION ONE QUARTER + 0x00a1, // #INVERTED EXCLAMATION MARK + 0x00ab, // #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + 0x00bb, // #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + 0x2591, // #LIGHT SHADE + 0x2592, // #MEDIUM SHADE + 0x2593, // #DARK SHADE + 0x2502, // #BOX DRAWINGS LIGHT VERTICAL + 0x2524, // #BOX DRAWINGS LIGHT VERTICAL AND LEFT + 0x2561, // #BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE + 0x2562, // #BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE + 0x2556, // #BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE + 0x2555, // #BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE + 0x2563, // #BOX DRAWINGS DOUBLE VERTICAL AND LEFT + 0x2551, // #BOX DRAWINGS DOUBLE VERTICAL + 0x2557, // #BOX DRAWINGS DOUBLE DOWN AND LEFT + 0x255d, // #BOX DRAWINGS DOUBLE UP AND LEFT + 0x255c, // #BOX DRAWINGS UP DOUBLE AND LEFT SINGLE + 0x255b, // #BOX DRAWINGS UP SINGLE AND LEFT DOUBLE + 0x2510, // #BOX DRAWINGS LIGHT DOWN AND LEFT + 0x2514, // #BOX DRAWINGS LIGHT UP AND RIGHT + 0x2534, // #BOX DRAWINGS LIGHT UP AND HORIZONTAL + 0x252c, // #BOX DRAWINGS LIGHT DOWN AND HORIZONTAL + 0x251c, // #BOX DRAWINGS LIGHT VERTICAL AND RIGHT + 0x2500, // #BOX DRAWINGS LIGHT HORIZONTAL + 0x253c, // #BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL + 0x255e, // #BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE + 0x255f, // #BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE + 0x255a, // #BOX DRAWINGS DOUBLE UP AND RIGHT + 0x2554, // #BOX DRAWINGS DOUBLE DOWN AND RIGHT + 0x2569, // #BOX DRAWINGS DOUBLE UP AND HORIZONTAL + 0x2566, // #BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL + 0x2560, // #BOX DRAWINGS DOUBLE VERTICAL AND RIGHT + 0x2550, // #BOX DRAWINGS DOUBLE HORIZONTAL + 0x256c, // #BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL + 0x2567, // #BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE + 0x2568, // #BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE + 0x2564, // #BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE + 0x2565, // #BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE + 0x2559, // #BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE + 0x2558, // #BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE + 0x2552, // #BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE + 0x2553, // #BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE + 0x256b, // #BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE + 0x256a, // #BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE + 0x2518, // #BOX DRAWINGS LIGHT UP AND LEFT + 0x250c, // #BOX DRAWINGS LIGHT DOWN AND RIGHT + 0x2588, // #FULL BLOCK + 0x2584, // #LOWER HALF BLOCK + 0x258c, // #LEFT HALF BLOCK + 0x2590, // #RIGHT HALF BLOCK + 0x2580, // #UPPER HALF BLOCK + 0x03b1, // #GREEK SMALL LETTER ALPHA + 0x00df, // #LATIN SMALL LETTER SHARP S + 0x0393, // #GREEK CAPITAL LETTER GAMMA + 0x03c0, // #GREEK SMALL LETTER PI + 0x03a3, // #GREEK CAPITAL LETTER SIGMA + 0x03c3, // #GREEK SMALL LETTER SIGMA + 0x00b5, // #MICRO SIGN + 0x03c4, // #GREEK SMALL LETTER TAU + 0x03a6, // #GREEK CAPITAL LETTER PHI + 0x0398, // #GREEK CAPITAL LETTER THETA + 0x03a9, // #GREEK CAPITAL LETTER OMEGA + 0x03b4, // #GREEK SMALL LETTER DELTA + 0x221e, // #INFINITY + 0x03c6, // #GREEK SMALL LETTER PHI + 0x03b5, // #GREEK SMALL LETTER EPSILON + 0x2229, // #INTERSECTION + 0x2261, // #IDENTICAL TO + 0x00b1, // #PLUS-MINUS SIGN + 0x2265, // #GREATER-THAN OR EQUAL TO + 0x2264, // #LESS-THAN OR EQUAL TO + 0x2320, // #TOP HALF INTEGRAL + 0x2321, // #BOTTOM HALF INTEGRAL + 0x00f7, // #DIVISION SIGN + 0x2248, // #ALMOST EQUAL TO + 0x00b0, // #DEGREE SIGN + 0x2219, // #BULLET OPERATOR + 0x00b7, // #MIDDLE DOT + 0x221a, // #SQUARE ROOT + 0x207f, // #SUPERSCRIPT LATIN SMALL LETTER N + 0x00b2, // #SUPERSCRIPT TWO + 0x25a0, // #BLACK SQUARE + 0x00a0, // #NO-BREAK SPACE + }; + + public char map_cp850_unicode(char x) { + if (x >= 0x100) + return x; + + return unimap[x]; + } + + private void _SetCursor(int row, int col) { + int maxr = height - 1; + int tm = getTopMargin(); + R = (row < 0) ? 0 : row; + C = (col < 0) ? 0 : (col >= width) ? width - 1 : col; + + if (!moveoutsidemargins) { + R += tm; + maxr = getBottomMargin(); + } + + if (R > maxr) R = maxr; + } + + private void putChar(char c, boolean isWide, boolean doshowcursor) { + int rows = this.height; //statusline + int columns = this.width; + + // byte msg[]; +// if (debug > 4) { +// debugStr.append("putChar(") +// .append(c) +// .append(" [") +// .append((int) c) +// .append("]) at R=") +// .append(R) +// .append(" , C=") +// .append(C) +// .append(", columns=") +// .append(columns) +// .append(", rows=") +// .append(rows); +// debug(debugStr.toString()); +// debugStr.setLength(0); +// } +// markLine(R, 1); +// if (c > 255) { +// if (debug > 0) +// debug("char > 255:" + (int) c); +// //return; +// } + switch (term_state) { + case TSTATE_DATA: + + /* FIXME: we shouldn't use chars with bit 8 set if ibmcharset. + * probably... but some BBS do anyway... + */ + if (!useibmcharset) { + boolean doneflag = true; + + switch (c) { + case OSC: + osc = ""; + term_state = TSTATE_OSC; + break; + + case RI: + if (R > getTopMargin()) + R--; + else + insertLine(R, 1, SCROLL_DOWN); + + if (debug > 1) + debug("RI"); + + break; + + case IND: + if (debug > 2) { + debugStr.append("IND at ") + .append(R) + .append(", tm is ") + .append(getTopMargin()) + .append(", bm is ") + .append(getBottomMargin()); + debug(debugStr.toString()); + debugStr.setLength(0); + } + + if (R == getBottomMargin() || R == rows - 1) + insertLine(R, 1, SCROLL_UP); + else + R++; + + if (debug > 1) + debug("IND (at " + R + " )"); + + break; + + case NEL: + if (R == getBottomMargin() || R == rows - 1) + insertLine(R, 1, SCROLL_UP); + else + R++; + + C = 0; + + if (debug > 1) + debug("NEL (at " + R + " )"); + + break; + + case HTS: + Tabs[C] = 1; + + if (debug > 1) + debug("HTS"); + + break; + + case DCS: + dcs = ""; + term_state = TSTATE_DCS; + break; + + default: + doneflag = false; + break; + } + + if (doneflag) break; + } + + switch (c) { + case SS3: + onegl = 3; + break; + + case SS2: + onegl = 2; + break; + + case CSI: // should be in the 8bit section, but some BBS use this + DCEvar = 0; + DCEvars[0] = 0; + DCEvars[1] = 0; + DCEvars[2] = 0; + DCEvars[3] = 0; + term_state = TSTATE_CSI; + break; + + case ESC: + term_state = TSTATE_ESC; + lastwaslf = 0; + break; + + case 5: /* ENQ */ + write(answerBack, false); + break; + + case 12: + /* FormFeed, Home for the BBS world */ + deleteArea(0, 0, columns, rows, attributes); + C = R = 0; + break; + + case '\b': /* 8 */ + C--; + + if (C < 0) + C = 0; + + lastwaslf = 0; + break; + + case '\t': + do { + // Don't overwrite or insert! TABS are not destructive, but movement! + C++; + } + while (C < columns && (Tabs[C] == 0)); + + lastwaslf = 0; + break; + + case '\r': // 13 CR + C = 0; + break; + + case '\n': // 10 LF + if (debug > 3) + debug("R= " + R + ", bm " + getBottomMargin() + ", tm=" + getTopMargin() + ", rows=" + rows); + + if (!vms) { + if (lastwaslf != 0 && lastwaslf != c) // Ray: I do not understand this logic. + break; + + lastwaslf = c; + /*C = 0;*/ + } + + if (R == getBottomMargin() || R >= rows - 1) + insertLine(R, 1, SCROLL_UP); + else + R++; + + break; + + case 7: + beep(); + break; + + case '\016': /* SMACS , as */ + /* ^N, Shift out - Put G1 into GL */ + gl = 1; + usedcharsets = true; + break; + + case '\017': /* RMACS , ae */ + /* ^O, Shift in - Put G0 into GL */ + gl = 0; + usedcharsets = true; + break; + + default: { + int thisgl = gl; + + if (onegl >= 0) { + thisgl = onegl; + onegl = -1; + } + + lastwaslf = 0; + + if (c < 32) { + if (c != 0) + if (debug > 0) + debug("TSTATE_DATA char: " + ((int) c)); + + /*break; some BBS really want those characters, like hearst etc. */ + if (c == 0) /* print 0 ... you bet */ + break; + } + + if (C >= columns) { + if (wraparound) { + int bot = rows; + + // If we're in the scroll region, check against the bottom margin + if (R <= getBottomMargin() && R >= getTopMargin()) + bot = getBottomMargin() + 1; + + if (R < bot - 1) + R++; + else { + if (debug > 3) debug("scrolling due to wrap at " + R); + + insertLine(R, 1, SCROLL_UP); + } + + C = 0; + } + else { + // cursor stays on last character. + C = columns - 1; + } + } + + boolean mapped = false; + + // Mapping if DEC Special is chosen charset + if (usedcharsets) { + if (c >= '\u0020' && c <= '\u007f') { + switch (gx[thisgl]) { + case '0': + + // Remap SCOANSI line drawing to VT100 line drawing chars + // for our SCO using customers. + if (terminalID.equals("scoansi") || terminalID.equals("ansi")) { + for (int i = 0; i < scoansi_acs.length(); i += 2) { + if (c == scoansi_acs.charAt(i)) { + c = scoansi_acs.charAt(i + 1); + break; + } + } + } + + if (c >= '\u005f' && c <= '\u007e') { + c = DECSPECIAL[(short) c - 0x5f]; + mapped = true; + } + + break; + + case '<': // 'user preferred' is currently 'ISO Latin-1 suppl + c = (char)((c & 0x7f) | 0x80); + mapped = true; + break; + + case 'A': + case 'B': // Latin-1 , ASCII -> fall through + mapped = true; + break; + + default: + debug("Unsupported GL mapping: " + gx[thisgl]); + break; + } + } + + if (!mapped && (c >= '\u0080' && c <= '\u00ff')) { + switch (gx[gr]) { + case '0': + if (c >= '\u00df' && c <= '\u00fe') { + c = DECSPECIAL[c - '\u00df']; + mapped = true; + } + + break; + + case '<': + case 'A': + case 'B': + mapped = true; + break; + + default: + debug("Unsupported GR mapping: " + gx[gr]); + break; + } + } + } + + if (!mapped && useibmcharset) + c = map_cp850_unicode(c); + + /*if(true || (statusmode == 0)) { */ + if (isWide) { + if (C >= columns - 1) { + if (wraparound) { + int bot = rows; + + // If we're in the scroll region, check against the bottom margin + if (R <= getBottomMargin() && R >= getTopMargin()) + bot = getBottomMargin() + 1; + + if (R < bot - 1) + R++; + else { + if (debug > 3) debug("scrolling due to wrap at " + R); + + insertLine(R, 1, SCROLL_UP); + } + + C = 0; + } + else { + // cursor stays on last wide character. + C = columns - 2; + } + } + } + + if (insertmode == 1) { + if (isWide) { + insertChar(C++, R, c, attributes | FULLWIDTH); + insertChar(C, R, ' ', attributes | FULLWIDTH); + } + else + insertChar(C, R, c, attributes); + } + else { + if (isWide) { + putChar(C++, R, c, attributes | FULLWIDTH); + putChar(C, R, ' ', attributes | FULLWIDTH); + } + else + putChar(C, R, c, attributes); + } + + /* + } else { + if (insertmode==1) { + insertChar(C, rows, c, attributes); + } else { + putChar(C, rows, c, attributes); + } + } + */ + C++; + break; + } + } /* switch(c) */ + + break; + + case TSTATE_OSC: + if ((c < 0x20) && (c != ESC)) {// NP - No printing character + handle_osc(osc); + term_state = TSTATE_DATA; + break; + } + + //but check for vt102 ESC \ + if (c == '\\' && osc.charAt(osc.length() - 1) == ESC) { + handle_osc(osc); + term_state = TSTATE_DATA; + break; + } + + osc = osc + c; + break; + + case TSTATE_ESCSPACE: + term_state = TSTATE_DATA; + + switch (c) { + case 'F': /* S7C1T, Disable output of 8-bit controls, use 7-bit */ + output8bit = false; + break; + + case 'G': /* S8C1T, Enable output of 8-bit control codes*/ + output8bit = true; + break; + + default: + debug("ESC " + c + " unhandled."); + } + + break; + + case TSTATE_ESC: + term_state = TSTATE_DATA; + + switch (c) { + case ' ': + term_state = TSTATE_ESCSPACE; + break; + + case '#': + term_state = TSTATE_ESCSQUARE; + break; + + case 'c': + /* Hard terminal reset */ + reset(); + break; + + case '[': + DCEvar = 0; + DCEvars[0] = 0; + DCEvars[1] = 0; + DCEvars[2] = 0; + DCEvars[3] = 0; + term_state = TSTATE_CSI; + break; + + case ']': + osc = ""; + term_state = TSTATE_OSC; + break; + + case 'P': + dcs = ""; + term_state = TSTATE_DCS; + break; + + case 'A': /* CUU */ + R--; + + if (R < 0) R = 0; + + break; + + case 'B': /* CUD */ + R++; + + if (R >= rows) R = rows - 1; + + break; + + case 'C': + C++; + + if (C >= columns) C = columns - 1; + + break; + + case 'I': // RI + insertLine(R, 1, SCROLL_DOWN); + break; + + case 'E': /* NEL */ + if (R == getBottomMargin() || R == rows - 1) + insertLine(R, 1, SCROLL_UP); + else + R++; + + C = 0; + + if (debug > 1) + debug("ESC E (at " + R + ")"); + + break; + + case 'D': /* IND */ + if (R == getBottomMargin() || R == rows - 1) + insertLine(R, 1, SCROLL_UP); + else + R++; + + if (debug > 1) + debug("ESC D (at " + R + " )"); + + break; + + case 'J': /* erase to end of screen */ + if (R < rows - 1) + deleteArea(0, R + 1, columns, rows - R - 1, attributes); + + if (C < columns - 1) + deleteArea(C, R, columns - C, 1, attributes); + + break; + + case 'K': + if (C < columns - 1) + deleteArea(C, R, columns - C, 1, attributes); + + break; + + case 'M': // RI + debug("ESC M : R is " + R + ", tm is " + getTopMargin() + ", bm is " + getBottomMargin()); + + if (R > getTopMargin()) { // just go up 1 line. + R--; + } + else { // scroll down + insertLine(R, 1, SCROLL_DOWN); + } + + /* else do nothing ; */ + if (debug > 2) + debug("ESC M "); + + break; + + case 'H': + if (debug > 1) + debug("ESC H at " + C); + + /* right border probably ...*/ + if (C >= columns) + C = columns - 1; + + Tabs[C] = 1; + break; + + case 'N': // SS2 + onegl = 2; + break; + + case 'O': // SS3 + onegl = 3; + break; + + case '=': + + /*application keypad*/ + if (debug > 0) + debug("ESC ="); + + keypadmode = true; + break; + + case '<': /* vt52 mode off */ + vt52mode = false; + break; + + case '>': /*normal keypad*/ + if (debug > 0) + debug("ESC >"); + + keypadmode = false; + break; + + case '7': /* DECSC: save cursor, attributes */ + Sc = C; + Sr = R; + Sgl = gl; + Sgr = gr; + Sa = attributes; + Sgx = new char[4]; + + for (int i = 0; i < 4; i++) Sgx[i] = gx[i]; + + if (debug > 1) + debug("ESC 7"); + + break; + + case '8': /* DECRC: restore cursor, attributes */ + C = Sc; + R = Sr; + gl = Sgl; + gr = Sgr; + + if (Sgx != null) + for (int i = 0; i < 4; i++) gx[i] = Sgx[i]; + + attributes = Sa; + + if (debug > 1) + debug("ESC 8"); + + break; + + case '(': /* Designate G0 Character set (ISO 2022) */ + term_state = TSTATE_SETG0; + usedcharsets = true; + break; + + case ')': /* Designate G1 character set (ISO 2022) */ + term_state = TSTATE_SETG1; + usedcharsets = true; + break; + + case '*': /* Designate G2 Character set (ISO 2022) */ + term_state = TSTATE_SETG2; + usedcharsets = true; + break; + + case '+': /* Designate G3 Character set (ISO 2022) */ + term_state = TSTATE_SETG3; + usedcharsets = true; + break; + + case '~': /* Locking Shift 1, right */ + gr = 1; + usedcharsets = true; + break; + + case 'n': /* Locking Shift 2 */ + gl = 2; + usedcharsets = true; + break; + + case '}': /* Locking Shift 2, right */ + gr = 2; + usedcharsets = true; + break; + + case 'o': /* Locking Shift 3 */ + gl = 3; + usedcharsets = true; + break; + + case '|': /* Locking Shift 3, right */ + gr = 3; + usedcharsets = true; + break; + + case 'Y': /* vt52 cursor address mode , next chars are x,y */ + term_state = TSTATE_VT52Y; + break; + + case '_': + term_state = TSTATE_TITLE; + break; + + case '\\': + // TODO save title + term_state = TSTATE_DATA; + break; + + default: + debug("ESC unknown letter: " + c + " (" + ((int) c) + ")"); + break; + } + + break; + + case TSTATE_VT52X: + C = c - 37; + + if (C < 0) + C = 0; + else if (C >= width) + C = width - 1; + + term_state = TSTATE_VT52Y; + break; + + case TSTATE_VT52Y: + R = c - 37; + + if (R < 0) + R = 0; + else if (R >= height) + R = height - 1; + + term_state = TSTATE_DATA; + break; + + case TSTATE_SETG0: + if (c != '0' && c != 'A' && c != 'B' && c != '<') + debug("ESC ( " + c + ": G0 char set? (" + ((int) c) + ")"); + else { + if (debug > 2) debug("ESC ( : G0 char set (" + c + " " + ((int) c) + ")"); + + gx[0] = c; + } + + term_state = TSTATE_DATA; + break; + + case TSTATE_SETG1: + if (c != '0' && c != 'A' && c != 'B' && c != '<') { + debug("ESC ) " + c + " (" + ((int) c) + ") :G1 char set?"); + } + else { + if (debug > 2) debug("ESC ) :G1 char set (" + c + " " + ((int) c) + ")"); + + gx[1] = c; + } + + term_state = TSTATE_DATA; + break; + + case TSTATE_SETG2: + if (c != '0' && c != 'A' && c != 'B' && c != '<') + debug("ESC*:G2 char set? (" + ((int) c) + ")"); + else { + if (debug > 2) debug("ESC*:G2 char set (" + c + " " + ((int) c) + ")"); + + gx[2] = c; + } + + term_state = TSTATE_DATA; + break; + + case TSTATE_SETG3: + if (c != '0' && c != 'A' && c != 'B' && c != '<') + debug("ESC+:G3 char set? (" + ((int) c) + ")"); + else { + if (debug > 2) debug("ESC+:G3 char set (" + c + " " + ((int) c) + ")"); + + gx[3] = c; + } + + term_state = TSTATE_DATA; + break; + + case TSTATE_ESCSQUARE: + switch (c) { + case '8': + for (int i = 0; i < columns; i++) + for (int j = 0; j < rows; j++) + putChar(i, j, 'E', 0); + + break; + + default: + debug("ESC # " + c + " not supported."); + break; + } + + term_state = TSTATE_DATA; + break; + + case TSTATE_DCS: + if (c == '\\' && dcs.charAt(dcs.length() - 1) == ESC) { + handle_dcs(dcs); + term_state = TSTATE_DATA; + break; + } + + dcs = dcs + c; + break; + + case TSTATE_DCEQ: + term_state = TSTATE_DATA; + + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + DCEvars[DCEvar] = DCEvars[DCEvar] * 10 + (c) - 48; + term_state = TSTATE_DCEQ; + break; + + case ';': + DCEvar++; + DCEvars[DCEvar] = 0; + term_state = TSTATE_DCEQ; + break; + + case 's': // XTERM_SAVE missing! + if (true || debug > 1) + debug("ESC [ ? " + DCEvars[0] + " s unimplemented!"); + + break; + + case 'r': // XTERM_RESTORE + if (true || debug > 1) + debug("ESC [ ? " + DCEvars[0] + " r"); + + /* DEC Mode reset */ + for (int i = 0; i <= DCEvar; i++) { + switch (DCEvars[i]) { + case 3: /* 80 columns*/ + setScreenSize(80, height, true); + break; + + case 4: /* scrolling mode, smooth */ + break; + + case 5: /* light background */ + break; + + case 6: /* DECOM (Origin Mode) move inside margins. */ + moveoutsidemargins = true; + break; + + case 7: /* DECAWM: Autowrap Mode */ + wraparound = false; + break; + + case 12:/* local echo off */ + break; + + case 9: /* X10 mouse */ + case 1000: /* xterm style mouse report on */ + case 1001: + case 1002: + case 1003: + mouserpt = DCEvars[i]; + break; + + default: + debug("ESC [ ? " + DCEvars[0] + " r, unimplemented!"); + } + } + + break; + + case 'h': // DECSET + if (debug > 0) + debug("ESC [ ? " + DCEvars[0] + " h"); + + /* DEC Mode set */ + for (int i = 0; i <= DCEvar; i++) { + switch (DCEvars[i]) { + case 1: /* Application cursor keys */ + KeyUp[0] = "\u001bOA"; + KeyDown[0] = "\u001bOB"; + KeyRight[0] = "\u001bOC"; + KeyLeft[0] = "\u001bOD"; + break; + + case 2: /* DECANM */ + vt52mode = false; + break; + + case 3: /* 132 columns*/ + setScreenSize(132, height, true); + break; + + case 6: /* DECOM: move inside margins. */ + moveoutsidemargins = false; + break; + + case 7: /* DECAWM: Autowrap Mode */ + wraparound = true; + break; + + case 25: /* turn cursor on */ + showCursor(true); + break; + + case 9: /* X10 mouse */ + case 1000: /* xterm style mouse report on */ + case 1001: + case 1002: + case 1003: + mouserpt = DCEvars[i]; + break; + + /* unimplemented stuff, fall through */ + /* 4 - scrolling mode, smooth */ + /* 5 - light background */ + /* 12 - local echo off */ + /* 18 - DECPFF - Printer Form Feed Mode -> On */ + /* 19 - DECPEX - Printer Extent Mode -> Screen */ + default: + debug("ESC [ ? " + DCEvars[0] + " h, unsupported."); + break; + } + } + + break; + + case 'i': // DEC Printer Control, autoprint, echo screenchars to printer + + // This is different to CSI i! + // Also: "Autoprint prints a final display line only when the + // cursor is moved off the line by an autowrap or LF, FF, or + // VT (otherwise do not print the line)." + switch (DCEvars[0]) { + case 1: + if (debug > 1) + debug("CSI ? 1 i : Print line containing cursor"); + + break; + + case 4: + if (debug > 1) + debug("CSI ? 4 i : Start passthrough printing"); + + break; + + case 5: + if (debug > 1) + debug("CSI ? 4 i : Stop passthrough printing"); + + break; + } + + break; + + case 'l': //DECRST + + /* DEC Mode reset */ + if (debug > 0) + debug("ESC [ ? " + DCEvars[0] + " l"); + + for (int i = 0; i <= DCEvar; i++) { + switch (DCEvars[i]) { + case 1: /* Application cursor keys */ + KeyUp[0] = "\u001b[A"; + KeyDown[0] = "\u001b[B"; + KeyRight[0] = "\u001b[C"; + KeyLeft[0] = "\u001b[D"; + break; + + case 2: /* DECANM */ + vt52mode = true; + break; + + case 3: /* 80 columns*/ + setScreenSize(80, height, true); + break; + + case 6: /* DECOM: move outside margins. */ + moveoutsidemargins = true; + break; + + case 7: /* DECAWM: Autowrap Mode OFF */ + wraparound = false; + break; + + case 25: /* turn cursor off */ + showCursor(false); + break; + + /* Unimplemented stuff: */ + /* 4 - scrolling mode, jump */ + /* 5 - dark background */ + /* 7 - DECAWM - no wrap around mode */ + /* 12 - local echo on */ + /* 18 - DECPFF - Printer Form Feed Mode -> Off*/ + /* 19 - DECPEX - Printer Extent Mode -> Scrolling Region */ + case 9: /* X10 mouse */ + case 1000: /* xterm style mouse report OFF */ + case 1001: + case 1002: + case 1003: + mouserpt = 0; + break; + + default: + debug("ESC [ ? " + DCEvars[0] + " l, unsupported."); + break; + } + } + + break; + + case 'n': + if (debug > 0) + debug("ESC [ ? " + DCEvars[0] + " n"); + + switch (DCEvars[0]) { + case 15: + /* printer? no printer. */ + write((ESC) + "[?13n", false); + debug("ESC[5n"); + break; + + default: + debug("ESC [ ? " + DCEvars[0] + " n, unsupported."); + break; + } + + break; + + default: + debug("ESC [ ? " + DCEvars[0] + " " + c + ", unsupported."); + break; + } + + break; + + case TSTATE_CSI_EX: + term_state = TSTATE_DATA; + + switch (c) { + case ESC: + term_state = TSTATE_ESC; + break; + + default: + debug("Unknown character ESC[! character is " + (int) c); + break; + } + + break; + + case TSTATE_CSI_TICKS: + term_state = TSTATE_DATA; + + switch (c) { + case 'p': + debug("Conformance level: " + DCEvars[0] + " (unsupported)," + DCEvars[1]); + + if (DCEvars[0] == 61) { + output8bit = false; + break; + } + + if (DCEvars[1] == 1) { + output8bit = false; + } + else { + output8bit = true; /* 0 or 2 */ + } + + break; + + default: + debug("Unknown ESC [... \"" + c); + break; + } + + break; + + case TSTATE_CSI_EQUAL: + term_state = TSTATE_DATA; + + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + DCEvars[DCEvar] = DCEvars[DCEvar] * 10 + (c) - 48; + term_state = TSTATE_CSI_EQUAL; + break; + + case ';': + DCEvar++; + DCEvars[DCEvar] = 0; + term_state = TSTATE_CSI_EQUAL; + break; + + case 'F': { /* SCO ANSI foreground */ + int newcolor; + debug("ESC [ = " + DCEvars[0] + " F"); + attributes &= ~COLOR_FG; + newcolor = ((DCEvars[0] & 1) << 2) | + (DCEvars[0] & 2) | + ((DCEvars[0] & 4) >> 2) ; + attributes |= (newcolor + 1) << COLOR_FG_SHIFT; + break; + } + + case 'G': { /* SCO ANSI background */ + int newcolor; + debug("ESC [ = " + DCEvars[0] + " G"); + attributes &= ~COLOR_BG; + newcolor = ((DCEvars[0] & 1) << 2) | + (DCEvars[0] & 2) | + ((DCEvars[0] & 4) >> 2) ; + attributes |= (newcolor + 1) << COLOR_BG_SHIFT; + break; + } + + default: + debugStr.append("Unknown ESC [ = "); + + for (int i = 0; i <= DCEvar; i++) { + debugStr.append(DCEvars[i]) + .append(','); + } + + debugStr.append(c); + debug(debugStr.toString()); + debugStr.setLength(0); + break; + } + + break; + + case TSTATE_CSI_DOLLAR: + term_state = TSTATE_DATA; + + switch (c) { + case '}': + debug("Active Status Display now " + DCEvars[0]); + statusmode = DCEvars[0]; + break; + + /* bad documentation? + case '-': + debug("Set Status Display now "+DCEvars[0]); + break; + */ + case '~': + debug("Status Line mode now " + DCEvars[0]); + break; + + default: + debug("UNKNOWN Status Display code " + c + ", with Pn=" + DCEvars[0]); + break; + } + + break; + + case TSTATE_CSI: + term_state = TSTATE_DATA; + + switch (c) { + case '"': + term_state = TSTATE_CSI_TICKS; + break; + + case '$': + term_state = TSTATE_CSI_DOLLAR; + break; + + case '=': + term_state = TSTATE_CSI_EQUAL; + break; + + case '!': + term_state = TSTATE_CSI_EX; + break; + + case '?': + DCEvar = 0; + DCEvars[0] = 0; + term_state = TSTATE_DCEQ; + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + DCEvars[DCEvar] = DCEvars[DCEvar] * 10 + (c) - 48; + term_state = TSTATE_CSI; + break; + + case ';': + DCEvar++; + DCEvars[DCEvar] = 0; + term_state = TSTATE_CSI; + break; + + case 'c':/* send primary device attributes */ + /* send (ESC[?61c) */ + String subcode = ""; + + if (terminalID.equals("vt320")) subcode = "63;"; + + if (terminalID.equals("vt220")) subcode = "62;"; + + if (terminalID.equals("vt100")) subcode = "61;"; + + write((ESC) + "[?" + subcode + "1;2c", false); + + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " c"); + + break; + + case 'q': + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " q"); + + break; + + case 'g': + + /* used for tabsets */ + switch (DCEvars[0]) { + case 3:/* clear them */ + Tabs = new byte[width]; + break; + + case 0: + Tabs[C] = 0; + break; + } + + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " g"); + + break; + + case 'h': + switch (DCEvars[0]) { + case 4: + insertmode = 1; + break; + + case 20: + debug("Setting CRLF to TRUE"); + sendcrlf = true; + break; + + default: + debug("unsupported: ESC [ " + DCEvars[0] + " h"); + break; + } + + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " h"); + + break; + + case 'i': // Printer Controller mode. + + // "Transparent printing sends all output, except the CSI 4 i + // termination string, to the printer and not the screen, + // uses an 8-bit channel if no parity so NUL and DEL will be + // seen by the printer and by the termination recognizer code, + // and all translation and character set selections are + // bypassed." + switch (DCEvars[0]) { + case 0: + if (debug > 1) + debug("CSI 0 i: Print Screen, not implemented."); + + break; + + case 4: + if (debug > 1) + debug("CSI 4 i: Enable Transparent Printing, not implemented."); + + break; + + case 5: + if (debug > 1) + debug("CSI 4/5 i: Disable Transparent Printing, not implemented."); + + break; + + default: + debug("ESC [ " + DCEvars[0] + " i, unimplemented!"); + } + + break; + + case 'l': + switch (DCEvars[0]) { + case 4: + insertmode = 0; + break; + + case 20: + debug("Setting CRLF to FALSE"); + sendcrlf = false; + break; + + default: + debug("ESC [ " + DCEvars[0] + " l, unimplemented!"); + break; + } + + break; + + case 'A': { // CUU + int limit; + + /* FIXME: xterm only cares about 0 and topmargin */ + if (R >= getTopMargin()) { + limit = getTopMargin(); + } + else + limit = 0; + + if (DCEvars[0] == 0) + R--; + else + R -= DCEvars[0]; + + if (R < limit) + R = limit; + + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " A"); + + break; + } + + case 'B': // CUD + /* cursor down n (1) times */ + { + int limit; + + if (R <= getBottomMargin()) { + limit = getBottomMargin(); + } + else + limit = rows - 1; + + if (DCEvars[0] == 0) + R++; + else + R += DCEvars[0]; + + if (R > limit) + R = limit; + else { + if (debug > 2) debug("Not limited."); + } + + if (debug > 2) debug("to: " + R); + + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " B (at C=" + C + ")"); + + break; + } + + case 'C': + if (DCEvars[0] == 0) + DCEvars[0] = 1; + + while (DCEvars[0]-- > 0) { + C++; + } + + if (C >= columns) + C = columns - 1; + + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " C"); + + break; + + case 'd': // CVA + R = DCEvars[0] - 1; + + if (R < 0) + R = 0; + else if (R >= height) + R = height - 1; + + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " d"); + + break; + + case 'D': + if (DCEvars[0] == 0) + DCEvars[0] = 1; + + while (DCEvars[0]-- > 0) { + C--; + } + + if (C < 0) C = 0; + + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " D"); + + break; + + case 'r': // DECSTBM + if (DCEvar > 0) { // Ray: Any argument is optional + R = DCEvars[1] - 1; + + if (R < 0) + R = rows - 1; + else if (R >= rows) { + R = rows - 1; + } + } + else + R = rows - 1; + + int bot = R; + + if (R >= DCEvars[0]) { + R = DCEvars[0] - 1; + + if (R < 0) + R = 0; + } + + setMargins(R, bot); + _SetCursor(0, 0); + + if (debug > 1) + debug("ESC [" + DCEvars[0] + " ; " + DCEvars[1] + " r"); + + break; + + case 'G': /* CUP / cursor absolute column */ + C = DCEvars[0]; + + if (C < 0) + C = 0; + else if (C >= width) + C = width - 1; + + if (debug > 1) debug("ESC [ " + DCEvars[0] + " G"); + + break; + + case 'H': /* CUP / cursor position */ + /* gets 2 arguments */ + _SetCursor(DCEvars[0] - 1, DCEvars[1] - 1); + + if (debug > 2) { + debug("ESC [ " + DCEvars[0] + ";" + DCEvars[1] + " H, moveoutsidemargins " + moveoutsidemargins); + debug(" -> R now " + R + ", C now " + C); + } + + break; + + case 'f': /* move cursor 2 */ + /* gets 2 arguments */ + R = DCEvars[0] - 1; + C = DCEvars[1] - 1; + + if (C < 0) + C = 0; + else if (C >= width) + C = width - 1; + + if (R < 0) + R = 0; + else if (R >= height) + R = height - 1; + + if (debug > 2) + debug("ESC [ " + DCEvars[0] + ";" + DCEvars[1] + " f"); + + break; + + case 'S': /* ind aka 'scroll forward' */ + if (DCEvars[0] == 0) + insertLine(getBottomMargin(), SCROLL_UP); + else + insertLine(getBottomMargin(), DCEvars[0], SCROLL_UP); + + break; + + case 'L': + + /* insert n lines */ + if (DCEvars[0] == 0) + insertLine(R, SCROLL_DOWN); + else + insertLine(R, DCEvars[0], SCROLL_DOWN); + + if (debug > 1) + debug("ESC [ " + DCEvars[0] + "" + (c) + " (at R " + R + ")"); + + break; + + case 'T': /* 'ri' aka scroll backward */ + if (DCEvars[0] == 0) + insertLine(getTopMargin(), SCROLL_DOWN); + else + insertLine(getTopMargin(), DCEvars[0], SCROLL_DOWN); + + break; + + case 'M': + if (debug > 1) + debug("ESC [ " + DCEvars[0] + "" + (c) + " at R=" + R); + + if (DCEvars[0] == 0) + deleteLine(R); + else + for (int i = 0; i < DCEvars[0]; i++) + deleteLine(R); + + break; + + case 'K': + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " K"); + + /* clear in line */ + switch (DCEvars[0]) { + case 6: /* 97801 uses ESC[6K for delete to end of line */ + case 0:/*clear to right*/ + if (C < columns - 1) + deleteArea(C, R, columns - C, 1, attributes); + + break; + + case 1:/*clear to the left, including this */ + if (C > 0) + deleteArea(0, R, C + 1, 1, attributes); + + break; + + case 2:/*clear whole line */ + deleteArea(0, R, columns, 1, attributes); + break; + } + + break; + + case 'J': + + /* clear below current line */ + switch (DCEvars[0]) { + case 0: + if (R < rows - 1) + deleteArea(0, R + 1, columns, rows - R - 1, attributes); + + if (C < columns - 1) + deleteArea(C, R, columns - C, 1, attributes); + + break; + + case 1: + if (R > 0) + deleteArea(0, 0, columns, R, attributes); + + if (C > 0) + deleteArea(0, R, C + 1, 1, attributes); // include up to and including current + + break; + + case 2: + deleteArea(0, 0, columns, rows, attributes); + break; + } + + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " J"); + + break; + + case '@': + if (DCEvars[0] == 0) DCEvars[0] = 1; + + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " @"); + + for (int i = 0; i < DCEvars[0]; i++) + insertChar(C, R, ' ', attributes); + + break; + + case 'X': { + int toerase = DCEvars[0]; + + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " X, C=" + C + ",R=" + R); + + if (toerase == 0) + toerase = 1; + + if (toerase + C > columns) + toerase = columns - C; + + deleteArea(C, R, toerase, 1, attributes); + // does not change cursor position + break; + } + + case 'P': + if (debug > 1) + debug("ESC [ " + DCEvars[0] + " P, C=" + C + ",R=" + R); + + if (DCEvars[0] == 0) DCEvars[0] = 1; + + for (int i = 0; i < DCEvars[0]; i++) + deleteChar(C, R); + + break; + + case 'n': + switch (DCEvars[0]) { + case 5: /* malfunction? No malfunction. */ + writeSpecial((ESC) + "[0n"); + + if (debug > 1) + debug("ESC[5n"); + + break; + + case 6: + // DO NOT offset R and C by 1! (checked against /usr/X11R6/bin/resize + // FIXME check again. + // FIXME: but vttest thinks different??? + writeSpecial((ESC) + "[" + R + ";" + C + "R"); + + if (debug > 1) + debug("ESC[6n"); + + break; + + default: + if (debug > 0) + debug("ESC [ " + DCEvars[0] + " n??"); + + break; + } + + break; + + case 's': /* DECSC - save cursor */ + Sc = C; + Sr = R; + Sa = attributes; + + if (debug > 3) + debug("ESC[s"); + + break; + + case 'u': /* DECRC - restore cursor */ + C = Sc; + R = Sr; + attributes = Sa; + + if (debug > 3) + debug("ESC[u"); + + break; + + case 'm': /* attributes as color, bold , blink,*/ + if (debug > 3) + debug("ESC [ "); + + if (DCEvar == 0 && DCEvars[0] == 0) + attributes = 0; + + for (int i = 0; i <= DCEvar; i++) { + switch (DCEvars[i]) { + case 0: + if (DCEvar > 0) { + if (terminalID.equals("scoansi")) { + attributes &= COLOR; /* Keeps color. Strange but true. */ + } + else { + attributes = 0; + } + } + + break; + + case 1: + attributes |= BOLD; + attributes &= ~LOW; + break; + + case 2: + + /* SCO color hack mode */ + if (terminalID.equals("scoansi") && ((DCEvar - i) >= 2)) { + int ncolor; + attributes &= ~(COLOR | BOLD); + ncolor = DCEvars[i + 1]; + + if ((ncolor & 8) == 8) + attributes |= BOLD; + + ncolor = ((ncolor & 1) << 2) | (ncolor & 2) | ((ncolor & 4) >> 2); + attributes |= ((ncolor) + 1) << COLOR_FG_SHIFT; + ncolor = DCEvars[i + 2]; + ncolor = ((ncolor & 1) << 2) | (ncolor & 2) | ((ncolor & 4) >> 2); + attributes |= ((ncolor) + 1) << COLOR_BG_SHIFT; + i += 2; + } + else { + attributes |= LOW; + } + + break; + + case 3: /* italics */ + attributes |= INVERT; + break; + + case 4: + attributes |= UNDERLINE; + break; + + case 7: + attributes |= INVERT; + break; + + case 8: + attributes |= INVISIBLE; + break; + + case 5: /* blink on */ + break; + + /* 10 - ANSI X3.64-1979, select primary font, don't display control + * chars, don't set bit 8 on output */ + case 10: + gl = 0; + usedcharsets = true; + break; + + /* 11 - ANSI X3.64-1979, select second alt. font, display control + * chars, set bit 8 on output */ + case 11: /* SMACS , as */ + case 12: + gl = 1; + usedcharsets = true; + break; + + case 21: /* normal intensity */ + attributes &= ~(LOW | BOLD); + break; + + case 23: /* italics off */ + attributes &= ~INVERT; + break; + + case 25: /* blinking off */ + break; + + case 27: + attributes &= ~INVERT; + break; + + case 28: + attributes &= ~INVISIBLE; + break; + + case 24: + attributes &= ~UNDERLINE; + break; + + case 22: + attributes &= ~BOLD; + break; + + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: + attributes &= ~COLOR_FG; + attributes |= ((DCEvars[i] - 30) + 1) << COLOR_FG_SHIFT; + break; + + case 38: + if (DCEvars[i + 1] == 5) { + attributes &= ~COLOR_FG; + attributes |= ((DCEvars[i + 2]) + 1) << COLOR_FG_SHIFT; + i += 2; + } + + break; + + case 39: + attributes &= ~COLOR_FG; + break; + + case 40: + case 41: + case 42: + case 43: + case 44: + case 45: + case 46: + case 47: + attributes &= ~COLOR_BG; + attributes |= ((DCEvars[i] - 40) + 1) << COLOR_BG_SHIFT; + break; + + case 48: + if (DCEvars[i + 1] == 5) { + attributes &= ~COLOR_BG; + attributes |= (DCEvars[i + 2] + 1) << COLOR_BG_SHIFT; + i += 2; + } + + break; + + case 49: + attributes &= ~COLOR_BG; + break; + + case 90: + case 91: + case 92: + case 93: + case 94: + case 95: + case 96: + case 97: + attributes &= ~COLOR_FG; + attributes |= ((DCEvars[i] - 82) + 1) << COLOR_FG_SHIFT; + break; + + case 100: + case 101: + case 102: + case 103: + case 104: + case 105: + case 106: + case 107: + attributes &= ~COLOR_BG; + attributes |= ((DCEvars[i] - 92) + 1) << COLOR_BG_SHIFT; + break; + + default: + debugStr.append("ESC [ ") + .append(DCEvars[i]) + .append(" m unknown..."); + debug(debugStr.toString()); + debugStr.setLength(0); + break; + } + + if (debug > 3) { + debugStr.append(DCEvars[i]) + .append(';'); + debug(debugStr.toString()); + debugStr.setLength(0); + } + } + + if (debug > 3) { + debugStr.append(" (attributes = ") + .append(attributes) + .append(")m"); + debug(debugStr.toString()); + debugStr.setLength(0); + } + + break; + + default: + debugStr.append("ESC [ unknown letter: ") + .append(c) + .append(" (") + .append((int)c) + .append(')'); + debug(debugStr.toString()); + debugStr.setLength(0); + break; + } + + break; + + case TSTATE_TITLE: + switch (c) { + case ESC: + term_state = TSTATE_ESC; + break; + + default: + // TODO save title + break; + } + + break; + + default: + term_state = TSTATE_DATA; + break; + } + + setCursorPosition(C, R); + } + + /* hard reset the terminal */ + public void reset() { + gx[0] = 'B'; + gx[1] = 'B'; + gx[2] = 'B'; + gx[3] = 'B'; + gl = 0; // default GL to G0 + gr = 2; // default GR to G2 + onegl = -1; // Single shift override + /* reset tabs */ + int nw = width; + + if (nw < 132) nw = 132; + + Tabs = new byte[nw]; + + for (int i = 0; i < nw; i += 8) { + Tabs[i] = 1; + } + + deleteArea(0, 0, width, height, attributes); + setMargins(0, height); + C = R = 0; + _SetCursor(0, 0); + + if (display != null) + display.resetColors(); + + showCursor(true); + /*FIXME:*/ + term_state = TSTATE_DATA; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/net/sourceforge/jsocks/Authentication.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/sourceforge/jsocks/Authentication.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,34 @@ +package net.sourceforge.jsocks; + +/** + The Authentication interface provides for performing method specific + authentication for SOCKS5 connections. +*/ +public interface Authentication{ + /** + This method is called when SOCKS5 server have selected a particular + authentication method, for whch an implementaion have been registered. + +

+ This method should return an array {inputstream,outputstream + [,UDPEncapsulation]}. The reason for that is that SOCKS5 protocol + allows to have method specific encapsulation of data on the socket for + purposes of integrity or security. And this encapsulation should be + performed by those streams returned from the method. It is also possible + to encapsulate datagrams. If authentication method supports such + encapsulation an instance of the UDPEncapsulation interface should be + returned as third element of the array, otherwise either null should be + returned as third element, or array should contain only 2 elements. + + @param methodId Authentication method selected by the server. + @param proxySocket Socket used to conect to the proxy. + @return Two or three element array containing + Input/Output streams which should be used on this connection. + Third argument is optional and should contain an instance + of UDPEncapsulation. It should be provided if the authentication + method used requires any encapsulation to be done on the + datagrams. + */ + Object[] doSocksAuthentication(int methodId,java.net.Socket proxySocket) + throws java.io.IOException; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/net/sourceforge/jsocks/AuthenticationNone.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/sourceforge/jsocks/AuthenticationNone.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,17 @@ +package net.sourceforge.jsocks; + +/** + SOCKS5 none authentication. Dummy class does almost nothing. +*/ +public class AuthenticationNone implements Authentication{ + + public Object[] doSocksAuthentication(int methodId, + java.net.Socket proxySocket) + throws java.io.IOException{ + + if(methodId!=0) return null; + + return new Object[] { proxySocket.getInputStream(), + proxySocket.getOutputStream()}; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/net/sourceforge/jsocks/CProxy.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/sourceforge/jsocks/CProxy.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,488 @@ +package net.sourceforge.jsocks; +import java.net.*; +import java.io.*; +import java.util.Hashtable; +import java.util.Enumeration; + +/** + Abstract class CProxy, base for classes Socks4Proxy and Socks5Proxy. + Defines methods for specifying default proxy, to be + used by all classes of this package. +*/ + +public abstract class CProxy{ + +//Data members + protected InetRange directHosts = new InetRange(); + + protected InetAddress proxyIP = null; + protected String proxyHost = null; + protected int proxyPort; + protected Socket proxySocket = null; + + protected InputStream in; + protected OutputStream out; + + protected int version; + + protected CProxy chainProxy = null; + + +//Protected static/class variables + protected static CProxy defaultProxy = null; + +//Constructors +//==================== + CProxy(CProxy chainProxy, + String proxyHost,int proxyPort)throws UnknownHostException{ + this.chainProxy = chainProxy; + this.proxyHost = proxyHost; + + if(chainProxy == null) + this.proxyIP = InetAddress.getByName(proxyHost); + + this.proxyPort = proxyPort; + } + + + CProxy(String proxyHost,int proxyPort)throws UnknownHostException{ + this(null,proxyHost,proxyPort); + } + + CProxy(CProxy chainProxy,InetAddress proxyIP,int proxyPort){ + this.chainProxy = chainProxy; + this.proxyIP = proxyIP; + this.proxyPort = proxyPort; + } + + CProxy(InetAddress proxyIP,int proxyPort){ + this(null,proxyIP,proxyPort); + } + + CProxy(CProxy p){ + this.proxyIP = p.proxyIP; + this.proxyPort = p.proxyPort; + this.version = p.version; + this.directHosts = p.directHosts; + } + +//Public instance methods +//======================== + + /** + Get the port on which proxy server is running. + * @return CProxy port. + */ + public int getPort(){ + return proxyPort; + } + /** + Get the ip address of the proxy server host. + * @return CProxy InetAddress. + */ + public InetAddress getInetAddress(){ + return proxyIP; + } + /** + * Adds given ip to the list of direct addresses. + * This machine will be accessed without using proxy. + */ + public void addDirect(InetAddress ip){ + directHosts.add(ip); + } + /** + * Adds host to the list of direct addresses. + * This machine will be accessed without using proxy. + */ + public boolean addDirect(String host){ + return directHosts.add(host); + } + /** + * Adds given range of addresses to the lsit of direct addresses, + * machines within this range will be accessed without using proxy. + */ + public void addDirect(InetAddress from,InetAddress to){ + directHosts.add(from,to); + } + /** + * Sets given InetRange as the list of direct address, previous + * list will be discarded, any changes done previously with + * addDirect(Inetaddress) will be lost. + * The machines in this range will be accessed without using proxy. + * @param ir InetRange which should be used to look up direct addresses. + * @see InetRange + */ + public void setDirect(InetRange ir){ + directHosts = ir; + } + + /** + Get the list of direct hosts. + * @return Current range of direct address as InetRange object. + * @see InetRange + */ + public InetRange getDirect(){ + return directHosts; + } + /** + Check wether the given host is on the list of direct address. + @param host Host name to check. + * @return true if the given host is specified as the direct addresses. + */ + public boolean isDirect(String host){ + return directHosts.contains(host); + } + /** + Check wether the given host is on the list of direct addresses. + @param host Host address to check. + * @return true if the given host is specified as the direct address. + */ + public boolean isDirect(InetAddress host){ + return directHosts.contains(host); + } + /** + Set the proxy which should be used to connect to given proxy. + @param chainProxy CProxy to use to connect to this proxy. + */ + public void setChainProxy(CProxy chainProxy){ + this.chainProxy = chainProxy; + } + + /** + Get proxy which is used to connect to this proxy. + @return CProxy which is used to connect to this proxy, or null + if proxy is to be contacted directly. + */ + public CProxy getChainProxy(){ + return chainProxy; + } + + /** + Get string representation of this proxy. + * @returns string in the form:proxyHost:proxyPort \t Version versionNumber + */ + public String toString(){ + return (""+proxyIP.getHostName()+":"+proxyPort+"\tVersion "+version); + } + + +//Public Static(Class) Methods +//============================== + + /** + * Sets SOCKS4 proxy as default. + @param hostName Host name on which SOCKS4 server is running. + @param port Port on which SOCKS4 server is running. + @param user Username to use for communications with proxy. + */ + public static void setDefaultProxy(String hostName,int port,String user) + throws UnknownHostException{ + defaultProxy = new Socks4Proxy(hostName,port,user); + } + + /** + * Sets SOCKS4 proxy as default. + @param ipAddress Host address on which SOCKS4 server is running. + @param port Port on which SOCKS4 server is running. + @param user Username to use for communications with proxy. + */ + public static void setDefaultProxy(InetAddress ipAddress,int port, + String user){ + defaultProxy = new Socks4Proxy(ipAddress,port,user); + } + /** + * Sets SOCKS5 proxy as default. + * Default proxy only supports no-authentication. + @param hostName Host name on which SOCKS5 server is running. + @param port Port on which SOCKS5 server is running. + */ + public static void setDefaultProxy(String hostName,int port) + throws UnknownHostException{ + defaultProxy = new Socks5Proxy(hostName,port); + } + /** + * Sets SOCKS5 proxy as default. + * Default proxy only supports no-authentication. + @param ipAddress Host address on which SOCKS5 server is running. + @param port Port on which SOCKS5 server is running. + */ + public static void setDefaultProxy(InetAddress ipAddress,int port){ + defaultProxy = new Socks5Proxy(ipAddress,port); + } + /** + * Sets default proxy. + @param p CProxy to use as default proxy. + */ + public static void setDefaultProxy(CProxy p){ + defaultProxy = p; + } + + /** + Get current default proxy. + * @return Current default proxy, or null if none is set. + */ + public static CProxy getDefaultProxy(){ + return defaultProxy; + } + + /** + Parses strings in the form: host[:port:user:password], and creates + proxy from information obtained from parsing. +

+ Defaults: port = 1080.
+ If user specified but not password, creates Socks4Proxy, if user + not specified creates Socks5Proxy, if both user and password are + speciefied creates Socks5Proxy with user/password authentication. + @param proxy_entry String in the form host[:port:user:password] + @return CProxy created from the string, null if entry was somehow + invalid(host unknown for example, or empty string) + */ + public static CProxy parseProxy(String proxy_entry){ + + String proxy_host; + int proxy_port = 1080; + String proxy_user = null; + String proxy_password = null; + CProxy proxy; + + java.util.StringTokenizer st = new java.util.StringTokenizer( + proxy_entry,":"); + if(st.countTokens() < 1) return null; + + proxy_host = st.nextToken(); + if(st.hasMoreTokens()) + try{ + proxy_port = Integer.parseInt(st.nextToken().trim()); + }catch(NumberFormatException nfe){} + + if(st.hasMoreTokens()) + proxy_user = st.nextToken(); + + if(st.hasMoreTokens()) + proxy_password = st.nextToken(); + + try{ + if(proxy_user == null) + proxy = new Socks5Proxy(proxy_host,proxy_port); + else if(proxy_password == null) + proxy = new Socks4Proxy(proxy_host,proxy_port,proxy_user); + else{ + proxy = new Socks5Proxy(proxy_host,proxy_port); + UserPasswordAuthentication upa = new UserPasswordAuthentication( + proxy_user, proxy_password); + + ((Socks5Proxy)proxy).setAuthenticationMethod(upa.METHOD_ID,upa); + } + }catch(UnknownHostException uhe){ + return null; + } + + return proxy; + } + + +//Protected Methods +//================= + + protected void startSession()throws SocksException{ + try{ + if(chainProxy == null) + proxySocket = new Socket(proxyIP,proxyPort); + else if(proxyIP != null) + proxySocket = new SocksSocket(chainProxy,proxyIP,proxyPort); + else + proxySocket = new SocksSocket(chainProxy,proxyHost,proxyPort); + + in = proxySocket.getInputStream(); + out = proxySocket.getOutputStream(); + }catch(SocksException se){ + throw se; + }catch(IOException io_ex){ + throw new SocksException(SOCKS_PROXY_IO_ERROR,""+io_ex); + } + } + + protected abstract CProxy copy(); + protected abstract ProxyMessage formMessage(int cmd,InetAddress ip,int port); + protected abstract ProxyMessage formMessage(int cmd,String host,int port) + throws UnknownHostException; + protected abstract ProxyMessage formMessage(InputStream in) + throws SocksException, + IOException; + + + protected ProxyMessage connect(InetAddress ip,int port) + throws SocksException{ + try{ + startSession(); + ProxyMessage request = formMessage(SOCKS_CMD_CONNECT, + ip,port); + return exchange(request); + }catch(SocksException se){ + endSession(); + throw se; + } + } + protected ProxyMessage connect(String host,int port) + throws UnknownHostException,SocksException{ + try{ + startSession(); + ProxyMessage request = formMessage(SOCKS_CMD_CONNECT, + host,port); + return exchange(request); + }catch(SocksException se){ + endSession(); + throw se; + } + } + + protected ProxyMessage bind(InetAddress ip,int port) + throws SocksException{ + try{ + startSession(); + ProxyMessage request = formMessage(SOCKS_CMD_BIND, + ip,port); + return exchange(request); + }catch(SocksException se){ + endSession(); + throw se; + } + } + protected ProxyMessage bind(String host,int port) + throws UnknownHostException,SocksException{ + try{ + startSession(); + ProxyMessage request = formMessage(SOCKS_CMD_BIND, + host,port); + return exchange(request); + }catch(SocksException se){ + endSession(); + throw se; + } + } + + protected ProxyMessage accept() + throws IOException,SocksException{ + ProxyMessage msg; + try{ + msg = formMessage(in); + }catch(InterruptedIOException iioe){ + throw iioe; + }catch(IOException io_ex){ + endSession(); + throw new SocksException(SOCKS_PROXY_IO_ERROR,"While Trying accept:" + +io_ex); + } + return msg; + } + + protected ProxyMessage udpAssociate(InetAddress ip,int port) + throws SocksException{ + try{ + startSession(); + ProxyMessage request = formMessage(SOCKS_CMD_UDP_ASSOCIATE, + ip,port); + if(request != null) + return exchange(request); + }catch(SocksException se){ + endSession(); + throw se; + } + //Only get here if request was null + endSession(); + throw new SocksException(SOCKS_METHOD_NOTSUPPORTED, + "This version of proxy does not support UDP associate, use version 5"); + } + protected ProxyMessage udpAssociate(String host,int port) + throws UnknownHostException,SocksException{ + try{ + startSession(); + ProxyMessage request = formMessage(SOCKS_CMD_UDP_ASSOCIATE, + host,port); + if(request != null) return exchange(request); + }catch(SocksException se){ + endSession(); + throw se; + } + //Only get here if request was null + endSession(); + throw new SocksException(SOCKS_METHOD_NOTSUPPORTED, + "This version of proxy does not support UDP associate, use version 5"); + } + + + protected void endSession(){ + try{ + if(proxySocket!=null) proxySocket.close(); + proxySocket = null; + }catch(IOException io_ex){ + } + } + + /** + *Sends the request to SOCKS server + */ + protected void sendMsg(ProxyMessage msg)throws SocksException, + IOException{ + msg.write(out); + } + + /** + * Reads the reply from the SOCKS server + */ + protected ProxyMessage readMsg()throws SocksException, + IOException{ + return formMessage(in); + } + /** + *Sends the request reads reply and returns it + *throws exception if something wrong with IO + *or the reply code is not zero + */ + protected ProxyMessage exchange(ProxyMessage request) + throws SocksException{ + ProxyMessage reply; + try{ + request.write(out); + reply = formMessage(in); + }catch(SocksException s_ex){ + throw s_ex; + }catch(IOException ioe){ + throw(new SocksException(SOCKS_PROXY_IO_ERROR,""+ioe)); + } + return reply; + } + + +//Private methods +//=============== + + +//Constants + + public static final int SOCKS_SUCCESS =0; + public static final int SOCKS_FAILURE =1; + public static final int SOCKS_BADCONNECT =2; + public static final int SOCKS_BADNETWORK =3; + public static final int SOCKS_HOST_UNREACHABLE =4; + public static final int SOCKS_CONNECTION_REFUSED =5; + public static final int SOCKS_TTL_EXPIRE =6; + public static final int SOCKS_CMD_NOT_SUPPORTED =7; + public static final int SOCKS_ADDR_NOT_SUPPORTED =8; + + public static final int SOCKS_NO_PROXY =1<<16; + public static final int SOCKS_PROXY_NO_CONNECT =2<<16; + public static final int SOCKS_PROXY_IO_ERROR =3<<16; + public static final int SOCKS_AUTH_NOT_SUPPORTED =4<<16; + public static final int SOCKS_AUTH_FAILURE =5<<16; + public static final int SOCKS_JUST_ERROR =6<<16; + + public static final int SOCKS_DIRECT_FAILED =7<<16; + public static final int SOCKS_METHOD_NOTSUPPORTED =8<<16; + + + public static final int SOCKS_CMD_CONNECT =0x1; + static final int SOCKS_CMD_BIND =0x2; + static final int SOCKS_CMD_UDP_ASSOCIATE =0x3; + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/net/sourceforge/jsocks/InetRange.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/sourceforge/jsocks/InetRange.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,440 @@ +package net.sourceforge.jsocks; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.*; + +/** + * Class InetRange provides the means of defining the range of inetaddresses. + * It's used by Proxy class to store and look up addresses of machines, that + * should be contacted directly rather then through the proxy. + *

+ * InetRange provides several methods to add either standalone addresses, or + * ranges (e.g. 100.200.300.0:100.200.300.255, which covers all addresses + * on on someones local network). It also provides methods for checking wether + * given address is in this range. Any number of ranges and standalone + * addresses can be added to the range. + */ +public class InetRange implements Cloneable{ + + Hashtable host_names; + Vector all; + Vector end_names; + + boolean useSeparateThread = true; + + /** + * Creates the empty range. + */ + public InetRange(){ + all = new Vector(); + host_names = new Hashtable(); + end_names = new Vector(); + } + + /** + * Adds another host or range to this range. + The String can be one of those: +

    +
  • Host name. eg.(Athena.myhost.com or 45.54.56.65) + +
  • Range in the form .myhost.net.au
    + In which case anything that ends with .myhost.net.au will + be considered in the range. + +
  • Range in the form ddd.ddd.ddd.
    + This will be treated as range ddd.ddd.ddd.0 to ddd.ddd.ddd.255. + It is not necessary to specify 3 first bytes you can use just + one or two. For example 130. will cover address between 130.0.0.0 + and 13.255.255.255. + +
  • Range in the form host_from[: \t\n\r\f]host_to.
    + That is two hostnames or ips separated by either whitespace + or colon. +
+ */ + public synchronized boolean add(String s){ + if(s == null) return false; + + s = s.trim(); + if(s.length() == 0) return false; + + Object[] entry; + + if(s.charAt(s.length()-1) == '.'){ + //thing like: 111.222.33. + //it is being treated as range 111.222.33.000 - 111.222.33.255 + + int[] addr = ip2intarray(s); + long from,to; + from = to = 0; + + if(addr == null) return false; + for(int i = 0; i< 4;++i){ + if(addr[i]>=0) + from += (((long)addr[i]) << 8*(3-i)); + else{ + to = from; + while(i<4) + to += 255l << 8*(3-i++); + break; + } + } + entry = new Object[] {s,null,new Long(from),new Long(to)}; + all.addElement(entry); + + }else if(s.charAt(0) == '.'){ + //Thing like: .myhost.com + + end_names.addElement(s); + all.addElement(new Object[]{s,null,null,null}); + }else{ + StringTokenizer tokens = new StringTokenizer(s," \t\r\n\f:"); + if(tokens.countTokens() > 1){ + entry = new Object[] {s,null,null,null}; + resolve(entry,tokens.nextToken(),tokens.nextToken()); + all.addElement(entry); + }else{ + entry = new Object[] {s,null,null,null}; + all.addElement(entry); + host_names.put(s,entry); + resolve(entry); + } + + } + + return true; + } + + /** + * Adds another ip for this range. + @param ip IP os the host which should be added to this range. + */ + public synchronized void add(InetAddress ip){ + long from, to; + from = to = ip2long(ip); + all.addElement(new Object[]{ip.getHostName(),ip,new Long(from), + new Long(to)}); + } + + /** + * Adds another range of ips for this range.Any host with ip address + greater than or equal to the address of from and smaller than or equal + to the address of to will be included in the range. + @param from IP from where range starts(including). + @param to IP where range ends(including). + */ + public synchronized void add(InetAddress from,InetAddress to){ + all.addElement(new Object[]{from.getHostAddress()+":"+to.getHostAddress() + ,null,new Long(ip2long(from)), + new Long(ip2long(to))}); + } + + /** + * Checks wether the givan host is in the range. Attempts to resolve + host name if required. + @param host Host name to check. + @return true If host is in the range, false otherwise. + * @see InetRange#contains(String,boolean) + */ + public synchronized boolean contains(String host){ + return contains(host,true); + } + + /** + * Checks wether the given host is in the range. + *

+ * Algorithm:
+ *

    + *
  1. Look up if the hostname is in the range (in the Hashtable). + *
  2. Check if it ends with one of the speciefied endings. + *
  3. Check if it is ip(eg.130.220.35.98). If it is check if it is + * in the range. + *
  4. If attemptResolve is true, host is name, rather than ip, and + * all previous attempts failed, try to resolve the hostname, and + * check wether the ip associated with the host is in the range.It + * also repeats all previos steps with the hostname obtained from + * InetAddress, but the name is not allways the full name,it is + * quite likely to be the same. Well it was on my machine. + *
+ @param host Host name to check. + @param attemptResolve Wether to lookup ip address which corresponds + to the host,if required. + @return true If host is in the range, false otherwise. + */ + public synchronized boolean contains(String host,boolean attemptResolve){ + if(all.size() ==0) return false; //Empty range + + host = host.trim(); + if(host.length() == 0) return false; + + if(checkHost(host)) return true; + if(checkHostEnding(host)) return true; + + long l = host2long(host); + if(l >=0) return contains(l); + + if(!attemptResolve) return false; + + try{ + InetAddress ip = InetAddress.getByName(host); + return contains(ip); + }catch(UnknownHostException uhe){ + + } + + return false; + } + + /** + * Checks wether the given ip is in the range. + @param ip Address of the host to check. + @return true If host is in the range, false otherwise. + */ + public synchronized boolean contains(InetAddress ip){ + if(checkHostEnding(ip.getHostName())) return true; + if(checkHost(ip.getHostName())) return true; + return contains(ip2long(ip)); + } + /** + Get all entries in the range as strings.
+ These strings can be used to delete entries from the range + with remove function. + @return Array of entries as strings. + @see InetRange#remove(String) + */ + public synchronized String[] getAll(){ + int size = all.size(); + Object entry[]; + String all_names[] = new String[size]; + + for(int i=0;i + @param s Entry to remove. + @return true if successfull. + */ + public synchronized boolean remove(String s){ + Enumeration eEnum = all.elements(); + while(eEnum.hasMoreElements()){ + Object[] entry = (Object[]) eEnum.nextElement(); + if(s.equals(entry[0])){ + all.removeElement(entry); + end_names.removeElement(s); + host_names.remove(s); + return true; + } + } + return false; + } + + /** Get string representaion of this Range.*/ + public String toString(){ + String all[] = getAll(); + if(all.length == 0) return ""; + + String s = all[0]; + for(int i=1;i= ip) return true; + + } + return false; + } + + private boolean checkHost(String host){ + return host_names.containsKey(host); + } + private boolean checkHostEnding(String host){ + Enumeration eEnum = end_names.elements(); + while(eEnum.hasMoreElements()){ + if(host.endsWith((String) eEnum.nextElement())) return true; + } + return false; + } + private void resolve(Object[] entry){ + //First check if it's in the form ddd.ddd.ddd.ddd. + long ip = host2long((String) entry[0]); + if(ip >= 0){ + entry[2] = entry[3] = new Long(ip); + }else{ + InetRangeResolver res = new InetRangeResolver(entry); + res.resolve(useSeparateThread); + } + } + private void resolve(Object[] entry,String from,String to){ + long f,t; + if((f=host2long(from))>= 0 && (t=host2long(to)) >= 0){ + entry[2] = new Long(f); + entry[3] = new Long(t); + }else{ + InetRangeResolver res = new InetRangeResolver(entry,from,to); + res.resolve(useSeparateThread); + } + } + + + +//Class methods +/////////////// + + //Converts ipv4 to long value(unsigned int) + /////////////////////////////////////////// + static long ip2long(InetAddress ip){ + long l=0; + byte[] addr = ip.getAddress(); + + if(addr.length ==4){ //IPV4 + for(int i=0;i<4;++i) + l += (((long)addr[i] &0xFF) << 8*(3-i)); + }else{ //IPV6 + return 0; //Have no idea how to deal with those + } + return l; + } + + + long host2long(String host){ + long ip=0; + + //check if it's ddd.ddd.ddd.ddd + if(!Character.isDigit(host.charAt(0))) return -1; + + int[] addr = ip2intarray(host); + if(addr == null) return -1; + + for(int i=0;i=0 ? addr[i] : 0)) << 8*(3-i); + + return ip; + } + + static int[] ip2intarray(String host){ + int[] address = {-1,-1,-1,-1}; + int i=0; + StringTokenizer tokens = new StringTokenizer(host,"."); + if(tokens.countTokens() > 4) return null; + while(tokens.hasMoreTokens()){ + try{ + address[i++] = Integer.parseInt(tokens.nextToken()) & 0xFF; + }catch(NumberFormatException nfe){ + return null; + } + + } + return address; + } + + +/* +//* This was the test main function +//********************************** + + public static void main(String args[])throws UnknownHostException{ + int i; + + InetRange ir = new InetRange(); + + + for(i=0;i + In order to use it you will need to implement ServerAuthenticator + interface. There is an implementation of this interface which does + no authentication ServerAuthenticatorNone, but it is very dangerous + to use, as it will give access to your local network to anybody + in the world. One should never use this authentication scheme unless + one have pretty good reason to do so. + There is a couple of other authentication schemes in socks.server package. + @see socks.server.ServerAuthenticator +*/ +public class ProxyServer implements Runnable{ + + ServerAuthenticator auth; + ProxyMessage msg = null; + + Socket sock=null,remote_sock=null; + ServerSocket ss=null; + UDPRelayServer relayServer = null; + InputStream in,remote_in; + OutputStream out,remote_out; + + int mode; + static final int START_MODE = 0; + static final int ACCEPT_MODE = 1; + static final int PIPE_MODE = 2; + static final int ABORT_MODE = 3; + + static final int BUF_SIZE = 8192; + + Thread pipe_thread1,pipe_thread2; + long lastReadTime; + + static int iddleTimeout = 180000; //3 minutes + static int acceptTimeout = 180000; //3 minutes + + static PrintStream log = null; + static CProxy proxy; + + +//Public Constructors +///////////////////// + + + /** + Creates a proxy server with given Authentication scheme. + @param auth Authentication scheme to be used. + */ + public ProxyServer(ServerAuthenticator auth){ + this.auth = auth; + } + +//Other constructors +//////////////////// + + ProxyServer(ServerAuthenticator auth,Socket s){ + this.auth = auth; + this.sock = s; + mode = START_MODE; + } + +//Public methods +///////////////// + + /** + Set the logging stream. Specifying null disables logging. + */ + public static void setLog(OutputStream out){ + if(out == null){ + log = null; + }else{ + log = new PrintStream(out,true); + } + + UDPRelayServer.log = log; + } + + /** + Set proxy. +

+ Allows CProxy chaining so that one CProxy server is connected to another + and so on. If proxy supports SOCKSv4, then only some SOCKSv5 requests + can be handled, UDP would not work, however CONNECT and BIND will be + translated. + + @param p CProxy which should be used to handle user requests. + */ + public static void setProxy(CProxy p){ + proxy =p; + UDPRelayServer.proxy = proxy; + } + + /** + Get proxy. + @return CProxy wich is used to handle user requests. + */ + public static CProxy getProxy(){ + return proxy; + } + + /** + Sets the timeout for connections, how long shoud server wait + for data to arrive before dropping the connection.
+ Zero timeout implies infinity.
+ Default timeout is 3 minutes. + */ + public static void setIddleTimeout(int timeout){ + iddleTimeout = timeout; + } + /** + Sets the timeout for BIND command, how long the server should + wait for the incoming connection.
+ Zero timeout implies infinity.
+ Default timeout is 3 minutes. + */ + public static void setAcceptTimeout(int timeout){ + acceptTimeout = timeout; + } + + /** + Sets the timeout for UDPRelay server.
+ Zero timeout implies infinity.
+ Default timeout is 3 minutes. + */ + public static void setUDPTimeout(int timeout){ + UDPRelayServer.setTimeout(timeout); + } + + /** + Sets the size of the datagrams used in the UDPRelayServer.
+ Default size is 64K, a bit more than maximum possible size of the + datagram. + */ + public static void setDatagramSize(int size){ + UDPRelayServer.setDatagramSize(size); + } + + + /** + Start the CProxy server at given port.
+ This methods blocks. + */ + public void start(int port){ + start(port,5,null); + } + + /** + Create a server with the specified port, listen backlog, and local + IP address to bind to. The localIP argument can be used on a multi-homed + host for a ServerSocket that will only accept connect requests to one of + its addresses. If localIP is null, it will default accepting connections + on any/all local addresses. The port must be between 0 and 65535, + inclusive.
+ This methods blocks. + */ + public void start(int port,int backlog,InetAddress localIP){ + try{ + ss = new ServerSocket(port,backlog,localIP); + log("Starting SOCKS Proxy on:"+ss.getInetAddress().getHostAddress()+":" + +ss.getLocalPort()); + while(true){ + Socket s = ss.accept(); + log("Accepted from:"+s.getInetAddress().getHostName()+":" + +s.getPort()); + ProxyServer ps = new ProxyServer(auth,s); + (new Thread(ps)).start(); + } + }catch(IOException ioe){ + ioe.printStackTrace(); + }finally{ + } + } + + /** + Stop server operation.It would be wise to interrupt thread running the + server afterwards. + */ + public void stop(){ + try{ + if(ss != null) ss.close(); + }catch(IOException ioe){ + } + } + +//Runnable interface +//////////////////// + public void run(){ + switch(mode){ + case START_MODE: + try{ + startSession(); + }catch(IOException ioe){ + handleException(ioe); + //ioe.printStackTrace(); + }finally{ + abort(); + if(auth!=null) auth.endSession(); + log("Main thread(client->remote)stopped."); + } + break; + case ACCEPT_MODE: + try{ + doAccept(); + mode = PIPE_MODE; + pipe_thread1.interrupt(); //Tell other thread that connection have + //been accepted. + pipe(remote_in,out); + }catch(IOException ioe){ + //log("Accept exception:"+ioe); + handleException(ioe); + }finally{ + abort(); + log("Accept thread(remote->client) stopped"); + } + break; + case PIPE_MODE: + try{ + pipe(remote_in,out); + }catch(IOException ioe){ + }finally{ + abort(); + log("Support thread(remote->client) stopped"); + } + break; + case ABORT_MODE: + break; + default: + log("Unexpected MODE "+mode); + } + } + +//Private methods +///////////////// + private void startSession() throws IOException{ + sock.setSoTimeout(iddleTimeout); + + try{ + auth = auth.startSession(sock); + }catch(IOException ioe){ + log("Auth throwed exception:"+ioe); + auth = null; + return; + } + + if(auth == null){ //Authentication failed + log("Authentication failed"); + return; + } + + in = auth.getInputStream(); + out = auth.getOutputStream(); + + msg = readMsg(in); + handleRequest(msg); + } + + private void handleRequest(ProxyMessage msg) + throws IOException{ + if(!auth.checkRequest(msg)) throw new + SocksException(CProxy.SOCKS_FAILURE); + + if(msg.ip == null){ + if(msg instanceof Socks5Message){ + msg.ip = InetAddress.getByName(msg.host); + }else + throw new SocksException(CProxy.SOCKS_FAILURE); + } + log(msg); + + switch(msg.command){ + case CProxy.SOCKS_CMD_CONNECT: + onConnect(msg); + break; + case CProxy.SOCKS_CMD_BIND: + onBind(msg); + break; + case CProxy.SOCKS_CMD_UDP_ASSOCIATE: + onUDP(msg); + break; + default: + throw new SocksException(CProxy.SOCKS_CMD_NOT_SUPPORTED); + } + } + + private void handleException(IOException ioe){ + //If we couldn't read the request, return; + if(msg == null) return; + //If have been aborted by other thread + if(mode == ABORT_MODE) return; + //If the request was successfully completed, but exception happened later + if(mode == PIPE_MODE) return; + + int error_code = CProxy.SOCKS_FAILURE; + + if(ioe instanceof SocksException) + error_code = ((SocksException)ioe).errCode; + else if(ioe instanceof NoRouteToHostException) + error_code = CProxy.SOCKS_HOST_UNREACHABLE; + else if(ioe instanceof ConnectException) + error_code = CProxy.SOCKS_CONNECTION_REFUSED; + else if(ioe instanceof InterruptedIOException) + error_code = CProxy.SOCKS_TTL_EXPIRE; + + if(error_code > CProxy.SOCKS_ADDR_NOT_SUPPORTED || error_code < 0){ + error_code = CProxy.SOCKS_FAILURE; + } + + sendErrorMessage(error_code); + } + + private void onConnect(ProxyMessage msg) throws IOException { + Socket s = null; + ProxyMessage response = null; + int iSock5Cmd = CProxy.SOCKS_FAILURE; //defaulting to failure + int iSock4Msg = Socks4Message.REPLY_NO_CONNECT; + InetAddress sIp = null; int iPort = 0; + + try { + if (proxy == null) { + s = new Socket(msg.ip, msg.port); + } else { + s = new SocksSocket(proxy, msg.ip, msg.port); + } + log("Connected to " + s.getInetAddress() + ":" + s.getPort()); + + iSock5Cmd = CProxy.SOCKS_SUCCESS; iSock4Msg = Socks4Message.REPLY_OK; + sIp = s.getInetAddress(); iPort = s.getPort(); + + } + catch (Exception sE) { + log("Failed connecting to remote socket. Exception: " + sE.getLocalizedMessage()); + + //TBD Pick proper socks error for corresponding socket error, below is too generic + iSock5Cmd = CProxy.SOCKS_CONNECTION_REFUSED; iSock4Msg = Socks4Message.REPLY_NO_CONNECT; + } + + if (msg instanceof Socks5Message) { + response = new Socks5Message(iSock5Cmd, sIp, iPort); + } else { + response = new Socks4Message(iSock4Msg, sIp, iPort); + } + + response.write(out); + + if (s != null) { + startPipe(s); + } + else { + throw (new RuntimeException("onConnect() Failed to create Socket()")); + } + + return; + } + + + private void onBind(ProxyMessage msg) throws IOException{ + ProxyMessage response = null; + + if(proxy == null) + ss = new ServerSocket(0); + else + ss = new SocksServerSocket(proxy, msg.ip, msg.port); + + ss.setSoTimeout(acceptTimeout); + + log("Trying accept on "+ss.getInetAddress()+":"+ss.getLocalPort()); + + if(msg.version == 5) + response = new Socks5Message(CProxy.SOCKS_SUCCESS,ss.getInetAddress(), + ss.getLocalPort()); + else + response = new Socks4Message(Socks4Message.REPLY_OK, + ss.getInetAddress(), + ss.getLocalPort()); + response.write(out); + + mode = ACCEPT_MODE; + + pipe_thread1 = Thread.currentThread(); + pipe_thread2 = new Thread(this); + pipe_thread2.start(); + + //Make timeout infinit. + sock.setSoTimeout(0); + int eof=0; + + try{ + while((eof=in.read())>=0){ + if(mode != ACCEPT_MODE){ + if(mode != PIPE_MODE) return;//Accept failed + + remote_out.write(eof); + break; + } + } + }catch(EOFException eofe){ + //System.out.println("EOF exception"); + return;//Connection closed while we were trying to accept. + }catch(InterruptedIOException iioe){ + //Accept thread interrupted us. + //System.out.println("Interrupted"); + if(mode != PIPE_MODE) + return;//If accept thread was not successfull return. + }finally{ + //System.out.println("Finnaly!"); + } + + if(eof < 0)//Connection closed while we were trying to accept; + return; + + //Do not restore timeout, instead timeout is set on the + //remote socket. It does not make any difference. + + pipe(in,remote_out); + } + + private void onUDP(ProxyMessage msg) throws IOException{ + if(msg.ip.getHostAddress().equals("0.0.0.0")) + msg.ip = sock.getInetAddress(); + log("Creating UDP relay server for "+msg.ip+":"+msg.port); + relayServer = new UDPRelayServer(msg.ip,msg.port, + Thread.currentThread(),sock,auth); + + ProxyMessage response; + + response = new Socks5Message(CProxy.SOCKS_SUCCESS, + relayServer.relayIP,relayServer.relayPort); + + response.write(out); + + relayServer.start(); + + //Make timeout infinit. + sock.setSoTimeout(0); + try{ + while(in.read()>=0) /*do nothing*/; + }catch(EOFException eofe){ + } + } + +//Private methods +////////////////// + + private void doAccept() throws IOException{ + Socket s; + long startTime = System.currentTimeMillis(); + + while(true){ + s = ss.accept(); + if(s.getInetAddress().equals(msg.ip)){ + //got the connection from the right host + //Close listenning socket. + ss.close(); + break; + }else if(ss instanceof SocksServerSocket){ + //We can't accept more then one connection + s.close(); + ss.close(); + throw new SocksException(CProxy.SOCKS_FAILURE); + }else{ + if(acceptTimeout!=0){ //If timeout is not infinit + int newTimeout = acceptTimeout-(int)(System.currentTimeMillis()- + startTime); + if(newTimeout <= 0) throw new InterruptedIOException( + "In doAccept()"); + ss.setSoTimeout(newTimeout); + } + s.close(); //Drop all connections from other hosts + } + } + + //Accepted connection + remote_sock = s; + remote_in = s.getInputStream(); + remote_out = s.getOutputStream(); + + //Set timeout + remote_sock.setSoTimeout(iddleTimeout); + + log("Accepted from "+s.getInetAddress()+":"+s.getPort()); + + ProxyMessage response; + + if(msg.version == 5) + response = new Socks5Message(CProxy.SOCKS_SUCCESS, s.getInetAddress(), + s.getPort()); + else + response = new Socks4Message(Socks4Message.REPLY_OK, + s.getInetAddress(), s.getPort()); + response.write(out); + } + + private ProxyMessage readMsg(InputStream in) throws IOException{ + PushbackInputStream push_in; + if(in instanceof PushbackInputStream) + push_in = (PushbackInputStream) in; + else + push_in = new PushbackInputStream(in); + + int version = push_in.read(); + push_in.unread(version); + + + ProxyMessage msg; + + if(version == 5){ + msg = new Socks5Message(push_in,false); + }else if(version == 4){ + msg = new Socks4Message(push_in,false); + }else{ + throw new SocksException(CProxy.SOCKS_FAILURE); + } + return msg; + } + + private void startPipe(Socket s){ + mode = PIPE_MODE; + remote_sock = s; + try{ + remote_in = s.getInputStream(); + remote_out = s.getOutputStream(); + pipe_thread1 = Thread.currentThread(); + pipe_thread2 = new Thread(this); + pipe_thread2.start(); + pipe(in,remote_out); + }catch(IOException ioe){ + } + } + + private void sendErrorMessage(int error_code){ + ProxyMessage err_msg; + if(msg instanceof Socks4Message) + err_msg = new Socks4Message(Socks4Message.REPLY_REJECTED); + else + err_msg = new Socks5Message(error_code); + try{ + err_msg.write(out); + }catch(IOException ioe){} + } + + private synchronized void abort(){ + if(mode == ABORT_MODE) return; + mode = ABORT_MODE; + try{ + log("Aborting operation"); + if(remote_sock != null) remote_sock.close(); + if(sock != null) sock.close(); + if(relayServer!=null) relayServer.stop(); + if(ss!=null) ss.close(); + if(pipe_thread1 != null) pipe_thread1.interrupt(); + if(pipe_thread2 != null) pipe_thread2.interrupt(); + }catch(IOException ioe){} + } + + static final void log(String s){ + if(log != null){ + log.println(s); + log.flush(); + } + } + + static final void log(ProxyMessage msg){ + log("Request version:"+msg.version+ + "\tCommand: "+command2String(msg.command)); + log("IP:"+msg.ip +"\tPort:"+msg.port+ + (msg.version==4?"\tUser:"+msg.user:"")); + } + + private void pipe(InputStream in,OutputStream out) throws IOException{ + lastReadTime = System.currentTimeMillis(); + byte[] buf = new byte[BUF_SIZE]; + int len = 0; + while(len >= 0){ + try{ + if(len!=0){ + out.write(buf,0,len); + out.flush(); + } + len= in.read(buf); + lastReadTime = System.currentTimeMillis(); + }catch(InterruptedIOException iioe){ + if(iddleTimeout == 0) return;//Other thread interrupted us. + long timeSinceRead = System.currentTimeMillis() - lastReadTime; + if(timeSinceRead >= iddleTimeout - 1000) //-1s for adjustment. + return; + len = 0; + + } + } + } + static final String command_names[] = {"CONNECT","BIND","UDP_ASSOCIATE"}; + + static final String command2String(int cmd){ + if(cmd > 0 && cmd < 4) return command_names[cmd-1]; + else return "Unknown Command "+cmd; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/net/sourceforge/jsocks/Socks4Message.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/sourceforge/jsocks/Socks4Message.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,157 @@ +package net.sourceforge.jsocks; +import java.io.*; +import java.net.*; + +/** + SOCKS4 Reply/Request message. +*/ + +public class Socks4Message extends ProxyMessage{ + + private byte[] msgBytes; + private int msgLength; + + /** + * Server failed reply, cmd command for failed request + */ + public Socks4Message(int cmd){ + super(cmd,null,0); + this.user = null; + + msgLength = 2; + msgBytes = new byte[2]; + + msgBytes[0] = (byte) 0; + msgBytes[1] = (byte) command; + } + + /** + * Server successfull reply + */ + public Socks4Message(int cmd,InetAddress ip,int port){ + this(0,cmd,ip,port,null); + } + + /** + * Client request + */ + public Socks4Message(int cmd,InetAddress ip,int port,String user){ + this(SOCKS_VERSION,cmd,ip,port,user); + } + + /** + * Most general constructor + */ + public Socks4Message(int version, int cmd, + InetAddress ip,int port,String user){ + super(cmd,ip,port); + this.user = user; + this.version = version; + + msgLength = user == null?8:9+user.length(); + msgBytes = new byte[msgLength]; + + msgBytes[0] = (byte) version; + msgBytes[1] = (byte) command; + msgBytes[2] = (byte) (port >> 8); + msgBytes[3] = (byte) port; + + byte[] addr; + + if(ip != null) + addr = ip.getAddress(); + else{ + addr = new byte[4]; + addr[0]=addr[1]=addr[2]=addr[3]=0; + } + System.arraycopy(addr,0,msgBytes,4,4); + + if(user != null){ + byte[] buf = user.getBytes(); + System.arraycopy(buf,0,msgBytes,8,buf.length); + msgBytes[msgBytes.length -1 ] = 0; + } + } + + /** + *Initialise from the stream + *If clientMode is true attempts to read a server response + *otherwise reads a client request + *see read for more detail + */ + public Socks4Message(InputStream in, boolean clientMode) throws IOException{ + msgBytes = null; + read(in,clientMode); + } + + public void read(InputStream in) throws IOException{ + read(in,true); + } + + public void read(InputStream in, boolean clientMode) throws IOException{ + DataInputStream d_in = new DataInputStream(in); + version= d_in.readUnsignedByte(); + command = d_in.readUnsignedByte(); + if(clientMode && command != REPLY_OK){ + String errMsg; + if(command >REPLY_OK && command < REPLY_BAD_IDENTD) + errMsg = replyMessage[command-REPLY_OK]; + else + errMsg = "Unknown Reply Code"; + throw new SocksException(command,errMsg); + } + port = d_in.readUnsignedShort(); + byte[] addr = new byte[4]; + d_in.readFully(addr); + ip=bytes2IP(addr); + host = ip.getHostName(); + if(!clientMode){ + int b = in.read(); + //Hope there are no idiots with user name bigger than this + byte[] userBytes = new byte[256]; + int i = 0; + for(i =0;i0;++i){ + userBytes[i] = (byte) b; + b = in.read(); + } + user = new String(userBytes,0,i); + } + } + public void write(OutputStream out) throws IOException{ + if(msgBytes == null){ + Socks4Message msg = new Socks4Message(version,command,ip,port,user); + msgBytes = msg.msgBytes; + msgLength = msg.msgLength; + } + out.write(msgBytes); + } + + //Class methods + static InetAddress bytes2IP(byte[] addr){ + String s = bytes2IPV4(addr,0); + try{ + return InetAddress.getByName(s); + }catch(UnknownHostException uh_ex){ + return null; + } + } + + //Constants + + static final String[] replyMessage ={ + "Request Granted", + "Request Rejected or Failed", + "Failed request, can't connect to Identd", + "Failed request, bad user name"}; + + static final int SOCKS_VERSION = 4; + + public final static int REQUEST_CONNECT = 1; + public final static int REQUEST_BIND = 2; + + public final static int REPLY_OK = 90; + public final static int REPLY_REJECTED = 91; + public final static int REPLY_NO_CONNECT = 92; + public final static int REPLY_BAD_IDENTD = 93; + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/net/sourceforge/jsocks/Socks4Proxy.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/sourceforge/jsocks/Socks4Proxy.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,121 @@ +package net.sourceforge.jsocks; +import java.net.*; +import java.io.*; +import java.util.Hashtable; +import java.util.Enumeration; + +/** + CProxy which describes SOCKS4 proxy. +*/ + +public class Socks4Proxy extends CProxy implements Cloneable{ + +//Data members + String user; + +//Public Constructors +//==================== + + /** + Creates the SOCKS4 proxy + @param p CProxy to use to connect to this proxy, allows proxy chaining. + @param proxyHost Address of the proxy server. + @param proxyPort Port of the proxy server + @param user User name to use for identification purposes. + @throws UnknownHostException If proxyHost can't be resolved. + */ + public Socks4Proxy(CProxy p,String proxyHost,int proxyPort,String user) + throws UnknownHostException{ + super(p,proxyHost,proxyPort); + this.user = new String(user); + version = 4; + } + + /** + Creates the SOCKS4 proxy + @param proxyHost Address of the proxy server. + @param proxyPort Port of the proxy server + @param user User name to use for identification purposes. + @throws UnknownHostException If proxyHost can't be resolved. + */ + public Socks4Proxy(String proxyHost,int proxyPort,String user) + throws UnknownHostException{ + this(null,proxyHost,proxyPort,user); + } + + /** + Creates the SOCKS4 proxy + @param p CProxy to use to connect to this proxy, allows proxy chaining. + @param proxyIP Address of the proxy server. + @param proxyPort Port of the proxy server + @param user User name to use for identification purposes. + */ + public Socks4Proxy(CProxy p,InetAddress proxyIP,int proxyPort,String user){ + super(p,proxyIP,proxyPort); + this.user = new String(user); + version = 4; + } + + /** + Creates the SOCKS4 proxy + @param proxyIP Address of the proxy server. + @param proxyPort Port of the proxy server + @param user User name to use for identification purposes. + */ + public Socks4Proxy(InetAddress proxyIP,int proxyPort,String user){ + this(null,proxyIP,proxyPort,user); + } + +//Public instance methods +//======================== + + /** + * Creates a clone of this proxy. Changes made to the clone should not + * affect this object. + */ + public Object clone(){ + Socks4Proxy newProxy = new Socks4Proxy(proxyIP,proxyPort,user); + newProxy.directHosts = (InetRange)directHosts.clone(); + newProxy.chainProxy = chainProxy; + return newProxy; + } + + +//Public Static(Class) Methods +//============================== + + +//Protected Methods +//================= + + protected CProxy copy(){ + Socks4Proxy copy = new Socks4Proxy(proxyIP,proxyPort,user); + copy.directHosts = this.directHosts; + copy.chainProxy = chainProxy; + return copy; + } + + protected ProxyMessage formMessage(int cmd,InetAddress ip,int port){ + switch(cmd){ + case SOCKS_CMD_CONNECT: + cmd = Socks4Message.REQUEST_CONNECT; + break; + case SOCKS_CMD_BIND: + cmd = Socks4Message.REQUEST_BIND; + break; + default: + return null; + } + return new Socks4Message(cmd,ip,port,user); + } + protected ProxyMessage formMessage(int cmd,String host,int port) + throws UnknownHostException{ + return formMessage(cmd,InetAddress.getByName(host),port); + } + protected ProxyMessage formMessage(InputStream in) + throws SocksException, + IOException{ + return new Socks4Message(in,true); + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/net/sourceforge/jsocks/Socks5DatagramSocket.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/sourceforge/jsocks/Socks5DatagramSocket.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,487 @@ +package net.sourceforge.jsocks; +import java.net.*; +import java.io.*; + +/** + Datagram socket to interract through the firewall.
+ Can be used same way as the normal DatagramSocket. One should + be carefull though with the datagram sizes used, as additional data + is present in both incomming and outgoing datagrams. +

+ SOCKS5 protocol allows to send host address as either: +

    +
  • IPV4, normal 4 byte address. (10 bytes header size) +
  • IPV6, version 6 ip address (not supported by Java as for now). + 22 bytes header size. +
  • Host name,(7+length of the host name bytes header size). +
+ As with other Socks equivalents, direct addresses are handled + transparently, that is data will be send directly when required + by the proxy settings. +

+ NOTE:
+ Unlike other SOCKS Sockets, it does not support proxy chaining, + and will throw an exception if proxy has a chain proxy attached. The + reason for that is not my laziness, but rather the restrictions of + the SOCKSv5 protocol. Basicaly SOCKSv5 proxy server, needs to know from + which host:port datagrams will be send for association, and returns address + to which datagrams should be send by the client, but it does not + inform client from which host:port it is going to send datagrams, in fact + there is even no guarantee they will be send at all and from the same address + each time. + + */ +public class Socks5DatagramSocket extends DatagramSocket{ + + InetAddress relayIP; + int relayPort; + Socks5Proxy proxy; + private boolean server_mode = false; + UDPEncapsulation encapsulation; + + + /** + Construct Datagram socket for communication over SOCKS5 proxy + server. This constructor uses default proxy, the one set with + CProxy.setDefaultProxy() method. If default proxy is not set or + it is set to version4 proxy, which does not support datagram + forwarding, throws SocksException. + + */ + public Socks5DatagramSocket() throws SocksException, + IOException{ + this(CProxy.defaultProxy,0,null); + } + /** + Construct Datagram socket for communication over SOCKS5 proxy + server. And binds it to the specified local port. + This constructor uses default proxy, the one set with + CProxy.setDefaultProxy() method. If default proxy is not set or + it is set to version4 proxy, which does not support datagram + forwarding, throws SocksException. + */ + public Socks5DatagramSocket(int port) throws SocksException, + IOException{ + this(CProxy.defaultProxy,port,null); + } + /** + Construct Datagram socket for communication over SOCKS5 proxy + server. And binds it to the specified local port and address. + This constructor uses default proxy, the one set with + CProxy.setDefaultProxy() method. If default proxy is not set or + it is set to version4 proxy, which does not support datagram + forwarding, throws SocksException. + */ + public Socks5DatagramSocket(int port,InetAddress ip) throws SocksException, + IOException{ + this(CProxy.defaultProxy,port,ip); + } + + /** + Constructs datagram socket for communication over specified proxy. + And binds it to the given local address and port. Address of null + and port of 0, signify any availabale port/address. + Might throw SocksException, if: +

    +
  1. Given version of proxy does not support UDP_ASSOCIATE. +
  2. CProxy can't be reached. +
  3. Authorization fails. +
  4. CProxy does not want to perform udp forwarding, for any reason. +
+ Might throw IOException if binding dtagram socket to given address/port + fails. + See java.net.DatagramSocket for more details. + */ + public Socks5DatagramSocket(CProxy p,int port,InetAddress ip) + throws SocksException, + IOException{ + super(port,ip); + if(p == null) throw new SocksException(CProxy.SOCKS_NO_PROXY); + if(!(p instanceof Socks5Proxy)) + throw new SocksException(-1,"Datagram Socket needs Proxy version 5"); + + if(p.chainProxy != null) + throw new SocksException(CProxy.SOCKS_JUST_ERROR, + "Datagram Sockets do not support proxy chaining."); + + proxy =(Socks5Proxy) p.copy(); + + ProxyMessage msg = proxy.udpAssociate(super.getLocalAddress(), + super.getLocalPort()); + relayIP = msg.ip; + if(relayIP.getHostAddress().equals("0.0.0.0")) relayIP = proxy.proxyIP; + relayPort = msg.port; + + encapsulation = proxy.udp_encapsulation; + + //debug("Datagram Socket:"+getLocalAddress()+":"+getLocalPort()+"\n"); + //debug("Socks5Datagram: "+relayIP+":"+relayPort+"\n"); + } + + /** + Used by UDPRelayServer. + */ + Socks5DatagramSocket(boolean server_mode,UDPEncapsulation encapsulation, + InetAddress relayIP,int relayPort) + throws IOException{ + super(); + this.server_mode = server_mode; + this.relayIP = relayIP; + this.relayPort = relayPort; + this.encapsulation = encapsulation; + this.proxy = null; + } + + /** + Sends the Datagram either through the proxy or directly depending + on current proxy settings and destination address.
+ + NOTE: DatagramPacket size should be at least 10 bytes less + than the systems limit. + +

+ See documentation on java.net.DatagramSocket + for full details on how to use this method. + @param dp Datagram to send. + @throws IOException If error happens with I/O. + */ + public void send(DatagramPacket dp) throws IOException{ + //If the host should be accessed directly, send it as is. + if(!server_mode && proxy.isDirect(dp.getAddress())){ + super.send(dp); + //debug("Sending directly:"); + return; + } + + byte[] head = formHeader(dp.getAddress(),dp.getPort()); + byte[] buf = new byte[head.length + dp.getLength()]; + byte[] data = dp.getData(); + //Merge head and data + System.arraycopy(head,0,buf,0,head.length); + //System.arraycopy(data,dp.getOffset(),buf,head.length,dp.getLength()); + System.arraycopy(data,0,buf,head.length,dp.getLength()); + + if(encapsulation != null) + buf = encapsulation.udpEncapsulate(buf,true); + + super.send(new DatagramPacket(buf,buf.length,relayIP,relayPort)); + } + /** + This method allows to send datagram packets with address type DOMAINNAME. + SOCKS5 allows to specify host as names rather than ip addresses.Using + this method one can send udp datagrams through the proxy, without having + to know the ip address of the destination host. +

+ If proxy specified for that socket has an option resolveAddrLocally set + to true host will be resolved, and the datagram will be send with address + type IPV4, if resolve fails, UnknownHostException is thrown. + @param dp Datagram to send, it should contain valid port and data + @param host Host name to which datagram should be send. + @throws IOException If error happens with I/O, or the host can't be + resolved when proxy settings say that hosts should be resolved locally. + @see Socks5Proxy#resolveAddrLocally(boolean) + */ + public void send(DatagramPacket dp, String host) throws IOException{ + if(proxy.isDirect(host)){ + dp.setAddress(InetAddress.getByName(host)); + super.send(dp); + return; + } + + if(((Socks5Proxy)proxy).resolveAddrLocally){ + dp.setAddress(InetAddress.getByName(host)); + } + + byte[] head = formHeader(host,dp.getPort()); + byte[] buf = new byte[head.length + dp.getLength()]; + byte[] data = dp.getData(); + //Merge head and data + System.arraycopy(head,0,buf,0,head.length); + //System.arraycopy(data,dp.getOffset(),buf,head.length,dp.getLength()); + System.arraycopy(data,0,buf,head.length,dp.getLength()); + + if(encapsulation != null) + buf = encapsulation.udpEncapsulate(buf,true); + + super.send(new DatagramPacket(buf,buf.length,relayIP,relayPort)); + } + + /** + * Receives udp packet. If packet have arrived from the proxy relay server, + * it is processed and address and port of the packet are set to the + * address and port of sending host.
+ * If the packet arrived from anywhere else it is not changed.
+ * NOTE: DatagramPacket size should be at least 10 bytes bigger + * than the largest packet you expect (this is for IPV4 addresses). + * For hostnames and IPV6 it is even more. + @param dp Datagram in which all relevent information will be copied. + */ + public void receive(DatagramPacket dp) throws IOException{ + super.receive(dp); + + if(server_mode){ + //Drop all datagrams not from relayIP/relayPort + int init_length = dp.getLength(); + int initTimeout = getSoTimeout(); + long startTime = System.currentTimeMillis(); + + while(!relayIP.equals(dp.getAddress()) || + relayPort != dp.getPort()){ + + //Restore datagram size + dp.setLength(init_length); + + //If there is a non-infinit timeout on this socket + //Make sure that it happens no matter how often unexpected + //packets arrive. + if(initTimeout != 0){ + int newTimeout = initTimeout - (int)(System.currentTimeMillis() - + startTime); + if(newTimeout <= 0) throw new InterruptedIOException( + "In Socks5DatagramSocket->receive()"); + setSoTimeout(newTimeout); + } + + super.receive(dp); + } + + //Restore timeout settings + if(initTimeout != 0) setSoTimeout(initTimeout); + + }else if(!relayIP.equals(dp.getAddress()) || + relayPort != dp.getPort()) + return; // Recieved direct packet + //If the datagram is not from the relay server, return it it as is. + + byte[] data; + data = dp.getData(); + + if(encapsulation != null) + data = encapsulation.udpEncapsulate(data,false); + + int offset = 0; //Java 1.1 + //int offset = dp.getOffset(); //Java 1.2 + + ByteArrayInputStream bIn = new ByteArrayInputStream(data,offset, + dp.getLength()); + + + ProxyMessage msg = new Socks5Message(bIn); + dp.setPort(msg.port); + dp.setAddress(msg.getInetAddress()); + + //what wasn't read by the Message is the data + int data_length = bIn.available(); + //Shift data to the left + System.arraycopy(data,offset+dp.getLength()-data_length, + data,offset,data_length); + + + dp.setLength(data_length); + } + + /** + * Returns port assigned by the proxy, to which datagrams are relayed. + * It is not the same port to which other party should send datagrams. + @return Port assigned by socks server to which datagrams are send + for association. + */ + public int getLocalPort(){ + if(server_mode) return super.getLocalPort(); + return relayPort; + } + /** + * Address assigned by the proxy, to which datagrams are send for relay. + * It is not necesseraly the same address, to which other party should send + * datagrams. + @return Address to which datagrams are send for association. + */ + public InetAddress getLocalAddress(){ + if(server_mode) return super.getLocalAddress(); + return relayIP; + } + + /** + * Closes datagram socket, and proxy connection. + */ + public void close(){ + if(!server_mode) proxy.endSession(); + super.close(); + } + + /** + This method checks wether proxy still runs udp forwarding service + for this socket. +

+ This methods checks wether the primary connection to proxy server + is active. If it is, chances are that proxy continues to forward + datagrams being send from this socket. If it was closed, most likely + datagrams are no longer being forwarded by the server. +

+ CProxy might decide to stop forwarding datagrams, in which case it + should close primary connection. This method allows to check, wether + this have been done. +

+ You can specify timeout for which we should be checking EOF condition + on the primary connection. Timeout is in milliseconds. Specifying 0 as + timeout implies infinity, in which case method will block, until + connection to proxy is closed or an error happens, and then return false. +

+ One possible scenario is to call isProxyactive(0) in separate thread, + and once it returned notify other threads about this event. + + @param timeout For how long this method should block, before returning. + @return true if connection to proxy is active, false if eof or error + condition have been encountered on the connection. + */ + public boolean isProxyAlive(int timeout){ + if(server_mode) return false; + if(proxy != null){ + try{ + proxy.proxySocket.setSoTimeout(timeout); + + int eof = proxy.in.read(); + if(eof < 0) return false; // EOF encountered. + else return true; // This really should not happen + + }catch(InterruptedIOException iioe){ + return true; // read timed out. + }catch(IOException ioe){ + return false; + } + } + return false; + } + +//PRIVATE METHODS +////////////////// + + + private byte[] formHeader(InetAddress ip, int port){ + Socks5Message request = new Socks5Message(0,ip,port); + request.data[0] = 0; + return request.data; + } + + + private byte[] formHeader(String host,int port){ + Socks5Message request = new Socks5Message(0,host,port); + request.data[0] = 0; + return request.data; + } + + +/*====================================================================== + +//Mainly Test functions +////////////////////// + + private String bytes2String(byte[] b){ + String s=""; + char[] hex_digit = { '0','1','2','3','4','5','6','7','8','9', + 'A','B','C','D','E','F'}; + for(int i=0;i> 4; + int i2 = b[i] & 0xF; + s+=hex_digit[i1]; + s+=hex_digit[i2]; + s+=" "; + } + return s; + } + private static final void debug(String s){ + if(DEBUG) + System.out.print(s); + } + + private static final boolean DEBUG = true; + + + public static void usage(){ + System.err.print( + "Usage: java Socks.SocksDatagramSocket host port [socksHost socksPort]\n"); + } + + static final int defaultProxyPort = 1080; //Default Port + static final String defaultProxyHost = "www-proxy"; //Default proxy + + public static void main(String args[]){ + int port; + String host; + int proxyPort; + String proxyHost; + InetAddress ip; + + if(args.length > 1 && args.length < 5){ + try{ + + host = args[0]; + port = Integer.parseInt(args[1]); + + proxyPort =(args.length > 3)? Integer.parseInt(args[3]) + : defaultProxyPort; + + host = args[0]; + ip = InetAddress.getByName(host); + + proxyHost =(args.length > 2)? args[2] + : defaultProxyHost; + + CProxy.setDefaultProxy(proxyHost,proxyPort); + CProxy p = CProxy.getDefaultProxy(); + p.addDirect("lux"); + + + DatagramSocket ds = new Socks5DatagramSocket(); + + + BufferedReader in = new BufferedReader( + new InputStreamReader(System.in)); + String s; + + System.out.print("Enter line:"); + s = in.readLine(); + + while(s != null){ + byte[] data = (s+"\r\n").getBytes(); + DatagramPacket dp = new DatagramPacket(data,0,data.length, + ip,port); + System.out.println("Sending to: "+ip+":"+port); + ds.send(dp); + dp = new DatagramPacket(new byte[1024],1024); + + System.out.println("Trying to recieve on port:"+ + ds.getLocalPort()); + ds.receive(dp); + System.out.print("Recieved:\n"+ + "From:"+dp.getAddress()+":"+dp.getPort()+ + "\n\n"+ + new String(dp.getData(),dp.getOffset(),dp.getLength())+"\n" + ); + System.out.print("Enter line:"); + s = in.readLine(); + + } + ds.close(); + System.exit(1); + + }catch(SocksException s_ex){ + System.err.println("SocksException:"+s_ex); + s_ex.printStackTrace(); + System.exit(1); + }catch(IOException io_ex){ + io_ex.printStackTrace(); + System.exit(1); + }catch(NumberFormatException num_ex){ + usage(); + num_ex.printStackTrace(); + System.exit(1); + } + + }else{ + usage(); + } + } +*/ + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/net/sourceforge/jsocks/Socks5Message.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/sourceforge/jsocks/Socks5Message.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,291 @@ +package net.sourceforge.jsocks; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.DataInputStream; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + SOCKS5 request/response message. +*/ + +public class Socks5Message extends ProxyMessage{ + /** Address type of given message*/ + public int addrType; + + byte[] data; + + /** + Server error response. + @param cmd Error code. + */ + public Socks5Message(int cmd){ + super(cmd,null,0); + data = new byte[3]; + data[0] = SOCKS_VERSION; //Version. + data[1] = (byte)cmd; //Reply code for some kind of failure. + data[2] = 0; //Reserved byte. + } + + /** + Construct client request or server response. + @param cmd - Request/Response code. + @param ip - IP field. + @paarm port - port field. + */ + public Socks5Message(int cmd,InetAddress ip,int port){ + super(cmd,ip,port); + this.host = ip==null?"0.0.0.0":ip.getHostName(); + this.version = SOCKS_VERSION; + + byte[] addr; + + if(ip == null){ + addr = new byte[4]; + addr[0]=addr[1]=addr[2]=addr[3]=0; + }else + addr = ip.getAddress(); + + addrType = addr.length == 4 ? SOCKS_ATYP_IPV4 + : SOCKS_ATYP_IPV6; + + data = new byte[6+addr.length]; + data[0] = (byte) SOCKS_VERSION; //Version + data[1] = (byte) command; //Command + data[2] = (byte) 0; //Reserved byte + data[3] = (byte) addrType; //Address type + + //Put Address + System.arraycopy(addr,0,data,4,addr.length); + //Put port + data[data.length-2] = (byte)(port>>8); + data[data.length-1] = (byte)(port); + } + + + /** + Construct client request or server response. + @param cmd - Request/Response code. + @param hostName - IP field as hostName, uses ADDR_TYPE of HOSTNAME. + @paarm port - port field. + */ + public Socks5Message(int cmd,String hostName,int port){ + super(cmd,null,port); + this.host = hostName; + this.version = SOCKS_VERSION; + + //System.out.println("Doing ATYP_DOMAINNAME"); + + addrType = SOCKS_ATYP_DOMAINNAME; + byte addr[] = hostName.getBytes(); + + data =new byte[7+addr.length]; + data[0] = (byte) SOCKS_VERSION; //Version + data[1] = (byte) command; //Command + data[2] = (byte) 0; //Reserved byte + data[3] = (byte) SOCKS_ATYP_DOMAINNAME; //Address type + data[4] = (byte) addr.length; //Length of the address + + //Put Address + System.arraycopy(addr,0,data,5,addr.length); + //Put port + data[data.length-2] = (byte)(port >>8); + data[data.length-1] = (byte)(port); + } + + /** + Initialises Message from the stream. Reads server response from + given stream. + @param in Input stream to read response from. + @throws SocksException If server response code is not SOCKS_SUCCESS(0), or + if any error with protocol occurs. + @throws IOException If any error happens with I/O. + */ + public Socks5Message(InputStream in) throws SocksException, + IOException{ + this(in,true); + } + + /** + Initialises Message from the stream. Reads server response or client + request from given stream. + + @param in Input stream to read response from. + @param clinetMode If true read server response, else read client request. + @throws SocksException If server response code is not SOCKS_SUCCESS(0) and + reading in client mode, or if any error with protocol occurs. + @throws IOException If any error happens with I/O. + */ + public Socks5Message(InputStream in,boolean clientMode)throws SocksException, + IOException{ + read(in,clientMode); + } + + + /** + Initialises Message from the stream. Reads server response from + given stream. + @param in Input stream to read response from. + @throws SocksException If server response code is not SOCKS_SUCCESS(0), or + if any error with protocol occurs. + @throws IOException If any error happens with I/O. + */ + public void read(InputStream in) throws SocksException, + IOException{ + read(in,true); + } + + + /** + Initialises Message from the stream. Reads server response or client + request from given stream. + + @param in Input stream to read response from. + @param clinetMode If true read server response, else read client request. + @throws SocksException If server response code is not SOCKS_SUCCESS(0) and + reading in client mode, or if any error with protocol occurs. + @throws IOException If any error happens with I/O. + */ + public void read(InputStream in,boolean clientMode) throws SocksException, + IOException{ + data = null; + ip = null; + + DataInputStream di = new DataInputStream(in); + + version = di.readUnsignedByte(); + command = di.readUnsignedByte(); + if(clientMode && command != 0) + throw new SocksException(command); + + int reserved = di.readUnsignedByte(); + addrType = di.readUnsignedByte(); + + byte addr[]; + + switch(addrType){ + case SOCKS_ATYP_IPV4: + addr = new byte[4]; + di.readFully(addr); + host = bytes2IPV4(addr,0); + break; + case SOCKS_ATYP_IPV6: + addr = new byte[SOCKS_IPV6_LENGTH];//I believe it is 16 bytes,huge! + di.readFully(addr); + host = bytes2IPV6(addr,0); + break; + case SOCKS_ATYP_DOMAINNAME: + //System.out.println("Reading ATYP_DOMAINNAME"); + addr = new byte[di.readUnsignedByte()];//Next byte shows the length + di.readFully(addr); + host = new String(addr); + break; + default: + throw(new SocksException(CProxy.SOCKS_JUST_ERROR)); + } + + port = di.readUnsignedShort(); + + if(addrType != SOCKS_ATYP_DOMAINNAME && doResolveIP){ + try{ + ip = InetAddress.getByName(host); + }catch(UnknownHostException uh_ex){ + } + } + } + + /** + Writes the message to the stream. + @param out Output stream to which message should be written. + */ + public void write(OutputStream out)throws SocksException, + IOException{ + if(data == null){ + Socks5Message msg; + + if(addrType == SOCKS_ATYP_DOMAINNAME) + msg = new Socks5Message(command,host,port); + else{ + if(ip == null){ + try{ + ip = InetAddress.getByName(host); + }catch(UnknownHostException uh_ex){ + throw new SocksException(CProxy.SOCKS_JUST_ERROR); + } + } + msg = new Socks5Message(command,ip,port); + } + data = msg.data; + } + out.write(data); + } + + /** + Returns IP field of the message as IP, if the message was created + with ATYP of HOSTNAME, it will attempt to resolve the hostname, + which might fail. + @throws UnknownHostException if host can't be resolved. + */ + public InetAddress getInetAddress() throws UnknownHostException{ + if(ip!=null) return ip; + + return (ip=InetAddress.getByName(host)); + } + + /** + Returns string representation of the message. + */ + public String toString(){ + String s= + "Socks5Message:"+"\n"+ + "VN "+version+"\n"+ + "CMD "+command+"\n"+ + "ATYP "+addrType+"\n"+ + "ADDR "+host+"\n"+ + "PORT "+port+"\n"; + return s; + } + + + /** + *Wether to resolve hostIP returned from SOCKS server + *that is wether to create InetAddress object from the + *hostName string + */ + static public boolean resolveIP(){ return doResolveIP;} + + /** + *Wether to resolve hostIP returned from SOCKS server + *that is wether to create InetAddress object from the + *hostName string + *@param doResolve Wether to resolve hostIP from SOCKS server. + *@return Previous value. + */ + static public boolean resolveIP(boolean doResolve){ + boolean old = doResolveIP; + doResolveIP = doResolve; + return old; + } + + /* + private static final void debug(String s){ + if(DEBUG) + System.out.print(s); + } + private static final boolean DEBUG = false; + */ + + //SOCKS5 constants + public static final int SOCKS_VERSION =5; + + public static final int SOCKS_ATYP_IPV4 =0x1; //Where is 2?? + public static final int SOCKS_ATYP_DOMAINNAME =0x3; //!!!!rfc1928 + public static final int SOCKS_ATYP_IPV6 =0x4; + + public static final int SOCKS_IPV6_LENGTH =16; + + static boolean doResolveIP = true; + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/net/sourceforge/jsocks/Socks5Proxy.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/sourceforge/jsocks/Socks5Proxy.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,248 @@ +package net.sourceforge.jsocks; +import java.net.*; +import java.io.*; +import java.util.Hashtable; +import java.util.Enumeration; + +/** + SOCKS5 CProxy. +*/ + +public class Socks5Proxy extends CProxy implements Cloneable{ + +//Data members + private Hashtable authMethods = new Hashtable(); + private int selectedMethod; + + boolean resolveAddrLocally = true; + UDPEncapsulation udp_encapsulation=null; + + +//Public Constructors +//==================== + + /** + Creates SOCKS5 proxy. + @param p CProxy to use to connect to this proxy, allows proxy chaining. + @param proxyHost Host on which a CProxy server runs. + @param proxyPort Port on which a CProxy server listens for connections. + @throws UnknownHostException If proxyHost can't be resolved. + */ + public Socks5Proxy(CProxy p,String proxyHost,int proxyPort) + throws UnknownHostException{ + super(p,proxyHost,proxyPort); + version = 5; + setAuthenticationMethod(0,new AuthenticationNone()); + } + + /** + Creates SOCKS5 proxy. + @param proxyHost Host on which a CProxy server runs. + @param proxyPort Port on which a CProxy server listens for connections. + @throws UnknownHostException If proxyHost can't be resolved. + */ + public Socks5Proxy(String proxyHost,int proxyPort) + throws UnknownHostException{ + this(null,proxyHost,proxyPort); + } + + + /** + Creates SOCKS5 proxy. + @param p CProxy to use to connect to this proxy, allows proxy chaining. + @param proxyIP Host on which a CProxy server runs. + @param proxyPort Port on which a CProxy server listens for connections. + */ + public Socks5Proxy(CProxy p,InetAddress proxyIP,int proxyPort){ + super(p,proxyIP,proxyPort); + version = 5; + setAuthenticationMethod(0,new AuthenticationNone()); + } + + /** + Creates SOCKS5 proxy. + @param proxyIP Host on which a CProxy server runs. + @param proxyPort Port on which a CProxy server listens for connections. + */ + public Socks5Proxy(InetAddress proxyIP,int proxyPort){ + this(null,proxyIP,proxyPort); + } + +//Public instance methods +//======================== + + + /** + * Wether to resolve address locally or to let proxy do so. +

+ SOCKS5 protocol allows to send host names rather then IPs in the + requests, this option controls wether the hostnames should be send + to the proxy server as names, or should they be resolved locally. + @param doResolve Wether to perform resolution locally. + @return Previous settings. + */ + public boolean resolveAddrLocally(boolean doResolve){ + boolean old = resolveAddrLocally; + resolveAddrLocally = doResolve; + return old; + } + /** + Get current setting on how the addresses should be handled. + @return Current setting for address resolution. + @see Socks5Proxy#resolveAddrLocally(boolean doResolve) + */ + public boolean resolveAddrLocally(){ + return resolveAddrLocally; + } + + /** + Adds another authentication method. + @param methodId Authentication method id, see rfc1928 + @param method Implementation of Authentication + @see Authentication + */ + public boolean setAuthenticationMethod(int methodId, + Authentication method){ + if(methodId<0 || methodId > 255) + return false; + if(method == null){ + //Want to remove a particular method + return (authMethods.remove(new Integer(methodId)) != null); + }else{//Add the method, or rewrite old one + authMethods.put(new Integer(methodId),method); + } + return true; + } + + /** + Get authentication method, which corresponds to given method id + @param methodId Authentication method id. + @return Implementation for given method or null, if one was not set. + */ + public Authentication getAuthenticationMethod(int methodId){ + Object method = authMethods.get(new Integer(methodId)); + if(method == null) return null; + return (Authentication)method; + } + + /** + Creates a clone of this CProxy. + */ + public Object clone(){ + Socks5Proxy newProxy = new Socks5Proxy(proxyIP,proxyPort); + newProxy.authMethods = (Hashtable) this.authMethods.clone(); + newProxy.directHosts = (InetRange)directHosts.clone(); + newProxy.resolveAddrLocally = resolveAddrLocally; + newProxy.chainProxy = chainProxy; + return newProxy; + } + +//Public Static(Class) Methods +//============================== + + +//Protected Methods +//================= + + protected CProxy copy(){ + Socks5Proxy copy = new Socks5Proxy(proxyIP,proxyPort); + copy.authMethods = this.authMethods; //same Hash, no copy + copy.directHosts = this.directHosts; + copy.chainProxy = this.chainProxy; + copy.resolveAddrLocally = this.resolveAddrLocally; + return copy; + } + /** + * + * + */ + protected void startSession()throws SocksException{ + super.startSession(); + Authentication auth; + Socket ps = proxySocket; //The name is too long + + try{ + + byte nMethods = (byte) authMethods.size(); //Number of methods + + byte[] buf = new byte[2+nMethods]; //2 is for VER,NMETHODS + buf[0] = (byte) version; + buf[1] = nMethods; //Number of methods + int i=2; + + Enumeration ids = authMethods.keys(); + while(ids.hasMoreElements()) + buf[i++] = (byte)((Integer)ids.nextElement()).intValue(); + + out.write(buf); + out.flush(); + + int versionNumber = in.read(); + selectedMethod = in.read(); + + if(versionNumber < 0 || selectedMethod < 0){ + //EOF condition was reached + endSession(); + throw(new SocksException(SOCKS_PROXY_IO_ERROR, + "Connection to proxy lost.")); + } + if(versionNumber < version){ + //What should we do?? + } + if(selectedMethod == 0xFF){ //No method selected + ps.close(); + throw ( new SocksException(SOCKS_AUTH_NOT_SUPPORTED)); + } + + auth = getAuthenticationMethod(selectedMethod); + if(auth == null){ + //This shouldn't happen, unless method was removed by other + //thread, or the server stuffed up + throw(new SocksException(SOCKS_JUST_ERROR, + "Speciefied Authentication not found!")); + } + Object[] in_out = auth.doSocksAuthentication(selectedMethod,ps); + if(in_out == null){ + //Authentication failed by some reason + throw(new SocksException(SOCKS_AUTH_FAILURE)); + } + //Most authentication methods are expected to return + //simply the input/output streams associated with + //the socket. However if the auth. method requires + //some kind of encryption/decryption being done on the + //connection it should provide classes to handle I/O. + + in = (InputStream) in_out[0]; + out = (OutputStream) in_out[1]; + if(in_out.length > 2) + udp_encapsulation = (UDPEncapsulation) in_out[2]; + + }catch(SocksException s_ex){ + throw s_ex; + }catch(UnknownHostException uh_ex){ + throw(new SocksException(SOCKS_PROXY_NO_CONNECT)); + }catch(SocketException so_ex){ + throw(new SocksException(SOCKS_PROXY_NO_CONNECT)); + }catch(IOException io_ex){ + //System.err.println(io_ex); + throw(new SocksException(SOCKS_PROXY_IO_ERROR,""+io_ex)); + } + } + + protected ProxyMessage formMessage(int cmd,InetAddress ip,int port){ + return new Socks5Message(cmd,ip,port); + } + protected ProxyMessage formMessage(int cmd,String host,int port) + throws UnknownHostException{ + if(resolveAddrLocally) + return formMessage(cmd,InetAddress.getByName(host),port); + else + return new Socks5Message(cmd,host,port); + } + protected ProxyMessage formMessage(InputStream in) + throws SocksException, + IOException{ + return new Socks5Message(in); + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/net/sourceforge/jsocks/SocksException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/sourceforge/jsocks/SocksException.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,78 @@ +package net.sourceforge.jsocks; + +/** + Exception thrown by various socks classes to indicate errors + with protocol or unsuccessfull server responses. +*/ +public class SocksException extends java.io.IOException{ + /** + Construct a SocksException with given errorcode. +

+ Tries to look up message which corresponds to this error code. + @param errCode Error code for this exception. + */ + public SocksException(int errCode){ + this.errCode = errCode; + if((errCode >> 16) == 0){ + //Server reply error message + errString = errCode <= serverReplyMessage.length ? + serverReplyMessage[errCode] : + UNASSIGNED_ERROR_MESSAGE; + }else{ + //Local error + errCode = (errCode >> 16) -1; + errString = errCode <= localErrorMessage.length ? + localErrorMessage[errCode] : + UNASSIGNED_ERROR_MESSAGE; + } + } + /** + Constructs a SocksException with given error code and message. + @param errCode Error code. + @param errString Error Message. + */ + public SocksException(int errCode,String errString){ + this.errCode = errCode; + this.errString = errString; + } + /** + Get the error code associated with this exception. + @return Error code associated with this exception. + */ + public int getErrorCode(){ + return errCode; + } + /** + Get human readable representation of this exception. + @return String represntation of this exception. + */ + public String toString(){ + return errString; + } + + static final String UNASSIGNED_ERROR_MESSAGE = + "Unknown error message"; + static final String serverReplyMessage[] = { + "Succeeded", + "General SOCKS server failure", + "Connection not allowed by ruleset", + "Network unreachable", + "Host unreachable", + "Connection refused", + "TTL expired", + "Command not supported", + "Address type not supported" }; + + static final String localErrorMessage[] ={ + "SOCKS server not specified", + "Unable to contact SOCKS server", + "IO error", + "None of Authentication methods are supported", + "Authentication failed", + "General SOCKS fault" }; + + String errString; + public int errCode; + +}//End of SocksException class + diff -r 208b31032318 -r d29cce60f393 app/src/main/java/net/sourceforge/jsocks/SocksServerSocket.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/sourceforge/jsocks/SocksServerSocket.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,207 @@ +package net.sourceforge.jsocks; + +import java.net.*; +import java.io.*; + +/** + SocksServerSocket allows to accept connections from one particular + host through the SOCKS4 or SOCKS5 proxy. +*/ +public class SocksServerSocket extends ServerSocket{ + //Data members + protected CProxy proxy; + protected String localHost; + protected InetAddress localIP; + protected int localPort; + + boolean doing_direct = false; + InetAddress remoteAddr; + + /** + * Creates ServerSocket capable of accepting one connection + * through the firewall, uses default CProxy. + *@param host Host from which the connection should be recieved. + *@param port Port number of the primary connection. + */ + public SocksServerSocket(String host,int port) + throws SocksException,UnknownHostException,IOException{ + this(CProxy.defaultProxy,host,port); + } + /** + *Creates ServerSocket capable of accepting one connection + *through the firewall, uses given proxy. + *@param p CProxy object to use. + *@param host Host from which the connection should be recieved. + *@param port Port number of the primary connection. + */ + public SocksServerSocket(CProxy p,String host,int port) + throws SocksException,UnknownHostException,IOException{ + + + super(0); + if(p == null) throw new SocksException(CProxy.SOCKS_NO_PROXY); + //proxy=p; + proxy = p.copy(); + if(proxy.isDirect(host)){ + remoteAddr = InetAddress.getByName(host); + proxy = null; + doDirect(); + }else{ + processReply(proxy.bind(host,port)); + } + } + + /** + * Creates ServerSocket capable of accepting one connection + * through the firewall, uses default CProxy. + *@param ip Host from which the connection should be recieved. + *@param port Port number of the primary connection. + */ + public SocksServerSocket(InetAddress ip, int port) throws SocksException, + IOException{ + this(CProxy.defaultProxy,ip,port); + } + + /** + *Creates ServerSocket capable of accepting one connection + *through the firewall, uses given proxy. + *@param p CProxy object to use. + *@param ip Host from which the connection should be recieved. + *@param port Port number of the primary connection. + */ + public SocksServerSocket(CProxy p,InetAddress ip, int port) + throws SocksException,IOException{ + super(0); + + if(p == null) throw new SocksException(CProxy.SOCKS_NO_PROXY); + this.proxy = p.copy(); + + if(proxy.isDirect(ip)){ + remoteAddr = ip; + doDirect(); + }else{ + processReply(proxy.bind(ip,port)); + } + } + + + /** + * Accepts the incoming connection. + */ + public Socket accept() throws IOException{ + Socket s; + + if(!doing_direct){ + if(proxy == null) return null; + + ProxyMessage msg = proxy.accept(); + s = msg.ip == null? new SocksSocket(msg.host,msg.port,proxy) + : new SocksSocket(msg.ip,msg.port,proxy); + //Set timeout back to 0 + proxy.proxySocket.setSoTimeout(0); + }else{ //Direct Connection + + //Mimic the proxy behaviour, + //only accept connections from the speciefed host. + while(true){ + s = super.accept(); + if(s.getInetAddress().equals(remoteAddr)){ + //got the connection from the right host + //Close listenning socket. + break; + }else + s.close(); //Drop all connections from other hosts + } + + } + proxy = null; + //Return accepted socket + return s; + } + + /** + * Closes the connection to proxy if socket have not been accepted, if + * the direct connection is used, closes direct ServerSocket. If the + * client socket have been allready accepted, does nothing. + */ + public void close() throws IOException{ + super.close(); + if(proxy != null) proxy.endSession(); + proxy = null; + } + + /** + Get the name of the host proxy is using to listen for incoming + connection. +

+ Usefull when address is returned by proxy as the hostname. + @return the hostname of the address proxy is using to listen + for incoming connection. + */ + public String getHost(){ + return localHost; + } + + /** + * Get address assigned by proxy to listen for incomming + * connections, or the local machine address if doing direct + * connection. + */ + public InetAddress getInetAddress(){ + if(localIP == null){ + try{ + localIP = InetAddress.getByName(localHost); + }catch(UnknownHostException e){ + return null; + } + } + return localIP; + } + + /** + * Get port assigned by proxy to listen for incoming connections, or + the port chosen by local system, if accepting directly. + */ + public int getLocalPort(){ + return localPort; + } + + /** + Set Timeout. + + @param timeout Amount of time in milliseconds, accept should wait for + incoming connection before failing with exception. + Zero timeout implies infinity. + */ + public void setSoTimeout(int timeout) throws SocketException{ + super.setSoTimeout(timeout); + if(!doing_direct) proxy.proxySocket.setSoTimeout(timeout); + } + + +//Private Methods +////////////////// + + private void processReply(ProxyMessage reply)throws SocksException{ + localPort = reply.port; + /* + * If the server have assigned same host as it was contacted on + * it might return an address of all zeros + */ + if(reply.host.equals("0.0.0.0")){ + localIP = proxy.proxyIP; + localHost = localIP.getHostName(); + }else{ + localHost = reply.host; + localIP = reply.ip; + } + } + + private void doDirect(){ + doing_direct = true; + localPort = super.getLocalPort(); + localIP = super.getInetAddress(); + localHost = localIP.getHostName(); + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/net/sourceforge/jsocks/SocksSocket.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/sourceforge/jsocks/SocksSocket.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,332 @@ +package net.sourceforge.jsocks; + +import java.net.*; +import java.io.*; + +/** + * SocksSocket tryies to look very similar to normal Socket, + * while allowing connections through the SOCKS4 or 5 proxy. + * To use this class you will have to identify proxy you need + * to use, CProxy class allows you to set default proxy, which + * will be used by all Socks aware sockets. You can also create + * either Socks4Proxy or Socks5Proxy, and use them by passing to the + * appropriate constructors. + *

+ * Using Socks package can be as easy as that: + * + *


+ *
+ *     import Socks.*;
+ *     ....
+ *
+ *     try{
+ *        //Specify SOCKS5 proxy
+ *        CProxy.setDefaultProxy("socks-proxy",1080);
+ *
+ *        //OR you still use SOCKS4
+ *        //Code below uses SOCKS4 proxy
+ *        //CProxy.setDefaultProxy("socks-proxy",1080,userName);
+ *
+ *        Socket s = SocksSocket("some.host.of.mine",13);
+ *        readTimeFromSock(s);
+ *     }catch(SocksException sock_ex){
+ *        //Usually it will turn in more or less meaningfull message
+ *        System.err.println("SocksException:"+sock_ex);
+ *     }
+ *
+ * 
+ *

+ * However if the need exist for more control, like resolving addresses + * remotely, or using some non-trivial authentication schemes, it can be done. + */ + +public class SocksSocket extends Socket{ + //Data members + protected CProxy proxy; + protected String localHost, remoteHost; + protected InetAddress localIP, remoteIP; + protected int localPort,remotePort; + + private Socket directSock = null; + + /** + * Tryies to connect to given host and port + * using default proxy. If no default proxy speciefied + * it throws SocksException with error code SOCKS_NO_PROXY. + @param host Machine to connect to. + @param port Port to which to connect. + * @see SocksSocket#SocksSocket(CProxy,String,int) + * @see Socks5Proxy#resolveAddrLocally + */ + public SocksSocket(String host,int port) + throws SocksException,UnknownHostException{ + this(CProxy.defaultProxy,host,port); + } + /** + * Connects to host port using given proxy server. + @param p CProxy to use. + @param host Machine to connect to. + @param port Port to which to connect. + @throws UnknownHostException + If one of the following happens: +

    + +
  1. CProxy settings say that address should be resolved locally, but + this fails. +
  2. CProxy settings say that the host should be contacted directly but + host name can't be resolved. +
+ @throws SocksException + If one of the following happens: +
    +
  • CProxy is is null. +
  • CProxy settings say that the host should be contacted directly but + this fails. +
  • Socks Server can't be contacted. +
  • Authentication fails. +
  • Connection is not allowed by the SOCKS proxy. +
  • SOCKS proxy can't establish the connection. +
  • Any IO error occured. +
  • Any protocol error occured. +
+ @throws IOexception if anything is wrong with I/O. + @see Socks5Proxy#resolveAddrLocally + */ + public SocksSocket(CProxy p,String host,int port) + throws SocksException,UnknownHostException{ + + + if(p == null) throw new SocksException(CProxy.SOCKS_NO_PROXY); + //proxy=p; + proxy = p.copy(); + remoteHost = host; + remotePort = port; + if(proxy.isDirect(host)){ + remoteIP = InetAddress.getByName(host); + doDirect(); + } + else + processReply(proxy.connect(host,port)); + } + + + /** + * Tryies to connect to given ip and port + * using default proxy. If no default proxy speciefied + * it throws SocksException with error code SOCKS_NO_PROXY. + @param ip Machine to connect to. + @param port Port to which to connect. + * @see SocksSocket#SocksSocket(CProxy,String,int) + */ + public SocksSocket(InetAddress ip, int port) throws SocksException{ + this(CProxy.defaultProxy,ip,port); + } + + /** + Connects to given ip and port using given CProxy server. + @param p CProxy to use. + @param ip Machine to connect to. + @param port Port to which to connect. + + */ + public SocksSocket(CProxy p,InetAddress ip, int port) throws SocksException{ + if(p == null) throw new SocksException(CProxy.SOCKS_NO_PROXY); + this.proxy = p.copy(); + this.remoteIP = ip; + this.remotePort = port; + this.remoteHost = ip.getHostName(); + if(proxy.isDirect(remoteIP)) + doDirect(); + else + processReply(proxy.connect(ip,port)); + } + + + /** + * These 2 constructors are used by the SocksServerSocket. + * This socket simply overrides remoteHost, remotePort + */ + protected SocksSocket(String host,int port,CProxy proxy){ + this.remotePort = port; + this.proxy = proxy; + this.localIP = proxy.proxySocket.getLocalAddress(); + this.localPort = proxy.proxySocket.getLocalPort(); + this.remoteHost = host; + } + protected SocksSocket(InetAddress ip,int port,CProxy proxy){ + remoteIP = ip; + remotePort = port; + this.proxy = proxy; + this.localIP = proxy.proxySocket.getLocalAddress(); + this.localPort = proxy.proxySocket.getLocalPort(); + remoteHost = remoteIP.getHostName(); + } + + /** + * Same as Socket + */ + public void close() throws IOException{ + if(proxy!= null)proxy.endSession(); + proxy = null; + } + /** + * Same as Socket + */ + public InputStream getInputStream(){ + return proxy.in; + } + /** + * Same as Socket + */ + public OutputStream getOutputStream(){ + return proxy.out; + } + /** + * Same as Socket + */ + public int getPort(){ + return remotePort; + } + /** + * Returns remote host name, it is usefull in cases when addresses + * are resolved by proxy, and we can't create InetAddress object. + @return The name of the host this socket is connected to. + */ + public String getHost(){ + return remoteHost; + } + /** + * Get remote host as InetAddress object, might return null if + * addresses are resolved by proxy, and it is not possible to resolve + * it locally + @return Ip address of the host this socket is connected to, or null + if address was returned by the proxy as DOMAINNAME and can't be + resolved locally. + */ + public InetAddress getInetAddress(){ + if(remoteIP == null){ + try{ + remoteIP = InetAddress.getByName(remoteHost); + }catch(UnknownHostException e){ + return null; + } + } + return remoteIP; + } + + /** + * Get the port assigned by the proxy for the socket, not + * the port on locall machine as in Socket. + @return Port of the socket used on the proxy server. + */ + public int getLocalPort(){ + return localPort; + } + + /** + * Get address assigned by proxy to make a remote connection, + * it might be different from the host specified for the proxy. + * Can return null if socks server returned this address as hostname + * and it can't be resolved locally, use getLocalHost() then. + @return Address proxy is using to make a connection. + */ + public InetAddress getLocalAddress(){ + if(localIP == null){ + try{ + localIP = InetAddress.getByName(localHost); + }catch(UnknownHostException e){ + return null; + } + } + return localIP; + } + /** + Get name of the host, proxy has assigned to make a remote connection + for this socket. This method is usefull when proxy have returned + address as hostname, and we can't resolve it on this machine. + @return The name of the host proxy is using to make a connection. + */ + public String getLocalHost(){ + return localHost; + } + + /** + Same as socket. + */ + public void setSoLinger(boolean on,int val) throws SocketException{ + proxy.proxySocket.setSoLinger(on,val); + } + /** + Same as socket. + */ + public int getSoLinger(int timeout) throws SocketException{ + return proxy.proxySocket.getSoLinger(); + } + /** + Same as socket. + */ + public void setSoTimeout(int timeout) throws SocketException{ + proxy.proxySocket.setSoTimeout(timeout); + } + /** + Same as socket. + */ + public int getSoTimeout(int timeout) throws SocketException{ + return proxy.proxySocket.getSoTimeout(); + } + /** + Same as socket. + */ + public void setTcpNoDelay(boolean on) throws SocketException{ + proxy.proxySocket.setTcpNoDelay(on); + } + /** + Same as socket. + */ + public boolean getTcpNoDelay() throws SocketException{ + return proxy.proxySocket.getTcpNoDelay(); + } + + /** + Get string representation of the socket. + */ + public String toString(){ + if(directSock!=null) return "Direct connection:"+directSock; + return ("Proxy:"+proxy+";"+"addr:"+remoteHost+",port:"+remotePort + +",localport:"+localPort); + + } + +//Private Methods +////////////////// + + private void processReply(ProxyMessage reply)throws SocksException{ + localPort = reply.port; + /* + * If the server have assigned same host as it was contacted on + * it might return an address of all zeros + */ + if(reply.host.equals("0.0.0.0")){ + localIP = proxy.proxyIP; + localHost = localIP.getHostName(); + }else{ + localHost = reply.host; + localIP = reply.ip; + } + } + private void doDirect()throws SocksException{ + try{ + //System.out.println("IP:"+remoteIP+":"+remotePort); + directSock = new Socket(remoteIP,remotePort); + proxy.out = directSock.getOutputStream(); + proxy.in = directSock.getInputStream(); + proxy.proxySocket = directSock; + localIP = directSock.getLocalAddress(); + localPort = directSock.getLocalPort(); + }catch(IOException io_ex){ + throw new SocksException(CProxy.SOCKS_DIRECT_FAILED, + "Direct connect failed:"+io_ex); + } + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/net/sourceforge/jsocks/UDPEncapsulation.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/sourceforge/jsocks/UDPEncapsulation.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,29 @@ +package net.sourceforge.jsocks; +/** + This interface provides for datagram encapsulation for SOCKSv5 protocol. +

+ SOCKSv5 allows for datagrams to be encapsulated for purposes of integrity + and/or authenticity. How it should be done is aggreed during the + authentication stage, and is authentication dependent. This interface is + provided to allow this encapsulation. + @see Authentication +*/ +public interface UDPEncapsulation{ + + /** + This method should provide any authentication depended transformation + on datagrams being send from/to the client. + + @param data Datagram data (including any SOCKS related bytes), to be + encapsulated/decapsulated. + @param out Wether the data is being send out. If true method should + encapsulate/encrypt data, otherwise it should decapsulate/ + decrypt data. + @throw IOException if for some reason data can be transformed correctly. + @return Should return byte array containing data after transformation. + It is possible to return same array as input, if transformation + only involves bit mangling, and no additional data is being + added or removed. + */ + byte[] udpEncapsulate(byte[] data, boolean out) throws java.io.IOException; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/net/sourceforge/jsocks/UDPRelayServer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/sourceforge/jsocks/UDPRelayServer.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,212 @@ +package net.sourceforge.jsocks; +import net.sourceforge.jsocks.server.*; +import java.net.*; +import java.io.*; + +/** + UDP Relay server, used by ProxyServer to perform udp forwarding. +*/ +class UDPRelayServer implements Runnable{ + + + DatagramSocket client_sock; + DatagramSocket remote_sock; + + Socket controlConnection; + + int relayPort; + InetAddress relayIP; + + Thread pipe_thread1,pipe_thread2; + Thread master_thread; + + ServerAuthenticator auth; + + long lastReadTime; + + static PrintStream log = null; + static CProxy proxy = null; + static int datagramSize = 0xFFFF;//64K, a bit more than max udp size + static int iddleTimeout = 180000;//3 minutes + + + /** + Constructs UDP relay server to communicate with client + on given ip and port. + @param clientIP Address of the client from whom datagrams + will be recieved and to whom they will be forwarded. + @param clientPort Clients port. + @param master_thread Thread which will be interrupted, when + UDP relay server stoppes for some reason. + @param controlConnection Socket which will be closed, before + interrupting the master thread, it is introduced due to a bug + in windows JVM which does not throw InterruptedIOException in + threads which block in I/O operation. + */ + public UDPRelayServer(InetAddress clientIP,int clientPort, + Thread master_thread, + Socket controlConnection, + ServerAuthenticator auth) + throws IOException{ + this.master_thread = master_thread; + this.controlConnection = controlConnection; + this.auth = auth; + + client_sock = new Socks5DatagramSocket(true,auth.getUdpEncapsulation(), + clientIP,clientPort); + relayPort = client_sock.getLocalPort(); + relayIP = client_sock.getLocalAddress(); + + if(relayIP.getHostAddress().equals("0.0.0.0")) + relayIP = InetAddress.getLocalHost(); + + if(proxy == null) + remote_sock = new DatagramSocket(); + else + remote_sock = new Socks5DatagramSocket(proxy,0,null); + } + + +//Public methods +///////////////// + + + /** + Sets the timeout for UDPRelay server.
+ Zero timeout implies infinity.
+ Default timeout is 3 minutes. + */ + + static public void setTimeout(int timeout){ + iddleTimeout = timeout; + } + + + /** + Sets the size of the datagrams used in the UDPRelayServer.
+ Default size is 64K, a bit more than maximum possible size of the + datagram. + */ + static public void setDatagramSize(int size){ + datagramSize = size; + } + + /** + Port to which client should send datagram for association. + */ + public int getRelayPort(){ + return relayPort; + } + /** + IP address to which client should send datagrams for association. + */ + public InetAddress getRelayIP(){ + return relayIP; + } + + /** + Starts udp relay server. + Spawns two threads of execution and returns. + */ + public void start() throws IOException{ + remote_sock.setSoTimeout(iddleTimeout); + client_sock.setSoTimeout(iddleTimeout); + + log("Starting UDP relay server on "+relayIP+":"+relayPort); + log("Remote socket "+remote_sock.getLocalAddress()+":"+ + remote_sock.getLocalPort()); + + pipe_thread1 = new Thread(this,"pipe1"); + pipe_thread2 = new Thread(this,"pipe2"); + + lastReadTime = System.currentTimeMillis(); + + pipe_thread1.start(); + pipe_thread2.start(); + } + + /** + Stops Relay server. +

+ Does not close control connection, does not interrupt master_thread. + */ + public synchronized void stop(){ + master_thread = null; + controlConnection = null; + abort(); + } + +//Runnable interface +//////////////////// + public void run(){ + try{ + if(Thread.currentThread().getName().equals("pipe1")) + pipe(remote_sock,client_sock,false); + else + pipe(client_sock,remote_sock,true); + }catch(IOException ioe){ + }finally{ + abort(); + log("UDP Pipe thread "+Thread.currentThread().getName()+" stopped."); + } + + } + +//Private methods +///////////////// + private synchronized void abort(){ + if(pipe_thread1 == null) return; + + log("Aborting UDP Relay Server"); + + remote_sock.close(); + client_sock.close(); + + if(controlConnection != null) + try{ controlConnection.close();} catch(IOException ioe){} + + if(master_thread!=null) master_thread.interrupt(); + + pipe_thread1.interrupt(); + pipe_thread2.interrupt(); + + pipe_thread1 = null; + } + + + static private void log(String s){ + if(log != null){ + log.println(s); + log.flush(); + } + } + + private void pipe(DatagramSocket from,DatagramSocket to,boolean out) + throws IOException{ + byte[] data = new byte[datagramSize]; + DatagramPacket dp = new DatagramPacket(data,data.length); + + while(true){ + try{ + from.receive(dp); + lastReadTime = System.currentTimeMillis(); + + if(auth.checkRequest(dp,out)) + to.send(dp); + + }catch(UnknownHostException uhe){ + log("Dropping datagram for unknown host"); + }catch(InterruptedIOException iioe){ + //log("Interrupted: "+iioe); + //If we were interrupted by other thread. + if(iddleTimeout == 0) return; + + //If last datagram was received, long time ago, return. + long timeSinceRead = System.currentTimeMillis() - lastReadTime; + if(timeSinceRead >= iddleTimeout -100) //-100 for adjustment + return; + } + dp.setLength(data.length); + } + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/net/sourceforge/jsocks/UserPasswordAuthentication.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/sourceforge/jsocks/UserPasswordAuthentication.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,75 @@ +package net.sourceforge.jsocks; + +/** + SOCKS5 User Password authentication scheme. +*/ +public class UserPasswordAuthentication implements Authentication{ + + /**SOCKS ID for User/Password authentication method*/ + public final static int METHOD_ID = 2; + + String userName, password; + byte[] request; + + /** + Create an instance of UserPasswordAuthentication. + @param userName User Name to send to SOCKS server. + @param password Password to send to SOCKS server. + */ + public UserPasswordAuthentication(String userName,String password){ + this.userName = userName; + this.password = password; + formRequest(); + } + /** Get the user name. + @return User name. + */ + public String getUser(){ + return userName; + } + /** Get password + @return Password + */ + public String getPassword(){ + return password; + } + /** + Does User/Password authentication as defined in rfc1929. + @return An array containnig in, out streams, or null if authentication + fails. + */ + public Object[] doSocksAuthentication(int methodId, + java.net.Socket proxySocket) + throws java.io.IOException{ + + if(methodId != METHOD_ID) return null; + + java.io.InputStream in = proxySocket.getInputStream(); + java.io.OutputStream out = proxySocket.getOutputStream(); + + out.write(request); + int version = in.read(); + if(version < 0) return null; //Server closed connection + int status = in.read(); + if(status != 0) return null; //Server closed connection, or auth failed. + + return new Object[] {in,out}; + } + +//Private methods +////////////////// + +/** Convert UserName password in to binary form, ready to be send to server*/ + private void formRequest(){ + byte[] user_bytes = userName.getBytes(); + byte[] password_bytes = password.getBytes(); + + request = new byte[3+user_bytes.length+password_bytes.length]; + request[0] = (byte) 1; + request[1] = (byte) user_bytes.length; + System.arraycopy(user_bytes,0,request,2,user_bytes.length); + request[2+user_bytes.length] = (byte) password_bytes.length; + System.arraycopy(password_bytes,0, + request,3+user_bytes.length,password_bytes.length); + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/net/sourceforge/jsocks/server/Ident.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/sourceforge/jsocks/server/Ident.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,158 @@ +package net.sourceforge.jsocks.server; +import net.sourceforge.jsocks.*; +import java.net.*; +import java.io.*; +import java.util.StringTokenizer; + +/** + Class Ident provides means to obtain user name of the owner of the socket + on remote machine, providing remote machine runs identd daemon. +

+ To use it: +

+   Socket s = ss.accept();
+   Ident id = new Ident(s);
+   if(id.successful) goUseUser(id.userName);
+   else handleIdentError(id.errorCode,id.errorMessage)
+   
+*/ +public class Ident{ + + /** Error Message can be null.*/ + public String errorMessage; + /** Host type as returned by daemon, can be null, if error happened*/ + public String hostType; + /** User name as returned by the identd daemon, or null, if it failed*/ + public String userName; + + /** If this is true then userName and hostType contain valid values. + Else errorCode contain the error code, and errorMessage contains + the corresponding message. + */ + public boolean successful; + /** Error code*/ + public int errorCode; + /** Identd on port 113 can't be contacted*/ + public static final int ERR_NO_CONNECT = 1; + /** Connection timed out*/ + public static final int ERR_TIMEOUT = 2; + /** Identd daemon responded with ERROR, in this case errorMessage + contains the string explanation, as send by the daemon. + */ + public static final int ERR_PROTOCOL = 3; + /** + When parsing server response protocol error happened. + */ + public static final int ERR_PROTOCOL_INCORRECT = 4; + + + /** Maximum amount of time we should wait before dropping the + connection to identd server.Setting it to 0 implies infinit + timeout. + */ + public static final int connectionTimeout = 10000; + + + /** + Constructor tries to connect to Identd daemon on the host of the + given socket, and retrieve user name of the owner of given socket + connection on remote machine. After constructor returns public + fields are initialised to whatever the server returned. +

+ If user name was successfully retrieved successful is set to true, + and userName and hostType are set to whatever server returned. If + however for some reason user name was not obtained, successful is set + to false and errorCode contains the code explaining the reason of + failure, and errorMessage contains human readable explanation. +

+ Constructor may block, for a while. + @param s Socket whose ownership on remote end should be obtained. + */ + public Ident(Socket s ){ + Socket sock = null; + successful = false; //We are pessimistic + + try{ + sock = new Socket(s.getInetAddress(),113); + sock.setSoTimeout(connectionTimeout); + byte[] request = (""+s.getPort()+" , "+ + s.getLocalPort()+"\r\n").getBytes(); + + sock.getOutputStream().write(request); + + BufferedReader in = new BufferedReader( + new InputStreamReader(sock.getInputStream())); + + parseResponse(in.readLine()); + + }catch(InterruptedIOException iioe){ + errorCode = ERR_TIMEOUT; + errorMessage = "Connection to identd timed out."; + }catch(ConnectException ce){ + errorCode = ERR_NO_CONNECT; + errorMessage = "Connection to identd server failed."; + + }catch(IOException ioe){ + errorCode = ERR_NO_CONNECT; + errorMessage = ""+ioe; + }finally{ + try{ if(sock!=null) sock.close();}catch(IOException ioe){}; + } + } + + private void parseResponse(String response){ + if(response == null){ + errorCode = ERR_PROTOCOL_INCORRECT; + errorMessage = "Identd server closed connection."; + return; + } + + StringTokenizer st = new StringTokenizer(response,":"); + if(st.countTokens() < 3){ + errorCode = ERR_PROTOCOL_INCORRECT; + errorMessage = "Can't parse server response."; + return; + } + + st.nextToken(); //Discard first token, it's basically what we have send + String command = st.nextToken().trim().toUpperCase(); + + if(command.equals("USERID") && st.countTokens() >= 2){ + successful = true; + hostType = st.nextToken().trim(); + userName = st.nextToken("").substring(1);//Get all that is left + }else if(command.equals("ERROR")){ + errorCode = ERR_PROTOCOL; + errorMessage = st.nextToken(); + }else{ + errorCode = ERR_PROTOCOL_INCORRECT; + System.out.println("Opa!"); + errorMessage = "Can't parse server response."; + } + + + } + +/////////////////////////////////////////////// +//USED for Testing +/* + public static void main(String[] args) throws IOException{ + + Socket s = null; + s = new Socket("gp101-16", 1391); + + Ident id = new Ident(s); + if(id.successful){ + System.out.println("User: "+id.userName); + System.out.println("HostType: "+id.hostType); + }else{ + System.out.println("ErrorCode: "+id.errorCode); + System.out.println("ErrorMessage: "+id.errorMessage); + + } + + if(s!= null) s.close(); + } +//*/ + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/net/sourceforge/jsocks/server/IdentAuthenticator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/sourceforge/jsocks/server/IdentAuthenticator.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,150 @@ +package net.sourceforge.jsocks.server; +import net.sourceforge.jsocks.InetRange; +import net.sourceforge.jsocks.ProxyMessage; +import java.util.Hashtable; +import java.util.Vector; +import java.util.Enumeration; +import java.net.*; +import java.io.*; + +/** + An implementation of socks.ServerAuthentication which provides + simple authentication based on the host from which the connection + is made and the name of the user on the remote machine, as reported + by identd daemon on the remote machine. +

+ It can also be used to provide authentication based only on the contacting + host address. +*/ + +public class IdentAuthenticator extends ServerAuthenticatorNone{ + /** Vector of InetRanges */ + Vector hosts; + + /** Vector of user hashes*/ + Vector users; + + String user; + + + /** + Constructs empty IdentAuthenticator. + */ + public IdentAuthenticator(){ + hosts = new Vector(); + users = new Vector(); + } + /** + Used to create instances returned from startSession. + @param in Input stream. + @param out OutputStream. + @param user Username associated with this connection,could be + null if name was not required. + */ + IdentAuthenticator(InputStream in,OutputStream out, String user){ + super(in,out); + this.user = user; + } + + /** + Adds range of addresses from which connection is allowed. Hashtable + users should contain user names as keys and anything as values + (value is not used and will be ignored). + @param hostRange Range of ip addresses from which connection is allowed. + @param users Hashtable of users for whom connection is allowed, or null + to indicate that anybody is allowed to connect from the hosts within given + range. + */ + public synchronized void add(InetRange hostRange,Hashtable users){ + this.hosts.addElement(hostRange); + this.users.addElement(users); + } + + /** + Grants permission only to those users, who connect from one of the + hosts registered with add(InetRange,Hashtable) and whose names, as + reported by identd daemon, are listed for the host the connection + came from. + */ + public ServerAuthenticator startSession(Socket s) + throws IOException{ + + int ind = getRangeIndex(s.getInetAddress()); + String user = null; + + //System.out.println("getRangeReturned:"+ind); + + if(ind < 0) return null; //Host is not on the list. + + ServerAuthenticatorNone auth = (ServerAuthenticatorNone) + super.startSession(s); + + //System.out.println("super.startSession() returned:"+auth); + if(auth == null) return null; + + //do the authentication + + Hashtable user_names = (Hashtable) users.elementAt(ind); + + if(user_names != null){ //If need to do authentication + Ident ident; + ident = new Ident(s); + //If can't obtain user name, fail + if(!ident.successful) return null; + //If user name is not listed for this address, fail + if(!user_names.containsKey(ident.userName)) return null; + user = ident.userName; + } + return new IdentAuthenticator(auth.in,auth.out,user); + + } + /** + For SOCKS5 requests allways returns true. For SOCKS4 requests + checks wether the user name supplied in the request corresponds + to the name obtained from the ident daemon. + */ + public boolean checkRequest(ProxyMessage msg,java.net.Socket s){ + //If it's version 5 request, or if anybody is permitted, return true; + if(msg.version == 5 || user == null) + return true; + + if(msg.version != 4) return false; //Who knows? + + return user.equals(msg.user); + } + + /** Get String representaion of the IdentAuthenticator.*/ + public String toString(){ + String s = ""; + + for(int i=0;i + At this point no data have been extracted from the connection. It is + responsibility of this method to ensure that the next byte in the + stream after this method have been called is the first byte of the + socks request message. For SOCKSv4 there is no authentication data and + the first byte in the stream is part of the request. With SOCKSv5 however + there is an authentication data first. It is expected that implementaions + will process this authentication data. +

+ If authentication was successful an instance of ServerAuthentication + should be returned, it later will be used by the server to perform + authorization and some other things. If authentication fails null should + be returned, or an exception may be thrown. + + @param s Accepted Socket. + @return An instance of ServerAuthenticator to be used for this connection + or null + */ + ServerAuthenticator startSession(Socket s) throws IOException; + + /** + This method should return input stream which should be used on the + accepted socket. +

+ SOCKSv5 allows to have multiple authentication methods, and these methods + might require some kind of transformations being made on the data. +

+ This method is called on the object returned from the startSession + function. + */ + InputStream getInputStream(); + /** + This method should return output stream to use to write to the accepted + socket. +

+ SOCKSv5 allows to have multiple authentication methods, and these methods + might require some kind of transformations being made on the data. +

+ This method is called on the object returned from the startSession + function. + */ + OutputStream getOutputStream(); + + /** + This method should return UDPEncapsulation, which should be used + on the datagrams being send in/out. +

+ If no transformation should be done on the datagrams, this method + should return null. +

+ This method is called on the object returned from the startSession + function. + */ + + UDPEncapsulation getUdpEncapsulation(); + + /** + This method is called when a request have been read. +

+ Implementation should decide wether to grant request or not. Returning + true implies granting the request, false means request should be rejected. +

+ This method is called on the object returned from the startSession + function. + @param msg Request message. + @return true to grant request, false to reject it. + */ + boolean checkRequest(ProxyMessage msg); + + /** + This method is called when datagram is received by the server. +

+ Implementaions should decide wether it should be forwarded or dropped. + It is expecteed that implementation will use datagram address and port + information to make a decision, as well as anything else. Address and + port of the datagram are always correspond to remote machine. It is + either destination or source address. If out is true address is destination + address, else it is a source address, address of the machine from which + datagram have been received for the client. +

+ Implementaions should return true if the datagram is to be forwarded, and + false if the datagram should be dropped. +

+ This method is called on the object returned from the startSession + function. + + @param out If true the datagram is being send out(from the client), + otherwise it is an incoming datagram. + @return True to forward datagram false drop it silently. + */ + boolean checkRequest(DatagramPacket dp, boolean out); + + /** + This method is called when session is completed. Either due to normal + termination or due to any error condition. +

+ This method is called on the object returned from the startSession + function. + */ + void endSession(); +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/net/sourceforge/jsocks/server/ServerAuthenticatorNone.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/sourceforge/jsocks/server/ServerAuthenticatorNone.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,170 @@ +package net.sourceforge.jsocks.server; +import net.sourceforge.jsocks.ProxyMessage; +import net.sourceforge.jsocks.UDPEncapsulation; + +import java.io.IOException; +import java.io.InputStream; +import java.io.DataInputStream; +import java.io.OutputStream; +import java.io.PushbackInputStream; +import java.net.Socket; + +/** + An implementation of ServerAuthenticator, which does not do + any authentication. +

+ Warning!!
Should not be +used on machines which are not behind the firewall. +

+It is only provided to make implementing other authentication schemes +easier.
+For Example:

+   class MyAuth extends socks.server.ServerAuthenticator{
+    ...
+    public ServerAuthenticator startSession(java.net.Socket s){
+      if(!checkHost(s.getInetAddress()) return null;
+      return super.startSession(s);
+    }
+
+    boolean checkHost(java.net.Inetaddress addr){
+      boolean allow;
+      //Do it somehow
+      return allow;
+    }
+   }
+
+*/ +public class ServerAuthenticatorNone implements ServerAuthenticator{ + + static final byte[] socks5response = {5,0}; + + InputStream in; + OutputStream out; + + /** + Creates new instance of the ServerAuthenticatorNone. + */ + public ServerAuthenticatorNone(){ + this.in = null; + this.out = null; + } + /** + Constructs new ServerAuthenticatorNone object suitable for returning + from the startSession function. + @param in Input stream to return from getInputStream method. + @param out Output stream to return from getOutputStream method. + */ + public ServerAuthenticatorNone(InputStream in, OutputStream out){ + this.in = in; + this.out = out; + } + /** + Grants access to everyone.Removes authentication related bytes from + the stream, when a SOCKS5 connection is being made, selects an + authentication NONE. + */ + public ServerAuthenticator startSession(Socket s) + throws IOException{ + + PushbackInputStream in = new PushbackInputStream(s.getInputStream()); + OutputStream out = s.getOutputStream(); + + int version = in.read(); + if(version == 5){ + if(!selectSocks5Authentication(in,out,0)) + return null; + }else if(version == 4){ + //Else it is the request message allready, version 4 + in.unread(version); + }else + return null; + + + return new ServerAuthenticatorNone(in,out); + } + + /** + Get input stream. + @return Input stream speciefied in the constructor. + */ + public InputStream getInputStream(){ + return in; + } + /** + Get output stream. + @return Output stream speciefied in the constructor. + */ + public OutputStream getOutputStream(){ + return out; + } + /** + Allways returns null. + @return null + */ + public UDPEncapsulation getUdpEncapsulation(){ + return null; + } + + /** + Allways returns true. + */ + public boolean checkRequest(ProxyMessage msg){ + return true; + } + + /** + Allways returns true. + */ + public boolean checkRequest(java.net.DatagramPacket dp, boolean out){ + return true; + } + + /** + Does nothing. + */ + public void endSession(){ + } + + /** + Convinience routine for selecting SOCKSv5 authentication. +

+ This method reads in authentication methods that client supports, + checks wether it supports given method. If it does, the notification + method is written back to client, that this method have been chosen + for authentication. If given method was not found, authentication + failure message is send to client ([5,FF]). + @param in Input stream, version byte should be removed from the stream + before calling this method. + @param out Output stream. + @param methodId Method which should be selected. + @return true if methodId was found, false otherwise. + */ + static public boolean selectSocks5Authentication(InputStream in, + OutputStream out, + int methodId) + throws IOException{ + + int num_methods = in.read(); + if (num_methods <= 0) return false; + byte method_ids[] = new byte[num_methods]; + byte response[] = new byte[2]; + boolean found = false; + + response[0] = (byte) 5; //SOCKS version + response[1] = (byte) 0xFF; //Not found, we are pessimistic + + int bread = 0; //bytes read so far + while(bread < num_methods) + bread += in.read(method_ids,bread,num_methods-bread); + + for(int i=0;i= rem ? rem : cbRemaining; + byte[] bArr = bb.array(); + char[] cArr = cb.array(); + int bStart = bb.position(); + int cStart = cb.position(); + int i; + + for (i = bStart; i < bStart + rem; i++) { + char in = (char)(bArr[i] & 0xFF); + + if (in >= 26) { + int index = (int)in - 26; + cArr[cStart++] = (char)arr[index]; + } + else { + cArr[cStart++] = (char)(in & 0xFF); + } + } + + bb.position(i); + cb.position(cStart); + + if (rem == cbRemaining && bb.hasRemaining()) return CoderResult.OVERFLOW; + } + else { + while (bb.hasRemaining()) { + if (cbRemaining == 0) return CoderResult.OVERFLOW; + + char in = (char)(bb.get() & 0xFF); + + if (in >= 26) { + int index = (int)in - 26; + cb.put(arr[index]); + } + else { + cb.put((char)(in & 0xFF)); + } + + cbRemaining--; + } + + /* + } + */ + } + + return CoderResult.UNDERFLOW; + } + + final static char[] arr = { + 0x001C, 0x001B, 0x007F, 0x001D, 0x001E, 0x001F, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x001A, + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, + 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, + 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, + 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, + 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, + 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, + 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x03BC, 0x03C4, + 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, + 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 + }; + } + + private static final class Encoder extends CharsetEncoder { + private Encoder(Charset cs) { + super(cs, 1, 1); + } + + private native void nEncode(long outAddr, int absolutePos, char[] array, int arrPosition, int[] res); + + protected CoderResult encodeLoop(CharBuffer cb, ByteBuffer bb) { + int bbRemaining = bb.remaining(); + + /* TODO: support direct byte buffers + if(CharsetProviderImpl.hasLoadedNatives() && bb.isDirect() && cb.hasRemaining() && cb.hasArray()){ + int toProceed = cb.remaining(); + int cbPos = cb.position(); + int bbPos = bb.position(); + boolean throwOverflow = false; + if( bbRemaining < toProceed ) { + toProceed = bbRemaining; + throwOverflow = true; + } + int[] res = {toProceed, 0}; + nEncode(AddressUtil.getDirectBufferAddress(bb), bbPos, cb.array(), cb.arrayOffset()+cbPos, res); + if( res[0] <= 0 ) { + bb.position(bbPos-res[0]); + cb.position(cbPos-res[0]); + if(res[1]!=0) { + if(res[1] < 0) + return CoderResult.malformedForLength(-res[1]); + else + return CoderResult.unmappableForLength(res[1]); + } + }else{ + bb.position(bbPos+res[0]); + cb.position(cbPos+res[0]); + if(throwOverflow) return CoderResult.OVERFLOW; + } + }else{ + */ + if (bb.hasArray() && cb.hasArray()) { + byte[] byteArr = bb.array(); + char[] charArr = cb.array(); + int rem = cb.remaining(); + int byteArrStart = bb.position(); + rem = bbRemaining <= rem ? bbRemaining : rem; + int x; + + for (x = cb.position(); x < cb.position() + rem; x++) { + char c = charArr[x]; + + if (c > (char)0x25A0) { + if (c >= 0xD800 && c <= 0xDFFF) { + if (x + 1 < cb.limit()) { + char c1 = charArr[x + 1]; + + if (c1 >= 0xD800 && c1 <= 0xDFFF) { + cb.position(x); bb.position(byteArrStart); + return CoderResult.unmappableForLength(2); + } + } + else { + cb.position(x); bb.position(byteArrStart); + return CoderResult.UNDERFLOW; + } + + cb.position(x); bb.position(byteArrStart); + return CoderResult.malformedForLength(1); + } + + cb.position(x); bb.position(byteArrStart); + return CoderResult.unmappableForLength(1); + } + else { + if (c < 0x1A) { + byteArr[byteArrStart++] = (byte)c; + } + else { + int index = (int)c >> 8; + index = encodeIndex[index]; + + if (index < 0) { + cb.position(x); bb.position(byteArrStart); + return CoderResult.unmappableForLength(1); + } + + index <<= 8; + index += (int)c & 0xFF; + + if ((byte)arr[index] != 0) { + byteArr[byteArrStart++] = (byte)arr[index]; + } + else { + cb.position(x); bb.position(byteArrStart); + return CoderResult.unmappableForLength(1); + } + } + } + } + + cb.position(x); + bb.position(byteArrStart); + + if (rem == bbRemaining && cb.hasRemaining()) { + return CoderResult.OVERFLOW; + } + } + else { + while (cb.hasRemaining()) { + if (bbRemaining == 0) return CoderResult.OVERFLOW; + + char c = cb.get(); + + if (c > (char)0x25A0) { + if (c >= 0xD800 && c <= 0xDFFF) { + if (cb.hasRemaining()) { + char c1 = cb.get(); + + if (c1 >= 0xD800 && c1 <= 0xDFFF) { + cb.position(cb.position() - 2); + return CoderResult.unmappableForLength(2); + } + else { + cb.position(cb.position() - 1); + } + } + else { + cb.position(cb.position() - 1); + return CoderResult.UNDERFLOW; + } + + cb.position(cb.position() - 1); + return CoderResult.malformedForLength(1); + } + + cb.position(cb.position() - 1); + return CoderResult.unmappableForLength(1); + } + else { + if (c < 0x1A) { + bb.put((byte)c); + } + else { + int index = (int)c >> 8; + index = encodeIndex[index]; + + if (index < 0) { + cb.position(cb.position() - 1); + return CoderResult.unmappableForLength(1); + } + + index <<= 8; + index += (int)c & 0xFF; + + if ((byte)arr[index] != 0) { + bb.put((byte)arr[index]); + } + else { + cb.position(cb.position() - 1); + return CoderResult.unmappableForLength(1); + } + } + + bbRemaining--; + } + } + + /* TODO: support direct byte buffers + } + */ + } + + return CoderResult.UNDERFLOW; + } + + final static char arr[] = { + + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x7F, 0x1B, 0x1A, 0x1D, 0x1E, 0x1F, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x1C, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xAD, 0x9B, 0x9C, 0x00, 0x9D, 0x00, 0x00, 0x00, 0x00, 0xA6, 0xAE, 0xAA, 0x00, 0x00, 0x00, + 0xF8, 0xF1, 0xFD, 0x00, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xA7, 0xAF, 0xAC, 0xAB, 0x00, 0xA8, + 0x00, 0x00, 0x00, 0x00, 0x8E, 0x8F, 0x92, 0x80, 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xA5, 0x00, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9A, 0x00, 0x00, 0xE1, + 0x85, 0xA0, 0x83, 0x00, 0x84, 0x86, 0x91, 0x87, 0x8A, 0x82, 0x88, 0x89, 0x8D, 0xA1, 0x8C, 0x8B, + 0x00, 0xA4, 0x95, 0xA2, 0x93, 0x00, 0x94, 0xF6, 0x00, 0x97, 0xA3, 0x96, 0x81, 0x00, 0x00, 0x98, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x9F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xE2, 0x00, 0x00, 0x00, 0x00, 0xE9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xE4, 0x00, 0x00, 0xE8, 0x00, 0x00, 0xEA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xE0, 0x00, 0x00, 0xEB, 0xEE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE6, 0x00, 0x00, 0x00, + 0xE3, 0x00, 0x00, 0xE5, 0xE7, 0x00, 0xED, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF9, 0xFB, 0x00, 0x00, 0x00, 0xEC, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xF0, 0x00, 0x00, 0xF3, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF4, 0xF5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0xC4, 0x00, 0xB3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDA, 0x00, 0x00, 0x00, + 0xBF, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xD9, 0x00, 0x00, 0x00, 0xC3, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC2, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xC1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC5, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xCD, 0xBA, 0xD5, 0xD6, 0xC9, 0xB8, 0xB7, 0xBB, 0xD4, 0xD3, 0xC8, 0xBE, 0xBD, 0xBC, 0xC6, 0xC7, + 0xCC, 0xB5, 0xB6, 0xB9, 0xD1, 0xD2, 0xCB, 0xCF, 0xD0, 0xCA, 0xD8, 0xD7, 0xCE, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xDF, 0x00, 0x00, 0x00, 0xDC, 0x00, 0x00, 0x00, 0xDB, 0x00, 0x00, 0x00, 0xDD, 0x00, 0x00, 0x00, + 0xDE, 0xB0, 0xB1, 0xB2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + final static int[] encodeIndex = { + 0, 1, -1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 3, -1, 4, 5, -1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + }; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/keyczar/jce/EcCore.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/keyczar/jce/EcCore.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,678 @@ +/* + * Copyright 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package org.keyczar.jce; + +import java.math.BigInteger; +import java.security.spec.ECFieldFp; +import java.security.spec.ECParameterSpec; + +/** + * This class implements the basic EC operations such as point addition and + * doubling and point multiplication. Only NSA Suite B / NIST curves are + * supported. + * + * Todo: + * - Add (more) comments - Performance optimizations - Cleanup ASN.1 code, + * possibly replace with own impl - ... + * + * References: + * + * [1] Software Implementation of the NIST Elliptic Curves Over Prime Fields, M. + * Brown et al. [2] Efficient elliptic curve exponentiation using mixed + * coordinates, H. Cohen et al. [3] SEC 1: Elliptic Curve Cryptography. [4] + * Guide to Elliptic Curve Cryptography, D. Hankerson et al., Springer. + * + * @author martclau@gmail.com + * + */ +// BEGIN connectbot-changed +public final class EcCore { +// END connectbot-changed +// BEGIN connectbot-removed +// private static final long serialVersionUID = -1376116429660095993L; +// +// private static final String INFO = "Google Keyczar (EC key/parameter generation; EC signing)"; +// +// public static final String NAME = "GooKey"; +// +// @SuppressWarnings("unchecked") +// public EcCore() { +// super(NAME, 0.1, INFO); +// AccessController.doPrivileged(new PrivilegedAction() { +// @Override +// public Object run() { +// put("Signature.SHA1withECDSA", "org.keyczar.jce.EcSignatureImpl$SHA1"); +// put("Alg.Alias.Signature.ECDSA", "SHA1withDSA"); +// put("Signature.SHA256withECDSA", +// "org.keyczar.jce.EcSignatureImpl$SHA256"); +// put("Signature.SHA384withECDSA", +// "org.keyczar.jce.EcSignatureImpl$SHA384"); +// put("Signature.SHA512withECDSA", +// "org.keyczar.jce.EcSignatureImpl$SHA512"); +// put("KeyPairGenerator.EC", "org.keyczar.jce.EcKeyPairGeneratorImpl"); +// put("KeyFactory.EC", "org.keyczar.jce.EcKeyFactoryImpl"); +// put("Signature.SHA1withECDSA KeySize", "521"); +// put("Signature.SHA1withECDSA ImplementedIn", "Software"); +// put("Signature.SHA256withECDSA KeySize", "521"); +// put("Signature.SHA256withECDSA ImplementedIn", "Software"); +// put("Signature.SHA384withECDSA KeySize", "521"); +// put("Signature.SHA384withECDSA ImplementedIn", "Software"); +// put("Signature.SHA512withECDSA KeySize", "521"); +// put("Signature.SHA512withECDSA ImplementedIn", "Software"); +// put("KeyPairGenerator.EC ImplementedIn", "Software"); +// put("KeyFactory.EC ImplementedIn", "Software"); +// return null; +// } +// }); +// } +// +// private static final ECParameterSpec P192 = new ECParameterSpec( +// new EllipticCurve( +// new ECFieldFp(new BigInteger( +// "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF", 16)), +// new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFC", 16), +// new BigInteger("64210519E59C80E70FA7E9AB72243049FEB8DEECC146B9B1", 16)), +// new ECPoint( +// new BigInteger("188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF1012", 16), +// new BigInteger("07192B95FFC8DA78631011ED6B24CDD573F977A11E794811", 16)), +// new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831", 16), 1); +// +// private static final ECParameterSpec P224 = new ECParameterSpec( +// new EllipticCurve(new ECFieldFp(new BigInteger( +// "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000001", 16)), +// new BigInteger( +// "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFE", 16), +// new BigInteger( +// "B4050A850C04B3ABF54132565044B0B7D7BFD8BA270B39432355FFB4", 16)), +// new ECPoint(new BigInteger( +// "B70E0CBD6BB4BF7F321390B94A03C1D356C21122343280D6115C1D21", 16), +// new BigInteger( +// "BD376388B5F723FB4C22DFE6CD4375A05A07476444D5819985007E34", 16)), +// new BigInteger( +// "FFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D", 16), 1); +// +// private static final ECParameterSpec P256 = new ECParameterSpec( +// new EllipticCurve(new ECFieldFp(new BigInteger( +// "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", +// 16)), new BigInteger( +// "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", +// 16), new BigInteger( +// "5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", +// 16)), new ECPoint(new BigInteger( +// "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", +// 16), new BigInteger( +// "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", +// 16)), new BigInteger( +// "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", +// 16), 1); +// +// private static final ECParameterSpec P384 = new ECParameterSpec( +// new EllipticCurve( +// new ECFieldFp( +// new BigInteger( +// "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF", +// 16)), +// new BigInteger( +// "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC", +// 16), +// new BigInteger( +// "B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF", +// 16)), +// new ECPoint( +// new BigInteger( +// "AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7", +// 16), +// new BigInteger( +// "3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F", +// 16)), +// new BigInteger( +// "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973", +// 16), 1); +// +// private static final ECParameterSpec P521 = new ECParameterSpec( +// new EllipticCurve( +// new ECFieldFp( +// new BigInteger( +// "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", +// 16)), +// new BigInteger( +// "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC", +// 16), +// new BigInteger( +// "0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00", +// 16)), +// new ECPoint( +// new BigInteger( +// "00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66", +// 16), +// new BigInteger( +// "011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650", +// 16)), +// new BigInteger( +// "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", +// 16), 1); +// +// public static final String EC_PARAMS_P192_OID = "1.2.840.10045.3.1.1"; +// public static final String EC_PARAMS_P224_OID = "1.3.132.0.33"; +// public static final String EC_PARAMS_P256_OID = "1.2.840.10045.3.1.7"; +// public static final String EC_PARAMS_P384_OID = "1.3.132.0.34"; +// public static final String EC_PARAMS_P521_OID = "1.3.132.0.35"; +// +// private static Map oidMap = new HashMap(); +// private static Map paramsMap = new HashMap(); +// private static Map friendlyNameMap = new HashMap(); +// +// static { +// oidMap.put(EC_PARAMS_P192_OID, P192); +// oidMap.put(EC_PARAMS_P224_OID, P224); +// oidMap.put(EC_PARAMS_P256_OID, P256); +// oidMap.put(EC_PARAMS_P384_OID, P384); +// oidMap.put(EC_PARAMS_P521_OID, P521); +// paramsMap.put(P192, EC_PARAMS_P192_OID); +// paramsMap.put(P224, EC_PARAMS_P224_OID); +// paramsMap.put(P256, EC_PARAMS_P256_OID); +// paramsMap.put(P384, EC_PARAMS_P384_OID); +// paramsMap.put(P521, EC_PARAMS_P521_OID); +// friendlyNameMap.put(P192, "P-192"); +// friendlyNameMap.put(P224, "P-224"); +// friendlyNameMap.put(P256, "P-256"); +// friendlyNameMap.put(P384, "P-384"); +// friendlyNameMap.put(P521, "P-521"); +// } +// +// public static ECParameterSpec getParams(String oid) { +// ECParameterSpec params; +// if ((params = oidMap.get(oid)) != null) return params; +// throw new IllegalArgumentException("Unsupported EC parameters: " + oid); +// } +// +// public static String getOID(ECParameterSpec params) { +// String oid; +// if ((oid = paramsMap.get(params)) != null) return oid; +// throw new IllegalArgumentException("Unsupport EC parameters"); +// } +// +// public static String getFriendlyName(ECParameterSpec params) { +// String name; +// if ((name = friendlyNameMap.get(params)) != null) return name; +// throw new IllegalArgumentException("Unsupport EC parameters"); +// } +// +// private static final BigInteger ZERO = BigInteger.ZERO; +// private static final BigInteger ONE = BigInteger.ONE; +// private static final BigInteger TWO = BigInteger.valueOf(2); +// END connectbot-removed + private static final BigInteger THREE = BigInteger.valueOf(3); +// BEGIN connectbot-removed +// private static final BigInteger FOUR = BigInteger.valueOf(4); +// private static final BigInteger EIGHT = BigInteger.valueOf(8); +// END connectbot-removed + + private static BigInteger[] doublePointA(BigInteger[] P, + ECParameterSpec params) { + final BigInteger p = ((ECFieldFp) params.getCurve().getField()).getP(); + final BigInteger a = params.getCurve().getA(); + + if (P[0] == null || P[1] == null) return P; + + BigInteger d = (P[0].pow(2).multiply(THREE).add(a)).multiply(P[1] + .shiftLeft(1).modInverse(p)); + BigInteger[] R = new BigInteger[2]; + R[0] = d.pow(2).subtract(P[0].shiftLeft(1)).mod(p); + R[1] = d.multiply(P[0].subtract(R[0])).subtract(P[1]).mod(p); + return R; + } + + private static BigInteger[] addPointsA(BigInteger[] P1, BigInteger[] P2, + ECParameterSpec params) { + final BigInteger p = ((ECFieldFp) params.getCurve().getField()).getP(); + + if (P2[0] == null || P2[1] == null) return P1; + + if (P1[0] == null || P1[1] == null) return P2; + + BigInteger d = (P2[1].subtract(P1[1])).multiply((P2[0].subtract(P1[0])) + .modInverse(p)); + BigInteger[] R = new BigInteger[2]; + R[0] = d.pow(2).subtract(P1[0]).subtract(P2[0]).mod(p); + R[1] = d.multiply(P1[0].subtract(R[0])).subtract(P1[1]).mod(p); + return R; + } + + public static BigInteger[] multiplyPointA(BigInteger[] P, BigInteger k, + ECParameterSpec params) { + BigInteger[] Q = new BigInteger[] {null, null}; + + for (int i = k.bitLength() - 1; i >= 0; i--) { + Q = doublePointA(Q, params); + + if (k.testBit(i)) Q = addPointsA(Q, P, params); + } + + return Q; + } + +// BEGIN connectbot-removed +// private static BigInteger[] doublePointJ(BigInteger[] P, +// ECParameterSpec params) { +// final BigInteger p = ((ECFieldFp) params.getCurve().getField()).getP(); +// BigInteger A, B, C, D; +// +// if (P[2].signum() == 0) // point at inf +// return P; +// +// A = FOUR.multiply(P[0]).multiply(P[1].pow(2)).mod(p); +// B = EIGHT.multiply(P[1].pow(4)).mod(p); +// C = THREE.multiply(P[0].subtract(P[2].pow(2))).multiply( +// P[0].add(P[2].pow(2))).mod(p); +// D = C.pow(2).subtract(A.add(A)).mod(p); +// +// return new BigInteger[] { +// D, C.multiply(A.subtract(D)).subtract(B).mod(p), +// TWO.multiply(P[1]).multiply(P[2]).mod(p)}; +// } +// +// private static BigInteger[] addPointsJA(BigInteger[] P1, BigInteger[] P2, +// ECParameterSpec params) { +// final BigInteger p = ((ECFieldFp) params.getCurve().getField()).getP(); +// BigInteger A, B, C, D; +// BigInteger X3; +// +// if (P1[2].signum() == 0) // point at inf +// return new BigInteger[] {P2[0], P2[1], ONE}; +// +// A = P2[0].multiply(P1[2].pow(2)).mod(p); +// B = P2[1].multiply(P1[2].pow(3)).mod(p); +// C = A.subtract(P1[0]).mod(p); +// D = B.subtract(P1[1]).mod(p); +// +// X3 = D.pow(2) +// .subtract(C.pow(3).add(TWO.multiply(P1[0]).multiply(C.pow(2)))).mod(p); +// return new BigInteger[] { +// X3, +// D.multiply(P1[0].multiply(C.pow(2)).subtract(X3)).subtract( +// P1[1].multiply(C.pow(3))).mod(p), P1[2].multiply(C).mod(p)}; +// } +// +// // Binary NAF method for point multiplication +// public static BigInteger[] multiplyPoint(BigInteger[] P, BigInteger k, +// ECParameterSpec params) { +// BigInteger h = THREE.multiply(k); +// +// BigInteger[] Pneg = new BigInteger[] {P[0], P[1].negate()}; +// BigInteger[] R = new BigInteger[] {P[0], P[1], ONE}; +// +// int bitLen = h.bitLength(); +// for (int i = bitLen - 2; i > 0; --i) { +// R = doublePointJ(R, params); +// if (h.testBit(i)) R = addPointsJA(R, P, params); +// if (k.testBit(i)) R = addPointsJA(R, Pneg, params); +// } +// +// // // +// // BigInteger[] SS = new BigInteger[] { R[0], R[1], R[2] }; +// // toAffine(SS, params); +// // BigInteger[] RR = multiplyPointA(P, k, params); +// // if (!SS[0].equals(RR[0]) || !SS[1].equals(RR[1])) +// // throw new RuntimeException("Internal mult error"); +// // // +// +// return R; +// } + +// // Simultaneous multiple point multiplication, also known as Shamir's trick +// static BigInteger[] multiplyPoints(BigInteger[] P, BigInteger k, +// BigInteger[] Q, BigInteger l, ECParameterSpec params) { +// BigInteger[] PQ = addPointsA(P, Q, params); +// BigInteger[] R = new BigInteger[] {null, null, ZERO}; +// +// int max = Math.max(k.bitLength(), l.bitLength()); +// for (int i = max - 1; i >= 0; --i) { +// R = doublePointJ(R, params); +// if (k.testBit(i)) { +// if (l.testBit(i)) +// R = addPointsJA(R, PQ, params); +// else +// R = addPointsJA(R, P, params); +// } else if (l.testBit(i)) R = addPointsJA(R, Q, params); +// } +// +// // // +// // BigInteger[] SS = new BigInteger[] { R[0], R[1], R[2] }; +// // toAffine(SS, params); +// // BigInteger[] AA = multiplyPointA(P, k, params); +// // BigInteger[] BB = multiplyPointA(Q, l, params); +// // BigInteger[] AB = addPointsA(AA, BB, params); +// // if (!SS[0].equals(AB[0]) || !SS[1].equals(AB[1])) +// // throw new RuntimeException("Internal mult error"); +// // // +// +// return R; +// } +// +// // SEC 1, 2.3.5 +// static byte[] fieldElemToBytes(BigInteger a, ECParameterSpec params) { +// int len = (((ECFieldFp) params.getCurve().getField()).getP().bitLength() + 7) / 8; +// byte[] bytes = a.toByteArray(); +// if (len < bytes.length) { +// byte[] tmp = new byte[len]; +// System.arraycopy(bytes, bytes.length - tmp.length, tmp, 0, tmp.length); +// return tmp; +// } else if (len > bytes.length) { +// byte[] tmp = new byte[len]; +// System.arraycopy(bytes, 0, tmp, tmp.length - bytes.length, bytes.length); +// return tmp; +// } +// return bytes; +// } +// +// static int fieldElemToBytes(BigInteger a, ECParameterSpec params, +// byte[] data, int off) { +// int len = (((ECFieldFp) params.getCurve().getField()).getP().bitLength() + 7) / 8; +// byte[] bytes = a.toByteArray(); +// if (len < bytes.length) { +// System.arraycopy(bytes, bytes.length - len, data, off, len); +// return len; +// } else if (len > bytes.length) { +// System.arraycopy(bytes, 0, data, len - bytes.length + off, bytes.length); +// return len; +// } +// System.arraycopy(bytes, 0, data, off, bytes.length); +// return bytes.length; +// } +// +// // SEC 1, 2.3.3 +// static byte[] ecPointToBytes(ECPoint a, ECParameterSpec params) { +// byte[] fe1 = fieldElemToBytes(a.getAffineX(), params); +// byte[] fe2 = fieldElemToBytes(a.getAffineY(), params); +// byte[] bytes = new byte[1 + fe1.length + fe2.length]; +// bytes[0] = 0x04; +// System.arraycopy(fe1, 0, bytes, 1, fe1.length); +// System.arraycopy(fe2, 0, bytes, 1 + fe1.length, fe2.length); +// return bytes; +// } +// +// // SEC 1, 2.3.4 +// static ECPoint bytesToECPoint(byte[] bytes, ECParameterSpec params) { +// switch (bytes[0]) { +// case 0x00: // point at inf +// throw new IllegalArgumentException( +// "Point at infinity is not a valid argument"); +// case 0x02: // point compression +// case 0x03: +// throw new UnsupportedOperationException( +// "Point compression is not supported"); +// case 0x04: +// final BigInteger p = ((ECFieldFp) params.getCurve().getField()).getP(); +// byte[] fe = new byte[(p.bitLength() + 7) / 8]; +// System.arraycopy(bytes, 1, fe, 0, fe.length); +// BigInteger x = new BigInteger(1, fe); +// System.arraycopy(bytes, 1 + fe.length, fe, 0, fe.length); +// return new ECPoint(x, new BigInteger(1, fe)); +// default: +// throw new IllegalArgumentException("Invalid point encoding"); +// } +// } +// +// // Convert Jacobian point to affine +// static void toAffine(BigInteger[] P, ECParameterSpec params) { +// final BigInteger p = ((ECFieldFp) params.getCurve().getField()).getP(); +// P[0] = P[0].multiply(P[2].pow(2).modInverse(p)).mod(p); +// P[1] = P[1].multiply(P[2].pow(3).modInverse(p)).mod(p); +// } +// +// static void toAffineX(BigInteger[] P, ECParameterSpec params) { +// final BigInteger p = ((ECFieldFp) params.getCurve().getField()).getP(); +// P[0] = P[0].multiply(P[2].pow(2).modInverse(p)).mod(p); +// } +// +// static BigInteger[] internalPoint(ECPoint P) { +// return new BigInteger[] {P.getAffineX(), P.getAffineY()}; +// } +// +// // private static void printPerf(String msg, long start, long stop) { +// // String unit = "ms"; +// // long diff = stop - start; +// // if (diff > 1000) { +// // diff /= 1000; +// // unit = "s"; +// // } +// // System.out.printf("%s: %d %s\n", msg, diff, unit); +// // } +// +// public static void main(String[] args) throws Exception { +// +// Security.insertProviderAt(new EcCore(), 0); +// +// // ---- +// // Test primitives +// // ---- +// +// // GooKey EC private key, 256 bit +// // Private value: +// // a9231e0d113abdacd3bb5edb24124fbef6f562c5f90b835670f5e48f775019f2 +// // Parameters: P-256 (1.2.840.10045.3.1.7) +// // GooKey EC public key, 256 bit +// // Public value (x coordinate): +// // 86645e0320c0f9dc1a9b8456396cc105754df67a9829c21e13ab6ecf944cf68c +// // Public value (y coordinate): +// // ea1721a578043d48f12738359b5eb5f0dac2242ec6128ee0ab6ff40c8fe0cae6 +// // Parameters: P-256 (1.2.840.10045.3.1.7) +// // GooKey EC private key, 256 bit +// // Private value: +// // b84d5cfab214fc3928864abb85f668a85b1006ca0147c78f22deb1dcc7e4a022 +// // Parameters: P-256 (1.2.840.10045.3.1.7) +// // GooKey EC public key, 256 bit +// // Public value (x coordinate): +// // 61f6f7264f0a19f0debcca3efd079667a0112cc0b8be07a815b4c375e96ad3d1 +// // Public value (y coordinate): +// // 3308c0016d776ed5aa9f021e43348b2e684b3b7a0f25dc9e4c8670b5d87cb705 +// // Parameters: P-256 (1.2.840.10045.3.1.7) +// +// // P = kG +// BigInteger k = new BigInteger( +// "a9231e0d113abdacd3bb5edb24124fbef6f562c5f90b835670f5e48f775019f2", 16); +// BigInteger[] P = new BigInteger[] { +// new BigInteger( +// "86645e0320c0f9dc1a9b8456396cc105754df67a9829c21e13ab6ecf944cf68c", +// 16), +// new BigInteger( +// "ea1721a578043d48f12738359b5eb5f0dac2242ec6128ee0ab6ff40c8fe0cae6", +// 16), ONE}; +// +// // Q = lG +// BigInteger l = new BigInteger( +// "b84d5cfab214fc3928864abb85f668a85b1006ca0147c78f22deb1dcc7e4a022", 16); +// BigInteger[] Q = new BigInteger[] { +// new BigInteger( +// "61f6f7264f0a19f0debcca3efd079667a0112cc0b8be07a815b4c375e96ad3d1", +// 16), +// new BigInteger( +// "3308c0016d776ed5aa9f021e43348b2e684b3b7a0f25dc9e4c8670b5d87cb705", +// 16), ONE}; +// +// // Known answer for P+Q +// BigInteger[] kat1 = new BigInteger[] { +// new BigInteger( +// "bc7adb05bca2460bbfeb4e0f88b61c384ea88ed3fd56017938ac2582513d4220", +// 16), +// new BigInteger( +// "a640a43df2e9df39eec11445b7e3f7835b743ef1ac4a83cecb570a060b3f1c6c", +// 16)}; +// +// BigInteger[] R = addPointsA(P, Q, P256); +// if (!R[0].equals(kat1[0]) || !R[1].equals(kat1[1])) +// throw new RuntimeException("kat1 failed"); +// +// R = addPointsJA(P, Q, P256); +// toAffine(R, P256); +// if (!R[0].equals(kat1[0]) || !R[1].equals(kat1[1])) +// throw new RuntimeException("kat1 failed"); +// +// +// // Known answer for Q+Q +// BigInteger[] kat2 = new BigInteger[] { +// new BigInteger( +// "c79d7f9100c14a70f0bb9bdce59654abf99e10d1ac5afc1a0f1b6bc650d6429b", +// 16), +// new BigInteger( +// "6856814e47adce42bc0d7c3bef308c6c737c418ed093effb31e21f53c7735c97", +// 16)}; +// +// R = doublePointA(P, P256); +// if (!R[0].equals(kat2[0]) || !R[1].equals(kat2[1])) +// throw new RuntimeException("kat2 failed"); +// +// R = doublePointJ(P, P256); +// toAffine(R, P256); +// if (!R[0].equals(kat2[0]) || !R[1].equals(kat2[1])) +// throw new RuntimeException("kat2 failed"); +// +// // Known answer for kP +// BigInteger[] kat3 = new BigInteger[] { +// new BigInteger( +// "97a82a834b9e6b50660ae30d43dac9b200276e8bcd2ed6a6593048de09276d1a", +// 16), +// new BigInteger( +// "30a9590a01066d8ef54a910afcc8648dbc7400c01750af423ce95547f2154d56", +// 16)}; +// +// R = multiplyPointA(P, k, P256); +// if (!R[0].equals(kat3[0]) || !R[1].equals(kat3[1])) +// throw new RuntimeException("kat3 failed"); +// +// R = multiplyPoint(P, k, P256); +// toAffine(R, P256); +// if (!R[0].equals(kat3[0]) || !R[1].equals(kat3[1])) +// throw new RuntimeException("kat3 failed"); +// +// // Known answer for kP+lQ +// BigInteger[] kat4 = new BigInteger[] { +// new BigInteger( +// "6fd51be5cf3d6a6bcb62594bbe41ccf549b37d8fefff6e293a5bea0836efcfc6", +// 16), +// new BigInteger( +// "9bc21a930137aa3814908974c431e4545a05dce61321253c337f3883129c42ca", +// 16)}; +// +// BigInteger[] RR = multiplyPointA(Q, l, P256); +// R = addPointsA(R, RR, P256); +// if (!R[0].equals(kat4[0]) || !R[1].equals(kat4[1])) +// throw new RuntimeException("kat4 failed"); +// +// R = multiplyPoints(P, k, Q, l, P256); +// toAffine(R, P256); +// if (!R[0].equals(kat4[0]) || !R[1].equals(kat4[1])) +// throw new RuntimeException("kat4 failed"); +// +// // ---- +// // Test ECDSA in various combinations +// // ---- +// +// Provider gooProv = Security.getProvider("GooKey"); +// Provider nssProv = Security.getProvider("SunPKCS11-NSS"); +// +// // Number of iterations: trust me, this is a (stress) good test +// // and does provoke bugs in a fuzzing way. +// int iter = 50; +// +// // Iterate over all key lengths and signature schemes. +// int[] keyLengths = {192, 224, 256, 384, 521}; +// String[] ecdsas = { +// "SHA1withECDSA", "SHA256withECDSA", "SHA384withECDSA", +// "SHA512withECDSA"}; +// for (int s = 0; s < ecdsas.length; s++) { +// System.out.println("Signature scheme " + ecdsas[s]); +// for (int i = 0; i < keyLengths.length; i++) { +// System.out.print("Testing P-" + keyLengths[i] + ": "); +// for (int n = 0; n < iter; n++) { +// System.out.print("."); +// +// KeyPairGenerator kpGen = KeyPairGenerator.getInstance("EC", gooProv); +// kpGen.initialize(keyLengths[i]); +// KeyPair ecKeyPair = kpGen.generateKeyPair(); +// +// ECPrivateKey ecPrivKey = (ECPrivateKey) ecKeyPair.getPrivate(); +// byte[] tmp = ecPrivKey.getEncoded(); +// KeyFactory keyFab = KeyFactory.getInstance("EC", gooProv); +// keyFab.generatePrivate(new PKCS8EncodedKeySpec(tmp)); +// ECPrivateKeySpec ecPrivSpec = new ECPrivateKeySpec(ecPrivKey.getS(), +// ecPrivKey.getParams()); +// keyFab.generatePrivate(ecPrivSpec); +// +// ECPublicKey ecPubKey = (ECPublicKey) ecKeyPair.getPublic(); +// tmp = ecPubKey.getEncoded(); // dont modify tmp now - is used below +// keyFab.generatePublic(new X509EncodedKeySpec(tmp)); +// ECPublicKeySpec ecPubSpec = new ECPublicKeySpec(ecPubKey.getW(), +// ecPubKey.getParams()); +// keyFab.generatePublic(ecPubSpec); +// +// Signature ecdsa = Signature.getInstance(ecdsas[s], gooProv); +// ecdsa.initSign(ecPrivKey); +// ecdsa.update(tmp); +// byte[] sig = ecdsa.sign(); +// ecdsa.initVerify(ecPubKey); +// ecdsa.update(tmp); +// if (!ecdsa.verify(sig)) +// throw new RuntimeException("Signature not verified: " +// + keyLengths[i]); +// +// // Cross verify using NSS if present +// if (nssProv != null) { +// keyFab = KeyFactory.getInstance("EC", nssProv); +// +// // For some reason NSS doesnt seem to work for P-192 and P-224?! +// if (keyLengths[i] == 192 || keyLengths[i] == 224) continue; +// +// ECPrivateKey nssPrivKey = (ECPrivateKey) keyFab +// .generatePrivate(new PKCS8EncodedKeySpec(ecPrivKey.getEncoded())); +// ECPublicKey nssPubKey = (ECPublicKey) keyFab +// .generatePublic(new X509EncodedKeySpec(ecPubKey.getEncoded())); +// +// ecdsa = Signature.getInstance(ecdsas[s], nssProv); +// ecdsa.initVerify(nssPubKey); +// ecdsa.update(tmp); +// if (!ecdsa.verify(sig)) +// throw new RuntimeException("Signature not verified 2: " +// + keyLengths[i]); +// +// ecdsa.initSign(nssPrivKey); +// ecdsa.update(tmp); +// sig = ecdsa.sign(); +// ecdsa = Signature.getInstance(ecdsas[s], gooProv); +// ecdsa.initVerify(ecPubKey); +// ecdsa.update(tmp); +// if (!ecdsa.verify(sig)) +// throw new RuntimeException("Signature not verified 3: " +// + keyLengths[i]); +// } +// } +// System.out.println(" done"); +// } +// } +// +// // Test Keyczar integration +// // Signer ecdsaSigner = new Signer("c:\\temp\\eckeyset"); +// // String tbs = "Sign this"; +// // String sig = ecdsaSigner.sign(tbs); +// // if (ecdsaSigner.verify(sig, tbs)) +// // System.out.println("Keyczar EC OK"); +// // else +// // System.out.println("Keyczar EC not OK"); +// } +//END connectbot-removed +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/openintents/intents/FileManagerIntents.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/openintents/intents/FileManagerIntents.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2008 OpenIntents.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openintents.intents; + +// Version Dec 9, 2008 + + +/** + * Provides OpenIntents actions, extras, and categories used by providers. + *

These specifiers extend the standard Android specifiers.

+ */ +public final class FileManagerIntents { + + /** + * Activity Action: Pick a file through the file manager, or let user + * specify a custom file name. + * Data is the current file name or file name suggestion. + * Returns a new file name as file URI in data. + * + *

Constant Value: "org.openintents.action.PICK_FILE"

+ */ + public static final String ACTION_PICK_FILE = "org.openintents.action.PICK_FILE"; + + /** + * Activity Action: Pick a directory through the file manager, or let user + * specify a custom file name. + * Data is the current directory name or directory name suggestion. + * Returns a new directory name as file URI in data. + * + *

Constant Value: "org.openintents.action.PICK_DIRECTORY"

+ */ + public static final String ACTION_PICK_DIRECTORY = "org.openintents.action.PICK_DIRECTORY"; + + /** + * The title to display. + * + *

This is shown in the title bar of the file manager.

+ * + *

Constant Value: "org.openintents.extra.TITLE"

+ */ + public static final String EXTRA_TITLE = "org.openintents.extra.TITLE"; + + /** + * The text on the button to display. + * + *

Depending on the use, it makes sense to set this to "Open" or "Save".

+ * + *

Constant Value: "org.openintents.extra.BUTTON_TEXT"

+ */ + public static final String EXTRA_BUTTON_TEXT = "org.openintents.extra.BUTTON_TEXT"; + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/TN5250jConstants.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/TN5250jConstants.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,590 @@ +/* + * @(#)TN5250jConstants.java + * Copyright: Copyright (c) 2001 + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ + +package org.tn5250j; +import java.util.HashMap; + +public class TN5250jConstants { + + // Version information + public static final String tn5250jRelease = "0"; + public static final String tn5250jVersion = ".7"; + public static final String tn5250jSubVer = ".4"; + + public static final String VERSION_INFO = tn5250jRelease + tn5250jVersion + tn5250jSubVer; + + // STATE + static final int STATE_DISCONNECTED = 0; + static final int STATE_CONNECTED = 1; + static final int STATE_REMOVE = 2; + + // SESSION Level key value pairs + public static final String SESSION_HOST = "SESSION_HOST"; + public static final String SESSION_HOST_PORT = "SESSION_HOST_PORT"; + public static final String SESSION_CONFIG_RESOURCE = "SESSION_CONFIG_RESOURCE"; + public static final String SESSION_TYPE = "SESSION_HOST_TYPE"; + public static final String SESSION_TN_ENHANCED = "SESSION_TN_ENHANCED"; + public static final String SESSION_SCREEN_SIZE = "SESSION_SCREEN_SIZE"; + public static final String SESSION_CODE_PAGE = "SESSION_CODE_PAGE"; + public static final String SESSION_PROXY_HOST = "SESSION_PROXY_HOST"; + public static final String SESSION_PROXY_PORT = "SESSION_PROXY_PORT"; + public static final String SESSION_USE_GUI = "SESSION_USE_GUI"; + public static final String SESSION_DEVICE_NAME = "SESSION_DEVICE_NAME"; + public static final String SESSION_NAMES_REFS = "SESSION_NAMES_REFS"; + public static final String SESSION_LOCALE = "SESSION_LOCALE"; + public static final String SESSION_CONFIG_FILE = "SESSION_CONFIG_FILE"; + public static final String SESSION_TERM_NAME_SYSTEM = "SESSION_TERM_NAME_SYSTEM"; + public static final String SESSION_TERM_NAME = "SESSION_TERM_NAME"; + public static final String SESSION_IS_APPLET = "SESSION_IS_APPLET"; + public static final String SESSION_HEART_BEAT = "SESSION_KEEP_ALIVE_ENABLED"; + +// public static final String GUI_MDI_TYPE = "GUI_MDI_TYPE"; + public static final String GUI_FRAME_WIDTH = "GUI_FRAME_WIDTH"; + public static final String GUI_FRAME_HEIGHT = "GUI_FRAME_HEIGHT"; + public static final String GUI_NO_TAB = "GUI_NO_TAB"; + public static final String NO_CHECK_RUNNING = "NO_CHECK_RUNNING"; + public static final String START_MONITOR_THREAD = "START_MONITOR_THREAD"; + +// public static final String SSL_TYPE = "TN5250J_SSL_TYPE"; + public static final String SSL_TYPE = "-sslType"; + public static final String SSL_TYPE_NONE = "NONE"; + public static final String SSL_TYPE_SSLv2 = "SSLv2"; + public static final String SSL_TYPE_SSLv3 = "SSLv3"; + public static final String SSL_TYPE_TLS = "TLS"; + + public static final String[] SSL_TYPES = {SSL_TYPE_NONE, + SSL_TYPE_SSLv2, + SSL_TYPE_SSLv3, + SSL_TYPE_TLS + }; + + // Session JUMP Directions + static final int JUMP_PREVIOUS = 0; + static final int JUMP_NEXT = 1; + +// // OS_OHIO_SESSION_TYPE type of sessions +// public static final String OS_OHIO_SESSION_TYPE_5250_STR = "2"; + + // SCREEN_SIZE Size of screen string + public static final String SCREEN_SIZE_24X80_STR = "0"; + public static final String SCREEN_SIZE_27X132_STR = "1"; + + // SCREEN_SIZE Size of screen int + public static final int SCREEN_SIZE_24X80 = 0; + public static final int SCREEN_SIZE_27X132 = 1; + + public static final int NUM_PARMS = 20; + + // mnemonic value constants + public static final int BACK_SPACE = 1001; + public static final int BACK_TAB = 1002; + public static final int UP = 1003; + public static final int DOWN = 1004; + public static final int LEFT = 1005; + public static final int RIGHT = 1006; + public static final int DELETE = 1007; + public static final int TAB = 1008; + public static final int EOF = 1009; + public static final int ERASE_EOF = 1010; + public static final int ERASE_FIELD = 1011; + public static final int INSERT = 1012; + public static final int HOME = 1013; + public static final int KEYPAD_0 = 1014; + public static final int KEYPAD_1 = 1015; + public static final int KEYPAD_2 = 1016; + public static final int KEYPAD_3 = 1017; + public static final int KEYPAD_4 = 1018; + public static final int KEYPAD_5 = 1019; + public static final int KEYPAD_6 = 1020; + public static final int KEYPAD_7 = 1021; + public static final int KEYPAD_8 = 1022; + public static final int KEYPAD_9 = 1023; + public static final int KEYPAD_PERIOD = 1024; + public static final int KEYPAD_COMMA = 1025; + public static final int KEYPAD_MINUS = 1026; + public static final int FIELD_EXIT = 1027; + public static final int FIELD_PLUS = 1028; + public static final int FIELD_MINUS = 1029; + public static final int BOF = 1030; + public static final int SYSREQ = 1031; + public static final int RESET = 1032; + public static final int NEXTWORD = 1033; + public static final int PREVWORD = 1034; + public static final int COPY = 1035; + public static final int PASTE = 1036; + public static final int ATTN = 1037; + public static final int MARK_UP = 1038; + public static final int MARK_DOWN = 1039; + public static final int MARK_LEFT = 1040; + public static final int MARK_RIGHT = 1041; + public static final int DUP_FIELD = 1042; + public static final int NEW_LINE = 1043; + public static final int JUMP_NEXT_SESS = 5000; + public static final int JUMP_PREV_SESS = 5001; + public static final int OPEN_NEW = 5002; + public static final int TOGGLE_CONNECTION = 5003; + public static final int HOTSPOTS = 5004; + public static final int GUI = 5005; + public static final int DSP_MSGS = 5006; + public static final int DSP_ATTRIBUTES = 5007; + public static final int PRINT_SCREEN = 5008; + public static final int CURSOR = 5009; + public static final int DEBUG = 5010; + public static final int CLOSE = 5011; + public static final int TRANSFER = 5012; + public static final int E_MAIL = 5013; + public static final int RUN_SCRIPT = 5014; + public static final int SPOOL_FILE = 5015; + public static final int QUICK_MAIL = 5016; + public static final int OPEN_SAME = 5017; + public static final int FAST_CURSOR_DOWN = 5018; + public static final int FAST_CURSOR_UP = 5019; + public static final int FAST_CURSOR_RIGHT = 5020; + public static final int FAST_CURSOR_LEFT = 5021; + + // PF Keys + public static final int PF1 = 0x31; + public static final int PF2 = 0x32; + public static final int PF3 = 0x33; + public static final int PF4 = 0x34; + public static final int PF5 = 0x35; + public static final int PF6 = 0x36; + public static final int PF7 = 0x37; + public static final int PF8 = 0x38; + public static final int PF9 = 0x39; + public static final int PF10 = 0x3A; + public static final int PF11 = 0x3B; + public static final int PF12 = 0x3C; + public static final int PF13 = 0xB1; + public static final int PF14 = 0xB2; + public static final int PF15 = 0xB3; + public static final int PF16 = 0xB4; + public static final int PF17 = 0xB5; + public static final int PF18 = 0xB6; + public static final int PF19 = 0xB7; + public static final int PF20 = 0xB8; + public static final int PF21 = 0xB9; + public static final int PF22 = 0xBA; + public static final int PF23 = 0xBB; + public static final int PF24 = 0xBC; + + public static final HashMap mnemonicMap = new HashMap(); + static { + mnemonicMap.put("[backspace]", 1001); + mnemonicMap.put("[backtab]", 1002); + mnemonicMap.put("[up]", 1003); + mnemonicMap.put("[down]", 1004); + mnemonicMap.put("[left]", 1005); + mnemonicMap.put("[right]", 1006); + mnemonicMap.put("[delete]", 1007); + mnemonicMap.put("[tab]", 1008); + mnemonicMap.put("[eof]", 1009); + mnemonicMap.put("[eraseeof]", 1010); + mnemonicMap.put("[erasefld]", 1011); + mnemonicMap.put("[insert]", 1012); + mnemonicMap.put("[home]", 1013); + mnemonicMap.put("[keypad0]", 1014); + mnemonicMap.put("[keypad1]", 1015); + mnemonicMap.put("[keypad2]", 1016); + mnemonicMap.put("[keypad3]", 1017); + mnemonicMap.put("[keypad4]", 1018); + mnemonicMap.put("[keypad5]", 1019); + mnemonicMap.put("[keypad6]", 1020); + mnemonicMap.put("[keypad7]", 1021); + mnemonicMap.put("[keypad8]", 1022); + mnemonicMap.put("[keypad9]", 1023); + mnemonicMap.put("[keypad.]", 1024); + mnemonicMap.put("[keypad,]", 1025); + mnemonicMap.put("[keypad-]", 1026); + mnemonicMap.put("[fldext]", 1027); + mnemonicMap.put("[field+]", 1028); + mnemonicMap.put("[field-]", 1029); + mnemonicMap.put("[bof]", 1030); + mnemonicMap.put("[enter]", 0xF1); + mnemonicMap.put("[pf1]", 0x31); + mnemonicMap.put("[pf2]", 0x32); + mnemonicMap.put("[pf3]", 0x33); + mnemonicMap.put("[pf4]", 0x34); + mnemonicMap.put("[pf5]", 0x35); + mnemonicMap.put("[pf6]", 0x36); + mnemonicMap.put("[pf7]", 0x37); + mnemonicMap.put("[pf8]", 0x38); + mnemonicMap.put("[pf9]", 0x39); + mnemonicMap.put("[pf10]", 0x3A); + mnemonicMap.put("[pf11]", 0x3B); + mnemonicMap.put("[pf12]", 0x3C); + mnemonicMap.put("[pf13]", 0xB1); + mnemonicMap.put("[pf14]", 0xB2); + mnemonicMap.put("[pf15]", 0xB3); + mnemonicMap.put("[pf16]", 0xB4); + mnemonicMap.put("[pf17]", 0xB5); + mnemonicMap.put("[pf18]", 0xB6); + mnemonicMap.put("[pf19]", 0xB7); + mnemonicMap.put("[pf20]", 0xB8); + mnemonicMap.put("[pf21]", 0xB9); + mnemonicMap.put("[pf22]", 0xBA); + mnemonicMap.put("[pf23]", 0xBB); + mnemonicMap.put("[pf24]", 0xBC); + mnemonicMap.put("[clear]", 0xBD); + mnemonicMap.put("[help]", 0xF3); + mnemonicMap.put("[pgup]", 0xF4); + mnemonicMap.put("[pgdown]", 0xF5); + mnemonicMap.put("[rollleft]", 0xD9); + mnemonicMap.put("[rollright]", 0xDA); + mnemonicMap.put("[hostprint]", 0xF6); + mnemonicMap.put("[pa1]", 0x6C); + mnemonicMap.put("[pa2]", 0x6E); + mnemonicMap.put("[pa3]", 0x6B); + mnemonicMap.put("[sysreq]", 1031); + mnemonicMap.put("[reset]", 1032); + mnemonicMap.put("[nextword]", 1033); + mnemonicMap.put("[prevword]", 1034); + mnemonicMap.put("[copy]", 1035); + mnemonicMap.put("[paste]", 1036); + mnemonicMap.put("[attn]", 1037); + mnemonicMap.put("[markup]", 1038); + mnemonicMap.put("[markdown]", 1039); + mnemonicMap.put("[markleft]", 1040); + mnemonicMap.put("[markright]", 1041); + mnemonicMap.put("[dupfield]", 1042); + mnemonicMap.put("[newline]", 1043); + mnemonicMap.put("[jumpnext]", 5000); + mnemonicMap.put("[jumpprev]", 5001); + mnemonicMap.put("[opennew]", 5002); + mnemonicMap.put("[togcon]", 5003); + mnemonicMap.put("[hotspots]", 5004); + mnemonicMap.put("[gui]", 5005); + mnemonicMap.put("[dspmsgs]", 5006); + mnemonicMap.put("[dspattr]", 5007); + mnemonicMap.put("[print]", 5008); + mnemonicMap.put("[cursor]", 5009); + mnemonicMap.put("[debug]", 5010); + mnemonicMap.put("[close]", 5011); + mnemonicMap.put("[transfer]", 5012); + mnemonicMap.put("[e-mail]", 5013); + mnemonicMap.put("[runscript]", 5014); + mnemonicMap.put("[spoolfile]", 5015); + mnemonicMap.put("[quick-mail]", 5016); + mnemonicMap.put("[open-same]", 5017); + mnemonicMap.put("[fastcursordown]", 5018); + mnemonicMap.put("[fastcursorup]", 5019); + mnemonicMap.put("[fastcursorright]", 5020); + mnemonicMap.put("[fastcursorleft]", 5021); + }; + + public static final String MNEMONIC_CLEAR = "[clear]"; + public static final String MNEMONIC_ENTER = "[enter]"; + public static final String MNEMONIC_HELP = "[help]"; + public static final String MNEMONIC_PAGE_DOWN = "[pgdown]"; + public static final String MNEMONIC_PAGE_UP = "[pgup]"; + public static final String MNEMONIC_PRINT = "[hostprint]"; + public static final String MNEMONIC_PF1 = "[pf1]"; + public static final String MNEMONIC_PF2 = "[pf2]"; + public static final String MNEMONIC_PF3 = "[pf3]"; + public static final String MNEMONIC_PF4 = "[pf4]"; + public static final String MNEMONIC_PF5 = "[pf5]"; + public static final String MNEMONIC_PF6 = "[pf6]"; + public static final String MNEMONIC_PF7 = "[pf7]"; + public static final String MNEMONIC_PF8 = "[pf8]"; + public static final String MNEMONIC_PF9 = "[pf9]"; + public static final String MNEMONIC_PF10 = "[pf10]"; + public static final String MNEMONIC_PF11 = "[pf11]"; + public static final String MNEMONIC_PF12 = "[pf12]"; + public static final String MNEMONIC_PF13 = "[pf13]"; + public static final String MNEMONIC_PF14 = "[pf14]"; + public static final String MNEMONIC_PF15 = "[pf15]"; + public static final String MNEMONIC_PF16 = "[pf16]"; + public static final String MNEMONIC_PF17 = "[pf17]"; + public static final String MNEMONIC_PF18 = "[pf18]"; + public static final String MNEMONIC_PF19 = "[pf19]"; + public static final String MNEMONIC_PF20 = "[pf20]"; + public static final String MNEMONIC_PF21 = "[pf21]"; + public static final String MNEMONIC_PF22 = "[pf22]"; + public static final String MNEMONIC_PF23 = "[pf23]"; + public static final String MNEMONIC_PF24 = "[pf24]"; + public static final String MNEMONIC_BACK_SPACE = "[backspace]"; + public static final String MNEMONIC_BACK_TAB = "[backtab]"; + public static final String MNEMONIC_UP = "[up]"; + public static final String MNEMONIC_DOWN = "[down]"; + public static final String MNEMONIC_LEFT = "[left]"; + public static final String MNEMONIC_RIGHT = "[right]"; + public static final String MNEMONIC_DELETE = "[delete]"; + public static final String MNEMONIC_TAB = "[tab]"; + public static final String MNEMONIC_END_OF_FIELD = "[eof]"; + public static final String MNEMONIC_ERASE_EOF = "[eraseeof]"; + public static final String MNEMONIC_ERASE_FIELD = "[erasefld]"; + public static final String MNEMONIC_INSERT = "[insert]"; + public static final String MNEMONIC_HOME = "[home]"; + public static final String MNEMONIC_KEYPAD0 = "[keypad0]"; + public static final String MNEMONIC_KEYPAD1 = "[keypad1]"; + public static final String MNEMONIC_KEYPAD2 = "[keypad2]"; + public static final String MNEMONIC_KEYPAD3 = "[keypad3]"; + public static final String MNEMONIC_KEYPAD4 = "[keypad4]"; + public static final String MNEMONIC_KEYPAD5 = "[keypad5]"; + public static final String MNEMONIC_KEYPAD6 = "[keypad6]"; + public static final String MNEMONIC_KEYPAD7 = "[keypad7]"; + public static final String MNEMONIC_KEYPAD8 = "[keypad8]"; + public static final String MNEMONIC_KEYPAD9 = "[keypad9]"; + public static final String MNEMONIC_KEYPAD_PERIOD = "[keypad.]"; + public static final String MNEMONIC_KEYPAD_COMMA = "[keypad,]"; + public static final String MNEMONIC_KEYPAD_MINUS = "[keypad-]"; + public static final String MNEMONIC_FIELD_EXIT = "[fldext]"; + public static final String MNEMONIC_FIELD_PLUS = "[field+]"; + public static final String MNEMONIC_FIELD_MINUS = "[field-]"; + public static final String MNEMONIC_BEGIN_OF_FIELD = "[bof]"; + public static final String MNEMONIC_PA1 = "[pa1]"; + public static final String MNEMONIC_PA2 = "[pa2]"; + public static final String MNEMONIC_PA3 = "[pa3]"; + public static final String MNEMONIC_SYSREQ = "[sysreq]"; + public static final String MNEMONIC_RESET = "[reset]"; + public static final String MNEMONIC_NEXTWORD = "[nextword]"; + public static final String MNEMONIC_PREVWORD = "[prevword]"; + public static final String MNEMONIC_ATTN = "[attn]"; + public static final String MNEMONIC_MARK_LEFT = "[markleft]"; + public static final String MNEMONIC_MARK_RIGHT = "[markright]"; + public static final String MNEMONIC_MARK_UP = "[markup]"; + public static final String MNEMONIC_MARK_DOWN = "[markdown]"; + public static final String MNEMONIC_DUP_FIELD = "[dupfield]"; + public static final String MNEMONIC_NEW_LINE = "[newline]"; + public static final String MNEMONIC_JUMP_NEXT = "[jumpnext]"; + public static final String MNEMONIC_JUMP_PREV = "[jumpprev]"; + public static final String MNEMONIC_OPEN_NEW = "[opennew]"; + public static final String MNEMONIC_TOGGLE_CONNECTION = "[togcon]"; + public static final String MNEMONIC_HOTSPOTS = "[hotspots]"; + public static final String MNEMONIC_GUI = "[gui]"; + public static final String MNEMONIC_DISP_MESSAGES = "[dspmsgs]"; + public static final String MNEMONIC_DISP_ATTRIBUTES = "[dspattr]"; + public static final String MNEMONIC_PRINT_SCREEN = "[print]"; + public static final String MNEMONIC_CURSOR = "[cursor]"; + public static final String MNEMONIC_DEBUG = "[debug]"; + public static final String MNEMONIC_CLOSE = "[close]"; + public static final String MNEMONIC_E_MAIL = "[e-mail]"; + public static final String MNEMONIC_COPY = "[copy]"; + public static final String MNEMONIC_PASTE = "[paste]"; + public static final String MNEMONIC_FILE_TRANSFER = "[transfer]"; + public static final String MNEMONIC_RUN_SCRIPT = "[runscript]"; + public static final String MNEMONIC_SPOOL_FILE = "[spoolfile]"; + public static final String MNEMONIC_QUICK_MAIL = "[quick-mail]"; + public static final String MNEMONIC_OPEN_SAME = "[open-same]"; + public static final String MNEMONIC_FAST_CURSOR_DOWN = "[fastcursordown]"; + public static final String MNEMONIC_FAST_CURSOR_UP = "[fastcursorup]"; + public static final String MNEMONIC_FAST_CURSOR_RIGHT = "[fastcursorright]"; + public static final String MNEMONIC_FAST_CURSOR_LEFT = "[fastcursorleft]"; + + // AID-Generating Keys + public static final int AID_CLEAR = 0xBD; + public static final int AID_ENTER = 0xF1; + public static final int AID_HELP = 0xF3; + public static final int AID_ROLL_UP = 0xF4; + public static final int AID_ROLL_DOWN = 0xF5; + public static final int AID_ROLL_LEFT = 0xD9; + public static final int AID_ROLL_RIGHT = 0xDA; + public static final int AID_PRINT = 0xF6; + public static final int AID_PF1 = 0x31; + public static final int AID_PF2 = 0x32; + public static final int AID_PF3 = 0x33; + public static final int AID_PF4 = 0x34; + public static final int AID_PF5 = 0x35; + public static final int AID_PF6 = 0x36; + public static final int AID_PF7 = 0x37; + public static final int AID_PF8 = 0x38; + public static final int AID_PF9 = 0x39; + public static final int AID_PF10 = 0x3A; + public static final int AID_PF11 = 0x3B; + public static final int AID_PF12 = 0x3C; + public static final int AID_PF13 = 0xB1; + public static final int AID_PF14 = 0xB2; + public static final int AID_PF15 = 0xB3; + public static final int AID_PF16 = 0xB4; + public static final int AID_PF17 = 0xB5; + public static final int AID_PF18 = 0xB6; + public static final int AID_PF19 = 0xB7; + public static final int AID_PF20 = 0xB8; + public static final int AID_PF21 = 0xB9; + public static final int AID_PF22 = 0xBA; + public static final int AID_PF23 = 0xBB; + public static final int AID_PF24 = 0xBC; + + // negative response categories + public static final int NR_REQUEST_REJECT = 0x08; + public static final int NR_REQUEST_ERROR = 0x10; + public static final int NR_STATE_ERROR = 0x20; + public static final int NR_USAGE_ERROR = 0x40; + public static final int NR_PATH_ERROR = 0x80; + + // commands + public static final byte CMD_WRITE_TO_DISPLAY = 0x11; // 17 + public static final byte CMD_CLEAR_UNIT = 0x40; // 64 + public static final byte CMD_CLEAR_UNIT_ALTERNATE = 0x20; // 32 + public static final byte CMD_CLEAR_FORMAT_TABLE = 0x50; // 80 + public static final byte CMD_READ_INPUT_FIELDS = 0x42; // 66 + public static final byte CMD_READ_MDT_FIELDS = 0x52; // 82 + public static final byte CMD_READ_MDT_IMMEDIATE_ALT = (byte)0x83; // 131 +// public static final byte CMD_READ_MDT_FIELDS_ALT = (byte)0x82; // 130 +// public static final byte CMD_READ_IMMEDIATE = 0x72; // 114 + public static final byte CMD_READ_SCREEN_IMMEDIATE = 0x62; // 98 + public static final byte CMD_WRITE_STRUCTURED_FIELD = (byte)243; // (byte)0xF3 -13 + public static final byte CMD_SAVE_SCREEN = 0x02; // 02 + public static final byte CMD_RESTORE_SCREEN = 0x12; // 18 + public static final byte CMD_WRITE_ERROR_CODE = 0x21; // 33 + public static final byte CMD_WRITE_ERROR_CODE_TO_WINDOW = 0x22; // 34 + public static final byte CMD_ROLL = 0x23; // 35 + public static final byte CMD_READ_SCREEN_TO_PRINT = (byte)0x66; // 102 + + // PLANES + public static final int PLANE_TEXT = 1; + public static final int PLANE_COLOR = 2; + public static final int PLANE_FIELD = 3; + public static final int PLANE_EXTENDED = 4; + public static final int PLANE_EXTENDED_GRAPHIC = 5; + public static final int PLANE_EXTENDED_FIELD = 6; + public static final int PLANE_ATTR = 7; + public static final int PLANE_IS_ATTR_PLACE = 8; + + // COLOR_BG + public static final char COLOR_BG_BLACK = 0; + public static final char COLOR_BG_BLUE = 1; + public static final char COLOR_BG_GREEN = 2; + public static final char COLOR_BG_CYAN = 3; + public static final char COLOR_BG_RED = 4; + public static final char COLOR_BG_MAGENTA = 5; + public static final char COLOR_BG_YELLOW = 6; + public static final char COLOR_BG_WHITE = 7; + + // COLOR_FG + public static final char COLOR_FG_BLACK = 0; + public static final char COLOR_FG_BLUE = 1; + public static final char COLOR_FG_GREEN = 2; + public static final char COLOR_FG_CYAN = 3; + public static final char COLOR_FG_RED = 4; + public static final char COLOR_FG_MAGENTA = 5; + public static final char COLOR_FG_YELLOW = 6; + public static final char COLOR_FG_WHITE = 7; + public static final char COLOR_FG_GRAY = 8; + public static final char COLOR_FG_LIGHT_BLUE = 9; + public static final char COLOR_FG_LIGHT_GREEN = 0xA; + public static final char COLOR_FG_LIGHT_CYAN = 0xB; + public static final char COLOR_FG_LIGHT_RED = 0xC; + public static final char COLOR_FG_LIGHT_MAGENTA = 0xD; + public static final char COLOR_FG_BROWN = 0xE; + public static final char COLOR_FG_WHITE_HIGH = 0xF; + + public static final int EXTENDED_5250_REVERSE = 0x10; + public static final int EXTENDED_5250_UNDERLINE = 0x08; + public static final int EXTENDED_5250_BLINK = 0x04; + public static final int EXTENDED_5250_COL_SEP = 0x02; + public static final int EXTENDED_5250_NON_DSP = 0x01; + + public static final char ATTR_32 = (COLOR_BG_BLACK << 8 & 0xff00) | + (COLOR_FG_GREEN & 0xff); + public static final char ATTR_33 = (COLOR_BG_GREEN << 8 & 0xff00) | + (COLOR_FG_BLACK & 0xff); + public static final char ATTR_34 = (COLOR_BG_BLACK << 8 & 0xff00) | + (COLOR_FG_WHITE & 0xff); + public static final char ATTR_35 = (COLOR_BG_WHITE << 8 & 0xff00) | + (COLOR_FG_BLACK & 0xff); + public static final char ATTR_36 = (COLOR_BG_BLACK << 8 & 0xff00) | + (COLOR_FG_GREEN & 0xff); + public static final char ATTR_37 = (COLOR_BG_GREEN << 8 & 0xff00) | + (COLOR_FG_BLACK & 0xff); + public static final char ATTR_38 = (COLOR_BG_BLACK << 8 & 0xff00) | + (COLOR_FG_WHITE & 0xff); + public static final char ATTR_40 = (COLOR_BG_BLACK << 8 & 0xff00) | + (COLOR_FG_RED & 0xff); + public static final char ATTR_41 = (COLOR_BG_RED << 8 & 0xff00) | + (COLOR_FG_BLACK & 0xff); + public static final char ATTR_42 = (COLOR_BG_BLACK << 8 & 0xff00) | + (COLOR_FG_RED & 0xff); + public static final char ATTR_43 = (COLOR_BG_RED << 8 & 0xff00) | + (COLOR_FG_BLACK & 0xff); + + public static final char ATTR_44 = (COLOR_BG_BLACK << 8 & 0xff00) | + (COLOR_FG_RED & 0xff); + public static final char ATTR_45 = (COLOR_BG_RED << 8 & 0xff00) | + (COLOR_FG_BLACK & 0xff); + public static final char ATTR_46 = (COLOR_BG_BLACK << 8 & 0xff00) | + (COLOR_FG_RED & 0xff); + + public static final char ATTR_48 = (COLOR_BG_BLACK << 8 & 0xff00) | + (COLOR_FG_CYAN & 0xff); + public static final char ATTR_49 = (COLOR_BG_CYAN << 8 & 0xff00) | + (COLOR_FG_BLACK & 0xff); + public static final char ATTR_50 = (COLOR_BG_BLACK << 8 & 0xff00) | + (COLOR_FG_YELLOW & 0xff); + + public static final char ATTR_51 = (COLOR_BG_YELLOW << 8 & 0xff00) | + (COLOR_FG_BLACK & 0xff); + public static final char ATTR_52 = (COLOR_BG_BLACK << 8 & 0xff00) | + (COLOR_FG_CYAN & 0xff); + public static final char ATTR_53 = (COLOR_BG_CYAN << 8 & 0xff00) | + (COLOR_FG_BLACK & 0xff); + public static final char ATTR_54 = (COLOR_BG_BLACK << 8 & 0xff00) | + (COLOR_FG_YELLOW & 0xff); + public static final char ATTR_56 = (COLOR_BG_BLACK << 8 & 0xff00) | + (COLOR_FG_MAGENTA & 0xff); + public static final char ATTR_57 = (COLOR_BG_MAGENTA << 8 & 0xff00) | + (COLOR_FG_BLACK & 0xff); + public static final char ATTR_58 = (COLOR_BG_BLACK << 8 & 0xff00) | + (COLOR_FG_BLUE & 0xff); + public static final char ATTR_59 = (COLOR_BG_BLUE << 8 & 0xff00) | + (COLOR_FG_BLACK & 0xff); + public static final char ATTR_60 = (COLOR_BG_BLACK << 8 & 0xff00) | + (COLOR_FG_MAGENTA & 0xff); + public static final char ATTR_61 = (COLOR_BG_MAGENTA << 8 & 0xff00) | + (COLOR_FG_BLACK & 0xff); + public static final char ATTR_62 = (COLOR_BG_BLACK << 8 & 0xff00) | + (COLOR_FG_BLUE & 0xff); + + public static final int NO_GUI = 0; + public static final int UPPER_LEFT = 1; + public static final int UPPER = 2; + public static final int UPPER_RIGHT = 3; + public static final int GUI_LEFT = 4; + public static final int GUI_RIGHT = 5; + public static final int LOWER_LEFT = 6; + public static final int BOTTOM = 7; + public static final int LOWER_RIGHT = 8; + public static final int FIELD_LEFT = 9; + public static final int FIELD_RIGHT = 10; + public static final int FIELD_MIDDLE = 11; + public static final int FIELD_ONE = 12; + public static final int BUTTON_LEFT = 13; + public static final int BUTTON_RIGHT = 14; + public static final int BUTTON_MIDDLE = 15; + public static final int BUTTON_ONE = 16; + public static final int BUTTON_LEFT_UP = 17; + public static final int BUTTON_RIGHT_UP = 18; + public static final int BUTTON_MIDDLE_UP = 19; + public static final int BUTTON_ONE_UP = 20; + public static final int BUTTON_LEFT_DN = 21; + public static final int BUTTON_RIGHT_DN = 22; + public static final int BUTTON_MIDDLE_DN = 23; + public static final int BUTTON_ONE_DN = 24; + public static final int BUTTON_LEFT_EB = 25; + public static final int BUTTON_RIGHT_EB = 26; + public static final int BUTTON_MIDDLE_EB = 27; + public static final int BUTTON_SB_UP = 28; + public static final int BUTTON_SB_DN = 29; + public static final int BUTTON_SB_GUIDE = 30; + public static final int BUTTON_SB_THUMB = 31; + public static final int BUTTON_LAST = 31; + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/AbstractCodePage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/AbstractCodePage.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,45 @@ +/** + * Title: CodePage.java + * Copyright: Copyright (c) 2001, 2002, 2003 + * Company: + * @author Kenneth J. Pouncey + * rewritten by LDC, WVL, Luc + * @version 0.4 + * + * Description: + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding; + +/** + * + * This class controls the translation from EBCDIC to ASCII and ASCII to EBCDIC + * + */ +public abstract class AbstractCodePage implements ICodePage { + + protected AbstractCodePage(String encoding) { + this.encoding = encoding; + } + + public String getEncoding() { + return encoding; + } + + protected String encoding; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/BuiltInCodePageFactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/BuiltInCodePageFactory.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,156 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2012 + * Company: + * @author: master_jaf + * + * Description: + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +import org.tn5250j.encoding.builtin.CCSID1025; +import org.tn5250j.encoding.builtin.CCSID1026; +import org.tn5250j.encoding.builtin.CCSID1112; +import org.tn5250j.encoding.builtin.CCSID1140; +import org.tn5250j.encoding.builtin.CCSID1141; +import org.tn5250j.encoding.builtin.CCSID1147; +import org.tn5250j.encoding.builtin.CCSID1148; +import org.tn5250j.encoding.builtin.CCSID273; +import org.tn5250j.encoding.builtin.CCSID277; +import org.tn5250j.encoding.builtin.CCSID278; +import org.tn5250j.encoding.builtin.CCSID280; +import org.tn5250j.encoding.builtin.CCSID284; +import org.tn5250j.encoding.builtin.CCSID285; +import org.tn5250j.encoding.builtin.CCSID297; +import org.tn5250j.encoding.builtin.CCSID37; +import org.tn5250j.encoding.builtin.CCSID424; +import org.tn5250j.encoding.builtin.CCSID500; +import org.tn5250j.encoding.builtin.CCSID870; +import org.tn5250j.encoding.builtin.CCSID871; +import org.tn5250j.encoding.builtin.CCSID875; +import org.tn5250j.encoding.builtin.ICodepageConverter; +import android.util.Log; + + +/** + * Methods for built-in code page support. + */ +class BuiltInCodePageFactory { + private static final String TAG = "BuiltInCodePageFactory"; + private static BuiltInCodePageFactory singleton; + + private final List> clazzes = new ArrayList>(); + + + private BuiltInCodePageFactory() { + register(); + } + + public static synchronized final BuiltInCodePageFactory getInstance() { + if (singleton == null) { + singleton = new BuiltInCodePageFactory(); + } + + return singleton; + } + + private void register() { + clazzes.add(CCSID37.class); + clazzes.add(CCSID273.class); + clazzes.add(CCSID277.class); + clazzes.add(CCSID278.class); + clazzes.add(CCSID280.class); + clazzes.add(CCSID284.class); + clazzes.add(CCSID285.class); + clazzes.add(CCSID297.class); + clazzes.add(CCSID424.class); + clazzes.add(CCSID500.class); + clazzes.add(CCSID870.class); + clazzes.add(CCSID871.class); + clazzes.add(CCSID875.class); + clazzes.add(CCSID1025.class); + clazzes.add(CCSID1026.class); + clazzes.add(CCSID1112.class); + clazzes.add(CCSID1140.class); + clazzes.add(CCSID1141.class); + clazzes.add(CCSID1147.class); + clazzes.add(CCSID1148.class); + } + + /** + * @return unsorted list of available code pages + */ + public String[] getAvailableCodePages() { + HashSet cpset = new HashSet(); + + for (Class clazz : clazzes) { + final ICodepageConverter converter = getConverterFromClassName(clazz); + + if (converter != null) { + cpset.add(converter.getName()); + } + } + + return cpset.toArray(new String[cpset.size()]); + } + + /** + * @param encoding + * @return an {@link ICodePage} object OR null, of not found + */ + public ICodePage getCodePage(String encoding) { + for (Class clazz : clazzes) { + final ICodepageConverter converter = getConverterFromClassName(clazz); + + if (converter != null && converter.getName().equals(encoding)) { + return converter; + } + } + + return null; + } + + /** + * Lazy loading converters takes time, + * but doesn't happen so often and saves memory. + * + * @param clazz {@link ICodepageConverter} + * @return + */ + private ICodepageConverter getConverterFromClassName(Class clazz) { + try { + final Constructor constructor = clazz.getConstructor(new Class[0]); + final ICodepageConverter converter = (ICodepageConverter) constructor.newInstance(); + converter.init(); + return converter; + } + catch (Exception e) { + Log.e(TAG, "Couldn't load code page converter class:" + clazz.getCanonicalName(), e); + return null; + } + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/CharMappings.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/CharMappings.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,91 @@ +/** + * Title: CharMappings.java + * Copyright: Copyright (c) 2001,2002,2003 + * Company: + * @author Kenneth J. Pouncey + * rewritten by LDC, WVL, Luc + * @version 0.4 + * + * Description: + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +/** + * Character Mappings for EBCDIC to ASCII and ASCII to EBCDIC translations + */ +public class CharMappings { + + public static final String DFT_ENC = "37"; + public static final int NATIVE_CP = 0; + public static final int TOOLBOX_CP = 1; + + private static final HashMap map = new HashMap(); + + public static String[] getAvailableCodePages() { + Set cpset = new HashSet(); // no double entries + + for (String cp : BuiltInCodePageFactory.getInstance().getAvailableCodePages()) { + cpset.add(cp); + } + + for (String cp : ToolboxCodePageFactory.getInstance().getAvailableCodePages()) { + cpset.add(cp); + } + + String[] cparray = cpset.toArray(new String[cpset.size()]); + Arrays.sort(cparray); + return cparray; + } + + public static ICodePage getCodePage(String encoding) { + if (map.containsKey(encoding)) { + return map.get(encoding); + } + + ICodePage cp = BuiltInCodePageFactory.getInstance().getCodePage(encoding); + + if (cp != null) { + map.put(encoding, cp); + return cp; + } + + cp = ToolboxCodePageFactory.getInstance().getCodePage(encoding); + + if (cp != null) { + map.put(encoding, cp); + return cp; + } + + cp = JavaCodePageFactory.getCodePage(encoding); + + if (cp != null) { + map.put(encoding, cp); + return cp; + } + + // unsupported codepage ==> return default + return BuiltInCodePageFactory.getInstance().getCodePage(DFT_ENC); + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/ICodePage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/ICodePage.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,22 @@ +package org.tn5250j.encoding; + +public interface ICodePage { + + /** + * Convert a single byte (or maybe more bytes which representing one character) to a Unicode character. + * + * @param index + * @return + */ + public abstract char ebcdic2uni(int index); + + /** + * Convert a Unicode character in it's byte representation. + * Therefore, only 8bit codepages are supported. + * + * @param index + * @return + */ + public abstract byte uni2ebcdic(char index); + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/JavaCodePageFactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/JavaCodePageFactory.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,98 @@ +/** + * Title: JavaCodePage + * Copyright: Copyright (c) 2001, 2002, 2003 + * Company: + * @author LDC, WVL, Luc, master_jaf + * @version 0.4 + * + * Description: + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; + +/* package */ class JavaCodePageFactory extends AbstractCodePage { + + private final CharsetEncoder encoder; + private final CharsetDecoder decoder; + + /* package */ JavaCodePageFactory(String encoding, CharsetEncoder encoder, CharsetDecoder decoder) { + super(encoding); + this.encoder = encoder; + this.decoder = decoder; + } + + /* (non-Javadoc) + * @see org.tn5250j.encoding.CodePage#ebcdic2uni(int) + */ + public char ebcdic2uni(int codepoint) { + try { + final ByteBuffer in = ByteBuffer.wrap(new byte[] { (byte) codepoint }); + final CharBuffer out = this.decoder.decode(in); + return out.get(0); + } + catch (Exception cce) { + return ' '; + } + } + + /* (non-Javadoc) + * @see org.tn5250j.encoding.CodePage#uni2ebcdic(char) + */ + public byte uni2ebcdic(char character) { + try { + final CharBuffer in = CharBuffer.wrap(new char[] {character}); + final ByteBuffer out = this.encoder.encode(in); + return out.get(0); + } + catch (Exception cce) { + return 0x0; + } + } + + /** + * @param encoding + * @return A new {@link CodePage} object OR null, if not available. + */ + /* package */ static ICodePage getCodePage(final String encoding) { + CharsetDecoder dec = null; + CharsetEncoder enc = null; + + try { + final Charset cs = java.nio.charset.Charset.forName(encoding); + dec = cs.newDecoder(); + enc = cs.newEncoder(); + } + catch (Exception e) { + enc = null; + dec = null; + } + + if ((enc != null) && (dec != null)) { + return new JavaCodePageFactory(encoding, enc, dec); + } + + return null; + } + +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/ToolboxCodePageFactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/ToolboxCodePageFactory.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,164 @@ +/** + * Title: ToolboxCodePage + * Copyright: Copyright (c) 2001, 2002, 2003 + * Company: + * @author Kenneth J. Pouncey + * rewritten by LDC, WVL, Luc + * @version 0.4 + * + * Description: + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import android.util.Log; + + +class ToolboxCodePageFactory { + private static final String TAG = "ToolboxCodePageFactory"; + private final static String[] CODEPAGES = { "Big5", "Cp037", "Cp273", "Cp277", "Cp278", + "Cp280", "Cp284", "Cp285", "Cp297", "Cp420", "Cp424", "Cp437", + "Cp500", "Cp737", "Cp775", "Cp838", "Cp850", "Cp852", "Cp855", + "Cp856", "Cp857", "Cp858", "Cp860", "Cp861", "Cp862", "Cp863", + "Cp864", "Cp865", "Cp866", "Cp868", "Cp869", "Cp870", + "Cp871", "Cp874", "Cp875", "Cp918", "Cp921", "Cp922", + "Cp923", // IBM Latin-9. + "Cp930", "Cp933", "Cp935", "Cp937", "Cp939", "Cp942", "Cp943", + "Cp948", "Cp949", "Cp950", "Cp964", "Cp970", "Cp1006", "Cp1025", + "Cp1026", "Cp1046", "Cp1097", "Cp1098", "Cp1112", "Cp1122", + "Cp1123", "Cp1124", "Cp1140", "Cp1141", "Cp1142", "Cp1143", + "Cp1144", "Cp1145", "Cp1146", "Cp1147", "Cp1148", "Cp1149", + "Cp1252", "Cp1250", "Cp1251", "Cp1253", "Cp1254", "Cp1255", + "Cp1256", "Cp1257", "Cp1258", "Cp1381", "Cp1383", "Cp33722" + }; + + private static final String CONVERTER_NAME = "com.ibm.as400.access.CharConverter"; + private static final String TOBYTES_NAME = "stringToByteArray"; + private static final String TOSTRING_NAME = "byteArrayToString"; + + private static ToolboxCodePageFactory singleton; + + + + private ToolboxCodePageFactory() { + /* private for singleton */ + } + + public static synchronized ToolboxCodePageFactory getInstance() { + if (singleton == null) { + singleton = new ToolboxCodePageFactory(); + } + + return singleton; + } + + /** + * @return + */ + public String[] getAvailableCodePages() { + try { + final ClassLoader loader = getClassLoader(); + Class.forName(CONVERTER_NAME, false, loader); + return CODEPAGES; + } + catch (Exception e) { + Log.i(TAG, "Couldn't locate JT400 Toolbox in classpath. Charset converters can't be used."); + return new String[0]; + } + } + + /** + * @param encoding + * @return + */ + public ICodePage getCodePage(String encoding) { + try { + ClassLoader loader = getClassLoader(); + Class conv_class = Class.forName(CONVERTER_NAME, true, loader); + Constructor conv_constructor = conv_class.getConstructor(new Class[] { String.class }); + Method toBytes_method = conv_class.getMethod(TOBYTES_NAME, new Class[] { String.class }); + Method toString_method = conv_class.getMethod(TOSTRING_NAME, new Class[] { byte[].class }); + Object convobj = conv_constructor.newInstance(new Object[] { encoding }); + return new ToolboxConverterProxy(convobj, toBytes_method, toString_method); + } + catch (Exception e) { + Log.w(TAG, "Can't load charset converter from JT400 Toolbox for code page " + encoding, e); + return null; + } + } + + private static final ClassLoader getClassLoader() { + ClassLoader loader = ToolboxCodePageFactory.class.getClassLoader(); + + if (loader == null) { + loader = ClassLoader.getSystemClassLoader(); + } + + return loader; + } + + private static class ToolboxConverterProxy implements ICodePage { + + private final Object converter; + private final Method tobytesMethod; + private final Method tostringMethod; + + private ToolboxConverterProxy(Object converterObject, Method tobytesMethod, Method tostringMethod) { + super(); + this.converter = converterObject; + this.tobytesMethod = tobytesMethod; + this.tostringMethod = tostringMethod; + } + + public char ebcdic2uni(int index) { + Object result; + + try { + result = tostringMethod.invoke(converter, new Object[] { new byte[] { (byte)(index & 0xFF) } }); + } + catch (Throwable t) { + result = null; + } + + if (result == null) + return 0x00; + + return ((String) result).charAt(0); + } + + public byte uni2ebcdic(char index) { + Object result; + + try { + result = tobytesMethod.invoke(converter, new Object[] { new String(new char[] { index }) }); + } + catch (Throwable t) { + result = null; + } + + if (result == null) + return 0x00; + + return ((byte[]) result)[0]; + } + } + +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/CCSID1025.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/CCSID1025.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,96 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * Alternative (extended) implementation of a codepage converter CCSID 1025<->Unicode. + * + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding.builtin; + +/** + * @author master_jaf + * @see http://www-01.ibm.com/software/globalization/ccsid/ccsid1025.jsp + */ +public final class CCSID1025 extends CodepageConverterAdapter { + + public final static String NAME = "1025"; + public final static String DESCR = "Cyrillic Multilingual"; + + /* + * Char maps manually extracted from JTOpen v6.4. Because char maps can't be + * covered by any license, this should legal. + */ + private static final char[] codepage = { '\u0000', '\u0001', '\u0002', + '\u0003', '\u009C', '\t', '\u0086', '\u007F', '\u0097', '\u008D', + '\u008E', '\u000B', '\f', '\r', '\u000E', '\u000F', '\u0010', + '\u0011', '\u0012', '\u0013', '\u009D', '\u0085', '\u0008', + '\u0087', '\u0018', '\u0019', '\u0092', '\u008F', '\u001C', + '\u001D', '\u001E', '\u001F', '\u0080', '\u0081', '\u0082', + '\u0083', '\u0084', '\n', '\u0017', '\u001B', '\u0088', '\u0089', + '\u008A', '\u008B', '\u008C', '\u0005', '\u0006', '\u0007', + '\u0090', '\u0091', '\u0016', '\u0093', '\u0094', '\u0095', + '\u0096', '\u0004', '\u0098', '\u0099', '\u009A', '\u009B', + '\u0014', '\u0015', '\u009E', '\u001A', ' ', '\u00A0', '\u0452', + '\u0453', '\u0451', '\u0454', '\u0455', '\u0456', '\u0457', + '\u0458', '[', '.', '<', '(', '+', '!', '&', '\u0459', '\u045A', + '\u045B', '\u045C', '\u045E', '\u045F', '\u042A', '\u2116', + '\u0402', ']', '$', '*', ')', ';', '^', '-', '/', '\u0403', + '\u0401', '\u0404', '\u0405', '\u0406', '\u0407', '\u0408', + '\u0409', '|', ',', '%', '_', '>', '?', '\u040A', '\u040B', + '\u040C', '\u00AD', '\u040E', '\u040F', '\u044E', '\u0430', + '\u0431', '`', ':', '#', '@', '\'', '=', '"', '\u0446', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', 'i', '\u0434', '\u0435', '\u0444', + '\u0433', '\u0445', '\u0438', '\u0439', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', '\u043A', '\u043B', '\u043C', '\u043D', + '\u043E', '\u043F', '\u044F', '~', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '\u0440', '\u0441', '\u0442', '\u0443', '\u0436', + '\u0432', '\u044C', '\u044B', '\u0437', '\u0448', '\u044D', + '\u0449', '\u0447', '\u044A', '\u042E', '\u0410', '\u0411', + '\u0426', '\u0414', '\u0415', '\u0424', '\u0413', '{', 'A', 'B', + 'C', 'D', 'E', 'F', 'G', 'H', 'I', '\u0425', '\u0418', '\u0419', + '\u041A', '\u041B', '\u041C', '}', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', '\u041D', '\u041E', '\u041F', '\u042F', '\u0420', + '\u0421', '\\', '\u00A7', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '\u0422', '\u0423', '\u0416', '\u0412', '\u042C', '\u042B', '0', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '\u0417', '\u0428', + '\u042D', '\u0429', '\u0427', '\u009F', + }; + + public String getName() { + return NAME; + } + + public String getDescription() { + return DESCR; + } + + public String getEncoding() { + return NAME; + } + + @Override + protected char[] getCodePage() { + return codepage; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/CCSID1026.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/CCSID1026.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,96 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * Alternative (extended) implementation of a codepage converter CCSID 1026<->Unicode. + * + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding.builtin; + +/** + * @author master_jaf + * @see http://www-01.ibm.com/software/globalization/ccsid/ccsid1026.jsp + */ +public final class CCSID1026 extends CodepageConverterAdapter { + + public final static String NAME = "1026"; + public final static String DESCR = "Turkey Latin 5"; + + /* + * Char maps manually extracted from JTOpen v6.4. Because char maps can't be + * covered by any license, this should legal. + */ + private static final char[] codepage = { '\u0000', '\u0001', '\u0002', + '\u0003', '\u009C', '\t', '\u0086', '\u007F', '\u0097', '\u008D', + '\u008E', '\u000B', '\f', '\r', '\u000E', '\u000F', '\u0010', + '\u0011', '\u0012', '\u0013', '\u009D', '\u0085', '\u0008', + '\u0087', '\u0018', '\u0019', '\u0092', '\u008F', '\u001C', + '\u001D', '\u001E', '\u001F', '\u0080', '\u0081', '\u0082', + '\u0083', '\u0084', '\n', '\u0017', '\u001B', '\u0088', '\u0089', + '\u008A', '\u008B', '\u008C', '\u0005', '\u0006', '\u0007', + '\u0090', '\u0091', '\u0016', '\u0093', '\u0094', '\u0095', + '\u0096', '\u0004', '\u0098', '\u0099', '\u009A', '\u009B', + '\u0014', '\u0015', '\u009E', '\u001A', ' ', '\u00A0', '\u00E2', + '\u00E4', '\u00E0', '\u00E1', '\u00E3', '\u00E5', '{', '\u00F1', + '\u00C7', '.', '<', '(', '+', '!', '&', '\u00E9', '\u00EA', + '\u00EB', '\u00E8', '\u00ED', '\u00EE', '\u00EF', '\u00EC', + '\u00DF', '\u011E', '\u0130', '*', ')', ';', '^', '-', '/', + '\u00C2', '\u00C4', '\u00C0', '\u00C1', '\u00C3', '\u00C5', '[', + '\u00D1', '\u015F', ',', '%', '_', '>', '?', '\u00F8', '\u00C9', + '\u00CA', '\u00CB', '\u00C8', '\u00CD', '\u00CE', '\u00CF', + '\u00CC', '\u0131', ':', '\u00D6', '\u015E', '\'', '=', '\u00DC', + '\u00D8', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', '\u00AB', + '\u00BB', '}', '`', '\u00A6', '\u00B1', '\u00B0', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', '\u00AA', '\u00BA', '\u00E6', + '\u00B8', '\u00C6', '\u00A4', '\u00B5', '\u00F6', 's', 't', 'u', + 'v', 'w', 'x', 'y', 'z', '\u00A1', '\u00BF', ']', '$', '@', + '\u00AE', '\u00A2', '\u00A3', '\u00A5', '\u00B7', '\u00A9', + '\u00A7', '\u00B6', '\u00BC', '\u00BD', '\u00BE', '\u00AC', '|', + '\u00AF', '\u00A8', '\u00B4', '\u00D7', '\u00E7', 'A', 'B', 'C', + 'D', 'E', 'F', 'G', 'H', 'I', '\u00AD', '\u00F4', '~', '\u00F2', + '\u00F3', '\u00F5', '\u011F', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', '\u00B9', '\u00FB', '\\', '\u00F9', '\u00FA', '\u00FF', + '\u00FC', '\u00F7', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '\u00B2', '\u00D4', '#', '\u00D2', '\u00D3', '\u00D5', '0', '1', + '2', '3', '4', '5', '6', '7', '8', '9', '\u00B3', '\u00DB', '"', + '\u00D9', '\u00DA', '\u009F', + }; + + public String getName() { + return NAME; + } + + public String getDescription() { + return DESCR; + } + + public String getEncoding() { + return NAME; + } + + @Override + protected char[] getCodePage() { + return codepage; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/CCSID1112.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/CCSID1112.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,96 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * Alternative (extended) implementation of a codepage converter CCSID 1112<->Unicode. + * + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding.builtin; + +/** + * @author master_jaf + * @see http://www-01.ibm.com/software/globalization/ccsid/ccsid1112.jsp + */ +public final class CCSID1112 extends CodepageConverterAdapter { + + public final static String NAME = "1112"; + public final static String DESCR = "Baltic, Multilingual"; + + /* + * Char maps manually extracted from JTOpen v6.4. Because char maps can't be + * covered by any license, this should legal. + */ + private static final char[] codepage = { '\u0000', '\u0001', '\u0002', + '\u0003', '\u009C', '\t', '\u0086', '\u007F', '\u0097', '\u008D', + '\u008E', '\u000B', '\f', '\r', '\u000E', '\u000F', '\u0010', + '\u0011', '\u0012', '\u0013', '\u009D', '\u0085', '\u0008', + '\u0087', '\u0018', '\u0019', '\u0092', '\u008F', '\u001C', + '\u001D', '\u001E', '\u001F', '\u0080', '\u0081', '\u0082', + '\u0083', '\u0084', '\n', '\u0017', '\u001B', '\u0088', '\u0089', + '\u008A', '\u008B', '\u008C', '\u0005', '\u0006', '\u0007', + '\u0090', '\u0091', '\u0016', '\u0093', '\u0094', '\u0095', + '\u0096', '\u0004', '\u0098', '\u0099', '\u009A', '\u009B', + '\u0014', '\u0015', '\u009E', '\u001A', ' ', '\u00A0', '\u0161', + '\u00E4', '\u0105', '\u012F', '\u016B', '\u00E5', '\u0113', + '\u017E', '\u00A2', '.', '<', '(', '+', '|', '&', '\u00E9', + '\u0119', '\u0117', '\u010D', '\u0173', '\u201E', '\u201C', + '\u0123', '\u00DF', '!', '$', '*', ')', ';', '\u00AC', '-', '/', + '\u0160', '\u00C4', '\u0104', '\u012E', '\u016A', '\u00C5', + '\u0112', '\u017D', '\u00A6', ',', '%', '_', '>', '?', '\u00F8', + '\u00C9', '\u0118', '\u0116', '\u010C', '\u0172', '\u012A', + '\u013B', '\u0122', '`', ':', '#', '@', '\'', '=', '"', '\u00D8', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', '\u00AB', '\u00BB', + '\u0101', '\u017C', '\u0144', '\u00B1', '\u00B0', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', '\u0156', '\u0157', '\u00E6', + '\u0137', '\u00C6', '\u00A4', '\u00B5', '~', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '\u201D', '\u017A', '\u0100', '\u017B', + '\u0143', '\u00AE', '^', '\u00A3', '\u012B', '\u00B7', '\u00A9', + '\u00A7', '\u00B6', '\u00BC', '\u00BD', '\u00BE', '[', ']', + '\u0179', '\u0136', '\u013C', '\u00D7', '{', 'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'I', '\u00AD', '\u014D', '\u00F6', '\u0146', + '\u00F3', '\u00F5', '}', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', + 'R', '\u00B9', '\u0107', '\u00FC', '\u0142', '\u015B', '\u2019', + '\\', '\u00F7', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\u00B2', + '\u014C', '\u00D6', '\u0145', '\u00D3', '\u00D5', '0', '1', '2', + '3', '4', '5', '6', '7', '8', '9', '\u00B3', '\u0106', '\u00DC', + '\u0141', '\u015A', '\u009F', + }; + + public String getName() { + return NAME; + } + + public String getDescription() { + return DESCR; + } + + public String getEncoding() { + return NAME; + } + + @Override + protected char[] getCodePage() { + return codepage; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/CCSID1140.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/CCSID1140.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,96 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * Alternative (extended) implementation of a codepage converter CCSID 1140<->Unicode. + * + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding.builtin; + +/** + * @author master_jaf + * @see http://www-01.ibm.com/software/globalization/ccsid/ccsid1140.jsp + */ +public final class CCSID1140 extends CodepageConverterAdapter { + + public final static String NAME = "1140"; + public final static String DESCR = "ECECP: USA, Canada, Netherlands, Portugal, Brazil, Australia, New Zealand"; + + /* + * Char maps manually extracted from JTOpen v6.4. Because char maps can't be + * covered by any license, this should legal. + */ + private static final char[] codepage = { '\u0000', '\u0001', '\u0002', + '\u0003', '\u009C', '\t', '\u0086', '\u007F', '\u0097', '\u008D', + '\u008E', '\u000B', '\f', '\r', '\u000E', '\u000F', '\u0010', + '\u0011', '\u0012', '\u0013', '\u009D', '\u0085', '\u0008', + '\u0087', '\u0018', '\u0019', '\u0092', '\u008F', '\u001C', + '\u001D', '\u001E', '\u001F', '\u0080', '\u0081', '\u0082', + '\u0083', '\u0084', '\n', '\u0017', '\u001B', '\u0088', '\u0089', + '\u008A', '\u008B', '\u008C', '\u0005', '\u0006', '\u0007', + '\u0090', '\u0091', '\u0016', '\u0093', '\u0094', '\u0095', + '\u0096', '\u0004', '\u0098', '\u0099', '\u009A', '\u009B', + '\u0014', '\u0015', '\u009E', '\u001A', ' ', '\u00A0', '\u00E2', + '\u00E4', '\u00E0', '\u00E1', '\u00E3', '\u00E5', '\u00E7', + '\u00F1', '\u00A2', '.', '<', '(', '+', '|', '&', '\u00E9', + '\u00EA', '\u00EB', '\u00E8', '\u00ED', '\u00EE', '\u00EF', + '\u00EC', '\u00DF', '!', '$', '*', ')', ';', '\u00AC', '-', '/', + '\u00C2', '\u00C4', '\u00C0', '\u00C1', '\u00C3', '\u00C5', + '\u00C7', '\u00D1', '\u00A6', ',', '%', '_', '>', '?', '\u00F8', + '\u00C9', '\u00CA', '\u00CB', '\u00C8', '\u00CD', '\u00CE', + '\u00CF', '\u00CC', '`', ':', '#', '@', '\'', '=', '"', '\u00D8', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', '\u00AB', '\u00BB', + '\u00F0', '\u00FD', '\u00FE', '\u00B1', '\u00B0', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', '\u00AA', '\u00BA', '\u00E6', + '\u00B8', '\u00C6', '\u20AC', '\u00B5', '~', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '\u00A1', '\u00BF', '\u00D0', '\u00DD', + '\u00DE', '\u00AE', '^', '\u00A3', '\u00A5', '\u00B7', '\u00A9', + '\u00A7', '\u00B6', '\u00BC', '\u00BD', '\u00BE', '[', ']', + '\u00AF', '\u00A8', '\u00B4', '\u00D7', '{', 'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'I', '\u00AD', '\u00F4', '\u00F6', '\u00F2', + '\u00F3', '\u00F5', '}', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', + 'R', '\u00B9', '\u00FB', '\u00FC', '\u00F9', '\u00FA', '\u00FF', + '\\', '\u00F7', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\u00B2', + '\u00D4', '\u00D6', '\u00D2', '\u00D3', '\u00D5', '0', '1', '2', + '3', '4', '5', '6', '7', '8', '9', '\u00B3', '\u00DB', '\u00DC', + '\u00D9', '\u00DA', '\u009F', + }; + + public String getName() { + return NAME; + } + + public String getDescription() { + return DESCR; + } + + public String getEncoding() { + return NAME; + } + + @Override + protected char[] getCodePage() { + return codepage; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/CCSID1141.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/CCSID1141.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,96 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * Alternative (extended) implementation of a codepage converter CCSID 1141<->Unicode. + * + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding.builtin; + +/** + * @author master_jaf + * @see http://www-01.ibm.com/software/globalization/ccsid/ccsid1141.jsp + */ +public final class CCSID1141 extends CodepageConverterAdapter { + + public final static String NAME = "1141"; + public final static String DESCR = "ECECP: Austria, Germany"; + + /* + * Char maps manually extracted from JTOpen v6.4. Because char maps can't be + * covered by any license, this should legal. + */ + private static final char[] codepage = { '\u0000', '\u0001', '\u0002', + '\u0003', '\u009C', '\t', '\u0086', '\u007F', '\u0097', '\u008D', + '\u008E', '\u000B', '\f', '\r', '\u000E', '\u000F', '\u0010', + '\u0011', '\u0012', '\u0013', '\u009D', '\u0085', '\u0008', + '\u0087', '\u0018', '\u0019', '\u0092', '\u008F', '\u001C', + '\u001D', '\u001E', '\u001F', '\u0080', '\u0081', '\u0082', + '\u0083', '\u0084', '\n', '\u0017', '\u001B', '\u0088', '\u0089', + '\u008A', '\u008B', '\u008C', '\u0005', '\u0006', '\u0007', + '\u0090', '\u0091', '\u0016', '\u0093', '\u0094', '\u0095', + '\u0096', '\u0004', '\u0098', '\u0099', '\u009A', '\u009B', + '\u0014', '\u0015', '\u009E', '\u001A', ' ', '\u00A0', '\u00E2', + '{', '\u00E0', '\u00E1', '\u00E3', '\u00E5', '\u00E7', '\u00F1', + '\u00C4', '.', '<', '(', '+', '!', '&', '\u00E9', '\u00EA', + '\u00EB', '\u00E8', '\u00ED', '\u00EE', '\u00EF', '\u00EC', '~', + '\u00DC', '$', '*', ')', ';', '^', '-', '/', '\u00C2', '[', + '\u00C0', '\u00C1', '\u00C3', '\u00C5', '\u00C7', '\u00D1', + '\u00F6', ',', '%', '_', '>', '?', '\u00F8', '\u00C9', '\u00CA', + '\u00CB', '\u00C8', '\u00CD', '\u00CE', '\u00CF', '\u00CC', '`', + ':', '#', '\u00A7', '\'', '=', '"', '\u00D8', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', '\u00AB', '\u00BB', '\u00F0', '\u00FD', + '\u00FE', '\u00B1', '\u00B0', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', '\u00AA', '\u00BA', '\u00E6', '\u00B8', '\u00C6', + '\u20AC', '\u00B5', '\u00DF', 's', 't', 'u', 'v', 'w', 'x', 'y', + 'z', '\u00A1', '\u00BF', '\u00D0', '\u00DD', '\u00DE', '\u00AE', + '\u00A2', '\u00A3', '\u00A5', '\u00B7', '\u00A9', '@', '\u00B6', + '\u00BC', '\u00BD', '\u00BE', '\u00AC', '|', '\u00AF', '\u00A8', + '\u00B4', '\u00D7', '\u00E4', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', '\u00AD', '\u00F4', '\u00A6', '\u00F2', '\u00F3', + '\u00F5', '\u00FC', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + '\u00B9', '\u00FB', '}', '\u00F9', '\u00FA', '\u00FF', '\u00D6', + '\u00F7', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\u00B2', + '\u00D4', '\\', '\u00D2', '\u00D3', '\u00D5', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '\u00B3', '\u00DB', ']', '\u00D9', + '\u00DA', '\u009F', + }; + + public String getName() { + return NAME; + } + + public String getDescription() { + return DESCR; + } + + public String getEncoding() { + return NAME; + } + + @Override + protected char[] getCodePage() { + return codepage; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/CCSID1147.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/CCSID1147.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,96 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * Alternative (extended) implementation of a codepage converter CCSID 1147<->Unicode. + * + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding.builtin; + +/** + * @author master_jaf + * @see http://www-01.ibm.com/software/globalization/ccsid/ccsid1147.jsp + */ +public final class CCSID1147 extends CodepageConverterAdapter { + + public final static String NAME = "1147"; + public final static String DESCR = "ECECP: France"; + + /* + * Char maps manually extracted from JTOpen v6.4. Because char maps can't be + * covered by any license, this should legal. + */ + private static final char[] codepage = { '\u0000', '\u0001', '\u0002', + '\u0003', '\u009C', '\t', '\u0086', '\u007F', '\u0097', '\u008D', + '\u008E', '\u000B', '\f', '\r', '\u000E', '\u000F', '\u0010', + '\u0011', '\u0012', '\u0013', '\u009D', '\u0085', '\u0008', + '\u0087', '\u0018', '\u0019', '\u0092', '\u008F', '\u001C', + '\u001D', '\u001E', '\u001F', '\u0080', '\u0081', '\u0082', + '\u0083', '\u0084', '\n', '\u0017', '\u001B', '\u0088', '\u0089', + '\u008A', '\u008B', '\u008C', '\u0005', '\u0006', '\u0007', + '\u0090', '\u0091', '\u0016', '\u0093', '\u0094', '\u0095', + '\u0096', '\u0004', '\u0098', '\u0099', '\u009A', '\u009B', + '\u0014', '\u0015', '\u009E', '\u001A', ' ', '\u00A0', '\u00E2', + '\u00E4', '@', '\u00E1', '\u00E3', '\u00E5', '\\', '\u00F1', + '\u00B0', '.', '<', '(', '+', '!', '&', '{', '\u00EA', '\u00EB', + '}', '\u00ED', '\u00EE', '\u00EF', '\u00EC', '\u00DF', '\u00A7', + '$', '*', ')', ';', '^', '-', '/', '\u00C2', '\u00C4', '\u00C0', + '\u00C1', '\u00C3', '\u00C5', '\u00C7', '\u00D1', '\u00F9', ',', + '%', '_', '>', '?', '\u00F8', '\u00C9', '\u00CA', '\u00CB', + '\u00C8', '\u00CD', '\u00CE', '\u00CF', '\u00CC', '\u00B5', ':', + '\u00A3', '\u00E0', '\'', '=', '"', '\u00D8', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', '\u00AB', '\u00BB', '\u00F0', '\u00FD', + '\u00FE', '\u00B1', '[', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', + 'r', '\u00AA', '\u00BA', '\u00E6', '\u00B8', '\u00C6', '\u20AC', + '`', '\u00A8', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '\u00A1', + '\u00BF', '\u00D0', '\u00DD', '\u00DE', '\u00AE', '\u00A2', '#', + '\u00A5', '\u00B7', '\u00A9', ']', '\u00B6', '\u00BC', '\u00BD', + '\u00BE', '\u00AC', '|', '\u00AF', '~', '\u00B4', '\u00D7', + '\u00E9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', '\u00AD', + '\u00F4', '\u00F6', '\u00F2', '\u00F3', '\u00F5', '\u00E8', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', '\u00B9', '\u00FB', + '\u00FC', '\u00A6', '\u00FA', '\u00FF', '\u00E7', '\u00F7', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\u00B2', '\u00D4', '\u00D6', + '\u00D2', '\u00D3', '\u00D5', '0', '1', '2', '3', '4', '5', '6', + '7', '8', '9', '\u00B3', '\u00DB', '\u00DC', '\u00D9', '\u00DA', + '\u009F', + }; + + public String getName() { + return NAME; + } + + public String getDescription() { + return DESCR; + } + + public String getEncoding() { + return NAME; + } + + @Override + protected char[] getCodePage() { + return codepage; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/CCSID1148.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/CCSID1148.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,96 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * Alternative (extended) implementation of a codepage converter CCSID 1148<->Unicode. + * + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding.builtin; + +/** + * @author master_jaf + * @see http://www-01.ibm.com/software/globalization/ccsid/ccsid1148.jsp + */ +public final class CCSID1148 extends CodepageConverterAdapter { + + public final static String NAME = "1148"; + public final static String DESCR = "ECECP: International 1"; + + /* + * Char maps manually extracted from JTOpen v6.4. Because char maps can't be + * covered by any license, this should legal. + */ + private static final char[] codepage = { '\u0000', '\u0001', '\u0002', + '\u0003', '\u009C', '\t', '\u0086', '\u007F', '\u0097', '\u008D', + '\u008E', '\u000B', '\f', '\r', '\u000E', '\u000F', '\u0010', + '\u0011', '\u0012', '\u0013', '\u009D', '\u0085', '\u0008', + '\u0087', '\u0018', '\u0019', '\u0092', '\u008F', '\u001C', + '\u001D', '\u001E', '\u001F', '\u0080', '\u0081', '\u0082', + '\u0083', '\u0084', '\n', '\u0017', '\u001B', '\u0088', '\u0089', + '\u008A', '\u008B', '\u008C', '\u0005', '\u0006', '\u0007', + '\u0090', '\u0091', '\u0016', '\u0093', '\u0094', '\u0095', + '\u0096', '\u0004', '\u0098', '\u0099', '\u009A', '\u009B', + '\u0014', '\u0015', '\u009E', '\u001A', ' ', '\u00A0', '\u00E2', + '\u00E4', '\u00E0', '\u00E1', '\u00E3', '\u00E5', '\u00E7', + '\u00F1', '[', '.', '<', '(', '+', '!', '&', '\u00E9', '\u00EA', + '\u00EB', '\u00E8', '\u00ED', '\u00EE', '\u00EF', '\u00EC', + '\u00DF', ']', '$', '*', ')', ';', '^', '-', '/', '\u00C2', + '\u00C4', '\u00C0', '\u00C1', '\u00C3', '\u00C5', '\u00C7', + '\u00D1', '\u00A6', ',', '%', '_', '>', '?', '\u00F8', '\u00C9', + '\u00CA', '\u00CB', '\u00C8', '\u00CD', '\u00CE', '\u00CF', + '\u00CC', '`', ':', '#', '@', '\'', '=', '"', '\u00D8', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', 'i', '\u00AB', '\u00BB', '\u00F0', + '\u00FD', '\u00FE', '\u00B1', '\u00B0', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', '\u00AA', '\u00BA', '\u00E6', '\u00B8', + '\u00C6', '\u20AC', '\u00B5', '~', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '\u00A1', '\u00BF', '\u00D0', '\u00DD', '\u00DE', + '\u00AE', '\u00A2', '\u00A3', '\u00A5', '\u00B7', '\u00A9', + '\u00A7', '\u00B6', '\u00BC', '\u00BD', '\u00BE', '\u00AC', '|', + '\u00AF', '\u00A8', '\u00B4', '\u00D7', '{', 'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'I', '\u00AD', '\u00F4', '\u00F6', '\u00F2', + '\u00F3', '\u00F5', '}', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', + 'R', '\u00B9', '\u00FB', '\u00FC', '\u00F9', '\u00FA', '\u00FF', + '\\', '\u00F7', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\u00B2', + '\u00D4', '\u00D6', '\u00D2', '\u00D3', '\u00D5', '0', '1', '2', + '3', '4', '5', '6', '7', '8', '9', '\u00B3', '\u00DB', '\u00DC', + '\u00D9', '\u00DA', '\u009F', + }; + + public String getName() { + return NAME; + } + + public String getDescription() { + return DESCR; + } + + public String getEncoding() { + return NAME; + } + + @Override + protected char[] getCodePage() { + return codepage; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/CCSID273.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/CCSID273.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,98 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * Alternative (extended) implementation of a codepage converter CCSID 273<->Unicode. + * + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding.builtin; + + +/** + * @author master_jaf + * @see http://www-01.ibm.com/software/globalization/ccsid/ccsid273.jsp + */ +public final class CCSID273 extends CodepageConverterAdapter { + + public final static String NAME = "273"; + public final static String DESCR = "CECP: Austria, Germany"; + + /* + * Char maps manually extracted from JTOpen v6.4. Because char maps can't be + * covered by any license, this should legal. + */ + private static final char[] codepage = { '\u0000', '\u0001', '\u0002', + '\u0003', '\u009C', '\t', '\u0086', '\u007F', '\u0097', '\u008D', + '\u008E', '\u000B', '\f', '\r', '\u000E', '\u000F', '\u0010', + '\u0011', '\u0012', '\u0013', '\u009D', '\u0085', '\u0008', + '\u0087', '\u0018', '\u0019', '\u0092', '\u008F', '\u001C', + '\u001D', '\u001E', '\u001F', '\u0080', '\u0081', '\u0082', + '\u0083', '\u0084', '\n', '\u0017', '\u001B', '\u0088', '\u0089', + '\u008A', '\u008B', '\u008C', '\u0005', '\u0006', '\u0007', + '\u0090', '\u0091', '\u0016', '\u0093', '\u0094', '\u0095', + '\u0096', '\u0004', '\u0098', '\u0099', '\u009A', '\u009B', + '\u0014', '\u0015', '\u009E', '\u001A', ' ', '\u00A0', '\u00E2', + '{', '\u00E0', '\u00E1', '\u00E3', '\u00E5', '\u00E7', '\u00F1', + '\u00C4', '.', '<', '(', '+', '!', '&', '\u00E9', '\u00EA', + '\u00EB', '\u00E8', '\u00ED', '\u00EE', '\u00EF', '\u00EC', '~', + '\u00DC', '$', '*', ')', ';', '^', '-', '/', '\u00C2', '[', + '\u00C0', '\u00C1', '\u00C3', '\u00C5', '\u00C7', '\u00D1', + '\u00F6', ',', '%', '_', '>', '?', '\u00F8', '\u00C9', '\u00CA', + '\u00CB', '\u00C8', '\u00CD', '\u00CE', '\u00CF', '\u00CC', '`', + ':', '#', '\u00A7', '\'', '=', '"', '\u00D8', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', '\u00AB', '\u00BB', '\u00F0', '\u00FD', + '\u00FE', '\u00B1', '\u00B0', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', '\u00AA', '\u00BA', '\u00E6', '\u00B8', '\u00C6', + '\u00A4', '\u00B5', '\u00DF', 's', 't', 'u', 'v', 'w', 'x', 'y', + 'z', '\u00A1', '\u00BF', '\u00D0', '\u00DD', '\u00DE', '\u00AE', + '\u00A2', '\u00A3', '\u00A5', '\u00B7', '\u00A9', '@', '\u00B6', + '\u00BC', '\u00BD', '\u00BE', '\u00AC', '|', '\u00AF', '\u00A8', + '\u00B4', '\u00D7', '\u00E4', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', '\u00AD', '\u00F4', '\u00A6', '\u00F2', '\u00F3', + '\u00F5', '\u00FC', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + '\u00B9', '\u00FB', '}', '\u00F9', '\u00FA', '\u00FF', '\u00D6', + '\u00F7', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\u00B2', + '\u00D4', '\\', '\u00D2', '\u00D3', '\u00D5', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '\u00B3', '\u00DB', ']', '\u00D9', + '\u00DA', '\u009F', + }; + + + public String getName() { + return NAME; + } + + public String getDescription() { + return DESCR; + } + + public String getEncoding() { + return NAME; + } + + @Override + protected char[] getCodePage() { + return codepage; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/CCSID277.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/CCSID277.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,96 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * Alternative (extended) implementation of a codepage converter CCSID 277<->Unicode. + * + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding.builtin; + +/** + * @author master_jaf + * @see http://www-01.ibm.com/software/globalization/ccsid/ccsid277.jsp + */ +public final class CCSID277 extends CodepageConverterAdapter { + + public final static String NAME = "277"; + public final static String DESCR = "CECP: Denmark, Norway"; + + /* + * Char maps manually extracted from JTOpen v6.4. Because char maps can't be + * covered by any license, this should legal. + */ + private static final char[] codepage = { '\u0000', '\u0001', '\u0002', + '\u0003', '\u009C', '\t', '\u0086', '\u007F', '\u0097', '\u008D', + '\u008E', '\u000B', '\f', '\r', '\u000E', '\u000F', '\u0010', + '\u0011', '\u0012', '\u0013', '\u009D', '\u0085', '\u0008', + '\u0087', '\u0018', '\u0019', '\u0092', '\u008F', '\u001C', + '\u001D', '\u001E', '\u001F', '\u0080', '\u0081', '\u0082', + '\u0083', '\u0084', '\n', '\u0017', '\u001B', '\u0088', '\u0089', + '\u008A', '\u008B', '\u008C', '\u0005', '\u0006', '\u0007', + '\u0090', '\u0091', '\u0016', '\u0093', '\u0094', '\u0095', + '\u0096', '\u0004', '\u0098', '\u0099', '\u009A', '\u009B', + '\u0014', '\u0015', '\u009E', '\u001A', ' ', '\u00A0', '\u00E2', + '\u00E4', '\u00E0', '\u00E1', '\u00E3', '}', '\u00E7', '\u00F1', + '#', '.', '<', '(', '+', '!', '&', '\u00E9', '\u00EA', '\u00EB', + '\u00E8', '\u00ED', '\u00EE', '\u00EF', '\u00EC', '\u00DF', + '\u00A4', '\u00C5', '*', ')', ';', '^', '-', '/', '\u00C2', + '\u00C4', '\u00C0', '\u00C1', '\u00C3', '$', '\u00C7', '\u00D1', + '\u00F8', ',', '%', '_', '>', '?', '\u00A6', '\u00C9', '\u00CA', + '\u00CB', '\u00C8', '\u00CD', '\u00CE', '\u00CF', '\u00CC', '`', + ':', '\u00C6', '\u00D8', '\'', '=', '"', '@', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', '\u00AB', '\u00BB', '\u00F0', '\u00FD', + '\u00FE', '\u00B1', '\u00B0', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', '\u00AA', '\u00BA', '{', '\u00B8', '[', ']', '\u00B5', + '\u00FC', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '\u00A1', + '\u00BF', '\u00D0', '\u00DD', '\u00DE', '\u00AE', '\u00A2', + '\u00A3', '\u00A5', '\u00B7', '\u00A9', '\u00A7', '\u00B6', + '\u00BC', '\u00BD', '\u00BE', '\u00AC', '|', '\u00AF', '\u00A8', + '\u00B4', '\u00D7', '\u00E6', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', '\u00AD', '\u00F4', '\u00F6', '\u00F2', '\u00F3', + '\u00F5', '\u00E5', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + '\u00B9', '\u00FB', '~', '\u00F9', '\u00FA', '\u00FF', '\\', + '\u00F7', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\u00B2', + '\u00D4', '\u00D6', '\u00D2', '\u00D3', '\u00D5', '0', '1', '2', + '3', '4', '5', '6', '7', '8', '9', '\u00B3', '\u00DB', '\u00DC', + '\u00D9', '\u00DA', '\u009F', + }; + + public String getName() { + return NAME; + } + + public String getDescription() { + return DESCR; + } + + public String getEncoding() { + return NAME; + } + + @Override + protected char[] getCodePage() { + return codepage; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/CCSID278.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/CCSID278.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,96 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * Alternative (extended) implementation of a codepage converter CCSID 278<->Unicode. + * + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding.builtin; + +/** + * @author master_jaf + * @see http://www-01.ibm.com/software/globalization/ccsid/ccsid278.jsp + */ +public final class CCSID278 extends CodepageConverterAdapter { + + public final static String NAME = "278"; + public final static String DESCR = "CECP: Finland, Sweden"; + + /* + * Char maps manually extracted from JTOpen v6.4. Because char maps can't be + * covered by any license, this should legal. + */ + private static final char[] codepage = { '\u0000', '\u0001', '\u0002', + '\u0003', '\u009C', '\t', '\u0086', '\u007F', '\u0097', '\u008D', + '\u008E', '\u000B', '\f', '\r', '\u000E', '\u000F', '\u0010', + '\u0011', '\u0012', '\u0013', '\u009D', '\u0085', '\u0008', + '\u0087', '\u0018', '\u0019', '\u0092', '\u008F', '\u001C', + '\u001D', '\u001E', '\u001F', '\u0080', '\u0081', '\u0082', + '\u0083', '\u0084', '\n', '\u0017', '\u001B', '\u0088', '\u0089', + '\u008A', '\u008B', '\u008C', '\u0005', '\u0006', '\u0007', + '\u0090', '\u0091', '\u0016', '\u0093', '\u0094', '\u0095', + '\u0096', '\u0004', '\u0098', '\u0099', '\u009A', '\u009B', + '\u0014', '\u0015', '\u009E', '\u001A', ' ', '\u00A0', '\u00E2', + '{', '\u00E0', '\u00E1', '\u00E3', '}', '\u00E7', '\u00F1', + '\u00A7', '.', '<', '(', '+', '!', '&', '`', '\u00EA', '\u00EB', + '\u00E8', '\u00ED', '\u00EE', '\u00EF', '\u00EC', '\u00DF', + '\u00A4', '\u00C5', '*', ')', ';', '^', '-', '/', '\u00C2', '#', + '\u00C0', '\u00C1', '\u00C3', '$', '\u00C7', '\u00D1', '\u00F6', + ',', '%', '_', '>', '?', '\u00F8', '\\', '\u00CA', '\u00CB', + '\u00C8', '\u00CD', '\u00CE', '\u00CF', '\u00CC', '\u00E9', ':', + '\u00C4', '\u00D6', '\'', '=', '"', '\u00D8', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', '\u00AB', '\u00BB', '\u00F0', '\u00FD', + '\u00FE', '\u00B1', '\u00B0', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', '\u00AA', '\u00BA', '\u00E6', '\u00B8', '\u00C6', ']', + '\u00B5', '\u00FC', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '\u00A1', '\u00BF', '\u00D0', '\u00DD', '\u00DE', '\u00AE', + '\u00A2', '\u00A3', '\u00A5', '\u00B7', '\u00A9', '[', '\u00B6', + '\u00BC', '\u00BD', '\u00BE', '\u00AC', '|', '\u00AF', '\u00A8', + '\u00B4', '\u00D7', '\u00E4', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', '\u00AD', '\u00F4', '\u00A6', '\u00F2', '\u00F3', + '\u00F5', '\u00E5', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + '\u00B9', '\u00FB', '~', '\u00F9', '\u00FA', '\u00FF', '\u00C9', + '\u00F7', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\u00B2', + '\u00D4', '@', '\u00D2', '\u00D3', '\u00D5', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '\u00B3', '\u00DB', '\u00DC', + '\u00D9', '\u00DA', '\u009F' + }; + + public String getName() { + return NAME; + } + + public String getDescription() { + return DESCR; + } + + public String getEncoding() { + return NAME; + } + + @Override + protected char[] getCodePage() { + return codepage; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/CCSID280.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/CCSID280.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,96 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * Alternative (extended) implementation of a codepage converter CCSID 280<->Unicode. + * + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding.builtin; + +/** + * @author master_jaf + * @see http://www-01.ibm.com/software/globalization/ccsid/ccsid280.jsp + */ +public final class CCSID280 extends CodepageConverterAdapter { + + public final static String NAME = "280"; + public final static String DESCR = "CECP: Italy"; + + /* + * Char maps manually extracted from JTOpen v6.4. Because char maps can't be + * covered by any license, this should legal. + */ + private static final char[] codepage = { '\u0000', '\u0001', '\u0002', + '\u0003', '\u009C', '\t', '\u0086', '\u007F', '\u0097', '\u008D', + '\u008E', '\u000B', '\f', '\r', '\u000E', '\u000F', '\u0010', + '\u0011', '\u0012', '\u0013', '\u009D', '\u0085', '\u0008', + '\u0087', '\u0018', '\u0019', '\u0092', '\u008F', '\u001C', + '\u001D', '\u001E', '\u001F', '\u0080', '\u0081', '\u0082', + '\u0083', '\u0084', '\n', '\u0017', '\u001B', '\u0088', '\u0089', + '\u008A', '\u008B', '\u008C', '\u0005', '\u0006', '\u0007', + '\u0090', '\u0091', '\u0016', '\u0093', '\u0094', '\u0095', + '\u0096', '\u0004', '\u0098', '\u0099', '\u009A', '\u009B', + '\u0014', '\u0015', '\u009E', '\u001A', ' ', '\u00A0', '\u00E2', + '\u00E4', '{', '\u00E1', '\u00E3', '\u00E5', '\\', '\u00F1', + '\u00B0', '.', '<', '(', '+', '!', '&', ']', '\u00EA', '\u00EB', + '}', '\u00ED', '\u00EE', '\u00EF', '~', '\u00DF', '\u00E9', '$', + '*', ')', ';', '^', '-', '/', '\u00C2', '\u00C4', '\u00C0', + '\u00C1', '\u00C3', '\u00C5', '\u00C7', '\u00D1', '\u00F2', ',', + '%', '_', '>', '?', '\u00F8', '\u00C9', '\u00CA', '\u00CB', + '\u00C8', '\u00CD', '\u00CE', '\u00CF', '\u00CC', '\u00F9', ':', + '\u00A3', '\u00A7', '\'', '=', '"', '\u00D8', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', '\u00AB', '\u00BB', '\u00F0', '\u00FD', + '\u00FE', '\u00B1', '[', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', + 'r', '\u00AA', '\u00BA', '\u00E6', '\u00B8', '\u00C6', '\u00A4', + '\u00B5', '\u00EC', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '\u00A1', '\u00BF', '\u00D0', '\u00DD', '\u00DE', '\u00AE', + '\u00A2', '#', '\u00A5', '\u00B7', '\u00A9', '@', '\u00B6', + '\u00BC', '\u00BD', '\u00BE', '\u00AC', '|', '\u00AF', '\u00A8', + '\u00B4', '\u00D7', '\u00E0', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', '\u00AD', '\u00F4', '\u00F6', '\u00A6', '\u00F3', + '\u00F5', '\u00E8', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + '\u00B9', '\u00FB', '\u00FC', '`', '\u00FA', '\u00FF', '\u00E7', + '\u00F7', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\u00B2', + '\u00D4', '\u00D6', '\u00D2', '\u00D3', '\u00D5', '0', '1', '2', + '3', '4', '5', '6', '7', '8', '9', '\u00B3', '\u00DB', '\u00DC', + '\u00D9', '\u00DA', '\u009F' + }; + + public String getName() { + return NAME; + } + + public String getDescription() { + return DESCR; + } + + public String getEncoding() { + return NAME; + } + + @Override + protected char[] getCodePage() { + return codepage; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/CCSID284.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/CCSID284.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,96 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * Alternative (extended) implementation of a codepage converter CCSID 284<->Unicode. + * + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding.builtin; + +/** + * @author master_jaf + * @see http://www-01.ibm.com/software/globalization/ccsid/ccsid284.jsp + */ +public final class CCSID284 extends CodepageConverterAdapter { + + public final static String NAME = "284"; + public final static String DESCR = "CECP: Spain, Latin America (Spanish)"; + + /* + * Char maps manually extracted from JTOpen v6.4. Because char maps can't be + * covered by any license, this should legal. + */ + private static final char[] codepage = { '\u0000', '\u0001', '\u0002', + '\u0003', '\u009C', '\t', '\u0086', '\u007F', '\u0097', '\u008D', + '\u008E', '\u000B', '\f', '\r', '\u000E', '\u000F', '\u0010', + '\u0011', '\u0012', '\u0013', '\u009D', '\u0085', '\u0008', + '\u0087', '\u0018', '\u0019', '\u0092', '\u008F', '\u001C', + '\u001D', '\u001E', '\u001F', '\u0080', '\u0081', '\u0082', + '\u0083', '\u0084', '\n', '\u0017', '\u001B', '\u0088', '\u0089', + '\u008A', '\u008B', '\u008C', '\u0005', '\u0006', '\u0007', + '\u0090', '\u0091', '\u0016', '\u0093', '\u0094', '\u0095', + '\u0096', '\u0004', '\u0098', '\u0099', '\u009A', '\u009B', + '\u0014', '\u0015', '\u009E', '\u001A', ' ', '\u00A0', '\u00E2', + '\u00E4', '\u00E0', '\u00E1', '\u00E3', '\u00E5', '\u00E7', + '\u00A6', '[', '.', '<', '(', '+', '|', '&', '\u00E9', '\u00EA', + '\u00EB', '\u00E8', '\u00ED', '\u00EE', '\u00EF', '\u00EC', + '\u00DF', ']', '$', '*', ')', ';', '\u00AC', '-', '/', '\u00C2', + '\u00C4', '\u00C0', '\u00C1', '\u00C3', '\u00C5', '\u00C7', '#', + '\u00F1', ',', '%', '_', '>', '?', '\u00F8', '\u00C9', '\u00CA', + '\u00CB', '\u00C8', '\u00CD', '\u00CE', '\u00CF', '\u00CC', '`', + ':', '\u00D1', '@', '\'', '=', '"', '\u00D8', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', '\u00AB', '\u00BB', '\u00F0', '\u00FD', + '\u00FE', '\u00B1', '\u00B0', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', '\u00AA', '\u00BA', '\u00E6', '\u00B8', '\u00C6', + '\u00A4', '\u00B5', '\u00A8', 's', 't', 'u', 'v', 'w', 'x', 'y', + 'z', '\u00A1', '\u00BF', '\u00D0', '\u00DD', '\u00DE', '\u00AE', + '\u00A2', '\u00A3', '\u00A5', '\u00B7', '\u00A9', '\u00A7', + '\u00B6', '\u00BC', '\u00BD', '\u00BE', '^', '!', '\u00AF', '~', + '\u00B4', '\u00D7', '{', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', '\u00AD', '\u00F4', '\u00F6', '\u00F2', '\u00F3', '\u00F5', + '}', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', '\u00B9', + '\u00FB', '\u00FC', '\u00F9', '\u00FA', '\u00FF', '\\', '\u00F7', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\u00B2', '\u00D4', + '\u00D6', '\u00D2', '\u00D3', '\u00D5', '0', '1', '2', '3', '4', + '5', '6', '7', '8', '9', '\u00B3', '\u00DB', '\u00DC', '\u00D9', + '\u00DA', '\u009F', + }; + + public String getName() { + return NAME; + } + + public String getDescription() { + return DESCR; + } + + public String getEncoding() { + return NAME; + } + + @Override + protected char[] getCodePage() { + return codepage; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/CCSID285.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/CCSID285.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,96 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * Alternative (extended) implementation of a codepage converter CCSID 285<->Unicode. + * + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding.builtin; + +/** + * @author master_jaf + * @see http://www-01.ibm.com/software/globalization/ccsid/ccsid285.jsp + */ +public final class CCSID285 extends CodepageConverterAdapter { + + public final static String NAME = "285"; + public final static String DESCR = "CECP: United Kingdom"; + + /* + * Char maps manually extracted from JTOpen v6.4. Because char maps can't be + * covered by any license, this should legal. + */ + private static final char[] codepage = { '\u0000', '\u0001', '\u0002', + '\u0003', '\u009C', '\t', '\u0086', '\u007F', '\u0097', '\u008D', + '\u008E', '\u000B', '\f', '\r', '\u000E', '\u000F', '\u0010', + '\u0011', '\u0012', '\u0013', '\u009D', '\u0085', '\u0008', + '\u0087', '\u0018', '\u0019', '\u0092', '\u008F', '\u001C', + '\u001D', '\u001E', '\u001F', '\u0080', '\u0081', '\u0082', + '\u0083', '\u0084', '\n', '\u0017', '\u001B', '\u0088', '\u0089', + '\u008A', '\u008B', '\u008C', '\u0005', '\u0006', '\u0007', + '\u0090', '\u0091', '\u0016', '\u0093', '\u0094', '\u0095', + '\u0096', '\u0004', '\u0098', '\u0099', '\u009A', '\u009B', + '\u0014', '\u0015', '\u009E', '\u001A', ' ', '\u00A0', '\u00E2', + '\u00E4', '\u00E0', '\u00E1', '\u00E3', '\u00E5', '\u00E7', + '\u00F1', '$', '.', '<', '(', '+', '|', '&', '\u00E9', '\u00EA', + '\u00EB', '\u00E8', '\u00ED', '\u00EE', '\u00EF', '\u00EC', + '\u00DF', '!', '\u00A3', '*', ')', ';', '\u00AC', '-', '/', + '\u00C2', '\u00C4', '\u00C0', '\u00C1', '\u00C3', '\u00C5', + '\u00C7', '\u00D1', '\u00A6', ',', '%', '_', '>', '?', '\u00F8', + '\u00C9', '\u00CA', '\u00CB', '\u00C8', '\u00CD', '\u00CE', + '\u00CF', '\u00CC', '`', ':', '#', '@', '\'', '=', '"', '\u00D8', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', '\u00AB', '\u00BB', + '\u00F0', '\u00FD', '\u00FE', '\u00B1', '\u00B0', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', '\u00AA', '\u00BA', '\u00E6', + '\u00B8', '\u00C6', '\u00A4', '\u00B5', '\u00AF', 's', 't', 'u', + 'v', 'w', 'x', 'y', 'z', '\u00A1', '\u00BF', '\u00D0', '\u00DD', + '\u00DE', '\u00AE', '\u00A2', '[', '\u00A5', '\u00B7', '\u00A9', + '\u00A7', '\u00B6', '\u00BC', '\u00BD', '\u00BE', '^', ']', '~', + '\u00A8', '\u00B4', '\u00D7', '{', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', '\u00AD', '\u00F4', '\u00F6', '\u00F2', '\u00F3', + '\u00F5', '}', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + '\u00B9', '\u00FB', '\u00FC', '\u00F9', '\u00FA', '\u00FF', '\\', + '\u00F7', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\u00B2', + '\u00D4', '\u00D6', '\u00D2', '\u00D3', '\u00D5', '0', '1', '2', + '3', '4', '5', '6', '7', '8', '9', '\u00B3', '\u00DB', '\u00DC', + '\u00D9', '\u00DA', '\u009F', + }; + + public String getName() { + return NAME; + } + + public String getDescription() { + return DESCR; + } + + public String getEncoding() { + return NAME; + } + + @Override + protected char[] getCodePage() { + return codepage; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/CCSID297.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/CCSID297.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,96 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * Alternative (extended) implementation of a codepage converter CCSID 297<->Unicode. + * + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding.builtin; + +/** + * @author master_jaf + * @see http://www-01.ibm.com/software/globalization/ccsid/ccsid297.jsp + */ +public final class CCSID297 extends CodepageConverterAdapter { + + public final static String NAME = "297"; + public final static String DESCR = "CECP: France"; + + /* + * Char maps manually extracted from JTOpen v6.4. Because char maps can't be + * covered by any license, this should legal. + */ + private static final char[] codepage = { '\u0000', '\u0001', '\u0002', + '\u0003', '\u009C', '\t', '\u0086', '\u007F', '\u0097', '\u008D', + '\u008E', '\u000B', '\f', '\r', '\u000E', '\u000F', '\u0010', + '\u0011', '\u0012', '\u0013', '\u009D', '\u0085', '\u0008', + '\u0087', '\u0018', '\u0019', '\u0092', '\u008F', '\u001C', + '\u001D', '\u001E', '\u001F', '\u0080', '\u0081', '\u0082', + '\u0083', '\u0084', '\n', '\u0017', '\u001B', '\u0088', '\u0089', + '\u008A', '\u008B', '\u008C', '\u0005', '\u0006', '\u0007', + '\u0090', '\u0091', '\u0016', '\u0093', '\u0094', '\u0095', + '\u0096', '\u0004', '\u0098', '\u0099', '\u009A', '\u009B', + '\u0014', '\u0015', '\u009E', '\u001A', ' ', '\u00A0', '\u00E2', + '\u00E4', '@', '\u00E1', '\u00E3', '\u00E5', '\\', '\u00F1', + '\u00B0', '.', '<', '(', '+', '!', '&', '{', '\u00EA', '\u00EB', + '}', '\u00ED', '\u00EE', '\u00EF', '\u00EC', '\u00DF', '\u00A7', + '$', '*', ')', ';', '^', '-', '/', '\u00C2', '\u00C4', '\u00C0', + '\u00C1', '\u00C3', '\u00C5', '\u00C7', '\u00D1', '\u00F9', ',', + '%', '_', '>', '?', '\u00F8', '\u00C9', '\u00CA', '\u00CB', + '\u00C8', '\u00CD', '\u00CE', '\u00CF', '\u00CC', '\u00B5', ':', + '\u00A3', '\u00E0', '\'', '=', '"', '\u00D8', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', '\u00AB', '\u00BB', '\u00F0', '\u00FD', + '\u00FE', '\u00B1', '[', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', + 'r', '\u00AA', '\u00BA', '\u00E6', '\u00B8', '\u00C6', '\u00A4', + '`', '\u00A8', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '\u00A1', + '\u00BF', '\u00D0', '\u00DD', '\u00DE', '\u00AE', '\u00A2', '#', + '\u00A5', '\u00B7', '\u00A9', ']', '\u00B6', '\u00BC', '\u00BD', + '\u00BE', '\u00AC', '|', '\u00AF', '~', '\u00B4', '\u00D7', + '\u00E9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', '\u00AD', + '\u00F4', '\u00F6', '\u00F2', '\u00F3', '\u00F5', '\u00E8', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', '\u00B9', '\u00FB', + '\u00FC', '\u00A6', '\u00FA', '\u00FF', '\u00E7', '\u00F7', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\u00B2', '\u00D4', '\u00D6', + '\u00D2', '\u00D3', '\u00D5', '0', '1', '2', '3', '4', '5', '6', + '7', '8', '9', '\u00B3', '\u00DB', '\u00DC', '\u00D9', '\u00DA', + '\u009F', + }; + + public String getName() { + return NAME; + } + + public String getDescription() { + return DESCR; + } + + public String getEncoding() { + return NAME; + } + + @Override + protected char[] getCodePage() { + return codepage; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/CCSID37.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/CCSID37.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,99 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * Alternative (extended) implementation of a codepage converter CCSID 37<->Unicode. + * + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding.builtin; + + +/** + * @author master_jaf + * @see http://www-01.ibm.com/software/globalization/ccsid/ccsid37.jsp + */ +public final class CCSID37 extends CodepageConverterAdapter { + + public final static String NAME = "37"; + public final static String DESCR = "CECP: USA, Canada (ESA*), Netherlands, Portugal, Brazil, Australia, New Zealand"; + + /* + * Char maps manually extracted from JTOpen v6.4. Because char maps can't be + * covered by any license, this should legal. + */ + private static final char[] codepage = { '\u0000', '\u0001', '\u0002', + '\u0003', '\u009C', '\t', '\u0086', '\u007F', '\u0097', '\u008D', + '\u008E', '\u000B', '\f', '\r', '\u000E', '\u000F', '\u0010', + '\u0011', '\u0012', '\u0013', '\u009D', '\u0085', '\u0008', + '\u0087', '\u0018', '\u0019', '\u0092', '\u008F', '\u001C', + '\u001D', '\u001E', '\u001F', '\u0080', '\u0081', '\u0082', + '\u0083', '\u0084', '\n', '\u0017', '\u001B', '\u0088', '\u0089', + '\u008A', '\u008B', '\u008C', '\u0005', '\u0006', '\u0007', + '\u0090', '\u0091', '\u0016', '\u0093', '\u0094', '\u0095', + '\u0096', '\u0004', '\u0098', '\u0099', '\u009A', '\u009B', + '\u0014', '\u0015', '\u009E', '\u001A', ' ', '\u00A0', '\u00E2', + '\u00E4', '\u00E0', '\u00E1', '\u00E3', '\u00E5', '\u00E7', + '\u00F1', '\u00A2', '.', '<', '(', '+', '|', '&', '\u00E9', + '\u00EA', '\u00EB', '\u00E8', '\u00ED', '\u00EE', '\u00EF', + '\u00EC', '\u00DF', '!', '$', '*', ')', ';', '\u00AC', '-', '/', + '\u00C2', '\u00C4', '\u00C0', '\u00C1', '\u00C3', '\u00C5', + '\u00C7', '\u00D1', '\u00A6', ',', '%', '_', '>', '?', '\u00F8', + '\u00C9', '\u00CA', '\u00CB', '\u00C8', '\u00CD', '\u00CE', + '\u00CF', '\u00CC', '`', ':', '#', '@', '\'', '=', '"', '\u00D8', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', '\u00AB', '\u00BB', + '\u00F0', '\u00FD', '\u00FE', '\u00B1', '\u00B0', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', '\u00AA', '\u00BA', '\u00E6', + '\u00B8', '\u00C6', '\u00A4', '\u00B5', '~', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '\u00A1', '\u00BF', '\u00D0', '\u00DD', + '\u00DE', '\u00AE', '^', '\u00A3', '\u00A5', '\u00B7', '\u00A9', + '\u00A7', '\u00B6', '\u00BC', '\u00BD', '\u00BE', '[', ']', + '\u00AF', '\u00A8', '\u00B4', '\u00D7', '{', 'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'I', '\u00AD', '\u00F4', '\u00F6', '\u00F2', + '\u00F3', '\u00F5', '}', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', + 'R', '\u00B9', '\u00FB', '\u00FC', '\u00F9', '\u00FA', '\u00FF', + '\\', '\u00F7', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\u00B2', + '\u00D4', '\u00D6', '\u00D2', '\u00D3', '\u00D5', '0', '1', '2', + '3', '4', '5', '6', '7', '8', '9', '\u00B3', '\u00DB', '\u00DC', + '\u00D9', '\u00DA', '\u009F', + }; + + + public String getName() { + return NAME; + } + + public String getDescription() { + return DESCR; + } + + public String getEncoding() { + return NAME; + } + + @Override + protected char[] getCodePage() { + return codepage; + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/CCSID424.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/CCSID424.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,96 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * Alternative (extended) implementation of a codepage converter CCSID 424<->Unicode. + * + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding.builtin; + +/** + * @author master_jaf + * @see http://www-01.ibm.com/software/globalization/ccsid/ccsid424.jsp + */ +public final class CCSID424 extends CodepageConverterAdapter { + + public final static String NAME = "424"; + public final static String DESCR = "Hebrew"; + + /* + * Char maps manually extracted from JTOpen v6.4. Because char maps can't be + * covered by any license, this should legal. + */ + private static final char[] codepage = { '\u0000', '\u0001', '\u0002', + '\u0003', '\u009C', '\t', '\u0086', '\u007F', '\u0097', '\u008D', + '\u008E', '\u000B', '\f', '\r', '\u000E', '\u000F', '\u0010', + '\u0011', '\u0012', '\u0013', '\u009D', '\u0085', '\u0008', + '\u0087', '\u0018', '\u0019', '\u0092', '\u008F', '\u001C', + '\u001D', '\u001E', '\u001F', '\u0080', '\u0081', '\u0082', + '\u0083', '\u0084', '\n', '\u0017', '\u001B', '\u0088', '\u0089', + '\u008A', '\u008B', '\u008C', '\u0005', '\u0006', '\u0007', + '\u0090', '\u0091', '\u0016', '\u0093', '\u0094', '\u0095', + '\u0096', '\u0004', '\u0098', '\u0099', '\u009A', '\u009B', + '\u0014', '\u0015', '\u009E', '\u001A', ' ', '\u05D0', '\u05D1', + '\u05D2', '\u05D3', '\u05D4', '\u05D5', '\u05D6', '\u05D7', + '\u05D8', '\u00A2', '.', '<', '(', '+', '|', '&', '\u05D9', + '\u05DA', '\u05DB', '\u05DC', '\u05DD', '\u05DE', '\u05DF', + '\u05E0', '\u05E1', '!', '$', '*', ')', ';', '\u00AC', '-', '/', + '\u05E2', '\u05E3', '\u05E4', '\u05E5', '\u05E6', '\u05E7', + '\u05E8', '\u05E9', '\u00A6', ',', '%', '_', '>', '?', '\u001A', + '\u05EA', '\u001A', '\u001A', '\u00A0', '\u001A', '\u001A', + '\u001A', '\u2017', '`', ':', '#', '@', '\'', '=', '"', '\u001A', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', '\u00AB', '\u00BB', + '\u001A', '\u001A', '\u001A', '\u00B1', '\u00B0', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', '\u001A', '\u001A', '\u20AC', + '\u00B8', '\u20AA', '\u00A4', '\u00B5', '~', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '\u001A', '\u001A', '\u001A', '\u001A', + '\u001A', '\u00AE', '^', '\u00A3', '\u00A5', '\u2022', '\u00A9', + '\u00A7', '\u00B6', '\u00BC', '\u00BD', '\u00BE', '[', ']', + '\u203E', '\u00A8', '\u00B4', '\u00D7', '{', 'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'I', '\u00AD', '\u001A', '\u001A', '\u001A', + '\u001A', '\u001A', '}', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', + 'R', '\u00B9', '\u202D', '\u202E', '\u202C', '\u001A', '\u001A', + '\\', '\u00F7', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\u00B2', + '\u001A', '\u001A', '\u001A', '\u001A', '\u001A', '0', '1', '2', + '3', '4', '5', '6', '7', '8', '9', '\u00B3', '\u202A', '\u202B', + '\u200E', '\u200F', '\u009F', + }; + + public String getName() { + return NAME; + } + + public String getDescription() { + return DESCR; + } + + public String getEncoding() { + return NAME; + } + + @Override + protected char[] getCodePage() { + return codepage; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/CCSID500.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/CCSID500.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,96 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * Alternative (extended) implementation of a codepage converter CCSID 500<->Unicode. + * + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding.builtin; + +/** + * @author master_jaf + * @see http://www-01.ibm.com/software/globalization/ccsid/ccsid500.jsp + */ +public final class CCSID500 extends CodepageConverterAdapter { + + public final static String NAME = "500"; + public final static String DESCR = "CECP: Belgium, Canada (AS/400*), Switzerland, International Latin-1"; + + /* + * Char maps manually extracted from JTOpen v6.4. Because char maps can't be + * covered by any license, this should legal. + */ + private static final char[] codepage = { '\u0000', '\u0001', '\u0002', + '\u0003', '\u009C', '\t', '\u0086', '\u007F', '\u0097', '\u008D', + '\u008E', '\u000B', '\f', '\r', '\u000E', '\u000F', '\u0010', + '\u0011', '\u0012', '\u0013', '\u009D', '\u0085', '\u0008', + '\u0087', '\u0018', '\u0019', '\u0092', '\u008F', '\u001C', + '\u001D', '\u001E', '\u001F', '\u0080', '\u0081', '\u0082', + '\u0083', '\u0084', '\n', '\u0017', '\u001B', '\u0088', '\u0089', + '\u008A', '\u008B', '\u008C', '\u0005', '\u0006', '\u0007', + '\u0090', '\u0091', '\u0016', '\u0093', '\u0094', '\u0095', + '\u0096', '\u0004', '\u0098', '\u0099', '\u009A', '\u009B', + '\u0014', '\u0015', '\u009E', '\u001A', ' ', '\u00A0', '\u00E2', + '\u00E4', '\u00E0', '\u00E1', '\u00E3', '\u00E5', '\u00E7', + '\u00F1', '[', '.', '<', '(', '+', '!', '&', '\u00E9', '\u00EA', + '\u00EB', '\u00E8', '\u00ED', '\u00EE', '\u00EF', '\u00EC', + '\u00DF', ']', '$', '*', ')', ';', '^', '-', '/', '\u00C2', + '\u00C4', '\u00C0', '\u00C1', '\u00C3', '\u00C5', '\u00C7', + '\u00D1', '\u00A6', ',', '%', '_', '>', '?', '\u00F8', '\u00C9', + '\u00CA', '\u00CB', '\u00C8', '\u00CD', '\u00CE', '\u00CF', + '\u00CC', '`', ':', '#', '@', '\'', '=', '"', '\u00D8', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', 'i', '\u00AB', '\u00BB', '\u00F0', + '\u00FD', '\u00FE', '\u00B1', '\u00B0', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', '\u00AA', '\u00BA', '\u00E6', '\u00B8', + '\u00C6', '\u00A4', '\u00B5', '~', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '\u00A1', '\u00BF', '\u00D0', '\u00DD', '\u00DE', + '\u00AE', '\u00A2', '\u00A3', '\u00A5', '\u00B7', '\u00A9', + '\u00A7', '\u00B6', '\u00BC', '\u00BD', '\u00BE', '\u00AC', '|', + '\u00AF', '\u00A8', '\u00B4', '\u00D7', '{', 'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'I', '\u00AD', '\u00F4', '\u00F6', '\u00F2', + '\u00F3', '\u00F5', '}', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', + 'R', '\u00B9', '\u00FB', '\u00FC', '\u00F9', '\u00FA', '\u00FF', + '\\', '\u00F7', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\u00B2', + '\u00D4', '\u00D6', '\u00D2', '\u00D3', '\u00D5', '0', '1', '2', + '3', '4', '5', '6', '7', '8', '9', '\u00B3', '\u00DB', '\u00DC', + '\u00D9', '\u00DA', '\u009F', + }; + + public String getName() { + return NAME; + } + + public String getDescription() { + return DESCR; + } + + public String getEncoding() { + return NAME; + } + + @Override + protected char[] getCodePage() { + return codepage; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/CCSID870.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/CCSID870.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,96 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * Alternative (extended) implementation of a codepage converter CCSID 870<->Unicode. + * + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding.builtin; + +/** + * @author master_jaf + * @see http://www-01.ibm.com/software/globalization/ccsid/ccsid870.jsp + */ +public final class CCSID870 extends CodepageConverterAdapter { + + public final static String NAME = "870"; + public final static String DESCR = "Latin 2 - EBCDIC Multilingual"; + + /* + * Char maps manually extracted from JTOpen v6.4. Because char maps can't be + * covered by any license, this should legal. + */ + private static final char[] codepage = { '\u0000', '\u0001', '\u0002', + '\u0003', '\u009C', '\t', '\u0086', '\u007F', '\u0097', '\u008D', + '\u008E', '\u000B', '\f', '\r', '\u000E', '\u000F', '\u0010', + '\u0011', '\u0012', '\u0013', '\u009D', '\u0085', '\u0008', + '\u0087', '\u0018', '\u0019', '\u0092', '\u008F', '\u001C', + '\u001D', '\u001E', '\u001F', '\u0080', '\u0081', '\u0082', + '\u0083', '\u0084', '\n', '\u0017', '\u001B', '\u0088', '\u0089', + '\u008A', '\u008B', '\u008C', '\u0005', '\u0006', '\u0007', + '\u0090', '\u0091', '\u0016', '\u0093', '\u0094', '\u0095', + '\u0096', '\u0004', '\u0098', '\u0099', '\u009A', '\u009B', + '\u0014', '\u0015', '\u009E', '\u001A', ' ', '\u00A0', '\u00E2', + '\u00E4', '\u0163', '\u00E1', '\u0103', '\u010D', '\u00E7', + '\u0107', '[', '.', '<', '(', '+', '!', '&', '\u00E9', '\u0119', + '\u00EB', '\u016F', '\u00ED', '\u00EE', '\u013E', '\u013A', + '\u00DF', ']', '$', '*', ')', ';', '^', '-', '/', '\u00C2', + '\u00C4', '\u02DD', '\u00C1', '\u0102', '\u010C', '\u00C7', + '\u0106', '|', ',', '%', '_', '>', '?', '\u02C7', '\u00C9', + '\u0118', '\u00CB', '\u016E', '\u00CD', '\u00CE', '\u013D', + '\u0139', '`', ':', '#', '@', '\'', '=', '"', '\u02D8', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', 'i', '\u015B', '\u0148', '\u0111', + '\u00FD', '\u0159', '\u015F', '\u00B0', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', '\u0142', '\u0144', '\u0161', '\u00B8', + '\u02DB', '\u00A4', '\u0105', '~', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '\u015A', '\u0147', '\u0110', '\u00DD', '\u0158', + '\u015E', '\u02D9', '\u0104', '\u017C', '\u0162', '\u017B', + '\u00A7', '\u017E', '\u017A', '\u017D', '\u0179', '\u0141', + '\u0143', '\u0160', '\u00A8', '\u00B4', '\u00D7', '{', 'A', 'B', + 'C', 'D', 'E', 'F', 'G', 'H', 'I', '\u00AD', '\u00F4', '\u00F6', + '\u0155', '\u00F3', '\u0151', '}', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', '\u011A', '\u0171', '\u00FC', '\u0165', '\u00FA', + '\u011B', '\\', '\u00F7', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '\u010F', '\u00D4', '\u00D6', '\u0154', '\u00D3', '\u0150', '0', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '\u010E', '\u0170', + '\u00DC', '\u0164', '\u00DA', '\u009F', + }; + + public String getName() { + return NAME; + } + + public String getDescription() { + return DESCR; + } + + public String getEncoding() { + return NAME; + } + + @Override + protected char[] getCodePage() { + return codepage; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/CCSID871.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/CCSID871.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,96 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * Alternative (extended) implementation of a codepage converter CCSID 871<->Unicode. + * + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding.builtin; + +/** + * @author master_jaf + * @see http://www-01.ibm.com/software/globalization/ccsid/ccsid871.jsp + */ +public final class CCSID871 extends CodepageConverterAdapter { + + public final static String NAME = "871"; + public final static String DESCR = "CECP: Iceland"; + + /* + * Char maps manually extracted from JTOpen v6.4. Because char maps can't be + * covered by any license, this should legal. + */ + private static final char[] codepage = { '\u0000', '\u0001', '\u0002', + '\u0003', '\u009C', '\t', '\u0086', '\u007F', '\u0097', '\u008D', + '\u008E', '\u000B', '\f', '\r', '\u000E', '\u000F', '\u0010', + '\u0011', '\u0012', '\u0013', '\u009D', '\u0085', '\u0008', + '\u0087', '\u0018', '\u0019', '\u0092', '\u008F', '\u001C', + '\u001D', '\u001E', '\u001F', '\u0080', '\u0081', '\u0082', + '\u0083', '\u0084', '\n', '\u0017', '\u001B', '\u0088', '\u0089', + '\u008A', '\u008B', '\u008C', '\u0005', '\u0006', '\u0007', + '\u0090', '\u0091', '\u0016', '\u0093', '\u0094', '\u0095', + '\u0096', '\u0004', '\u0098', '\u0099', '\u009A', '\u009B', + '\u0014', '\u0015', '\u009E', '\u001A', ' ', '\u00A0', '\u00E2', + '\u00E4', '\u00E0', '\u00E1', '\u00E3', '\u00E5', '\u00E7', + '\u00F1', '\u00DE', '.', '<', '(', '+', '!', '&', '\u00E9', + '\u00EA', '\u00EB', '\u00E8', '\u00ED', '\u00EE', '\u00EF', + '\u00EC', '\u00DF', '\u00C6', '$', '*', ')', ';', '\u00D6', '-', + '/', '\u00C2', '\u00C4', '\u00C0', '\u00C1', '\u00C3', '\u00C5', + '\u00C7', '\u00D1', '\u00A6', ',', '%', '_', '>', '?', '\u00F8', + '\u00C9', '\u00CA', '\u00CB', '\u00C8', '\u00CD', '\u00CE', + '\u00CF', '\u00CC', '\u00F0', ':', '#', '\u00D0', '\'', '=', '"', + '\u00D8', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', '\u00AB', + '\u00BB', '`', '\u00FD', '{', '\u00B1', '\u00B0', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', '\u00AA', '\u00BA', '}', '\u00B8', + ']', '\u00A4', '\u00B5', '\u00F6', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '\u00A1', '\u00BF', '@', '\u00DD', '[', '\u00AE', + '\u00A2', '\u00A3', '\u00A5', '\u00B7', '\u00A9', '\u00A7', + '\u00B6', '\u00BC', '\u00BD', '\u00BE', '\u00AC', '|', '\u00AF', + '\u00A8', '\\', '\u00D7', '\u00FE', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', '\u00AD', '\u00F4', '~', '\u00F2', '\u00F3', + '\u00F5', '\u00E6', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + '\u00B9', '\u00FB', '\u00FC', '\u00F9', '\u00FA', '\u00FF', + '\u00B4', '\u00F7', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '\u00B2', '\u00D4', '^', '\u00D2', '\u00D3', '\u00D5', '0', '1', + '2', '3', '4', '5', '6', '7', '8', '9', '\u00B3', '\u00DB', + '\u00DC', '\u00D9', '\u00DA', '\u009F', + }; + + public String getName() { + return NAME; + } + + public String getDescription() { + return DESCR; + } + + public String getEncoding() { + return NAME; + } + + @Override + protected char[] getCodePage() { + return codepage; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/CCSID875.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/CCSID875.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,96 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * Alternative (extended) implementation of a codepage converter CCSID 875<->Unicode. + * + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding.builtin; + +/** + * @author master_jaf + * @see http://www-01.ibm.com/software/globalization/ccsid/ccsid875.jsp + */ +public final class CCSID875 extends CodepageConverterAdapter { + + public final static String NAME = "875"; + public final static String DESCR = "Greek"; + + /* + * Char maps manually extracted from JTOpen v6.4. Because char maps can't be + * covered by any license, this should legal. + */ + private static final char[] codepage = { '\u0000', '\u0001', '\u0002', + '\u0003', '\u009C', '\t', '\u0086', '\u007F', '\u0097', '\u008D', + '\u008E', '\u000B', '\f', '\r', '\u000E', '\u000F', '\u0010', + '\u0011', '\u0012', '\u0013', '\u009D', '\u0085', '\u0008', + '\u0087', '\u0018', '\u0019', '\u0092', '\u008F', '\u001C', + '\u001D', '\u001E', '\u001F', '\u0080', '\u0081', '\u0082', + '\u0083', '\u0084', '\n', '\u0017', '\u001B', '\u0088', '\u0089', + '\u008A', '\u008B', '\u008C', '\u0005', '\u0006', '\u0007', + '\u0090', '\u0091', '\u0016', '\u0093', '\u0094', '\u0095', + '\u0096', '\u0004', '\u0098', '\u0099', '\u009A', '\u009B', + '\u0014', '\u0015', '\u009E', '\u001A', ' ', '\u0391', '\u0392', + '\u0393', '\u0394', '\u0395', '\u0396', '\u0397', '\u0398', + '\u0399', '[', '.', '<', '(', '+', '!', '&', '\u039A', '\u039B', + '\u039C', '\u039D', '\u039E', '\u039F', '\u03A0', '\u03A1', + '\u03A3', ']', '$', '*', ')', ';', '^', '-', '/', '\u03A4', + '\u03A5', '\u03A6', '\u03A7', '\u03A8', '\u03A9', '\u03AA', + '\u03AB', '|', ',', '%', '_', '>', '?', '\u00A8', '\u0386', + '\u0388', '\u0389', '\u00A0', '\u038A', '\u038C', '\u038E', + '\u038F', '`', ':', '#', '@', '\'', '=', '"', '\u0385', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', 'i', '\u03B1', '\u03B2', '\u03B3', + '\u03B4', '\u03B5', '\u03B6', '\u00B0', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', '\u03B7', '\u03B8', '\u03B9', '\u03BA', + '\u03BB', '\u03BC', '\u00B4', '~', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '\u03BD', '\u03BE', '\u03BF', '\u03C0', '\u03C1', + '\u03C3', '\u00A3', '\u03AC', '\u03AD', '\u03AE', '\u03CA', + '\u03AF', '\u03CC', '\u03CD', '\u03CB', '\u03CE', '\u03C2', + '\u03C4', '\u03C5', '\u03C6', '\u03C7', '\u03C8', '{', 'A', 'B', + 'C', 'D', 'E', 'F', 'G', 'H', 'I', '\u00AD', '\u03C9', '\u0390', + '\u03B0', '\u2018', '\u2015', '}', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', '\u00B1', '\u00BD', '\u001A', '\u0387', '\u2019', + '\u00A6', '\\', '\u001A', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '\u00B2', '\u00A7', '\u001A', '\u001A', '\u00AB', '\u00AC', '0', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '\u00B3', '\u00A9', + '\u001A', '\u001A', '\u00BB', '\u009F', + }; + + public String getName() { + return NAME; + } + + public String getDescription() { + return DESCR; + } + + public String getEncoding() { + return NAME; + } + + @Override + protected char[] getCodePage() { + return codepage; + } +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/CodepageConverterAdapter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/CodepageConverterAdapter.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,85 @@ +/** + * $Id$ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.encoding.builtin; + +import java.util.Arrays; + +/** + * Adapter class for converters using 8bit codepages. + * + * @author master_jaf + */ +public abstract class CodepageConverterAdapter implements ICodepageConverter { + + private char[] codepage = null; + private int[] reverse_codepage = null; + + /* (non-Javadoc) + * @see org.tn5250j.cp.ICodepageConverter#init() + */ + public ICodepageConverter init() { + codepage = getCodePage(); + int size = 0; + + for (int i = 0; i < codepage.length; i++) { + size = Math.max(size, codepage[i]); + } + + assert(size + 1) < 1024 * 1024; // some kind of maximum size limiter. + reverse_codepage = new int[size + 1]; + Arrays.fill(reverse_codepage, '?'); + + for (int i = 0; i < codepage.length; i++) { + reverse_codepage[codepage[i]] = i; + } + + return this; + } + + /* (non-Javadoc) + * @see org.tn5250j.cp.ICodepageConverter#uni2ebcdic(char) + */ + public byte uni2ebcdic(char index) { + assert index < reverse_codepage.length; + return (byte)reverse_codepage[index]; + } + + /* (non-Javadoc) + * @see org.tn5250j.cp.ICodepageConverter#ebcdic2uni(int) + */ + public char ebcdic2uni(int index) { + index = index & 0xFF; + assert index < 256; + return codepage[index]; + } + + /** + * @return The oringal 8bit codepage. + */ + protected abstract char[] getCodePage(); + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/encoding/builtin/ICodepageConverter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/encoding/builtin/ICodepageConverter.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,34 @@ +package org.tn5250j.encoding.builtin; + +import org.tn5250j.encoding.ICodePage; + +/** + * Interface for classes which do the translation from + * EBCDIC bytes to Unicode characters and vice versa. + * + */ +public interface ICodepageConverter extends ICodePage { + + /** + * Returns an name/ID for this converter. + * Example '273' or 'CP1252'. This name should be unique, + * cause it's used in user settungs and so on. + * + * @return + */ + public abstract String getName(); + + /** + * Returns a short description for this converter. + * For Example '273 - German, EBCDIC' + * + * @return + */ + public abstract String getDescription(); + + /** + * Does special initialization stuff for this converter. + */ + public abstract ICodepageConverter init(); + +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/event/ScreenOIAListener.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/event/ScreenOIAListener.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,45 @@ +/** + * + *

Title: ScreenOIAListener

+ *

Description: Main interface to draw the graphical image of the screen

+ *

Copyright: Copyright (c) 2000 - 2002

+ *

+ * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + *

+ * @author Kenneth J. Pouncey + * @version 0.5 + */ + +package org.tn5250j.event; + +import org.tn5250j.framework.tn5250.ScreenOIA; + +public interface ScreenOIAListener { + + public static final int OIA_CHANGED_INSERT_MODE = 0; + public static final int OIA_CHANGED_KEYS_BUFFERED = 1; + public static final int OIA_CHANGED_KEYBOARD_LOCKED = 2; + public static final int OIA_CHANGED_MESSAGELIGHT = 3; + public static final int OIA_CHANGED_SCRIPT = 4; + public static final int OIA_CHANGED_BELL = 5; + public static final int OIA_CHANGED_CLEAR_SCREEN = 6; + public static final int OIA_CHANGED_INPUTINHIBITED = 7; + public static final int OIA_CHANGED_CURSOR = 8; + + + public void onOIAChanged(ScreenOIA oia, int change); + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/framework/tn5250/DataStreamProducer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/framework/tn5250/DataStreamProducer.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,372 @@ +package org.tn5250j.framework.tn5250; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.SocketException; +import java.util.concurrent.BlockingQueue; +import java.util.Timer; +import java.util.TimerTask; + +import org.tn5250j.encoding.ICodePage; + +import android.util.Log; + + +public class DataStreamProducer implements Runnable { + private static final String TAG = "DataStreamProducer"; + private tnvt vt; + private BufferedInputStream bin; + private ByteArrayOutputStream baosin; + private Thread me; + private byte[] saveStream; + private final BlockingQueue dsq; + private byte[] abyte2; + private FileOutputStream fw; + private BufferedOutputStream dw; + private boolean dumpBytes = false; + private ICodePage codePage; + + + + public DataStreamProducer(tnvt vt, BufferedInputStream bin, BlockingQueue queue, byte[] init) { + this.bin = bin; + this.vt = vt; + baosin = new ByteArrayOutputStream(); + dsq = queue; + abyte2 = init; + } + + public void setInputStream(ByteArrayOutputStream is) { + baosin = is; + } + + public final void run() { + boolean done = false; + me = Thread.currentThread(); + // load the first response screen + loadStream(abyte2, 0); + + while (!done) { + try { + byte[] abyte0 = readIncoming(); + + // WVL - LDC : 17/05/2004 : Device name negotiations send TIMING MARK + // Restructured to the readIncoming() method to return null + // on TIMING MARK. Don't process in that case (abyte0 == null)! + if (abyte0 != null) { + // WVL - LDC : 16/07/2003 : TR.000345 + // When the socket has been closed, the reading returns + // no bytes (an empty byte arrray). + // But the loadStream fails on this, so we check it here! + if (abyte0.length > 0) { + loadStream(abyte0, 0); + } + // WVL - LDC : 16/07/2003 : TR.000345 + // Returning no bytes means the input buffer has + // reached end-of-stream, so we do a disconnect! + else { + done = true; + vt.disconnect(); + } + } + } + catch (SocketException se) { + Log.w(TAG, " DataStreamProducer thread interrupted and stopping " + se.getMessage()); + done = true; + } + catch (IOException ioe) { + Log.w(TAG, ioe.getMessage()); + + if (me.isInterrupted()) + done = true; + } + catch (Exception ex) { + Log.w(TAG, ex.getMessage()); + + if (me.isInterrupted()) + done = true; + } + } + } + + private final void loadStream(byte abyte0[], int i) { + int j = 0; + int size = 0; + + if (saveStream == null) { + j = (abyte0[i] & 0xff) << 8 | abyte0[i + 1] & 0xff; + size = abyte0.length; + } + else { + size = saveStream.length + abyte0.length; + byte[] inter = new byte[size]; + System.arraycopy(saveStream, 0, inter, 0, saveStream.length); + System.arraycopy(abyte0, 0, inter, saveStream.length, abyte0.length); + abyte0 = new byte[size]; + System.arraycopy(inter, 0, abyte0, 0, size); + saveStream = null; + inter = null; + j = (abyte0[i] & 0xff) << 8 | abyte0[i + 1] & 0xff; + Log.d(TAG, "partial stream found"); + } + + if (j > size) { + saveStream = new byte[abyte0.length]; + System.arraycopy(abyte0, 0, saveStream, 0, abyte0.length); + Log.d(TAG, "partial stream saved"); + } + else { + byte abyte1[]; + + try { + abyte1 = new byte[j + 2]; + System.arraycopy(abyte0, i, abyte1, 0, j + 2); + dsq.put(abyte1); + + if (abyte0.length > abyte1.length + i) + loadStream(abyte0, i + j + 2); + } + catch (Exception ex) { + Log.w(TAG, "load stream error " + ex.getMessage()); + // ex.printStackTrace(); + // dump(abyte0); + } + } + } + + public final byte[] readIncoming() + throws IOException { + boolean done = false; + boolean negotiate = false; + baosin.reset(); + int j = -1; + int i = 0; + Timer timer = new Timer("data.stream", true); + TimerTask task = null; + + while (!done) { + if (bin.available() == 0) { + task = new TimerTask() { + public void run() { + try { + dsq.put(new Integer(0)); // trigger buffer.testChanged() + } + catch (Exception ex) { + Log.w(TAG, "readIncoming error " + ex.getMessage()); + } + } + }; + timer.schedule(task, 10); // 10 ms delay + } + + i = bin.read(); + + if (task != null) { + task.cancel(); + task = null; + } + + // WVL - LDC : 16/07/2003 : TR.000345 + // The inStream return -1 when end-of-stream is reached. This + // happens e.g. when the connection is closed from the AS/400. + // So we stop in this case! + // ==> an empty byte array is returned from this method. + if (i == -1) { // nothing read! + done = true; + vt.disconnect(); + continue; + } + + // We use the values instead of the static values IAC and EOR + // because they are defined as bytes. + // + // The > if(i != 255 || j != 255) < is a hack for the double FF FF's + // that are being returned. I do not know why this is like this and + // can not find any documentation for it. It is also being returned + // on my Client Access tcp dump as well so they are handling it. + // + // my5250 + // 0000: 00 50 DA 44 C8 45 42 00 00 00 00 24 08 00 45 00 .P.D.EB....$..E. + // 0010: 04 2A BC F9 00 00 40 06 D0 27 C1 A8 33 04 C1 A8 .*....@..'..3... + // 0020: 33 58 00 17 04 18 6F A2 83 CB 00 1E D1 BA 50 18 3X....o.......P. + // 0030: 20 00 8A 9A 00 00 03 FF FF 12 A0 00 00 04 00 00 ............... + // --------------------------- || || ------------------------------------- + // 0040: 03 04 40 04 11 00 20 01 07 00 00 00 18 00 00 10 ..@... ......... + + if (j == 255 && i == 255) { + j = -1; + continue; + } + + baosin.write(i); + + // check for end of record EOR and IAC - FFEF + if (j == 255 && i == 239) + done = true; + + // This is to check for the TELNET TIMING MARK OPTION + // rfc860 explains this in more detail. When we receive it + // we will negotiate with the server by sending a WONT'T TIMING-MARK + // This will let the server know that we processed the information + // and are just waiting for the user to enter some data so keep the + // socket alive. This is more or less a AYT (ARE YOU THERE) or not. + if (i == 253 && j == 255) { + done = true; + negotiate = true; + } + + j = i; + } + + // after the initial negotiation we might get other options such as + // timing marks ?????????????? do we ???????????? look at telnet spec + // yes we do. rfc860 explains about timing marks. + // WVL - LDC : 17/05/2004 : Device name negotiations send TIMING MARK + // to existing device! + // Handled incorrectly: we cannot continue processing the TIMING MARK DO + // after we have handled it in the vt.negotiate() + // We should not return the bytes; + // ==> restructured to return null after negotiation! + // Impacts the run method! Added the null check. + byte[] rBytes = baosin.toByteArray(); + + if (dumpBytes) { + dump(rBytes); + } + + if (negotiate) { + if (bin.available() == 0) { + task = new TimerTask() { + public void run() { + try { + dsq.put(new Integer(0)); // trigger buffer.testChanged() + } + catch (Exception ex) { + Log.w(TAG, "readIncoming error " + ex.getMessage()); + } + } + }; + timer.schedule(task, 10); // 10 ms delay + } + + // get the negotiation option + baosin.write(bin.read()); + + if (task != null) { + task.cancel(); + task = null; + } + + vt.negotiate(rBytes); + return null; + } + + return rBytes; + } + + protected final void toggleDebug(ICodePage cp) { + if (codePage == null) + codePage = cp; + + dumpBytes = !dumpBytes; + + if (dumpBytes) { + try { + if (fw == null) { + fw = new FileOutputStream("log.txt"); + dw = new BufferedOutputStream(fw); + } + } + catch (FileNotFoundException fnfe) { + Log.w(TAG, fnfe.getMessage()); + } + } + else { + try { + if (dw != null) + dw.close(); + + if (fw != null) + fw.close(); + + dw = null; + fw = null; + codePage = null; + } + catch (IOException ioe) { + Log.w(TAG, ioe.getMessage()); + } + } + + Log.i(TAG, "Data Stream output is now " + dumpBytes); + } + + public void dump(byte[] abyte0) { + try { + Log.i(TAG, "\n Buffer Dump of data from AS400: "); + dw.write("\r\n Buffer Dump of data from AS400: ".getBytes()); + StringBuffer h = new StringBuffer(); + + for (int x = 0; x < abyte0.length; x++) { + if (x % 16 == 0) { + System.out.println(" " + h.toString()); + dw.write((" " + h.toString() + "\r\n").getBytes()); + h.setLength(0); + h.append("+0000"); + h.setLength(5 - Integer.toHexString(x).length()); + h.append(Integer.toHexString(x).toUpperCase()); + System.out.print(h.toString()); + dw.write(h.toString().getBytes()); + h.setLength(0); + } + + char ac = codePage.ebcdic2uni(abyte0[x]); + + if (ac < ' ') + h.append('.'); + else + h.append(ac); + + if (x % 4 == 0) { + System.out.print(" "); + dw.write((" ").getBytes()); + } + + if (Integer.toHexString(abyte0[x] & 0xff).length() == 1) { + System.out.print("0" + Integer.toHexString(abyte0[x] & 0xff).toUpperCase()); + dw.write(("0" + Integer.toHexString(abyte0[x] & 0xff).toUpperCase()).getBytes()); + } + else { + System.out.print(Integer.toHexString(abyte0[x] & 0xff).toUpperCase()); + dw.write((Integer.toHexString(abyte0[x] & 0xff).toUpperCase()).getBytes()); + } + } + + System.out.println(); + dw.write("\r\n".getBytes()); + dw.flush(); + } + catch (EOFException _ex) { } + catch (Exception _ex) { + Log.w(TAG, "Cannot dump from host\n\r"); + } + } + +// public void dumpBytes() { +// byte shit[] = bk.buffer; +// for (int i = 0;i < shit.length;i++) +// System.out.println(i + ">" + shit[i] + "< - ascii - >" + getASCIIChar(shit[i]) + "<"); +// } +// +// public void dumpHexBytes(byte[] abyte) { +// byte shit[] = abyte; +// for (int i = 0;i < shit.length;i++) +// System.out.println(i + ">" + shit[i] + "< hex >" + Integer.toHexString((shit[i] & 0xff))); +// } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/framework/tn5250/KbdTypesCodePages.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/framework/tn5250/KbdTypesCodePages.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,112 @@ +package org.tn5250j.framework.tn5250; + +/** + * IBM i 7.1 Information Center > Programmierung > i5/OS globalization > Globalization reference information > Keyboard reference information + * + * @see National language keyboard types and SBCS code pages + */ +public enum KbdTypesCodePages { + + ALI("Albanian", "ALI", "697", "500", "500"), + CLB("Arabic", "CLB", "235", "420", "420"), + AGB("Austrian/ German", "AGB", "697", "273", "273"), + AGE("Austrian/ German", "AGB", "695", "1141", "1141"), + AGI("Austrian/German (MNCS)", "AGI", "697", "500", "500"), + BLI("Belgian MNCS", "BLI", "697", "500", "500"), + BRB("Brazilian Portuguese", "BRB", "697", "37", "37"), + BGB("Bulgarian", "BGB", "1150", "1025", "1025"), + CAB("Canadian French", "CAB", "341", "260", "65535"), + CAI("Canadian French MNCS", "CAI", "697", "500", "500"), +// YGI("Croatian","YGI","959","870","870"), + CYB("Cyrillic", "CYB", "960", "880", "880"), + CSB("Czech", "CSB", "959", "870", "870"), + DMB("Danish", "DMB", "697", "277", "277"), + DMI("Danish MNCS", "DMI", "697", "500", "500"), + FNB("Finnish/Swedish", "FNB", "697", "278", "278"), + FNI("Finnish/Swedish MNCS", "FNI", "697", "500", "500"), + FAB("French (Azerty)", "FAB", "697", "297", "297"), + FAI("French (Azerty) MNCS", "FAI", "697", "500", "500"), + FQB("French (Qwerty)", "FQB", "697", "297", "297"), + FQI("French (Qwerty) MNCS", "FQI", "697", "500", "500"), + GNB("Greek (See note 2.)", "GNB", "925", "875", "875"), + NCB("Hebrew", "NCB", "941", "424", "424"), + HNB("Hungarian", "HNB", "959", "870", "870"), + ICB("Icelandic", "ICB", "697", "871", "871"), + ICI("Icelandic MNCS", "ICI", "697", "500", "500"), + INB("International", "INB", "697", "500", "500"), + INBX("International-X", "INB", "697", "500", "500-ch"), +// INB("International MNCS","INB","697","500","500"), + IRB("Farsi (Iran)", "IRB", "1219", "1097", "1097"), + ITB("Italian", "ITB", "697", "280", "280"), + ITI("Italian MNCS", "ITI", "697", "500", "500"), + JEB("Japanese-English", "JEB", "697", "281", "65535"), + JEI("Japanese- English MNCS", "JEI", "697", "500", "500"), + JKB("Japanese Kanji and Katakana", "JKB", "1172", "290", "5026"), +// JUB("Japanese Kanji and US English","JUB","697","37","See note 3."), + KAB("Japanese Katakana", "KAB", "332", "290", "290"), + JPB("Japanese Latin Extended", "JPB", "1172", "1027", "1027"), + KOB("Korean", "KOB", "1173", "833", "833"), + ROB("Latin 2", "ROB", "959", "870", "870"), + MKB("Macedonian", "MKB", "1150", "1025", "1025"), + NEB("Dutch (Netherlands)", "NEB", "697", "37", "37"), + NEI("Dutch (Netherlands) MNCS", "NEI", "697", "500", "500"), + NWB("Norwegian", "NWB", "697", "277", "277"), + NWI("Norwegian MNCS", "NWI", "697", "500", "500"), + PLB("Polish", "PLB", "959", "870", "870"), + PLBX("Polish 870-pl", "PLB", "959", "870", "870-pl"), // Workaround, to catch up Java codepage '870-pl' + PRB("Portuguese", "PRB", "697", "37", "37"), + PRI("Portuguese MNCS", "PRI", "697", "500", "500"), + RMB("Romanian", "RMB", "959", "870", "870"), + RUB("Russian", "RUB", "1150", "1025", "1025"), + SQB("Serbian, Cyrillic", "SQB", "1150", "1025", "1025"), + YGI("Serbian, Latin", "YGI", "959", "870", "870"), + RCB("Simplified Chinese", "RCB", "1174", "836", "836"), + SKB("Slovakian", "SKB", "959", "870", "870"), + SKBX("Slovakian 870-sk", "SKB", "959", "870", "870-sk"), // Workaround, to catch up Java codepage '870-sk' +// YGI("Slovenian","YGI","959","870","870"), + SPB("Spanish", "SPB", "697", "284", "284"), + SPI("Spanish MNCS", "SPI", "697", "500", "500"), + SSB("Spanish Speaking", "SSB", "697", "284", "284"), + SSI("Spanish Speaking MNCS", "SSI", "697", "500", "500"), + SWB("Swedish", "SWB", "697", "278", "278"), + SWI("Swedish MNCS", "SWI", "697", "500", "500"), + SFI("French (Switzerland) MNCS", "SFI", "697", "500", "500"), + SGI("German (Switzerland) MNCS", "SGI", "697", "500", "500"), + THB("Thai", "THB", "1176", "838", "838"), + TAB("Traditional Chinese", "TAB", "1175", "37", "937"), + TKB("Turkish (Qwerty)", "TKB", "1152", "1026", "1026"), + TRB("Turkish (F)", "TRB", "1152", "1026", "1026"), + UKB("English (United Kingdom)", "UKB", "697", "285", "285"), + UKI("English (United Kingdom) MNCS", "UKI", "697", "500", "500"), + USB("English (United States and Canada)", "USB", "697", "37", "37"), + USI("English (United States and Canada) MNCS", "USI", "697", "500", "500"); + + public final String description; + public final String kbdType; + public final String charset; + public final String codepage; + public final String ccsid; + + /** + * @param description + * @param kbdType + * @param charset + * @param codepage + * @param ccsid + */ + private KbdTypesCodePages(String description, String kbdType, String charset, String codepage, String ccsid) { + this.description = description; + this.kbdType = kbdType; + this.charset = charset; + this.codepage = codepage; + this.ccsid = ccsid; + } + + @Override + public String toString() { + return "[description=" + description + ", kbdType=" + kbdType + + ", charset=" + charset + ", codepage=" + codepage + + ", ccsid=" + ccsid + "]"; + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/framework/tn5250/KeyStrokenizer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/framework/tn5250/KeyStrokenizer.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,160 @@ +/* + * @(#)KeyStrokenizer.java + * Copyright: Copyright (c) 2001 + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ + +package org.tn5250j.framework.tn5250; + +import android.util.Log; + + + +public class KeyStrokenizer { + private static final String TAG = "KeyStrokenizer"; + private StringBuffer keyStrokes; + private StringBuffer sb; + private int index; + private int length; + + + + public KeyStrokenizer() { + sb = new StringBuffer(); + setKeyStrokes(null); + } + + public void setKeyStrokes(String strokes) { + if (strokes != null) { + keyStrokes.setLength(0); + Log.d(TAG, "set " + strokes); + length = strokes.length(); + } + else { + keyStrokes = new StringBuffer(); + length = 0; + } + + keyStrokes.append(strokes); + index = 0; + } + + public boolean hasMoreKeyStrokes() { + return length > index; + } + + public String nextKeyStroke() { + String s = ""; + boolean gotOne = false; + + if (length > index) { + sb.setLength(0); + char c = keyStrokes.charAt(index); + + switch (c) { + case '[': + sb.append(c); + index++; + + // we need to throw an error here + if (index >= length) { + Log.w(TAG, " mnemonic key was incomplete :1 " + + "at position " + index + " len " + length); + } + else { + c = keyStrokes.charAt(index); + + if (c == '[') + index++; + else { + while (!gotOne) { + if (c == ']') { // did we find an ending + sb.append(c); + index++; + gotOne = true; + } + else { + sb.append(c); + index++; + + // we need to throw an error here because we did not + // find an ending for the potential mnemonic + if (index >= length) { + Log.w(TAG, + " mnemonic key was incomplete ending not found :2 " + + "at position " + index); + } + + c = keyStrokes.charAt(index); + } + } + } + } + + break; + + case ']': + index++; + + if (index >= length) { + Log.w(TAG, + " mnemonic key was incomplete ending not found :3 " + + "at position " + index); + sb.append(c); + index++; + } + else { + c = keyStrokes.charAt(index); + + if (c == ']') { + sb.append(c); + index++; + } + else { + Log.w(TAG, + " mnemonic key was incomplete beginning not found :4 " + + "at position " + index); + } + } + + break; + + default: + sb.append(c); + index++; + break; + } + + if (sb != null) { + s = new String(sb); + } + } + + Log.d(TAG, "next " + keyStrokes); + return s; + } + + public String getUnprocessedKeyStroked() { + if (index >= length) { + return null; + } + + return keyStrokes.substring(index); + } + +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/framework/tn5250/Rect.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/framework/tn5250/Rect.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,61 @@ +/** + * $Id: Rect.java 1092 2011-01-16 20:27:56Z master_jaf $ + * + * Title: tn5250J + * Copyright: Copyright (c) 2001,2009 + * Company: + * @author: master_jaf + * + * Description: + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.framework.tn5250; + + +/** + * Simplified rectangle class. Very much similar like java.awt.Rectangle, + * but we want to decouple the packages ... + */ +public class Rect { + + /* default */ int x; + /* default */ int y; + /* default */ int height; + /* default */ int width; + + /** + * @param rect + */ + public void setBounds(Rect rect) { + setBounds(rect.x, rect.y, rect.width, rect.height); + } + + /** + * @param x the new X coordinate for the upper-left corner of this rectangle + * @param y the new Y coordinate for the upper-left corner of this rectangle + * @param width the new width for this rectangle + * @param height the new height for this rectangle + */ + public void setBounds(int x, int y, int width, int height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/framework/tn5250/Screen5250.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/framework/tn5250/Screen5250.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,3738 @@ +/* + * Title: Screen5250.java + * Copyright: Copyright (c) 2001 - 2004 + * Company: + * @author Kenneth J. Pouncey + * @version 0.5 + * + * Description: + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.framework.tn5250; + +import static org.tn5250j.TN5250jConstants.*; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.NumberFormat; +import java.text.ParseException; +import java.util.Vector; + +import org.tn5250j.TN5250jConstants; + +import android.util.Log; +import de.mud.terminal.VDUBuffer; +import de.mud.terminal.vt320; +import com.five_ten_sg.connectbot.service.FontSizeChangedListener; + + +public class Screen5250 implements FontSizeChangedListener { + private static final String TAG = "Screen5250"; + private ScreenFields screenFields; + private int lastAttr; + private int lastPos; + private int lenScreen; + private KeyStrokenizer strokenizer; + private tnvt sessionVT; + private vt320 buffer; // used to draw the screen + private int numRows = 0; + private int numCols = 0; + protected static final int initAttr = 32; + protected static final char initChar = 0; + public boolean cursorActive = false; + public boolean cursorShown = false; + protected boolean insertMode = false; + private boolean keyProcessed = false; + private Rect dirtyScreen = new Rect(); + + public int homePos = 0; + public int saveHomePos = 0; + private String bufferedKeys; + public boolean pendingInsert = false; + + public final static byte STATUS_SYSTEM = 1; + public final static byte STATUS_ERROR_CODE = 2; + public final static byte STATUS_VALUE_ON = 1; + public final static byte STATUS_VALUE_OFF = 2; + + private StringBuffer hsMore = new StringBuffer("More..."); + private StringBuffer hsBottom = new StringBuffer("Bottom"); + + // error codes to be sent to the host on an error + private final static int ERR_CURSOR_PROTECTED = 0x05; + private final static int ERR_INVALID_SIGN = 0x11; + private final static int ERR_NO_ROOM_INSERT = 0x12; + private final static int ERR_NUMERIC_ONLY = 0x09; + private final static int ERR_DUP_KEY_NOT_ALLOWED = 0x19; + private final static int ERR_NUMERIC_09 = 0x10; + private final static int ERR_FIELD_MINUS = 0x16; + private final static int ERR_FIELD_EXIT_INVALID = 0x18; + private final static int ERR_ENTER_NO_ALLOWED = 0x20; + private final static int ERR_MANDATORY_ENTER = 0x21; + + private boolean guiInterface = false; + private boolean resetRequired = false; + private boolean backspaceError = true; + private boolean feError; + + // Operator Information Area + private ScreenOIA oia; + + // screen planes + protected ScreenPlanes planes; + + + + public Screen5250() { + try { + jbInit(); + } + catch (Exception ex) { + Log.w(TAG, "In constructor: ", ex); + } + } + + void jbInit() throws Exception { + lastAttr = 32; + // default number of rows and columns + numRows = 24; + numCols = 80; + setCursor(1, 1); // set initial cursor position + oia = new ScreenOIA(this); + oia.setKeyBoardLocked(true); + lenScreen = numRows * numCols; + planes = new ScreenPlanes(this, numRows); + screenFields = new ScreenFields(this); + strokenizer = new KeyStrokenizer(); + } + + protected ScreenPlanes getPlanes() { + return planes; + } + + public final ScreenOIA getOIA() { + return oia; + } + + protected final void setRowsCols(int rows, int cols) { + int oldRows = numRows; + int oldCols = numCols; + // default number of rows and columns + numRows = rows; + numCols = cols; + lenScreen = numRows * numCols; + planes.setSize(rows); + + // If they are not the same then we need to inform the listeners that + // the size changed. + if (oldRows != numRows || oldCols != numCols) + fireScreenSizeChanged(); + } + + + public boolean isCursorActive() { + return cursorActive; + } + + public boolean isCursorShown() { + return cursorShown; + } + + public void setUseGUIInterface(boolean gui) { + guiInterface = gui; + } + + public void toggleGUIInterface() { + guiInterface = !guiInterface; + } + + public void setResetRequired(boolean reset) { + resetRequired = reset; + } + + public void setBackspaceError(boolean onError) { + backspaceError = onError; + } + + /** + * Copy & Paste support + * + * @see {@link #pasteText(String, boolean)} + * @see {@link #copyTextField(int)} + */ + public final String copyText(Rect area) { + StringBuilder sb = new StringBuilder(); + Rect workR = new Rect(); + workR.setBounds(area); + Log.d(TAG, "Copying " + workR); + // loop through all the screen characters to send them to the clip board + int m = workR.x; + int i = 0; + int t = 0; + + while (workR.height-- > 0) { + t = workR.width; + i = workR.y; + + while (t-- > 0) { + // only copy printable characters (in this case >= ' ') + char c = planes.getChar(getPos(m - 1, i - 1)); + + if (c >= ' ' && (planes.screenExtended[getPos(m - 1, i - 1)] & EXTENDED_5250_NON_DSP) + == 0) + sb.append(c); + else + sb.append(' '); + + i++; + } + + sb.append('\n'); + m++; + } + + return sb.toString(); + } + + /** + * Copy & Paste support + * + * @param content + * @see {@link #copyText(Rectangle)} + */ + public final void pasteText(String content, boolean special) { + Log.d(TAG, "Pasting, special:" + special); + setCursorActive(false); + StringBuilder sb = new StringBuilder(content); + StringBuilder pd = new StringBuilder(); + // character counters within the string to be pasted. + int nextChar = 0; + int nChars = sb.length(); + int lr = getRow(lastPos); + int lc = getCol(lastPos); + resetDirty(lastPos); + int cpos = lastPos; + int length = getScreenLength(); + char c = 0; + boolean setIt; + // save our current place within the FFT. + screenFields.saveCurrentField(); + + for (int x = nextChar; x < nChars; x++) { + c = sb.charAt(x); + + if ((c == '\n') || (c == '\r')) { + Log.i(TAG, "pasted cr-lf>" + pd + "<"); + pd.setLength(0); + // if we read in a cr lf in the data stream we need to go + // to the starting column of the next row and start from there + cpos = getPos(getRow(cpos) + 1, lc); + + // If we go paste the end of the screen then let's start over from + // the beginning of the screen space. + if (cpos > length) + cpos = 0; + } + else { + // we will default to set the character always. + setIt = true; + + // If we are in a special paste scenario then we check for valid + // characters to paste. + if (special && (!Character.isLetter(c) && !Character.isDigit(c))) + setIt = false; + + // we will only push a character to the screen space if we are in + // a field + if (isInField(cpos) && setIt) { + planes.setChar(cpos, c); + setDirty(cpos); + screenFields.setCurrentFieldMDT(); + } + + // If we placed a character then we go to the next position. + if (setIt) + cpos++; + + // we will append the information to our debug buffer. + pd.append(c); + } + } + + // if we have anything else not logged then log it out. + if (pd.length() > 0) + Log.i(TAG, "pasted >" + pd + "<"); + + // restore out position within the FFT. + screenFields.restoreCurrentField(); + updateDirty(); + // restore our cursor position. + setCursor(lr + 1, lc + 1); + setCursorActive(true); + } + + /** + * Copy & Paste support + * + * @param position + * @return + * @see {@link #copyText(int)} + */ + public final String copyTextField(int position) { + screenFields.saveCurrentField(); + isInField(position); + String result = screenFields.getCurrentFieldText(); + screenFields.restoreCurrentField(); + return result; + } + + /** + * + * Copy & Paste end code + * + */ + + /** + * Sum them + * + * @param which + * formatting option to use + * @return vector string of numberic values + */ + public final Vector sumThem(boolean which, Rect area) { + StringBuilder sb = new StringBuilder(); + Rect workR = new Rect(); + workR.setBounds(area); + // gui.rubberband.reset(); + // gui.repaint(); + Log.d(TAG, "Summing"); + // obtain the decimal format for parsing + DecimalFormat df = (DecimalFormat) NumberFormat.getInstance(); + DecimalFormatSymbols dfs = df.getDecimalFormatSymbols(); + + if (which) { + dfs.setDecimalSeparator('.'); + dfs.setGroupingSeparator(','); + } + else { + dfs.setDecimalSeparator(','); + dfs.setGroupingSeparator('.'); + } + + df.setDecimalFormatSymbols(dfs); + Vector sumVector = new Vector(); + // loop through all the screen characters to send them to the clip board + int m = workR.x; + int i = 0; + int t = 0; + double sum = 0.0; + + while (workR.height-- > 0) { + t = workR.width; + i = workR.y; + + while (t-- > 0) { + // only copy printable numeric characters (in this case >= ' ') + // char c = screen[getPos(m - 1, i - 1)].getChar(); + char c = planes.getChar(getPos(m - 1, i - 1)); + // if (((c >= '0' && c <= '9') || c == '.' || c == ',' || c == '-') + // && !screen[getPos(m - 1, i - 1)].nonDisplay) { + + // TODO: update me here to implement the nonDisplay check as well + if (((c >= '0' && c <= '9') || c == '.' || c == ',' || c == '-')) { + sb.append(c); + } + + i++; + } + + if (sb.length() > 0) { + if (sb.charAt(sb.length() - 1) == '-') { + sb.insert(0, '-'); + sb.deleteCharAt(sb.length() - 1); + } + + try { + Number n = df.parse(sb.toString()); + // System.out.println(s + " " + n.doubleValue()); + sumVector.add(new Double(n.doubleValue())); + sum += n.doubleValue(); + } + catch (ParseException pe) { + Log.w(TAG, pe.getMessage() + " at " + + pe.getErrorOffset()); + } + } + + sb.setLength(0); + m++; + } + + Log.d(TAG, "" + sum); + return sumVector; + } + + public void setVT(tnvt v) { + sessionVT = v; + } + + public void setBuffer(vt320 buffer) { + this.buffer = buffer; + } + + /** + * converts mnemonic string values into aid integers + * + * @see #sendKeys + * @param mnem string mnemonic value + * @return key value of Mnemonic + */ + private int getMnemonicValue(String mnem) { + if (mnemonicMap.containsKey(mnem)) return mnemonicMap.get(mnem); + + return 0; + } + + protected void setPrehelpState(boolean setErrorCode, boolean lockKeyboard, + boolean unlockIfLocked) { + if (oia.isKeyBoardLocked() && unlockIfLocked) + oia.setKeyBoardLocked(false); + else + oia.setKeyBoardLocked(lockKeyboard); + + bufferedKeys = null; + oia.setKeysBuffered(false); + } + + /** + * Activate the cursor on screen + * + * @param activate + */ + public void setCursorActive(boolean activate) { + // System.out.println("cursor active " + updateCursorLoc + " " + + // cursorActive + " " + activate); + if (cursorActive && !activate) { + setCursorOff(); + cursorActive = activate; + } + else { + if (!cursorActive && activate) { + cursorActive = activate; + setCursorOn(); + } + } + } + + /** + * Set the cursor on + */ + public void setCursorOn() { + cursorShown = true; + updateCursorLoc(); + } + + /** + * Set the cursor off + */ + public void setCursorOff() { + cursorShown = false; + updateCursorLoc(); + // System.out.println("cursor off " + updateCursorLoc + " " + + // cursorActive); + } + + /** + * + */ + private void updateCursorLoc() { + if (cursorActive) { + fireCursorChanged(); + } + } + + /** + * The sendKeys method sends a string of keys to the virtual screen. This + * method acts as if keystrokes were being typed from the keyboard. The + * keystrokes will be sent to the location given. The string being passed + * can also contain mnemonic values such as [enter] enter key,[tab] tab key, + * [pf1] pf1 etc... + * + * These will be processed as if you had pressed these keys from the + * keyboard. All the valid special key values are contained in the MNEMONIC + * enumeration: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
MNEMONIC_CLEAR[clear]
MNEMONIC_ENTER[enter]
MNEMONIC_HELP[help]
MNEMONIC_PAGE_DOWN[pgdown]
MNEMONIC_PAGE_UP[pgup]
MNEMONIC_PRINT[print]
MNEMONIC_PF1[pf1]
MNEMONIC_PF2[pf2]
MNEMONIC_PF3[pf3]
MNEMONIC_PF4[pf4]
MNEMONIC_PF5[pf5]
MNEMONIC_PF6[pf6]
MNEMONIC_PF7[pf7]
MNEMONIC_PF8[pf8]
MNEMONIC_PF9[pf9]
MNEMONIC_PF10[pf10]
MNEMONIC_PF11[pf11]
MNEMONIC_PF12[pf12]
MNEMONIC_PF13[pf13]
MNEMONIC_PF14[pf14]
MNEMONIC_PF15[pf15]
MNEMONIC_PF16[pf16]
MNEMONIC_PF17[pf17]
MNEMONIC_PF18[pf18]
MNEMONIC_PF19[pf19]
MNEMONIC_PF20[pf20]
MNEMONIC_PF21[pf21]
MNEMONIC_PF22[pf22]
MNEMONIC_PF23[pf23]
MNEMONIC_PF24[pf24]
MNEMONIC_BACK_SPACE[backspace]
MNEMONIC_BACK_TAB[backtab]
MNEMONIC_UP[up]
MNEMONIC_DOWN[down]
MNEMONIC_LEFT[left]
MNEMONIC_RIGHT[right]
MNEMONIC_DELETE[delete]
MNEMONIC_TAB"[tab]
MNEMONIC_END_OF_FIELD[eof]
MNEMONIC_ERASE_EOF[eraseeof]
MNEMONIC_ERASE_FIELD[erasefld]
MNEMONIC_INSERT[insert]
MNEMONIC_HOME[home]
MNEMONIC_KEYPAD0[keypad0]
MNEMONIC_KEYPAD1[keypad1]
MNEMONIC_KEYPAD2[keypad2]
MNEMONIC_KEYPAD3[keypad3]
MNEMONIC_KEYPAD4[keypad4]
MNEMONIC_KEYPAD5[keypad5]
MNEMONIC_KEYPAD6[keypad6]
MNEMONIC_KEYPAD7[keypad7]
MNEMONIC_KEYPAD8[keypad8]
MNEMONIC_KEYPAD9[keypad9]
MNEMONIC_KEYPAD_PERIOD[keypad.]
MNEMONIC_KEYPAD_COMMA[keypad,]
MNEMONIC_KEYPAD_MINUS[keypad-]
MNEMONIC_FIELD_EXIT[fldext]
MNEMONIC_FIELD_PLUS[field+]
MNEMONIC_FIELD_MINUS[field-]
MNEMONIC_BEGIN_OF_FIELD[bof]
MNEMONIC_PA1[pa1]
MNEMONIC_PA2[pa2]
MNEMONIC_PA3[pa3]
MNEMONIC_SYSREQ[sysreq]
MNEMONIC_RESET[reset]
MNEMONIC_ATTN[attn]
MNEMONIC_MARK_LEFT[markleft]
MNEMONIC_MARK_RIGHT[markright]
MNEMONIC_MARK_UP[markup]
MNEMONIC_MARK_DOWN[markdown]
+ * + * @param text + * The string of characters to be sent + * + * @see #sendAid + * + */ + + public synchronized void sendKeys(String text) { + if (isStatusErrorCode() && !resetRequired) { + setCursorActive(false); + simulateMnemonic(getMnemonicValue("[reset]")); + setCursorActive(true); + } + + if (oia.isKeyBoardLocked()) { + if (text.equals("[reset]") || text.equals("[sysreq]") + || text.equals("[attn]")) { + setCursorActive(false); + simulateMnemonic(getMnemonicValue(text)); + setCursorActive(true); + } + else { + if (isStatusErrorCode()) { + sessionVT.signalBell(); + return; + } + + oia.setKeysBuffered(true); + + if (bufferedKeys == null) bufferedKeys = text; + else bufferedKeys += text; + + return; + } + } + else { + if (oia.isKeysBuffered()) { + if (bufferedKeys != null) { + text = bufferedKeys + text; + } + + oia.setKeysBuffered(false); + bufferedKeys = null; + } + + isInField(); + + if (text.length() == 1 && !text.equals("[") && !text.equals("]")) { + setCursorActive(false); + simulateKeyStroke(text.charAt(0)); + setCursorActive(true); + } + else { + strokenizer.setKeyStrokes(text); + String s; + boolean done = false; + // setCursorOff2(); + setCursorActive(false); + + while (!done) { + if (strokenizer.hasMoreKeyStrokes()) { + isInField(); + s = strokenizer.nextKeyStroke(); + + if (s.length() == 1) { + simulateKeyStroke(s.charAt(0)); + } + else { + simulateMnemonic(getMnemonicValue(s)); + } + + if (oia.isKeyBoardLocked()) { + bufferedKeys = strokenizer + .getUnprocessedKeyStroked(); + + if (bufferedKeys != null) { + oia.setKeysBuffered(true); + } + + done = true; + } + } + else { + done = true; + } + } + + setCursorActive(true); + } + } + } + + /** + * The sendAid method sends an "aid" keystroke to the virtual screen. These + * aid keys can be thought of as special keystrokes, like the Enter key, + * PF1-24 keys or the Page Up key. All the valid special key values are + * contained in the AID_ enumeration: + * + * @param aidKey + * The aid key to be sent to the host + * + * @see #sendKeys + * @see TN5250jConstants#AID_CLEAR + * @see #AID_ENTER + * @see #AID_HELP + * @see #AID_ROLL_UP + * @see #AID_ROLL_DOWN + * @see #AID_ROLL_LEFT + * @see #AID_ROLL_RIGHT + * @see #AID_PRINT + * @see #AID_PF1 + * @see #AID_PF2 + * @see #AID_PF3 + * @see #AID_PF4 + * @see #AID_PF5 + * @see #AID_PF6 + * @see #AID_PF7 + * @see #AID_PF8 + * @see #AID_PF9 + * @see #AID_PF10 + * @see #AID_PF11 + * @see #AID_PF12 + * @see #AID_PF13 + * @see #AID_PF14 + * @see #AID_PF15 + * @see #AID_PF16 + * @see #AID_PF17 + * @see #AID_PF18 + * @see #AID_PF19 + * @see #AID_PF20 + * @see #AID_PF21 + * @see #AID_PF22 + * @see #AID_PF23 + * @see #AID_PF24 + */ + public void sendAid(int aidKey) { + sessionVT.sendAidKey(aidKey); + } + + /** + * Restores the error line and sets the error mode off. + * + */ + protected void resetError() { + restoreErrorLine(); + setStatus(STATUS_ERROR_CODE, STATUS_VALUE_OFF, ""); + } + + protected boolean simulateMnemonic(int mnem) { + boolean simulated = false; + + switch (mnem) { + case AID_CLEAR: + case AID_ENTER: + case AID_PF1: + case AID_PF2: + case AID_PF3: + case AID_PF4: + case AID_PF5: + case AID_PF6: + case AID_PF7: + case AID_PF8: + case AID_PF9: + case AID_PF10: + case AID_PF11: + case AID_PF12: + case AID_PF13: + case AID_PF14: + case AID_PF15: + case AID_PF16: + case AID_PF17: + case AID_PF18: + case AID_PF19: + case AID_PF20: + case AID_PF21: + case AID_PF22: + case AID_PF23: + case AID_PF24: + case AID_ROLL_DOWN: + case AID_ROLL_UP: + case AID_ROLL_LEFT: + case AID_ROLL_RIGHT: + if (!screenFields.isCanSendAid()) { + displayError(ERR_ENTER_NO_ALLOWED); + } + else + sendAid(mnem); + + simulated = true; + break; + + case AID_HELP: + sessionVT.sendHelpRequest(); + simulated = true; + break; + + case AID_PRINT: + sessionVT.hostPrint(1); + simulated = true; + break; + + case BACK_SPACE: + if (screenFields.getCurrentField() != null + && screenFields.withinCurrentField(lastPos) + && !screenFields.isCurrentFieldBypassField()) { + if (screenFields.getCurrentField().startPos() == lastPos) { + if (backspaceError) + displayError(ERR_CURSOR_PROTECTED); + else { + gotoFieldPrev(); + goto_XY(screenFields.getCurrentField().endPos()); + updateDirty(); + } + } + else { + screenFields.getCurrentField().getKeyPos(lastPos); + screenFields.getCurrentField().changePos(-1); + resetDirty(screenFields.getCurrentField().getCurrentPos()); + shiftLeft(screenFields.getCurrentField().getCurrentPos()); + updateDirty(); + screenFields.setCurrentFieldMDT(); + simulated = true; + } + } + else { + displayError(ERR_CURSOR_PROTECTED); + } + + break; + + case BACK_TAB: + if (screenFields.getCurrentField() != null + && screenFields.isCurrentFieldHighlightedEntry()) { + resetDirty(screenFields.getCurrentField().startPos); + gotoFieldPrev(); + updateDirty(); + } + else + gotoFieldPrev(); + + if (screenFields.isCurrentFieldContinued()) { + do { + gotoFieldPrev(); + } + while (screenFields.isCurrentFieldContinuedMiddle() + || screenFields.isCurrentFieldContinuedLast()); + } + + isInField(); + simulated = true; + break; + + case UP: + case MARK_UP: + process_XY(lastPos - numCols); + simulated = true; + break; + + case DOWN: + case MARK_DOWN: + process_XY(lastPos + numCols); + simulated = true; + break; + + case LEFT: + case MARK_LEFT: + process_XY(lastPos - 1); + simulated = true; + break; + + case RIGHT: + case MARK_RIGHT: + process_XY(lastPos + 1); + simulated = true; + break; + + case NEXTWORD: + gotoNextWord(); + simulated = true; + break; + + case PREVWORD: + gotoPrevWord(); + simulated = true; + break; + + case DELETE: + if (screenFields.getCurrentField() != null + && screenFields.withinCurrentField(lastPos) + && !screenFields.isCurrentFieldBypassField()) { + resetDirty(lastPos); + screenFields.getCurrentField().getKeyPos(lastPos); + shiftLeft(screenFields.getCurrentFieldPos()); + screenFields.setCurrentFieldMDT(); + updateDirty(); + simulated = true; + } + else { + displayError(ERR_CURSOR_PROTECTED); + } + + break; + + case TAB: + if (screenFields.getCurrentField() != null + && !screenFields.isCurrentFieldContinued()) { + if (screenFields.isCurrentFieldHighlightedEntry()) { + resetDirty(screenFields.getCurrentField().startPos); + gotoFieldNext(); + updateDirty(); + } + else + gotoFieldNext(); + } + else { + do { + gotoFieldNext(); + } + while (screenFields.getCurrentField() != null + && (screenFields.isCurrentFieldContinuedMiddle() || screenFields + .isCurrentFieldContinuedLast())); + } + + isInField(); + simulated = true; + break; + + case EOF: + if (screenFields.getCurrentField() != null + && screenFields.withinCurrentField(lastPos) + && !screenFields.isCurrentFieldBypassField()) { + int where = endOfField(screenFields.getCurrentField() + .startPos(), true); + + if (where > 0) { + setCursor((where / numCols) + 1, (where % numCols) + 1); + } + + simulated = true; + } + else { + displayError(ERR_CURSOR_PROTECTED); + } + + resetDirty(lastPos); + break; + + case ERASE_EOF: + if (screenFields.getCurrentField() != null + && screenFields.withinCurrentField(lastPos) + && !screenFields.isCurrentFieldBypassField()) { + int where = lastPos; + resetDirty(lastPos); + + if (fieldExit()) { + screenFields.setCurrentFieldMDT(); + + if (!screenFields.isCurrentFieldContinued()) { + gotoFieldNext(); + } + else { + do { + gotoFieldNext(); + + if (screenFields.isCurrentFieldContinued()) + fieldExit(); + } + while (screenFields.isCurrentFieldContinuedMiddle() + || screenFields.isCurrentFieldContinuedLast()); + } + } + + updateDirty(); + goto_XY(where); + simulated = true; + } + else { + displayError(ERR_CURSOR_PROTECTED); + } + + break; + + case ERASE_FIELD: + if (screenFields.getCurrentField() != null + && screenFields.withinCurrentField(lastPos) + && !screenFields.isCurrentFieldBypassField()) { + int where = lastPos; + lastPos = screenFields.getCurrentField().startPos(); + resetDirty(lastPos); + + if (fieldExit()) { + screenFields.setCurrentFieldMDT(); + + if (!screenFields.isCurrentFieldContinued()) { + gotoFieldNext(); + } + else { + do { + gotoFieldNext(); + + if (screenFields.isCurrentFieldContinued()) + fieldExit(); + } + while (screenFields.isCurrentFieldContinuedMiddle() + || screenFields.isCurrentFieldContinuedLast()); + } + } + + updateDirty(); + goto_XY(where); + simulated = true; + } + else { + displayError(ERR_CURSOR_PROTECTED); + } + + break; + + case INSERT: + // we toggle it + oia.setInsertMode(oia.isInsertMode() ? false : true); + break; + + case HOME: + + // position to the home position set + if (lastPos + numCols + 1 != homePos) { + goto_XY(homePos - numCols - 1); + isInField(); + } + else + gotoField(1); + + break; + + case KEYPAD_0: + simulated = simulateKeyStroke('0'); + break; + + case KEYPAD_1: + simulated = simulateKeyStroke('1'); + break; + + case KEYPAD_2: + simulated = simulateKeyStroke('2'); + break; + + case KEYPAD_3: + simulated = simulateKeyStroke('3'); + break; + + case KEYPAD_4: + simulated = simulateKeyStroke('4'); + break; + + case KEYPAD_5: + simulated = simulateKeyStroke('5'); + break; + + case KEYPAD_6: + simulated = simulateKeyStroke('6'); + break; + + case KEYPAD_7: + simulated = simulateKeyStroke('7'); + break; + + case KEYPAD_8: + simulated = simulateKeyStroke('8'); + break; + + case KEYPAD_9: + simulated = simulateKeyStroke('9'); + break; + + case KEYPAD_PERIOD: + simulated = simulateKeyStroke('.'); + break; + + case KEYPAD_COMMA: + simulated = simulateKeyStroke(','); + break; + + case KEYPAD_MINUS: + if (screenFields.getCurrentField() != null + && screenFields.withinCurrentField(lastPos) + && !screenFields.isCurrentFieldBypassField()) { + int s = screenFields.getCurrentField().getFieldShift(); + + if (s == 3 || s == 5 || s == 7) { + planes.setChar(lastPos, '-'); + resetDirty(lastPos); + advancePos(); + + if (fieldExit()) { + screenFields.setCurrentFieldMDT(); + + if (!screenFields.isCurrentFieldContinued()) { + gotoFieldNext(); + } + else { + do { + gotoFieldNext(); + } + while (screenFields + .isCurrentFieldContinuedMiddle() + || screenFields + .isCurrentFieldContinuedLast()); + } + + simulated = true; + updateDirty(); + + if (screenFields.isCurrentFieldAutoEnter()) + sendAid(AID_ENTER); + } + } + else { + displayError(ERR_FIELD_MINUS); + } + } + else { + displayError(ERR_CURSOR_PROTECTED); + } + + break; + + case FIELD_EXIT: + if (screenFields.getCurrentField() != null + && screenFields.withinCurrentField(lastPos) + && !screenFields.isCurrentFieldBypassField()) { + resetDirty(lastPos); + boolean autoFE = screenFields.isCurrentFieldAutoEnter(); + + if (fieldExit()) { + screenFields.setCurrentFieldMDT(); + + if (!screenFields.isCurrentFieldContinued() && + !screenFields.isCurrentFieldAutoEnter()) { + gotoFieldNext(); + } + else { + do { + gotoFieldNext(); + + if (screenFields.isCurrentFieldContinued()) + fieldExit(); + } + while (screenFields.isCurrentFieldContinuedMiddle() + || screenFields.isCurrentFieldContinuedLast()); + } + } + + updateDirty(); + simulated = true; + + if (autoFE) + sendAid(AID_ENTER); + } + else { + displayError(ERR_CURSOR_PROTECTED); + } + + break; + + case FIELD_PLUS: + if (screenFields.getCurrentField() != null + && screenFields.withinCurrentField(lastPos) + && !screenFields.isCurrentFieldBypassField()) { + resetDirty(lastPos); + boolean autoFE = screenFields.isCurrentFieldAutoEnter(); + + if (fieldExit()) { + screenFields.setCurrentFieldMDT(); + + if (!screenFields.isCurrentFieldContinued() && + !screenFields.isCurrentFieldAutoEnter()) { + gotoFieldNext(); + } + else { + do { + gotoFieldNext(); + } + while (screenFields.isCurrentFieldContinuedMiddle() + || screenFields.isCurrentFieldContinuedLast()); + } + } + + updateDirty(); + simulated = true; + + if (autoFE) + sendAid(AID_ENTER); + } + else { + displayError(ERR_CURSOR_PROTECTED); + } + + break; + + case FIELD_MINUS: + if (screenFields.getCurrentField() != null + && screenFields.withinCurrentField(lastPos) + && !screenFields.isCurrentFieldBypassField()) { + int s = screenFields.getCurrentField().getFieldShift(); + + if (s == 3 || s == 5 || s == 7) { + planes.setChar(lastPos, '-'); + resetDirty(lastPos); + advancePos(); + boolean autoFE = screenFields.isCurrentFieldAutoEnter(); + + if (fieldExit()) { + screenFields.setCurrentFieldMDT(); + + if (!screenFields.isCurrentFieldContinued() + && !screenFields.isCurrentFieldAutoEnter()) { + gotoFieldNext(); + } + else { + do { + gotoFieldNext(); + } + while (screenFields.isCurrentFieldContinuedMiddle() + || screenFields.isCurrentFieldContinuedLast()); + } + } + + updateDirty(); + simulated = true; + + if (autoFE) + sendAid(AID_ENTER); + } + else { + displayError(ERR_FIELD_MINUS); + } + } + else { + displayError(ERR_CURSOR_PROTECTED); + } + + break; + + case BOF: + if (screenFields.getCurrentField() != null + && screenFields.withinCurrentField(lastPos) + && !screenFields.isCurrentFieldBypassField()) { + int where = screenFields.getCurrentField().startPos(); + + if (where > 0) { + goto_XY(where); + } + + simulated = true; + } + else { + displayError(ERR_CURSOR_PROTECTED); + } + + resetDirty(lastPos); + break; + + case SYSREQ: + sessionVT.systemRequest(); + simulated = true; + break; + + case RESET: + if (isStatusErrorCode()) { + resetError(); + isInField(); + updateDirty(); + } + else { + setPrehelpState(false, oia.isKeyBoardLocked(), false); + } + + simulated = true; + break; + + case ATTN: + sessionVT.sendAttentionKey(); + simulated = true; + break; + + case DUP_FIELD: + if (screenFields.getCurrentField() != null + && screenFields.withinCurrentField(lastPos) + && !screenFields.isCurrentFieldBypassField()) { + if (screenFields.isCurrentFieldDupEnabled()) { + resetDirty(lastPos); + screenFields.getCurrentField().setFieldChar(lastPos, + (char) 0x1C); + screenFields.setCurrentFieldMDT(); + gotoFieldNext(); + updateDirty(); + simulated = true; + } + else { + displayError(ERR_DUP_KEY_NOT_ALLOWED); + } + } + else { + displayError(ERR_CURSOR_PROTECTED); + } + + break; + + case NEW_LINE: + if (screenFields.getSize() > 0) { + int startRow = getRow(lastPos) + 1; + int startPos = lastPos; + + if (startRow == getRows()) + startRow = 0; + + setCursor(++startRow, 1); + + if (!isInField() && screenFields.getCurrentField() != null + && !screenFields.isCurrentFieldBypassField()) { + while (!isInField() + && screenFields.getCurrentField() != null + && !screenFields.isCurrentFieldBypassField()) { + // lets keep going + advancePos(); + + // Have we looped the screen? + if (lastPos == startPos) { + // if so then go back to starting point + goto_XY(startPos); + break; + } + } + } + } + + simulated = true; + break; + + case FAST_CURSOR_DOWN: + int rowNow = (getCurrentRow() - 1) + 3; + + if (rowNow > getRows() - 1) + rowNow = rowNow - getRows(); + + this.goto_XY(getPos(rowNow, getCurrentCol() - 1)); + simulated = true; + break; + + case FAST_CURSOR_UP: + rowNow = (getCurrentRow() - 1) - 3; + + if (rowNow < 0) + rowNow = (getRows()) + rowNow; + + this.goto_XY(getPos(rowNow, getCurrentCol() - 1)); + simulated = true; + break; + + case FAST_CURSOR_LEFT: + int colNow = (getCurrentCol() - 1) - 3; + rowNow = getCurrentRow() - 1; + + if (colNow <= 0) { + colNow = getColumns() + colNow; + rowNow--; + } + + if (rowNow < 0) + rowNow = getRows() - 1; + + process_XY(getPos(rowNow, colNow)); + simulated = true; + break; + + case FAST_CURSOR_RIGHT: + colNow = (getCurrentCol() - 1) + 3; + rowNow = getCurrentRow() - 1; + + if (colNow >= getColumns()) { + colNow = colNow - getColumns(); + rowNow++; + } + + if (rowNow > getRows() - 1) + rowNow = getRows() - rowNow; + + process_XY(getPos(rowNow, colNow)); + simulated = true; + break; + + default: + Log.i(TAG, " Mnemonic not supported " + mnem); + break; + } + + return simulated; + } + + protected boolean simulateKeyStroke(char c) { + if (isStatusErrorCode() && !Character.isISOControl(c) && !keyProcessed) { + if (resetRequired) return false; + + resetError(); + } + + boolean updateField = false; + boolean numericError = false; + boolean updatePos = false; + boolean autoEnter = false; + + if (!Character.isISOControl(c)) { + if (screenFields.getCurrentField() != null + && screenFields.withinCurrentField(lastPos) + && !screenFields.isCurrentFieldBypassField()) { + if (screenFields.isCurrentFieldFER() + && !screenFields.withinCurrentField(screenFields + .getCurrentFieldPos()) + && lastPos == screenFields.getCurrentField().endPos() + && screenFields.getCurrentFieldPos() > screenFields + .getCurrentField().endPos()) { + displayError(ERR_FIELD_EXIT_INVALID); + feError = true; + return false; + } + + switch (screenFields.getCurrentFieldShift()) { + case 0: // Alpha shift + case 2: // Numeric Shift + case 4: // Kakana Shift + updateField = true; + break; + + case 1: // Alpha Only + if (Character.isLetter(c) || c == ',' || c == '-' + || c == '.' || c == ' ') + updateField = true; + + break; + + case 3: // Numeric only + if (Character.isDigit(c) || c == '+' || c == ',' + || c == '-' || c == '.' || c == ' ') + updateField = true; + else + numericError = true; + + break; + + case 5: // Digits only + if (Character.isDigit(c)) + updateField = true; + else + displayError(ERR_NUMERIC_09); + + break; + + case 7: // Signed numeric + if (Character.isDigit(c) || c == '+' || c == '-') + if (lastPos == screenFields.getCurrentField().endPos() + && (c != '+' && c != '-')) + displayError(ERR_INVALID_SIGN); + else + updateField = true; + else + displayError(ERR_NUMERIC_09); + + break; + } + + if (updateField) { + if (screenFields.isCurrentFieldToUpper()) + c = Character.toUpperCase(c); + + updatePos = true; + resetDirty(lastPos); + + if (oia.isInsertMode()) { + if (endOfField(false) != screenFields.getCurrentField() + .endPos()) + shiftRight(lastPos); + else { + displayError(ERR_NO_ROOM_INSERT); + updatePos = false; + } + } + + if (updatePos) { + screenFields.getCurrentField().getKeyPos( + getRow(lastPos), getCol(lastPos)); + screenFields.getCurrentField().changePos(1); + planes.setChar(lastPos, c); + screenFields.setCurrentFieldMDT(); + + // if we have gone passed the end of the field then goto + // the next field + if (!screenFields.withinCurrentField(screenFields + .getCurrentFieldPos())) { + if (screenFields.isCurrentFieldAutoEnter()) { + autoEnter = true; + } + else if (!screenFields.isCurrentFieldFER()) + gotoFieldNext(); + else { + // screenFields.getCurrentField().changePos(1); + // + // if (screenFields. + // cursorPos == endPos) + // System.out.println("end of field"); + // + // feError != feError; + // if (feError) + // displayError(ERR_FIELD_EXIT_INVALID); + } + } + else + setCursor(screenFields.getCurrentField() + .getCursorRow() + 1, screenFields + .getCurrentField().getCursorCol() + 1); + } + + fireScreenChanged(); + + if (autoEnter) + sendAid(AID_ENTER); + } + else { + if (numericError) { + displayError(ERR_NUMERIC_ONLY); + } + } + } + else { + displayError(ERR_CURSOR_PROTECTED); + } + } + + return updatePos; + } + + /** + * Method: endOfField + *

+ * + * convenience method that call endOfField with lastRow lastCol and passes + * the posSpace to that method + * + * @param posSpace + * value of type boolean - specifying to return the position of + * the the last space or not + * @return a value of type int - the screen postion (row * columns) + col + * + */ + private int endOfField(boolean posSpace) { + return endOfField(lastPos, posSpace); + } + + /** + * Method: endOfField + *

+ * + * gets the position of the last character of the current field posSpace + * parameter tells the routine whether to return the position of the last + * space ( <= ' ') or the last non space posSpace == true last occurrence of + * char <= ' ' posSpace == false last occurrence of char > ' ' + * + * @param pos + * value of type int - position to start from + * @param posSpace + * value of type boolean - specifying to return the position of + * the the last space or not + * @return a value of type int - the screen postion (row * columns) + col + * + */ + private int endOfField(int pos, boolean posSpace) { + int endPos = screenFields.getCurrentField().endPos(); + int fePos = endPos; + // get the number of characters to the right + int count = endPos - pos; + + // first lets get the real ending point without spaces and the such + while (planes.getChar(endPos) <= ' ' && count-- > 0) { + endPos--; + } + + if (endPos == fePos) { + return endPos; + } + + screenFields.getCurrentField().getKeyPos(endPos); + + if (posSpace) screenFields.getCurrentField().changePos(+1); + + return screenFields.getCurrentFieldPos(); + } + + private boolean fieldExit() { + int pos = lastPos; + boolean mdt = false; + int end = endOfField(false); // get the ending position of the first + // non blank character in field + ScreenField sf = screenFields.getCurrentField(); + + if (sf.isMandatoryEnter() && end == sf.startPos()) { + displayError(ERR_MANDATORY_ENTER); + return false; + } + + // save off the current pos of the field for checking field exit required + // positioning. the getKeyPos resets this information so it is useless + // for comparing if we are positioned passed the end of field. + // Maybe this should be changed to not update the current cursor position + // of the field. + int currentPos = sf.getCurrentPos(); + // get the number of characters to the right + int count = (end - sf.startPos()) - sf.getKeyPos(pos); + + if (count == 0 && sf.isFER()) { + if (currentPos > sf.endPos()) { + mdt = true; + return mdt; + } + } + + for (; count >= 0; count--) { + planes.setChar(pos, initChar); + setDirty(pos); + pos++; + mdt = true; + } + + // This checks for a field minus because a field minus places + // a negative sign and then advances a position. If it is the + // end of the field where the minus is placed then this offset will + // place the count as -1. + if (count == -1) { + int s = sf.getFieldShift(); + + if (s == 3 || s == 5 || s == 7) { + mdt = true; + } + } + + int adj = sf.getAdjustment(); + + if (adj != 0) { + switch (adj) { + case 5: + rightAdjustField('0'); + sf.setRightAdjusted(); + break; + + case 6: + rightAdjustField(' '); + sf.setRightAdjusted(); + break; + + case 7: + sf.setMandatoryEntered(); + break; + } + } + else { + // we need to right adjust signed numeric fields as well. + if (sf.isSignedNumeric()) { + rightAdjustField(' '); + } + } + + return mdt; + } + + private void rightAdjustField(char fill) { + int end = endOfField(false); // get the ending position of the first + // non blank character in field + // get the number of characters to the right + int count = screenFields.getCurrentField().endPos() - end; + + // subtract 1 from count for signed numeric - note for later + if (screenFields.getCurrentField().isSignedNumeric()) { + if (planes.getChar(end - 1) != '-') + count--; + } + + int pos = screenFields.getCurrentField().startPos(); + + while (count-- >= 0) { + shiftRight(pos); + planes.setChar(pos, fill); + setDirty(pos); + } + } + + private void shiftLeft(int sPos) { + int endPos = 0; + int pos = sPos; + int pPos = sPos; + ScreenField sf = screenFields.getCurrentField(); + int end; + int count; + + do { + end = endOfField(pPos, false); // get the ending position of the + // first + // non blank character in field + count = (end - screenFields.getCurrentField().startPos()) + - screenFields.getCurrentField().getKeyPos(pPos); + + // now we loop through and shift the remaining characters to the + // left + while (count-- > 0) { + pos++; + planes.setChar(pPos, planes.getChar(pos)); + setDirty(pPos); + pPos = pos; + } + + if (screenFields.isCurrentFieldContinued()) { + gotoFieldNext(); + + if (screenFields.getCurrentField().isContinuedFirst()) + break; + + pos = screenFields.getCurrentField().startPos(); + planes.setChar(pPos, planes.getChar(pos)); + setDirty(pPos); + pPos = pos; + } + } + while (screenFields.isCurrentFieldContinued() + && !screenFields.getCurrentField().isContinuedFirst()); + + if (end >= 0 && count >= -1) { + endPos = end; + } + else { + endPos = sPos; + } + + screenFields.setCurrentField(sf); + planes.setChar(endPos, initChar); + setDirty(endPos); + goto_XY(screenFields.getCurrentFieldPos()); + sf = null; + } + + private void shiftRight(int sPos) { + int end = endOfField(true); // get the ending position of the first + // non blank character in field + int pos = end; + int pPos = end; + int count = end - sPos; + + // now we loop through and shift the remaining characters to the right + while (count-- > 0) { + pos--; + planes.setChar(pPos, planes.getChar(pos)); + setDirty(pPos); + pPos = pos; + } + } + + public int getRow(int pos) { + // if (pos == 0) + // return 1; + int row = pos / numCols; + + if (row < 0) { + row = lastPos / numCols; + } + + if (row > (lenScreen / numCols) - 1) + row = (lenScreen / numCols) - 1; + + return row; + } + + public int getCol(int pos) { + int col = pos % (getColumns()); + + if (col > 0) return col; + + return 0; + } + + /** + * This routine is 0 based offset. So to get row 20,1 then pass row 19,0 + * + * @param row + * @param col + * @return + */ + public int getPos(int row, int col) { + return (row * numCols) + col; + } + + /** + * Current position is based on offsets of 1,1 not 0,0 of the current + * position of the screen + * + * @return int + */ + public int getCurrentPos() { + // return lastPos + numCols + 1; + return lastPos + 1; + } + + /** + * I got this information from a tcp trace of each error. I could not find + * any documenation for this. Maybe there is but I could not find it. If + * anybody finds this documention could you please send me a copy. Please + * note that I did not look that hard either. + *

+ * 0000: 00 50 73 1D 89 81 00 50 DA 44 C8 45 08 00 45 00 .Ps....P.D.E..E. + *

+ *

+ * 0010: 00 36 E9 1C 40 00 80 06 9B F9 C1 A8 33 58 C0 A8 .6..@...k....3X.. + *

+ *

+ * 0020: C0 02 06 0E 00 17 00 52 6E 88 73 40 DE CB 50 18 .......Rn.s@..P. + *

+ *

+ * 0030: 20 12 3C 53 00 00 00 0C 12 A0 00 00 04 01 00 00 . + *

+ * 0040: 00 05 FF EF .... ----------|| The 00 XX is the code to be sent. I + * found the following + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ERR_CURSOR_PROTECTED0x05
ERR_INVALID_SIGN0x11
ERR_NO_ROOM_INSERT0x12
ERR_NUMERIC_ONLY0x09
ERR_NUMERIC_090x10
ERR_FIELD_MINUS0x16
ERR_ENTER_NOT_ALLOWED0x20
ERR_MANDATORY_ENTER0x21
ERR_ENTER_NOT_ALLOWED0x20
I am tired of typing and they should be self explanitory. Finding + * them in the first place was the pain. + *

+ * + * @param ec error code + */ + private void displayError(int ec) { + saveHomePos = homePos; + homePos = lastPos + numCols + 1; + pendingInsert = true; + sessionVT.sendNegResponse2(ec); + } + + private void process_XY(int pos) { + if (pos < 0) + pos = lenScreen + pos; + + if (pos > lenScreen - 1) + pos = pos - lenScreen; + + // if there was a field exit error then we need to treat the movement + // of the cursor in a special way that equals that of Client Access. + // If the cursor is moved from the field then we need to reset the + // position within the field so that the last character can be typed + // over again instead of sending the field exit error again. + // We also need to reset the field exit error flag. + // + // How we know we have a field exit error is when the field position is + // set beyond the end of the field and a character is then typed we can + // not position that character. To reset this we need to set the next + // position of the field to not be beyond the end of field but to the + // last character. + // + // Now to make it work like Client Access if the cursor is a back space + // then do not move the cursor but place it on the last field. All + // other keys will reset the field position so that entering over the + // last character will not cause an error but replace that character or + // just plain move the cursor if the key was to do that. + ScreenField sf = screenFields.getCurrentField(); + + if (feError) { + feError = false; + sf.changePos(-1); + } + else { + if (sf != null && sf.isFER()) { + if ((sf.getCurrentPos() + > sf.endPos())) { + if (sf.withinField(pos)) { + sf.getKeyPos(pos); + return; + } + + sf.getKeyPos(sf.endPos()); + } + } + + goto_XY(pos); + } + } + + public boolean isUsingGuiInterface() { + return guiInterface; + } + + /** + * Convinience class to return if the cursor is in a field or not. + * + * @return true or false + */ + + protected boolean isInField() { + return isInField(lastPos, true); + } + + /** + * + * Convinience class to return if the position that is passed is in a field + * or not. If it is then the chgToField parameter will change the current + * field to this field where the position indicates + * + * @param pos + * @param chgToField + * @return true or false + */ + public boolean isInField(int pos, boolean chgToField) { + return screenFields.isInField(pos, chgToField); + } + + /** + * + * Convinience class to return if the position that is passed is in a field + * or not. If it is then the field at this position becomes the current + * working field + * + * @param pos + * @return true or false + */ + public boolean isInField(int pos) { + return screenFields.isInField(pos, true); + } + + /** + * Convinience class to return if the position at row and column that is + * passed is in a field or not. If it is then the field at this position + * becomes the current working field. + * + * @param row + * @param col + * @return true or false + */ + public boolean isInField(int row, int col) { + return isInField(row, col, true); + } + + /** + * + * Convinience class to return if the position at row and column that is + * passed is in a field or not. If it is then the chgToField parameter will + * change the current field to this field where the row and column + * indicates. + * + * @param row + * @param col + * @param chgToField + * @return true or false + */ + public boolean isInField(int row, int col, boolean chgToField) { + return screenFields.isInField((row * numCols) + col, chgToField); + } + + /** + * Gets the length of the screen - number of rows times number of columns + * + * @return int value of screen length + */ + public int getScreenLength() { + return lenScreen; + } + + /** + * Get the number or rows available. + * + * @return number of rows + */ + public int getRows() { + return numRows; + } + + /** + * Get the number of columns available. + * + * @return number of columns + */ + public int getColumns() { + return numCols; + } + + /** + * Get the current row where the cursor is + * + * @return the cursor current row position 1,1 based + */ + public int getCurrentRow() { + return (lastPos / numCols) + 1; + } + + /** + * Get the current column where the cursor is + * + * @return the cursor current column position 1,1 based + */ + public int getCurrentCol() { + return (lastPos % numCols) + 1; + } + + /** + * The last position of the cursor on the screen - Note - position is based + * 0,0 + * + * @return last position + */ + protected int getLastPos() { + return lastPos; + } + + /** + * Hotspot More... string + * + * @return string literal of More... + */ + public StringBuffer getHSMore() { + return hsMore; + } + + /** + * Hotspot Bottom string + * + * @return string literal of Bottom + */ + public StringBuffer getHSBottom() { + return hsBottom; + } + + /** + * + * Return the screen represented as a character array + * + * @return character array containing the text + */ + public char[] getScreenAsChars() { + char[] sac = new char[lenScreen]; + char c; + + for (int x = 0; x < lenScreen; x++) { + c = planes.getChar(x); + + // only draw printable characters (in this case >= ' ') + if ((c >= ' ') && (!planes.isAttributePlace(x))) { + sac[x] = c; + // TODO: implement the underline check here + // if (screen[x].underLine && c <= ' ') + // sac[x] = '_'; + } + else + sac[x] = ' '; + } + + return sac; + } + + public char[] getData(int startRow, int startCol, int endRow, int endCol, int plane) { + try { + int from = getPos(startRow, startCol); + int to = getPos(endRow, endCol); + + if (from > to) { + int f = from; + to = f; + from = f; + } + + return planes.getPlaneData(from, to, plane); + } + catch (Exception oe) { + return null; + } + } + + /** + *

+ * GetScreen retrieves the various planes associated with the presentation + * space. The data is returned as a linear array of character values in the + * array provided. The array is not terminated by a null character except + * when data is retrieved from the text plane, in which case a single null + * character is appended. + *

+ *

+ * The application must supply a buffer for the returned data and the length + * of the buffer. Data is returned starting from the beginning of the + * presentation space and continuing until the buffer is full or the entire + * plane has been copied. For text plane data, the buffer must include one + * extra position for the terminating null character. + *

+ * + * @param buffer + * @param bufferLength + * @param plane + * @return The number of characters copied to the buffer + * @throws OhioException + */ + + public synchronized int GetScreen(char buffer[], int bufferLength, int plane) + // throws OhioException { + { + return GetScreen(buffer, bufferLength, 0, lenScreen, plane); + } + + /** + *

+ * GetScreen retrieves the various planes associated with the presentation + * space. The data is returned as a linear array of character values in the + * array provided. The array is not terminated by a null character except + * when data is retrieved from the text plane, in which case a single null + * character is appended. + *

+ *

+ * The application must supply a buffer for the returned data and the length + * of the buffer. Data is returned starting from the given position and + * continuing until the specified number of characters have been copied, the + * buffer is full or the entire plane has been copied. For text plane data, + * the buffer must include one extra position for the terminating null character. + *

+ * + * @param buffer + * @param bufferLength + * @param from + * @param length + * @param plane + * @return The number of characters copied to the buffer + * @throws OhioException + */ + + public synchronized int GetScreen(char buffer[], int bufferLength, int from, + int length, int plane) + // throws OhioException { + { + return planes.GetScreen(buffer, bufferLength, from, length, plane); + } + + /** + *

+ * GetScreen retrieves the various planes associated with the presentation + * space. The data is returned as a linear array of character values in the + * array provided. The array is not terminated by a null character except + * when data is retrieved from the text plane, in which case a single null + * character is appended. + *

+ *

+ * The application must supply a buffer for the returned data and the length + * of the buffer. Data is returned starting from the given coordinates and + * continuing until the specified number of characters have been copied, + * the buffer is full, or the entire plane has been copied. For text plane + * data, the buffer must include one extra position for the terminating null + * character. + *

+ * + * @param buffer + * @param bufferLength + * @param row + * @param col + * @param length + * @param plane + * @return The number of characters copied to the buffer. + * @throws OhioException + */ + + public synchronized int GetScreen(char buffer[], int bufferLength, int row, + int col, int length, int plane) + // throws OhioException { + { + // Call GetScreen function after converting row and column to + // a position. + return planes.GetScreen(buffer, bufferLength, row, col, length, plane); + } + + /** + *

+ * GetScreenRect retrieves data from the various planes associated with the + * presentation space. The data is returned as a linear array of character + * values in the buffer provided. + *

+ * + *

+ * The application supplies two positions that represent opposing corners of + * a rectangle within the presentation space. The starting and ending + * positions can have any spatial relationship to each other. The data + * returned starts from the row containing the upper-most point to the row + * containing the lower-most point, and from the left-most column to the + * right-most column. + *

+ *

+ * The specified buffer must be at least large enough to contain the number + * of characters in the rectangle. If the buffer is too small, no data is + * copied and zero is returned by the method. Otherwise, the method returns + * the number of characters copied. + *

+ * + * @param buffer + * @param bufferLength + * @param startPos + * @param endPos + * @param plane + * @return The number of characters copied to the buffer + * @throws OhioException + */ + + public synchronized int GetScreenRect(char buffer[], int bufferLength, + int startPos, int endPos, int plane) + // throws OhioException { + { + return planes.GetScreenRect(buffer, bufferLength, startPos, endPos, plane); + } + + /** + *

+ * GetScreenRect retrieves data from the various planes associated with the + * presentation space. The data is returned as a linear array of character + * values in the buffer provided. The buffer is not terminated by a null + * character. + *

+ *

+ * The application supplies two coordinates that represent opposing corners + * of a rectangle within the presentation space. The starting and ending + * coordinates can have any spatial relationship to each other. The data + * returned starts from the row containing the upper-most point to the row + * containing the lower-most point, and from the left-most column to the + * right-most column. + *

+ *

+ * The specified buffer must be at least large enough to contain the number + * of characters in the rectangle. If the buffer is too small, no data is + * copied and zero is returned by the method. Otherwise, the method returns + * the number of characters copied. + *

+ * + * @param buffer + * @param bufferLength + * @param startRow + * @param startCol + * @param endRow + * @param endCol + * @param plane + * @return The number characters copied to the buffer + * @throws OhioException + */ + + public synchronized int GetScreenRect(char buffer[], int bufferLength, + int startRow, int startCol, + int endRow, int endCol, int plane) + // throws OhioException { + { + return planes.GetScreenRect(buffer, bufferLength, startRow, startCol, endRow, + endCol, plane); + } + + public synchronized boolean[] getActiveAidKeys() { + return sessionVT.getActiveAidKeys(); + } + + protected synchronized void setScreenData(String text, int location) { + // throws OhioException { + if (location < 0 || location > lenScreen) { + return; + // throw new OhioException(sessionVT.getSessionConfiguration(), + // OhioScreen5250.class.getName(), "osohio.screen.ohio00300", 1); + } + + int pos = location; + int l = text.length(); + boolean updated = false; + boolean flag = false; + int x = 0; + + for (; x < l; x++) { + if (isInField(pos + x, true)) { + if (!screenFields.getCurrentField().isBypassField()) { + if (!flag) { + screenFields.getCurrentField().setMDT(); + updated = true; + resetDirty(pos + x); + screenFields.setMasterMDT(); + flag = true; + } + + planes.screen[pos + x] = text.charAt(x); + setDirty(pos + x); + } + } + } + + lastPos = pos + x; + + if (updated) { + fireScreenChanged(); + } + } + + /** + * This routine is based on offset 1,1 not 0,0 it will translate to offset + * 0,0 and call the goto_XY(int pos) it is mostly used from external classes + * that use the 1,1 offset + * + * @param row + * @param col + */ + public void setCursor(int row, int col) { + goto_XY(((row - 1) * numCols) + (col - 1)); + } + + // this routine is based on offset 0,0 not 1,1 + protected void goto_XY(int pos) { + lastPos = pos; + updateCursorLoc(); + } + + /* + * set the content of the field at (l,c) to data + * if l == -1, set the current field contents to data + */ + public void setField(int l, int c, char [] data) { + ScreenField cf; + + if (l >= 0) { + lastPos = l * numCols + c; + + while (!isInField()) advancePos(); + + setDirty(lastPos); + fireCursorChanged(); + } + + if ((data != null) && (data.length > 0)) { + cf = screenFields.getCurrentField(); + cf.setString(new String(data)); + lastPos = cf.getStartPos(); + setDirty(lastPos); + setDirty(lastPos + cf.getLength()); + lastPos += data.length; + + if (!isInField()) { + gotoFieldNext(); + isInField(); + cf = screenFields.getCurrentField(); + lastPos = cf.getStartPos(); + } + + setDirty(lastPos); + fireCursorChanged(); + } + + updateDirty(); + } + + /** + * Set the current working field to the field number specified. + * + * @param f - + * numeric field number on the screen + * @return true or false whether it was sucessful + */ + public boolean gotoField(int f) { + int sizeFields = screenFields.getSize(); + + if (f > sizeFields || f <= 0) + return false; + + screenFields.setCurrentField(screenFields.getField(f - 1)); + + while (screenFields.isCurrentFieldBypassField() && f < sizeFields) { + screenFields.setCurrentField(screenFields.getField(f++)); + } + + return gotoField(screenFields.getCurrentField()); + } + + /** + * Convenience method to set the field object passed as the currect working + * screen field + * + * @param f + * @return true or false whether it was sucessful + * @see org.tn5250j.ScreenField + */ + protected boolean gotoField(ScreenField f) { + if (f != null) { + goto_XY(f.startPos()); + return true; + } + + return false; + } + + /** + * Convenience class to position the cursor to the next word on the screen + * + */ + private void gotoNextWord() { + int pos = lastPos; + + if (planes.getChar(lastPos) > ' ') { + advancePos(); + + // get the next space character + while (planes.getChar(lastPos) > ' ' && pos != lastPos) { + advancePos(); + } + } + else + advancePos(); + + // now that we are positioned on the next space character get the + // next none space character + while (planes.getChar(lastPos) <= ' ' && pos != lastPos) { + advancePos(); + } + } + + /** + * Convenience class to position the cursor to the previous word on the + * screen + * + */ + private void gotoPrevWord() { + int pos = lastPos; + changePos(-1); + + // position previous white space character + while (planes.getChar(lastPos) <= ' ') { + changePos(-1); + + if (pos == lastPos) + break; + } + + changePos(-1); + + // get the previous space character + while (planes.getChar(lastPos) > ' ' && pos != lastPos) { + changePos(-1); + } + + // and position one position more should give us the beginning of word + advancePos(); + } + + /** + * Convinience class to position to the next field on the screen. + * + * @see org.tn5250j.ScreenFields + */ + private void gotoFieldNext() { + if (screenFields.isCurrentFieldHighlightedEntry()) + unsetFieldHighlighted(screenFields.getCurrentField()); + + screenFields.gotoFieldNext(); + + if (screenFields.isCurrentFieldHighlightedEntry()) + setFieldHighlighted(screenFields.getCurrentField()); + } + + /** + * Convinience class to position to the previous field on the screen. + * + * @see org.tn5250j.ScreenFields + */ + private void gotoFieldPrev() { + if (screenFields.isCurrentFieldHighlightedEntry()) + unsetFieldHighlighted(screenFields.getCurrentField()); + + screenFields.gotoFieldPrev(); + + if (screenFields.isCurrentFieldHighlightedEntry()) + setFieldHighlighted(screenFields.getCurrentField()); + } + + /* *** NEVER USED LOCALLY ************************************************** */ + // /** + // * Used to restrict the cursor to a particular position on the screen. Used + // * in combination with windows to restrict the cursor to the active window + // * show on the screen. + // * + // * Not supported yet. Please implement me :-( + // * + // * @param depth + // * @param width + // */ + // protected void setRestrictCursor(int depth, int width) { + // + // restrictCursor = true; + // // restriction + // + // } + + /** + * Creates a window on the screen + * + * @param depth + * @param width + * @param type + * @param gui + * @param monoAttr + * @param colorAttr + * @param ul + * @param upper + * @param ur + * @param left + * @param right + * @param ll + * @param bottom + * @param lr + */ + protected void createWindow(int depth, int width, int type, boolean gui, + int monoAttr, int colorAttr, int ul, int upper, int ur, int left, + int right, int ll, int bottom, int lr) { + int c = getCol(lastPos); + int w = 0; + width++; + w = width; + // set leading attribute byte + // screen[lastPos].setCharAndAttr(initChar, initAttr, true); + planes.setScreenCharAndAttr(lastPos, initChar, initAttr, true); + setDirty(lastPos); + advancePos(); + // set upper left + // screen[lastPos].setCharAndAttr((char) ul, colorAttr, false); + planes.setScreenCharAndAttr(lastPos, (char) ul, colorAttr, false); + + if (gui) { + // screen[lastPos].setUseGUI(UPPER_LEFT); + planes.setUseGUI(lastPos, UPPER_LEFT); + } + + setDirty(lastPos); + advancePos(); + + // draw top row + + while (w-- >= 0) { + // screen[lastPos].setCharAndAttr((char) upper, colorAttr, false); + planes.setScreenCharAndAttr(lastPos, (char) upper, colorAttr, false); + + if (gui) { + // screen[lastPos].setUseGUI(UPPER); + planes.setUseGUI(lastPos, UPPER); + } + + setDirty(lastPos); + advancePos(); + } + + // set upper right + // screen[lastPos].setCharAndAttr((char) ur, colorAttr, false); + planes.setScreenCharAndAttr(lastPos, (char) ur, colorAttr, false); + + if (gui) { + // screen[lastPos].setUseGUI(UPPER_RIGHT); + planes.setUseGUI(lastPos, UPPER_RIGHT); + } + + setDirty(lastPos); + advancePos(); + // set ending attribute byte + planes.setScreenCharAndAttr(lastPos, initChar, initAttr, true); + setDirty(lastPos); + lastPos = ((getRow(lastPos) + 1) * numCols) + c; + + // now handle body of window + while (depth-- > 0) { + // set leading attribute byte + planes.setScreenCharAndAttr(lastPos, initChar, initAttr, true); + setDirty(lastPos); + advancePos(); + // set left + planes.setScreenCharAndAttr(lastPos, (char) left, colorAttr, false); + + if (gui) { + planes.setUseGUI(lastPos, GUI_LEFT); + } + + setDirty(lastPos); + advancePos(); + w = width; + + // fill it in + while (w-- >= 0) { + // screen[lastPos].setCharAndAttr(initChar, initAttr, true); + planes.setScreenCharAndAttr(lastPos, initChar, initAttr, true); + // screen[lastPos].setUseGUI(NO_GUI); + planes.setUseGUI(lastPos, NO_GUI); + setDirty(lastPos); + advancePos(); + } + + // set right + // screen[lastPos].setCharAndAttr((char) right, colorAttr, false); + planes.setScreenCharAndAttr(lastPos, (char) right, colorAttr, false); + + if (gui) { + // screen[lastPos].setUseGUI(RIGHT); + planes.setUseGUI(lastPos, GUI_RIGHT); + } + + setDirty(lastPos); + advancePos(); + // set ending attribute byte + // screen[lastPos].setCharAndAttr(initChar, initAttr, true); + planes.setScreenCharAndAttr(lastPos, initChar, initAttr, true); + setDirty(lastPos); + lastPos = ((getRow(lastPos) + 1) * numCols) + c; + } + + // set leading attribute byte + // screen[lastPos].setCharAndAttr(initChar, initAttr, true); + planes.setScreenCharAndAttr(lastPos, initChar, initAttr, true); + setDirty(lastPos); + advancePos(); + // set lower left + // screen[lastPos].setCharAndAttr((char) ll, colorAttr, false); + planes.setScreenCharAndAttr(lastPos, (char) ll, colorAttr, false); + + if (gui) { + // screen[lastPos].setUseGUI(LOWER_LEFT); + planes.setUseGUI(lastPos, LOWER_LEFT); + } + + setDirty(lastPos); + advancePos(); + w = width; + + // draw bottom row + while (w-- >= 0) { + planes.setScreenCharAndAttr(lastPos, (char) bottom, colorAttr, false); + + if (gui) { + planes.setUseGUI(lastPos, BOTTOM); + } + + setDirty(lastPos); + advancePos(); + } + + // set lower right + planes.setScreenCharAndAttr(lastPos, (char) lr, colorAttr, false); + + if (gui) { + planes.setUseGUI(lastPos, LOWER_RIGHT); + } + + setDirty(lastPos); + advancePos(); + // set ending attribute byte + planes.setScreenCharAndAttr(lastPos, initChar, initAttr, true); + setDirty(lastPos); + } + + /** + * Creates a scroll bar on the screen using the parameters provided. + * ** we only support vertical scroll bars at the time. + * + * @param flag - + * type to draw - vertical or horizontal + * @param totalRowScrollable + * @param totalColScrollable + * @param sliderRowPos + * @param sliderColPos + * @param sbSize + */ + protected void createScrollBar(int flag, int totalRowScrollable, + int totalColScrollable, int sliderRowPos, int sliderColPos, + int sbSize) { + // System.out.println("Scrollbar flag: " + flag + + // " scrollable Rows: " + totalRowScrollable + + // " scrollable Cols: " + totalColScrollable + + // " thumb Row: " + sliderRowPos + + // " thumb Col: " + sliderColPos + + // " size: " + sbSize + + // " row: " + getRow(lastPos) + + // " col: " + getCol(lastPos)); + int sp = lastPos; + int size = sbSize - 2; + int thumbPos = (int)(size * ((float) sliderColPos / (float) totalColScrollable)); + // System.out.println(thumbPos); + planes.setScreenCharAndAttr(sp, ' ', 32, false); + planes.setUseGUI(sp, BUTTON_SB_UP); + int ctr = 0; + + while (ctr < size) { + sp += numCols; + planes.setScreenCharAndAttr(sp, ' ', 32, false); + + if (ctr == thumbPos) + planes.setUseGUI(sp, BUTTON_SB_THUMB); + else + planes.setUseGUI(sp, BUTTON_SB_GUIDE); + + ctr++; + } + + sp += numCols; + planes.setScreenCharAndAttr(sp, ' ', 32, false); + planes.setUseGUI(sp, BUTTON_SB_DN); + } + + /** + * Write the title of the window that is on the screen + * + * @param pos + * @param depth + * @param width + * @param orientation + * @param monoAttr + * @param colorAttr + * @param title + */ + protected void writeWindowTitle(int pos, int depth, int width, + byte orientation, int monoAttr, int colorAttr, StringBuffer title) { + int len = title.length(); + + // get bit 0 and 1 for interrogation + switch (orientation & 0xc0) { + case 0x40: // right + pos += (4 + width - len); + break; + + case 0x80: // left + pos += 2; + break; + + default: // center + // this is to place the position to the first text position of the + // window + // the position passed in is the first attribute position, the next + // is the border character and then there is another attribute after + // that. + pos += (3 + ((width / 2) - (len / 2))); + break; + } + + // if bit 2 is on then this is a footer + if ((orientation & 0x20) == 0x20) + pos += ((depth + 1) * numCols); + + // System.out.println(pos + "," + width + "," + len+ "," + getRow(pos) + // + "," + getCol(pos) + "," + ((orientation >> 6) & 0xf0)); + + for (int x = 0; x < len; x++) { + planes.setChar(pos, title.charAt(x)); + planes.setUseGUI(pos++, NO_GUI); + } + } + + /** + * Roll the screen up or down. + * + * Byte 1: Bit 0 0 = Roll up 1 = Roll down Bits 1-2 Reserved Bits 3-7 Number + * of lines that the designated area is to be rolled Byte 2: Bits 0-7 Line + * number defining the top line of the area that will participate in the + * roll. Byte 3: Bits 0-7 Line number defining the bottom line of the area + * that will participate in the roll. + * + * @param direction + * @param topLine + * @param bottomLine + */ + protected void rollScreen(int direction, int topLine, int bottomLine) { + // get the number of lines which are the last 5 bits + /* int lines = direction & 0x7F; */ + // get the direction of the roll which is the first bit + // 0 - up + // 1 - down + int updown = direction & 0x80; + final int lines = direction & 0x7F; + // calculate the reference points for the move. + int start = this.getPos(topLine - 1, 0); + int end = this.getPos(bottomLine - 1, numCols - 1); + int len = end - start; + + // System.out.println(" starting roll"); + // dumpScreen(); + switch (updown) { + case 0: + + // Now round em up and head em UP. + for (int x = start; x < end + numCols; x++) { + if (x + lines * numCols >= lenScreen) { + //Clear at the end + planes.setChar(x, ' '); + } + else { + planes.setChar(x, planes.getChar(x + lines * numCols)); + } + } + + break; + + case 1: + + // Now round em up and head em DOWN. + for (int x = end + numCols; x > 0; x--) { + if ((x - lines * numCols) < 0) { + //Do nothing ... tooo small!!! + } + else { + planes.setChar(x - lines * numCols, planes.getChar(x)); + //and clear + planes.setChar(x, ' '); + } + } + + break; + + default: + Log.w(TAG, " Invalid roll parameter - please report this"); + } + + // System.out.println(" end roll"); + // dumpScreen(); + } + + public void dumpScreen() { + StringBuffer sb = new StringBuffer(); + char[] s = getScreenAsChars(); + int c = getColumns(); + int l = getRows() * c; + int col = 0; + + for (int x = 0; x < l; x++, col++) { + sb.append(s[x]); + + if (col == c) { + sb.append('\n'); + col = 0; + } + } + + Log.i(TAG, sb.toString()); + } + + /** + * Add a field to the field format table. + * + * @param attr - Field attribute + * @param len - length of field + * @param ffw1 - Field format word 1 + * @param ffw2 - Field format word 2 + * @param fcw1 - Field control word 1 + * @param fcw2 - Field control word 2 + */ + protected void addField(int attr, int len, int ffw1, int ffw2, int fcw1, + int fcw2) { + lastAttr = attr; + planes.setScreenCharAndAttr(lastPos, initChar, lastAttr, true); + setDirty(lastPos); + advancePos(); + ScreenField sf = null; + + // from 14.6.12 for Start of Field Order 5940 function manual + // examine the format table for an entry that begins at the current + // starting address plus 1. + if (screenFields.existsAtPos(lastPos)) { + screenFields.setCurrentFieldFFWs(ffw1, ffw2); + } + else { + sf = screenFields.setField(attr, getRow(lastPos), getCol(lastPos), + len, ffw1, ffw2, fcw1, fcw2); + lastPos = sf.startPos(); + int x = len; + boolean gui = guiInterface; + + if (sf.isBypassField()) + gui = false; + + while (x-- > 0) { + if (planes.getChar(lastPos) == 0) + planes.setScreenCharAndAttr(lastPos, ' ', lastAttr, false); + else + planes.setScreenAttr(lastPos, lastAttr); + + if (gui) { + planes.setUseGUI(lastPos, FIELD_MIDDLE); + } + + // now we set the field plane attributes + planes.setScreenFieldAttr(lastPos, ffw1); + advancePos(); + } + + if (gui) + if (len > 1) { + planes.setUseGUI(sf.startPos(), FIELD_LEFT); + + if (lastPos > 0) + planes.setUseGUI(lastPos - 1, FIELD_RIGHT); + else + planes.setUseGUI(lastPos, FIELD_RIGHT); + } + else { + planes.setUseGUI(lastPos - 1, FIELD_ONE); + } + + // screen[lastPos].setCharAndAttr(initChar,initAttr,true); + setEndingAttr(initAttr); + lastPos = sf.startPos(); + } + + // if (fcw1 != 0 || fcw2 != 0) { + // System.out.println("lr = " + lastRow + " lc = " + lastCol + " " + + // sf.toString()); + // } + sf = null; + } + + + // public void addChoiceField(int attr, int len, int ffw1, int ffw2, int + // fcw1, int fcw2) { + // + // lastAttr = attr; + // + // screen[lastPos].setCharAndAttr(initChar,lastAttr,true); + // setDirty(lastPos); + // + // advancePos(); + // + // boolean found = false; + // ScreenField sf = null; + // + // // from 14.6.12 for Start of Field Order 5940 function manual + // // examine the format table for an entry that begins at the current + // // starting address plus 1. + // for (int x = 0;x < sizeFields; x++) { + // sf = screenFields[x]; + // + // if (lastPos == sf.startPos()) { + // screenFields.getCurrentField() = sf; + // screenFields.getCurrentField().setFFWs(ffw1,ffw2); + // found = true; + // } + // + // } + // + // if (!found) { + // sf = + // setField(attr,getRow(lastPos),getCol(lastPos),len,ffw1,ffw2,fcw1,fcw2); + // + // lastPos = sf.startPos(); + // int x = len; + // + // boolean gui = guiInterface; + // if (sf.isBypassField()) + // gui = false; + // + // while (x-- > 0) { + // + // if (screen[lastPos].getChar() == 0) + // screen[lastPos].setCharAndAttr(' ',lastAttr,false); + // else + // screen[lastPos].setAttribute(lastAttr); + // + // if (gui) + // screen[lastPos].setUseGUI(FIELD_MIDDLE); + // + // advancePos(); + // + // } + // + // if (gui) + // if (len > 1) { + // screen[sf.startPos()].setUseGUI(FIELD_LEFT); + // if (lastPos > 0) + // screen[lastPos-1].setUseGUI(FIELD_RIGHT); + // else + // screen[lastPos].setUseGUI(FIELD_RIGHT); + // + // } + // else + // screen[lastPos-1].setUseGUI(FIELD_ONE); + // + // setEndingAttr(initAttr); + // + // lastPos = sf.startPos(); + // } + // + // // if (fcw1 != 0 || fcw2 != 0) { + // // + // // System.out.println("lr = " + lastRow + " lc = " + lastCol + " " + + // sf.toString()); + // // } + // sf = null; + // + // } + + /** + * Return the fields that are contained in the Field Format Table + * + * @return ScreenFields object + * @see org.tn5250j.ScreenFields + */ + public ScreenFields getScreenFields() { + return screenFields; + } + + /** + * Redraw the fields on the screen. Used for gui enhancement to redraw the + * fields when toggling + * + */ + protected void drawFields() { + ScreenField sf; + int sizeFields = screenFields.getSize(); + + for (int x = 0; x < sizeFields; x++) { + sf = screenFields.getField(x); + + if (!sf.isBypassField()) { + int pos = sf.startPos(); + int l = sf.length; + boolean f = true; + + if (l >= lenScreen) + l = lenScreen - 1; + + if (l > 1) { + while (l-- > 0) { + if (guiInterface && f) { + planes.setUseGUI(pos, FIELD_LEFT); + f = false; + } + else { + planes.setUseGUI(pos, FIELD_MIDDLE); + } + + if (guiInterface && l == 0) { + planes.setUseGUI(pos, FIELD_RIGHT); + } + + setDirty(pos++); + } + } + else { + planes.setUseGUI(pos, FIELD_ONE); + } + } + } + + //updateDirty(); + } + + /** + * Draws the field on the screen. Used to redraw or change the attributes of + * the field. + * + * @param sf - + * Field to be redrawn + * @see org.tn5250j.ScreenField.java + */ + protected void drawField(ScreenField sf) { + int pos = sf.startPos(); + int x = sf.length; + + while (x-- > 0) { + setDirty(pos++); + } + + updateDirty(); + } + + /** + * Set the field to be displayed as highlighted. + * + * @param sf - + * Field to be highlighted + */ + protected void setFieldHighlighted(ScreenField sf) { + int pos = sf.startPos(); + int x = sf.length; + int na = sf.getHighlightedAttr(); + + while (x-- > 0) { + planes.setScreenAttr(pos, na); + setDirty(pos++); + } + + fireScreenChanged(); + } + + /** + * Draw the field as un higlighted. This is used to reset the field + * presentation on the screen after the field is exited. + * + * @param sf - + * Field to be unhighlighted + */ + protected void unsetFieldHighlighted(ScreenField sf) { + int pos = sf.startPos(); + int x = sf.length; + int na = sf.getAttr(); + + while (x-- > 0) { + planes.setScreenAttr(pos, na); + setDirty(pos++); + } + + fireScreenChanged(); + } + + protected void setChar(int cByte) { + if (lastPos > 0) { + lastAttr = planes.getCharAttr(lastPos - 1); + } + + if (cByte > 0 && (char)cByte < ' ') { + planes.setScreenCharAndAttr(lastPos, (char) 0x00, 33, false); + setDirty(lastPos); + advancePos(); + } + else { + planes.setScreenCharAndAttr(lastPos, (char) cByte, lastAttr, false); + setDirty(lastPos); + + if (guiInterface && !isInField(lastPos, false)) { + planes.setUseGUI(lastPos, NO_GUI); + } + + advancePos(); + } + } + + protected void setEndingAttr(int cByte) { + int attr = lastAttr; + setAttr(cByte); + lastAttr = attr; + } + + protected void setAttr(int cByte) { + lastAttr = cByte; + // int sattr = screen[lastPos].getCharAttr(); + // System.out.println("changing from " + sattr + " to attr " + lastAttr + // + + // " at " + (this.getRow(lastPos) + 1) + "," + (this.getCol(lastPos) + + // 1)); + planes.setScreenCharAndAttr(lastPos, initChar, lastAttr, true); + setDirty(lastPos); + advancePos(); + int pos = lastPos; + int times = 0; + // sattr = screen[lastPos].getCharAttr(); + // System.out.println(" next position after change " + sattr + " last + // attr " + lastAttr + + // " at " + (this.getRow(lastPos) + 1) + "," + (this.getCol(lastPos) + + // 1) + + // " attr place " + screen[lastPos].isAttributePlace()); + + while (planes.getCharAttr(lastPos) != lastAttr + && !planes.isAttributePlace(lastPos)) { + planes.setScreenAttr(lastPos, lastAttr); + + if (guiInterface && !isInField(lastPos, false)) { + int g = planes.getWhichGUI(lastPos); + + if (g >= FIELD_LEFT && g <= FIELD_ONE) + planes.setUseGUI(lastPos, NO_GUI); + } + + setDirty(lastPos); + times++; + advancePos(); + } + + // sanity check for right now + // if (times > 200) + // System.out.println(" setAttr = " + times + " start = " + (sr + 1) + + // "," + (sc + 1)); + lastPos = pos; + } + + protected void setScreenCharAndAttr(char right, int colorAttr, boolean isAttr) { + planes.setScreenCharAndAttr(lastPos, right, colorAttr, isAttr); + setDirty(lastPos); + advancePos(); + } + + protected void setScreenCharAndAttr(char right, int colorAttr, + int whichGui, boolean isAttr) { + planes.setScreenCharAndAttr(lastPos, right, colorAttr, isAttr); + planes.setUseGUI(lastPos, whichGui); + setDirty(lastPos); + advancePos(); + } + + /** + * Draw or redraw the dirty parts of the screen and display them. + * + * Rectangle dirty holds the dirty area of the screen to be updated. + * + * If you want to change the screen in anyway you need to set the screen + * attributes before calling this routine. + */ + protected void updateDirty() { + fireScreenChanged(); + } + + protected void setDirty(int pos) { + int minr = Math.min(getRow(pos), getRow(dirtyScreen.x)); + int minc = Math.min(getCol(pos), getCol(dirtyScreen.x)); + int maxr = Math.max(getRow(pos), getRow(dirtyScreen.y)); + int maxc = Math.max(getCol(pos), getCol(dirtyScreen.y)); + int x1 = getPos(minr, minc); + int x2 = getPos(maxr, maxc); + dirtyScreen.setBounds(x1, x2, 0, 0); + } + + private void resetDirty(int pos) { + dirtyScreen.setBounds(pos, pos, 0, 0); + } + + /** + * Change the screen position by one column + */ + protected void advancePos() { + changePos(1); + } + + /** + * Change position of the screen by the increment of parameter passed. + * + * If the position change is under the minimum of the first screen position + * then the position is moved to the last row and column of the screen. + * + * If the position change is over the last row and column of the screen then + * cursor is moved to first position of the screen. + * + * @param i + */ + protected void changePos(int i) { + lastPos += i; + + while (lastPos < 0) lastPos += lenScreen; + + while (lastPos >= lenScreen) lastPos -= lenScreen; + } + + + protected void goHome() { + // now we try to move to first input field according to + // 14.6 WRITE TO DISPLAY Command + // ? If the WTD command is valid, after the command is processed, + // the cursor moves to one of three locations: + // - The location set by an insert cursor order (unless control + // character byte 1, bit 1 is equal to B'1'.) + // - The start of the first non-bypass input field defined in the + // format table + // - A default starting address of row 1 column 1. + if (pendingInsert && homePos > 0) { + setCursor(getRow(homePos), getCol(homePos)); + isInField(); + } + else { + if (!gotoField(1)) { + homePos = getPos(1, 1); + setCursor(1, 1); + isInField(0, 0); + } + else { + homePos = getPos(getCurrentRow(), getCurrentCol()); + } + } + } + + protected void setPendingInsert(boolean flag, int icX, int icY) { + pendingInsert = flag; + + if (pendingInsert) { + homePos = getPos(icX, icY); + } + + if (!isStatusErrorCode()) { + setCursor(icX, icY); + } + } + + protected void setPendingInsert(boolean flag) { + if (homePos != -1) + pendingInsert = flag; + } + + /** + * Set the error line number to that of number passed. + * + * @param line + */ + protected void setErrorLine(int line) { + planes.setErrorLine(line); + } + + /** + * Returns the current error line number + * + * @return current error line number + */ + protected int getErrorLine() { + return planes.getErrorLine(); + } + + /** + * Saves off the current error line characters to be used later. + * + */ + protected void saveErrorLine() { + planes.saveErrorLine(); + } + + /** + * Restores the error line characters from the save buffer. + * + * @see #saveErrorLine() + */ + protected void restoreErrorLine() { + if (planes.isErrorLineSaved()) { + planes.restoreErrorLine(); + fireScreenChanged(planes.getErrorLine() - 1, 0, planes.getErrorLine() - 1, numCols - 1); + } + } + + protected void setStatus(byte attr, byte value, String s) { + // set the status area + switch (attr) { + case STATUS_SYSTEM: + if (value == STATUS_VALUE_ON) { + oia.setInputInhibited(ScreenOIA.INPUTINHIBITED_SYSTEM_WAIT, ScreenOIA.OIA_LEVEL_INPUT_INHIBITED, s); + } + else { + oia.setInputInhibited(ScreenOIA.INPUTINHIBITED_NOTINHIBITED, ScreenOIA.OIA_LEVEL_NOT_INHIBITED, s); + } + + break; + + case STATUS_ERROR_CODE: + if (value == STATUS_VALUE_ON) { + setPrehelpState(true, true, false); + oia.setInputInhibited(ScreenOIA.INPUTINHIBITED_SYSTEM_WAIT, + ScreenOIA.OIA_LEVEL_INPUT_ERROR, s); + sessionVT.signalBell(); + } + else { + oia.setInputInhibited(ScreenOIA.INPUTINHIBITED_NOTINHIBITED, + ScreenOIA.OIA_LEVEL_NOT_INHIBITED); + setPrehelpState(false, true, true); + homePos = saveHomePos; + saveHomePos = 0; + pendingInsert = false; + } + + break; + } + } + + protected boolean isStatusErrorCode() { + return oia.getLevel() == ScreenOIA.OIA_LEVEL_INPUT_ERROR; + } + + /** + * This routine clears the screen, resets row and column to 0, resets the + * last attribute to 32, clears the fields, turns insert mode off, + * clears/initializes the screen character array. + */ + protected void clearAll() { + lastAttr = 32; + lastPos = 0; + clearTable(); + clearScreen(); + planes.setScreenAttr(0, initAttr); + oia.setInsertMode(false); + } + + /** + * Clear the fields table + */ + protected void clearTable() { + oia.setKeyBoardLocked(true); + screenFields.clearFFT(); + planes.initalizeFieldPlanes(); + pendingInsert = false; + homePos = -1; + } + + /** + * Clear the gui constructs + * + */ + protected void clearGuiStuff() { + for (int x = 0; x < lenScreen; x++) { + planes.setUseGUI(x, NO_GUI); + } + + dirtyScreen.setBounds(0, lenScreen - 1, 0, 0); + } + + /** + * Clear the screen by setting the initial character and initial attribute + * to all the positions on the screen + */ + protected void clearScreen() { + planes.initalizePlanes(); + dirtyScreen.setBounds(0, lenScreen - 1, 0, 0); + oia.clearScreen(); + } + + protected void restoreScreen() { + lastAttr = 32; + dirtyScreen.setBounds(0, lenScreen - 1, 0, 0); + updateDirty(); + } + + public void onFontSizeChanged(float size) { + fireScreenChanged(0, 0, numRows - 1, numCols - 1); + } + + /** + * repaint part of the screen + * + */ + private void fireScreenChanged(int startRow, int startCol, int endRow, int endCol) { + int [] vt320color = {0x0, // black + 0x4, // blue + 0x2, // green + 0x6, // cyan + 0x1, // red + 0x5, // magenta/purple + 0xb, // yellow + 0x7, // light gray/white + 0x8, // dark gray + 0xc, // light blue + 0xa, // light green + 0xe, // light cyan + 0x9, // light red + 0xd, // light magenta/purple + 0x3, // brown + 0xf // bright white + }; + + for (int r = startRow; r <= endRow; r++) { + for (int c = startCol; c <= endCol; c++) { + int p = getPos(r, c); + char ch = planes.getChar(p); + char co = planes.getCharColor(p); + char at = planes.getCharExtended(p); + boolean ia = planes.isAttributePlace(p); + + if (ch < ' ') ch = ' '; + + int bg = vt320color[(co >> 8) & 0x0f] + 1; + int fg = vt320color[co & 0x0f] + 1; + int ul = at & EXTENDED_5250_UNDERLINE; + int nd = at & EXTENDED_5250_NON_DSP; + int vt_attr = (fg << VDUBuffer.COLOR_FG_SHIFT) + (bg << VDUBuffer.COLOR_BG_SHIFT); + + if (ul > 0) vt_attr |= VDUBuffer.UNDERLINE; + + if (ia || (nd > 0)) vt_attr |= VDUBuffer.INVISIBLE; + + buffer.putChar(c, r, ch, vt_attr); + } + } + + buffer.redrawPassthru(); + dirtyScreen.setBounds(lenScreen, 0, 0, 0); + } + + /** + * repaint the dirty part of the screen + * + */ + + private synchronized void fireScreenChanged() { + if (dirtyScreen.x > dirtyScreen.y) return; + fireScreenChanged(getRow(dirtyScreen.x), getCol(dirtyScreen.x), + getRow(dirtyScreen.y), getCol(dirtyScreen.y)); + } + + /** + * update the cursor position + * + */ + + private synchronized void fireCursorChanged() { + int l = getRow(lastPos); + int c = getCol(lastPos); + buffer.setCursorPosition(c, l); + } + + /** + * update the screen size. + */ + private void fireScreenSizeChanged() { + buffer.setScreenSize(numCols, numRows, true); + } + + /** + * This method does a complete refresh of the screen. + */ + public final void updateScreen() { + repaintScreen(); + setCursorActive(false); + setCursorActive(true); + } + + /** + * Utility method to share the repaint behaviour between setBounds() and + * updateScreen. + */ + public void repaintScreen() { + setCursorOff(); + dirtyScreen.setBounds(0, lenScreen - 1, 0, 0); + updateDirty(); + + // restore statuses that were on the screen before resize + if (oia.getLevel() == ScreenOIA.OIA_LEVEL_INPUT_ERROR) { + oia.setInputInhibited(ScreenOIA.INPUTINHIBITED_SYSTEM_WAIT, + ScreenOIA.OIA_LEVEL_INPUT_ERROR); + } + + if (oia.getLevel() == ScreenOIA.OIA_LEVEL_INPUT_INHIBITED) { + oia.setInputInhibited(ScreenOIA.INPUTINHIBITED_SYSTEM_WAIT, + ScreenOIA.OIA_LEVEL_INPUT_INHIBITED); + } + + if (oia.isMessageWait()) + oia.setMessageLightOn(); + + setCursorOn(); + } + + // ADDED BY BARRY - changed by Kenneth to use the character plane + // This should be replaced with the getPlane methods when they are implemented + public char[] getCharacters() { + return planes.screen; + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/framework/tn5250/ScreenField.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/framework/tn5250/ScreenField.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,537 @@ +/** + * Title: tn5250J + * Copyright: Copyright (c) 2001 + * Company: + * @author Kenneth J. Pouncey + * @version 0.4 + * + * Description: + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.framework.tn5250; + +public class ScreenField { + + protected ScreenField(Screen5250 s) { + this.s = s; + } + + protected ScreenField setField(int attr, int len, int ffw1, int ffw2, + int fcw1, int fcw2) { + return setField(attr, + s.getCurrentRow() - 1, + s.getCurrentCol() - 1, + len, + ffw1, + ffw2, + fcw1, + fcw2); + } + + protected ScreenField setField(int attr, int row, int col, int len, int ffw1, int ffw2, + int fcw1, int fcw2) { +// startRow = row; +// startCol = col; + cursorProg = 0; + fieldId = 0; + length = len; + startPos = (row * s.getColumns()) + col; + endPos = startPos + length - 1; + this.attr = attr; + setFFWs(ffw1, ffw2); + setFCWs(fcw1, fcw2); + next = null; + prev = null; + return this; + } + + public int getAttr() { + return attr; + } + + public int getHighlightedAttr() { + return (fcw2 & 0x0f) | 0x20; + } + + public int getLength() { + return length; + } + + protected boolean setFFWs(int ffw1, int ffw2) { + this.ffw1 = ffw1; + this.ffw2 = ffw2; + int adj = getAdjustment(); + + if (adj > 0) { + checkCanSend = true; + + switch (adj) { + case 5: + case 6: + rightAdjd = false; + break; + + case 7: + mandatory = false; + break; + } + } + + mdt = (ffw1 & 0x8) == 0x8; +// if (mdt) +// s.masterMDT = true; + return mdt; + } + + + public int getFFW1() { + return ffw1; + } + public int getFFW2() { + return ffw2; + } + + protected void setFCWs(int fcw1, int fcw2) { + this.fcw1 = fcw1; + this.fcw2 = fcw2; + +// if ((fcw1 & 0x88) == 0x88) { + if (fcw1 == 0x88) { + cursorProg = fcw2; + } + } + + public int getFCW1() { + return fcw1; + } + + public int getFCW2() { + return fcw2; + } + + public int getFieldLength() { + return length; + } + + public int getCursorProgression() { + return cursorProg; + } + + public int getFieldId() { + return fieldId; + } + + protected void setFieldId(int fi) { + fieldId = fi; + } + + public int getCursorRow() { + return cursorPos / s.getColumns(); + } + + public int getCursorCol() { + return cursorPos % s.getColumns(); + } + + protected void changePos(int i) { + cursorPos += i; + } + + protected String getText() { + StringBuffer text = new StringBuffer(); + getKeyPos(endPos); + int x = length; + text.setLength(x); + + while (x-- > 0) { + // here we manipulate the unicode characters a little for attributes + // that are imbedded in input fields. We will offset them by unicode + // \uff00. All routines that process these fields will have to + // return them to their proper offsets. + // example: + // if we read an attribute byte of 32 for normal display the unicode + // character for this is \u0020 and the unicode character for + // a space is also \u0020 thus the offset. + if (s.planes.isAttributePlace(cursorPos)) { + text.setCharAt(x, (char)('\uff00' + s.planes.getCharAttr(cursorPos))); + } + else { + text.setCharAt(x, s.planes.getChar(cursorPos)); + } + + changePos(-1); + } + + // Since only the mdt of the first continued field is set we will get + // the text of the next continued field if we are dealing with continued + // fields. See routine setMDT for the whys of this. This is only + // executed if this is the first field of a continued field. + if (isContinued() && isContinuedFirst()) { + ScreenField sf = this; + + do { + sf = sf.next; + text.append(sf.getText()); + } + while (!sf.isContinuedLast()); + + sf = null; + } + + return text.toString(); + } + + public String getString() { + StringBuffer text = new StringBuffer(); + getKeyPos(endPos); + int x = length; + text.setLength(x); + + while (x-- > 0) { + // here we manipulate the unicode characters a little for attributes + // that are imbedded in input fields. We will offset them by unicode + // \uff00. All routines that process these fields will have to + // return them to their proper offsets. + // example: + // if we read an attribute byte of 32 for normal display the unicode + // character for this is \u0020 and the unicode character for + // a space is also \u0020 thus the offset. + if (s.planes.isAttributePlace(cursorPos)) { + text.setCharAt(x, (char)('\uff00' + s.planes.getCharAttr(cursorPos))); + } + else { + if (s.planes.getChar(cursorPos) < ' ') + text.setCharAt(x, ' '); + else + text.setCharAt(x, s.planes.getChar(cursorPos)); + } + + changePos(-1); + } + + // Since only the mdt of the first continued field is set we will get + // the text of the next continued field if we are dealing with continued + // fields. See routine setMDT for the whys of this. This is only + // executed if this is the first field of a continued field. + if (isContinued() && isContinuedFirst()) { + ScreenField sf = this; + + do { + sf = sf.next; + text.append(sf.getString()); + } + while (!sf.isContinuedLast()); + + sf = null; + } + + return text.toString(); + } + + public void setFieldChar(char c) { + int x = length; + cursorPos = startPos; + + while (x-- > 0) { + s.planes.setChar(cursorPos, c); + changePos(1); + } + } + + public void setFieldChar(int lastPos, char c) { + int x = endPos - lastPos + 1; + cursorPos = lastPos; + + while (x-- > 0) { + s.planes.setChar(cursorPos, c); + s.setDirty(cursorPos); + changePos(1); + } + } + + protected void setRightAdjusted() { + rightAdjd = true; + } + + protected void setMandatoryEntered() { + mandatory = true; + } + + protected void resetMDT() { + mdt = false; + } + + protected void setMDT() { + // get the first field of a continued edit field if it is continued + if (isContinued() && !isContinuedFirst()) { + ScreenField sf = prev; + + while (sf.isContinued() && !sf.isContinuedFirst()) { + sf = sf.prev; + } + + sf.setMDT(); + sf = null; + } + else { + mdt = true; + } + } + + public boolean isBypassField() { + return (ffw1 & 0x20) == 0x20; + } + + public int getAdjustment() { + return (ffw2 & 0x7); + } + + // is field exit required + public boolean isFER() { + return (ffw2 & 0x40) == 0x40; + } + + // is field mandatory enter + public boolean isMandatoryEnter() { + return (ffw2 & 0x8) == 0x8; + } + + public boolean isToUpper() { + return (ffw2 & 0x20) == 0x20; + } + + // bits 5 - 7 + public int getFieldShift() { + return (ffw1 & 0x7); + } + + public boolean isHiglightedEntry() { + return (fcw1 == 0x89); + } + + public boolean isAutoEnter() { + return (ffw2 & 0x80) == 0x80; + } + + public boolean isSignedNumeric() { + return (getFieldShift() == 7); + } + + public boolean isRightToLeft() { + return (getFieldShift() == 0x04); + } + + public boolean isNumeric() { + return (getFieldShift() == 3); + } + + public boolean isDupEnabled() { + return (ffw1 & 0x10) == 0x10; + } + + public boolean isContinued() { + return (fcw1 & 0x86) == 0x86 && (fcw2 >= 1 && fcw2 <= 3) ; + } + + public boolean isContinuedFirst() { + return (fcw1 & 0x86) == 0x86 && (fcw2 == 1); + } + + public boolean isContinuedMiddle() { + return (fcw1 & 0x86) == 0x86 && (fcw2 == 3); + } + + public boolean isContinuedLast() { + return (fcw1 & 0x86) == 0x86 && (fcw2 == 2); + } + + protected boolean isCanSend() { + int adj = getAdjustment(); + + // here we need to check the Field Exit Required value first before checking + // the adjustments. If the last character has been entered and we are + // now setting past the last position then we are allowed to process the + // the field without continuing. + if (isFER() && cursorPos > endPos) { + return true; + } + + // signed numeric fields need to be checked as well. + if (isSignedNumeric() && cursorPos < endPos - 1) { + return false; + } + + if (adj > 0) { + switch (adj) { + case 5: + case 6: + return rightAdjd; + + case 7: + return mandatory; + + default: + return true; + } + } + + return true; + } + + public boolean isSelectionField() { + return isSelectionField; + } + + public void setSelectionFieldInfo(int type, int index, int position) { + selectionFieldType = type; + selectionIndex = index; + selectionPos = position; + isSelectionField = true; + } + + protected int getKeyPos(int row1, int col1) { + int x = ((row1 * s.getColumns()) + col1); + int y = x - startPos(); + cursorPos = x; + return y; + } + + protected int getKeyPos(int pos) { + int y = pos - startPos(); + cursorPos = pos; + return y; + } + + public int getCurrentPos() { + return cursorPos; + } + + public boolean withinField(int pos) { + if (pos >= startPos && pos <= endPos) + return true; + + return false; + } + + public int startPos() { + return startPos; + } + + /** + * Get the starting row of the field. Offset is 0 so row 6 returned + * is row 7 mapped to screen + * @return int starting row of the field offset 0 + */ + public int startRow() { + return startPos / s.getColumns(); + } + + /** + * Get the starting column of the field. Offset is 0 so column 6 returned + * is column 7 mapped to screen + * @return int starting column of the field offset 0 + */ + public int startCol() { + return startPos % s.getColumns(); + } + + public int endPos() { + return endPos; + } + + /** + * Sets the field's text plane to the specified string. If the string is + * shorter than the length of the field, the rest of the field is cleared. + * If the string is longer than the field, the text is truncated. A subsequent + * call to getText on this field will not show the changed text. To see the + * changed text, do a refresh on the iOhioFields collection and retrieve the + * refreshed field object. + * + * @param text - The text to be placed in the field's text plane. + */ + public void setString(String text) { + cursorPos = startPos; + + if (isRightToLeft()) { + text = new StringBuilder(text).reverse().toString(); + } + + final ScreenPlanes planes = s.getPlanes(); + for (int x = 0,len = text.length(); x < length; x++) { + char tc = (x < len) ? text.charAt(x) : ' '; + planes.setChar(cursorPos, tc); + changePos(1); + } + setMDT(); + s.getScreenFields().setMasterMDT(); + } + + @Override + public String toString() { + int fcw = (fcw1 & 0xff) << 8 | fcw2 & 0xff; + return "startRow = " + startRow() + " startCol = " + startCol() + + " length = " + length + " ffw1 = (0x" + Integer.toHexString(ffw1) + + ") ffw2 = (0x" + Integer.toHexString(ffw2) + + ") fcw1 = (0x" + Integer.toHexString(fcw1) + + ") fcw2 = (0x" + Integer.toHexString(fcw2) + + ") fcw = (" + Integer.toBinaryString(fcw) + + ") fcw hex = (0x" + Integer.toHexString(fcw) + + ") is bypass field = " + isBypassField() + + ") is autoenter = " + isAutoEnter() + + ") is mandatoryenter = " + isMandatoryEnter() + + ") is field exit required = " + isFER() + + ") is Numeric = " + isNumeric() + + ") is Signed Numeric = " + isSignedNumeric() + + ") is cursor progression = " + (fcw1 == 0x88) + + ") next progression field = " + fcw2 + + ") field id " + fieldId + + " continued edit field = " + isContinued() + + " first continued edit field = " + isContinuedFirst() + + " middle continued edit field = " + isContinuedMiddle() + + " last continued edit field = " + isContinuedLast() + + " mdt = " + mdt; + } + + public int getStartPos() { + return startPos; + } + + int startPos = 0; + int endPos = 0; + boolean mdt = false; + protected boolean checkCanSend; + protected boolean rightAdjd; + protected boolean mandatory; + boolean canSend = true; + int attr = 0; + int length = 0; + int ffw1 = 0; + int ffw2 = 0; + int fcw1 = 0; + int fcw2 = 0; + int cursorPos = 0; + Screen5250 s; + int cursorProg = 0; + int fieldId = 0; + ScreenField next = null; + ScreenField prev = null; + boolean isSelectionField; + int selectionFieldType; + int selectionIndex; + int selectionPos; +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/framework/tn5250/ScreenFields.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/framework/tn5250/ScreenFields.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,685 @@ +/** + * Title: tn5250J + * Copyright: Copyright (c) 2001 + * Company: + * @author Kenneth J. Pouncey + * @version 0.5 + * + * Description: + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.framework.tn5250; + +import static org.tn5250j.TN5250jConstants.CMD_READ_INPUT_FIELDS; +import static org.tn5250j.TN5250jConstants.CMD_READ_MDT_FIELDS; +import static org.tn5250j.TN5250jConstants.CMD_READ_MDT_IMMEDIATE_ALT; + +import java.io.ByteArrayOutputStream; + +import org.tn5250j.encoding.ICodePage; + +public class ScreenFields { + + private ScreenField[] screenFields; + private ScreenField currentField; + private ScreenField saveCurrent; + private int sizeFields; + private boolean cpfExists; + private int nextField; + private int fieldIds; + private Screen5250 screen; + private boolean masterMDT; + protected boolean currentModified; + + public ScreenFields(Screen5250 s) { + screen = s; + screenFields = new ScreenField[256]; + } + + protected void clearFFT() { + sizeFields = nextField = fieldIds = 0; + cpfExists = false; // clear the cursor progression fields flag + currentField = null; + masterMDT = false; + } + + protected boolean existsAtPos(int lastPos) { + ScreenField sf = null; + + // from 14.6.12 for Start of Field Order 5940 function manual + // examine the format table for an entry that begins at the current + // starting address plus 1. + for (int x = 0; x < sizeFields; x++) { + sf = screenFields[x]; + + if (lastPos == sf.startPos()) { + currentField = sf; + currentModified = false; + return true; + } + } + + return false; + } + + public boolean isMasterMDT() { + return masterMDT; + } + + protected void setMasterMDT() { + masterMDT = true; + } + + public boolean isCurrentField() { + return currentField == null; + } + + public boolean isCurrentFieldFER() { + return currentField.isFER(); + } + + public boolean isCurrentFieldDupEnabled() { + return currentField.isDupEnabled(); + } + + public boolean isCurrentFieldToUpper() { + return currentField.isToUpper(); + } + + public boolean isCurrentFieldBypassField() { + return currentField.isBypassField(); + } + + public boolean isCurrentFieldHighlightedEntry() { + if (currentField != null) + return currentField.isHiglightedEntry(); + else + return false; + } + + public boolean isCurrentFieldAutoEnter() { + return currentField.isAutoEnter(); + } + + public boolean withinCurrentField(int pos) { + return currentField.withinField(pos); + } + + public boolean isCurrentFieldContinued() { + return currentField.isContinued(); + } + + public boolean isCurrentFieldContinuedFirst() { + return currentField.isContinuedFirst(); + } + + public boolean isCurrentFieldContinuedMiddle() { + return currentField.isContinuedMiddle(); + } + + public boolean isCurrentFieldContinuedLast() { + return currentField.isContinuedLast(); + } + + public boolean isCurrentFieldModified() { + return currentModified; + } + + /** + * This routine is used to check if we can send the Aid key to the host + * + * Taken from Section 16.2.1.2 Enter/Rec Adv Key + * + * In the normal unlocked state, when the workstation operator presses the + * Enter/Rec Adv key: + * + * 1. The 5494 checks for the completion of mandatory-fill, self-check, and + * right-adjust fields when in an active field. (An active field is one in + * which the workstation operator has begun entering data.) If the + * requirements of the field have not been satisfied, an error occurs. + * + * @return + * + */ + public boolean isCanSendAid() { + // We also have to check if we are still in the field. + if (currentField != null && + (currentField.getAdjustment() > 0 || currentField.isSignedNumeric()) + && currentModified && isInField() + && !currentField.isCanSend()) + return false; + else + return true; + } + + protected void saveCurrentField() { + saveCurrent = currentField; + } + + protected void restoreCurrentField() { + currentField = saveCurrent; + } + + protected void setCurrentField(ScreenField sf) { + currentField = sf; + } + + protected void setCurrentFieldMDT() { + currentField.setMDT(); + currentModified = true; + masterMDT = true; + } + + protected void setCurrentFieldFFWs(int ffw1, int ffw2) { + masterMDT = currentField.setFFWs(ffw1, ffw2); + } + + + protected ScreenField setField(int attr, int row, int col, int len, int ffw1, + int ffw2, int fcw1, int fcw2) { + ScreenField sf = null; + screenFields[nextField] = new ScreenField(screen); + screenFields[nextField].setField(attr, row, col, len, ffw1, ffw2, fcw1, fcw2); + sf = screenFields[nextField++]; + sizeFields++; + // set the field id if it is not a bypass field + // this is used for cursor progression + // changed this because of problems not allocating field id's for + // all fields. kjp 2002/10/21 +// if (!sf.isBypassField()) + sf.setFieldId(++fieldIds); + + // check if the cursor progression field flag should be set. +// if ((fcw1 & 0x88) == 0x88) + if (fcw1 == 0x88) + cpfExists = true; + + if (currentField != null) { + currentField.next = sf; + sf.prev = currentField; + } + + currentField = sf; + + // check if the Modified Data Tag was set while creating the field + if (!masterMDT) + masterMDT = currentField.mdt; + + currentModified = false; + return currentField; + } + + public ScreenField getField(int index) { + return screenFields[index]; + } + + public ScreenField getCurrentField() { + return currentField; + } + + public int getCurrentFieldPos() { + return currentField.getCurrentPos(); + } + + protected int getCurrentFieldShift() { + return currentField.getFieldShift(); + } + + public String getCurrentFieldText() { + return currentField.getText(); + } + + public int getCurrentFieldHighlightedAttr() { + return currentField.getHighlightedAttr(); + } + + public int getSize() { + return sizeFields; + } + + public int getFieldCount() { + return sizeFields; + } + + protected boolean isInField(int pos) { + return isInField(pos, true); + } + + protected boolean isInField() { + return isInField(screen.getLastPos(), true); + } + + protected boolean isInField(int pos, boolean chgToField) { + ScreenField sf; + + for (int x = 0; x < sizeFields; x++) { + sf = screenFields[x]; + + if (sf.withinField(pos)) { + if (chgToField) { + if (!currentField.equals(sf)) + currentModified = false; + + currentField = sf; + } + + return true; + } + } + + return false; + } + + /** + * Searches the collection for the target string and returns the iOhioField + * object containing that string. The string must be totally contained + * within the field to be considered a match. + * + * @param targetString The target string. + * @param startPos The row and column where to start the search. The position + * is inclusive (for example, row 1, col 1 means that + * position 1,1 will be used as the starting location and + * 1,1 will be included in the search). + * @param length The length from startPos to include in the search. + * @param dir An OHIO_DIRECTION value: + * + * + * + * + * + * + * + * + *
Constant ValueDescription
OS_OHIO_DIRECTION_FORWARD 0Forward (beginning towards end)
OS_OHIO_DIRECTION_BACKWARD 1Backward (end towards beginning)
+ * Constant Value Description + * ignoreCase - Indicates whether the search is case sensitive. + * True means that case will be ignored. False means the search will + * be case sensitive. + * @return If found, an iOhioField object containing the target string. If + * not found, returns a null. + */ + public ScreenField findByString(String targetString, + int startPos, + int length, + int dir, + boolean ignoreCase) { + // first lets check if the string exists in the screen space +// iOhioPosition pos = screen.findString(targetString, startPos, length, +// dir, ignoreCase); + // if it does exist then lets search the fields by the position that + // was found and return the results of that search. +// if (pos != null) { + return findByPosition(startPos); +// } + //return null; + } + + /** + * Searches the collection for the target position and returns the ScreenField + * object containing that position. + * + * @param targetPosition The target row and column expressed as a linear + * position within the presentation space. + * + * @return If found, a ScreenField object containing the target position. + * If not found, returns a null. + */ + public ScreenField findByPosition(int targetPosition) { + ScreenField sf = null; + + for (int x = 0; x < sizeFields; x++) { + sf = screenFields[x]; + + if (sf.withinField(targetPosition)) { + return sf; + } + } + + return null; + } + + /** + * Searches the collection for the target position and returns the ScreenField + * object containing that position. + * + * @param row The beginning row to start search with in the presentation space. + * @param col The beginning column to start search with in the presentation space. + * + * @return If found, a ScreenField object containing the target position. + * If not found, returns a null. + */ + public ScreenField findByPosition(int row, int col) { + return findByPosition(screen.getPos(row, col)); + } + + public ScreenField[] getFields() { + ScreenField[] fields = new ScreenField[sizeFields]; + + for (int x = 0; x < sizeFields; x++) { + fields[x] = screenFields[x]; + } + + return fields; + } + + public ScreenField getFirstInputField() { + if (sizeFields <= 0) + return null; + + int f = 0; + ScreenField sf = screenFields[f]; + + while (sf.isBypassField() && f++ < sizeFields) { + sf = screenFields[f]; + } + + if (sf.isBypassField()) + return null; + else + return sf; + } + + public void gotoFieldNext() { + // sanity check - we were getting null pointers after a restore of screen + // and cursor was not positioned on a field when returned + // *** Note *** to myself + // maybe this is fixed I will have to check this some time + int lastPos = screen.getLastPos(); + + if (currentField == null && (sizeFields != 0) && !isInField(lastPos, true)) { + int pos = lastPos; + screen.setCursorOff(); + screen.advancePos(); + lastPos = screen.getLastPos(); + + while (!isInField() && pos != lastPos) { + screen.advancePos(); + } + + screen.setCursorOn(); + } + + // if we are still null do nothing + if (currentField == null) + return; + + ScreenField sf = currentField; + + if (!sf.withinField(lastPos)) { + screen.setCursorOff(); + + if (sizeFields > 0) { + // lets get the current position so we can test if we have looped + // the screen and not found a valid field. + int pos = lastPos; + int savPos = lastPos; + boolean done = false; + + do { + screen.advancePos(); + lastPos = screen.getLastPos(); + + if (isInField(lastPos) + || pos == lastPos) { + if (!currentField.isBypassField()) { + screen.gotoField(currentField); + done = true; + } + } + } + while (!done && lastPos != savPos); + } + + currentModified = false; + screen.setCursorOn(); + } + else { + if (!cpfExists) { + do { + sf = sf.next; + } + while (sf != null && sf.isBypassField()); + } + else { + int f = 0; + int cp = sf.getCursorProgression(); + + if (cp == 0) { + do { + sf = sf.next; + } + while (sf != null && sf.isBypassField()); + } + else { + ScreenField sf1 = null; + boolean found = false; + + while (!found && f < sizeFields) { + sf1 = screenFields[f++]; + + if (sf1.getFieldId() == cp) + found = true; + } + + if (found) + sf = sf1; + else { + do { + sf = sf.next; + } + while (sf != null && sf.isBypassField()); + } + + sf1 = null; + } + } + + if (sf == null) + screen.gotoField(1); + else { + currentField = sf; + screen.gotoField(currentField); + } + + currentModified = false; + } + } + + public void gotoFieldPrev() { + ScreenField sf = currentField; + int lastPos = screen.getLastPos(); + + if (!sf.withinField(lastPos)) { + screen.setCursorOff(); + + if (sizeFields > 0) { + // lets get the current position so we can test if we have looped + // the screen and not found a valid field. + int pos = lastPos; + int savPos = lastPos; + boolean done = false; + + do { + screen.changePos(-1); + lastPos = screen.getLastPos(); + + if (isInField(lastPos) + || (pos == lastPos)) { + if (!currentField.isBypassField()) { + screen.gotoField(currentField); + done = true; + } + } + } + while (!done && lastPos != savPos); + } + + screen.setCursorOn(); + } + else { + if (sf.startPos() == lastPos) { + if (!cpfExists) { + do { + sf = sf.prev; + } + while (sf != null && sf.isBypassField()); + } + else { + int f = 0; + int cp = sf.getFieldId(); + ScreenField sf1 = null; + boolean found = false; + + while (!found && f < sizeFields) { + sf1 = screenFields[f++]; + + if (sf1.getCursorProgression() == cp) + found = true; + } + + if (found) + sf = sf1; + else { + do { + sf = sf.prev; + } + while (sf != null && sf.isBypassField()); + } + + sf1 = null; + } + } + + if (sf == null) { + int size = sizeFields; + sf = screenFields[size - 1]; + + while (sf.isBypassField() && size-- > 0) { + sf = screenFields[size]; + } + } + + currentField = sf; + currentModified = false; + screen.gotoField(currentField); + } + } + + protected void readFormatTable(ByteArrayOutputStream baosp, int readType, ICodePage codePage) { + ScreenField sf; + boolean isSigned = false; + char c; + + if (masterMDT) { + StringBuffer sb = new StringBuffer(); + + for (int x = 0; x < sizeFields; x++) { + isSigned = false; + sf = screenFields[x]; + + if (sf.mdt || (readType == CMD_READ_INPUT_FIELDS)) { + sb.setLength(0); + sb.append(sf.getText()); + + if (readType == CMD_READ_MDT_FIELDS || + readType == CMD_READ_MDT_IMMEDIATE_ALT) { + int len = sb.length() - 1; + + // we strip out all '\u0020' and less + while (len >= 0 && +// (sb.charAt(len) <= ' ' || sb.charAt(len) >= '\uff20' )) { + (sb.charAt(len) < ' ' || sb.charAt(len) >= '\uff20')) { + // if we have the dup character and dup is enabled then we + // stop here + if (sb.charAt(len) == 0x1C && sf.isDupEnabled()) + break; + + sb.deleteCharAt(len--); + } + } + +// System.out.println("field " + sf.toString()); +// System.out.println(">" + sb.toString() + "<"); +// System.out.println(" field is all nulls"); + if (sf.isSignedNumeric() && sb.length() > 0 && sb.charAt(sb.length() - 1) == '-') { + isSigned = true; + sb.setLength(sb.length() - 1); + } + + int len3 = sb.length(); + + if (len3 > 0 || (readType == CMD_READ_MDT_FIELDS || + readType == CMD_READ_MDT_IMMEDIATE_ALT)) { + if ((readType == CMD_READ_MDT_FIELDS || + readType == CMD_READ_MDT_IMMEDIATE_ALT)) { + baosp.write(17); // start of field data + + if (sf.isSelectionField()) { + baosp.write(screen.getRow(sf.selectionPos) + 1); + baosp.write(screen.getCol(sf.selectionPos) + 1); + } + else { + baosp.write(sf.startRow() + 1); + baosp.write(sf.startCol() + 1); + } + } + +// int len = sb.length(); + if (sf.isSelectionField()) { + baosp.write(0); + baosp.write(sf.selectionIndex + 0x1F); + } + else { + for (int k = 0; k < len3; k++) { + c = sb.charAt(k); + + // here we have to check for special instances of the + // characters in the string field. Attribute bytes + // are encoded with an offset of \uff00 + // This is a hack !!!!!!!!!!! + // See ScreenField object for a description + if (c < ' ' || c >= '\uff20') { + // if it is an offset attribute byte we just pass + // it straight on to the output stream + if (c >= '\uff20' && c <= '\uff3f') { + baosp.write(c - '\uff00'); + } + else + + // check for dup character + if (c == 0x1C) + baosp.write(c); + else + baosp.write(codePage.uni2ebcdic(' ')); + } + else { + if (isSigned && k == len3 - 1) { + baosp.write(0xd0 | (0x0f & c)); + } + else + baosp.write(codePage.uni2ebcdic(c)); + } + } + } + } + } + } + } + } + +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/framework/tn5250/ScreenOIA.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/framework/tn5250/ScreenOIA.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,282 @@ +/** + *

Title: ScreenOIA.java

+ *

Description: Main interface to control Operator information area screen

+ *

Copyright: Copyright (c) 2000 - 2002

+ *

+ * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + *

+ * @author Kenneth J. Pouncey + * @version 0.5 + */ +package org.tn5250j.framework.tn5250; + +import java.util.Vector; + +import org.tn5250j.event.ScreenOIAListener; + +/** + * The operator information area of a host session. This area is used to provide + * status information regarding the state of the host session and location of + * the cursor. A ScreenOIA object can be obtained using the GetOIA() method on + * an instance of Screen5250. + * + * + */ +public class ScreenOIA { + // OIA_LEVEL + public static final int OIA_LEVEL_INPUT_INHIBITED = 1; + public static final int OIA_LEVEL_NOT_INHIBITED = 2; + public static final int OIA_LEVEL_MESSAGE_LIGHT_ON = 3; + public static final int OIA_LEVEL_MESSAGE_LIGHT_OFF = 4; + public static final int OIA_LEVEL_AUDIBLE_BELL = 5; + public static final int OIA_LEVEL_INSERT_MODE = 6; + public static final int OIA_LEVEL_KEYBOARD = 7; + public static final int OIA_LEVEL_CLEAR_SCREEN = 8; + public static final int OIA_LEVEL_SCREEN_SIZE = 9; + public static final int OIA_LEVEL_INPUT_ERROR = 10; + public static final int OIA_LEVEL_KEYS_BUFFERED = 11; + public static final int OIA_LEVEL_SCRIPT = 12; + + // INPUTINHIBITED + public static final int INPUTINHIBITED_NOTINHIBITED = 0; + public static final int INPUTINHIBITED_SYSTEM_WAIT = 1; + public static final int INPUTINHIBITED_COMMCHECK = 2; + public static final int INPUTINHIBITED_PROGCHECK = 3; + public static final int INPUTINHIBITED_MACHINECHECK = 4; + public static final int INPUTINHIBITED_OTHER = 5; + + public ScreenOIA(Screen5250 screen) { + source = screen; + } + + public boolean isInsertMode() { + return insertMode; + } + + protected void setInsertMode(boolean mode) { + level = OIA_LEVEL_INSERT_MODE; + insertMode = mode; + fireOIAChanged(ScreenOIAListener.OIA_CHANGED_INSERT_MODE); + } + + public int getCommCheckCode() { + return commCheck; + } + + public int getInputInhibited() { + return inputInhibited; + } + + public int getMachineCheckCode() { + return machineCheck; + } + + public int getOwner() { + return owner; + } + + public int getProgCheckCode() { + return 0; + } + + /** + * Is the keyboard locked or not + * + * @return locked or not + */ + public boolean isKeyBoardLocked() { + return locked; + } + + public boolean isKeysBuffered() { + return keysBuffered; + } + + public void setKeysBuffered(boolean kb) { + level = OIA_LEVEL_KEYS_BUFFERED; + boolean oldKB = keysBuffered; + keysBuffered = kb; + + if (keysBuffered != oldKB) + fireOIAChanged(ScreenOIAListener.OIA_CHANGED_KEYS_BUFFERED); + } + + protected void setKeyBoardLocked(boolean lockIt) { + level = OIA_LEVEL_KEYBOARD; + boolean oldLocked = locked; + locked = lockIt; + + if (!lockIt) { + if (isKeysBuffered()) { + source.sendKeys(""); + } + } + + if (locked != oldLocked) + fireOIAChanged(ScreenOIAListener.OIA_CHANGED_KEYBOARD_LOCKED); + } + + public boolean isMessageWait() { + return messageWait; + } + + protected void setMessageLightOn() { + level = OIA_LEVEL_MESSAGE_LIGHT_ON; + messageWait = true; + fireOIAChanged(ScreenOIAListener.OIA_CHANGED_MESSAGELIGHT); + } + + protected void setMessageLightOff() { + level = OIA_LEVEL_MESSAGE_LIGHT_OFF; + messageWait = false; + fireOIAChanged(ScreenOIAListener.OIA_CHANGED_MESSAGELIGHT); + } + + public void setScriptActive(boolean running) { + level = OIA_LEVEL_SCRIPT; + scriptRunning = running; + fireOIAChanged(ScreenOIAListener.OIA_CHANGED_SCRIPT); + } + + public boolean isScriptActive() { + return scriptRunning; + } + + public void setAudibleBell() { + level = OIA_LEVEL_AUDIBLE_BELL; + fireOIAChanged(ScreenOIAListener.OIA_CHANGED_BELL); + } + + protected void clearScreen() { + level = OIA_LEVEL_CLEAR_SCREEN; + fireOIAChanged(ScreenOIAListener.OIA_CHANGED_CLEAR_SCREEN); + } + + /** + * Add a ScreenOIAListener to the listener list. + * + * @param listener The ScreenOIAListener to be added + */ + public void addOIAListener(ScreenOIAListener listener) { + if (listeners == null) { + listeners = new java.util.Vector(3); + } + + listeners.addElement(listener); + } + + /** + * Remove a iOhioSessionListener from the listener list. + * + * @param listener The iOhioSessionListener to be removed + */ + public void removeOIAListener(ScreenOIAListener listener) { + if (listeners == null) { + return; + } + + listeners.removeElement(listener); + } + + // object methods + public Screen5250 getSource() { + return source; + } + + + public void setSource(Screen5250 screen) { + source = screen; + } + + public void setOwner(int newOwner) { + owner = newOwner; + } + + public int getLevel() { + return level; + } + + public String getInhibitedText() { + return inhibitedText; + } + + public void setInputInhibited(int inhibit , int whatCode) { + setInputInhibited(inhibit, whatCode, null); + } + + public void setInputInhibited(int inhibit , int whatCode, String message) { + inputInhibited = inhibit; + level = OIA_LEVEL_INPUT_INHIBITED; + inhibitedText = message; + +// if (saveInhibit != inhibit || saveInhibitLevel != whatCode) { + switch (inhibit) { + case INPUTINHIBITED_COMMCHECK : + commCheck = whatCode; + break; + + case INPUTINHIBITED_PROGCHECK : +// progCheck = whatCode; // never used + break; + + case INPUTINHIBITED_MACHINECHECK : + machineCheck = whatCode; + break; + + case INPUTINHIBITED_SYSTEM_WAIT : + level = whatCode; + break; + + case INPUTINHIBITED_NOTINHIBITED : + level = whatCode; + break; + } + + fireOIAChanged(ScreenOIAListener.OIA_CHANGED_INPUTINHIBITED); +// } + } + + /** + * Notify all registered listeners of the onOIAChanged event. + * + */ + private void fireOIAChanged(int change) { + if (listeners != null) { + int size = listeners.size(); + + for (int i = 0; i < size; i++) { + ScreenOIAListener target = + listeners.elementAt(i); + target.onOIAChanged(this, change); + } + } + } + + private Vector listeners = null; + private boolean insertMode; + private boolean locked; + private boolean keysBuffered; + private int owner = 0; + private int level = 0; + private Screen5250 source = null; + private int commCheck = 0; + private int machineCheck = 0; + private boolean messageWait; + private boolean scriptRunning; + private int inputInhibited = INPUTINHIBITED_NOTINHIBITED; + private String inhibitedText; + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/framework/tn5250/ScreenPlanes.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/framework/tn5250/ScreenPlanes.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,803 @@ +/** + * Title: ScreenPlanes.java + * Copyright: Copyright (c) 2001 + * Company: + * @author Kenneth J. Pouncey + * @version 0.5 + * + * Description: + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.framework.tn5250; + +import static org.tn5250j.TN5250jConstants.*; + +public class ScreenPlanes { + + private final Screen5250 scr; + private int screenSize; + private int numRows; + private int numCols; + private int errorLineNum; + + private static final int initAttr = 32; + private static final char initChar = 0; + + protected char[] screen; // text plane + private char[] screenAttr; // attribute plane + private char[] screenGUI; // gui plane + private char[] screenIsAttr; + private char[] fieldExtended; + private char[] screenField; + private char[] screenColor; // color plane + protected char[] screenExtended; // extended plane + private char[] screenIsChanged; + + private char[] initArray; + + private char[] errorLine; + private char[] errorLineAttr; + private char[] errorLineIsAttr; + private char[] errorLineGui; + + public ScreenPlanes(Screen5250 s5250, int size) { + scr = s5250; + setSize(size); + } + + protected void setSize(int newSize) { + screenSize = newSize; + numCols = 80; + + switch (newSize) { + case 24: + numRows = 24; + break; + + case 27: + numRows = 27; + numCols = 132; + break; + } + + // this is used here when size changes + setErrorLine(numRows); + screenSize = numRows * numCols; + screen = new char[screenSize]; + screenAttr = new char[screenSize]; + screenIsAttr = new char[screenSize]; + screenGUI = new char[screenSize]; + screenColor = new char[screenSize]; + screenExtended = new char[screenSize]; + fieldExtended = new char[screenSize]; + screenIsChanged = new char[screenSize]; + screenField = new char[screenSize]; + initArray = new char[screenSize]; + initalizePlanes(); + } + + protected void setErrorLine(int line) { + // * NOTE * for developers I have changed the send qry to pass different + // parameters to the host so check setsize for setting error line as well. + // + if (line == 0 || line > numRows) + errorLineNum = numRows; + else + errorLineNum = line; + } + + /** + * Returns the current error line number + * + * @return current error line number + */ + protected int getErrorLine() { + return errorLineNum; + } + + protected void saveErrorLine() { + // if there is already an error line saved then do not save it again + // This signifies that there was a previous error and the original error + // line was not restored yet. + if (errorLine == null) { + errorLine = new char[numCols]; + errorLineAttr = new char[numCols]; + errorLineIsAttr = new char[numCols]; + errorLineGui = new char[numCols]; + int r = scr.getPos(errorLineNum - 1, 0); + + for (int x = 0; x < numCols; x++) { + errorLine[x] = screen[r + x]; + errorLineAttr[x] = screenAttr[r + x]; + errorLineIsAttr[x] = screenIsAttr[r + x]; + errorLineGui[x] = screenGUI[r + x]; + } + } + } + + /** + * Restores the error line characters from the save buffer. + * + * @see #saveErrorLine() + */ + protected void restoreErrorLine() { + if (errorLine != null) { + int r = scr.getPos(errorLineNum - 1, 0); + + for (int x = 0; x < numCols - 1; x++) { + setScreenCharAndAttr(r + x, errorLine[x], errorLineAttr[x], + (errorLineIsAttr[x] == '1' ? true : false)); + screenGUI[x] = errorLineGui[x]; + } + + errorLine = null; + errorLineAttr = null; + errorLineIsAttr = null; + errorLineGui = null; + } + } + + protected boolean isErrorLineSaved() { + return errorLine == null ? false : true; + } + + protected void setScreenCharAndAttr(int pos, char c, int attr, boolean isAttr) { + screen[pos] = c; + screenAttr[pos] = (char)attr; + disperseAttribute(pos, attr); + screenIsAttr[pos] = (isAttr ? (char)1 : (char)0); + screenGUI[pos] = NO_GUI; + } + + protected void setScreenAttr(int pos, int attr, boolean isAttr) { + screenAttr[pos] = (char)attr; + screenIsAttr[pos] = isAttr ? (char)1 : (char)0; + disperseAttribute(pos, attr); + screenGUI[pos] = initChar; + } + + protected void setScreenAttr(int pos, int attr) { + screenAttr[pos] = (char)attr; + //screenGUI[pos] = initChar; + disperseAttribute(pos, attr); + } + + protected void setScreenFieldAttr(int pos, int attr) { + screenField[pos] = (char)attr; + } + + protected final void setChar(int pos, char c) { + screenIsChanged[pos] = screen[pos] == c ? '0' : '1'; + screen[pos] = c; + + if (screenIsAttr[pos] == 1) + setScreenCharAndAttr(pos, c, 32, false); + } + + protected final char getChar(int pos) { + return screen[pos]; + } + + protected final char getCharColor(int pos) { + return screenColor[pos]; + } + + protected final int getCharAttr(int pos) { + return screenAttr[pos]; + } + + protected final char getCharExtended(int pos) { + return screenExtended[pos]; + } + + protected final boolean isAttributePlace(int pos) { + return screenIsAttr[pos] == 1 ? true : false; + } + + public final void setUseGUI(int pos, int which) { + screenIsChanged[pos] = screenGUI[pos] == which ? '0' : '1'; + screenGUI[pos] = (char)which; + } + + private void disperseAttribute(int pos, int attr) { + char c = 0; + char cs = 0; + char ul = 0; + char nd = 0; + + if (attr == 0) + return; + + switch (attr) { + case 32: // green normal + c = ATTR_32; + break; + + case 33: // green/revers + c = ATTR_33; + break; + + case 34: // white normal + c = ATTR_34; + break; + + case 35: // white/reverse + c = ATTR_35; + break; + + case 36: // green/underline + c = ATTR_36; + ul = EXTENDED_5250_UNDERLINE; + break; + + case 37: // green/reverse/underline + c = ATTR_37; + ul = EXTENDED_5250_UNDERLINE; + break; + + case 38: // white/underline + c = ATTR_38; + ul = EXTENDED_5250_UNDERLINE; + break; + + case 39: + nd = EXTENDED_5250_NON_DSP; + break; + + case 40: + case 42: // red/normal + c = ATTR_40; + break; + + case 41: + case 43: // red/reverse + c = ATTR_41; + break; + + case 44: + case 46: // red/underline + c = ATTR_44; + ul = EXTENDED_5250_UNDERLINE; + break; + + case 45: // red/reverse/underline + c = ATTR_45; + ul = EXTENDED_5250_UNDERLINE; + break; + + case 47: + nd = EXTENDED_5250_NON_DSP; + break; + + case 48: + c = ATTR_48; + cs = EXTENDED_5250_COL_SEP; + break; + + case 49: + c = ATTR_49; + cs = EXTENDED_5250_COL_SEP; + break; + + case 50: + c = ATTR_50; + cs = EXTENDED_5250_COL_SEP; + break; + + case 51: + c = ATTR_51; + cs = EXTENDED_5250_COL_SEP; + break; + + case 52: + c = ATTR_52; + // colSep = true; + ul = EXTENDED_5250_UNDERLINE; + break; + + case 53: + c = ATTR_53; + // colSep = true; + ul = EXTENDED_5250_UNDERLINE; + break; + + case 54: + c = ATTR_54; + // colSep = true; + ul = EXTENDED_5250_UNDERLINE; + break; + + case 55: + nd = EXTENDED_5250_NON_DSP; + break; + + case 56: // pink + c = ATTR_56; + break; + + case 57: // pink/reverse + c = ATTR_57; + break; + + case 58: // blue/reverse + c = ATTR_58; + break; + + case 59: // blue + c = ATTR_59; + break; + + case 60: // pink/underline + c = ATTR_60; + ul = EXTENDED_5250_UNDERLINE; + break; + + case 61: // pink/reverse/underline + c = ATTR_61; + ul = EXTENDED_5250_UNDERLINE; + break; + + case 62: // blue/underline + c = ATTR_62; + ul = EXTENDED_5250_UNDERLINE; + break; + + case 63: // nondisplay + nd = EXTENDED_5250_NON_DSP; + cs = EXTENDED_5250_COL_SEP; + break; + + default: + c = (COLOR_BG_BLACK << 8 & 0xff00) | + (COLOR_FG_YELLOW & 0xff); + break; + } + + screenColor[pos] = c; + screenExtended[pos] = (char)(ul | cs | nd); + } + + protected void initalizePlanes() { + char c = (COLOR_BG_BLACK << 8 & 0xff00) | + (COLOR_FG_GREEN & 0xff); + + for (int y = 0; y < screenSize; y++) { + screenAttr[y] = initAttr; + screenColor[y] = c; + } + + // here we will just copy the initialized plane onto the other planes + // using arraycopy which will be faster. I hope. + System.arraycopy(initArray, 0, screen, 0, screenSize); + System.arraycopy(initArray, 0, screenGUI, 0, screenSize); + System.arraycopy(initArray, 0, screenIsAttr, 0, screenSize); + System.arraycopy(initArray, 0, screenExtended, 0, screenSize); + System.arraycopy(initArray, 0, fieldExtended, 0, screenSize); + System.arraycopy(initArray, 0, screenField, 0, screenSize); + } + + protected void initalizeFieldPlanes() { + System.arraycopy(initArray, 0, fieldExtended, 0, screenSize); + System.arraycopy(initArray, 0, screenField, 0, screenSize); + } + + protected final int getWhichGUI(int pos) { + return screenGUI[pos]; + } + + protected final boolean isChanged(int pos) { + return screenIsChanged[pos] == 0 ? false : true; + } + + protected final boolean isUseGui(int pos) { + return screenGUI[pos] == NO_GUI ? false : true; + } + + /** + * Return the data associated with the plane that is passed. + * + * @param from Position from which to start + * @param to Position to end + * @param plane From which plane to obtain the data + * @return Character array containing the data requested + */ + + protected synchronized char[] getPlaneData(int from, int to, int plane) { + int len = (to - from); + char[] planeChars = new char[len + 1]; + + switch (plane) { + case PLANE_TEXT: + System.arraycopy(screen, from, planeChars, 0, len); + break; + + case PLANE_ATTR: + System.arraycopy(screenAttr, from, planeChars, 0, len); + break; + + case PLANE_COLOR: + System.arraycopy(screenColor, from, planeChars, 0, len); + break; + + case PLANE_EXTENDED: + System.arraycopy(screenExtended, from, planeChars, 0, len); + break; + + case PLANE_EXTENDED_GRAPHIC: + System.arraycopy(screenGUI, from, planeChars, 0, len); + break; + + case PLANE_FIELD: + System.arraycopy(screenField, from, planeChars, 0, len); + break; + + case PLANE_IS_ATTR_PLACE: + System.arraycopy(screenIsAttr, from, planeChars, 0, len); + break; + + default: + System.arraycopy(screen, from, planeChars, 0, len); + } + + return planeChars; + } + + /** + * Converts a linear presentation space position to its corresponding row. + * + * @param pos The position to be converted + * @return The row which corresponds to the position given + * @throws OhioException + */ + private int convertPosToRow(int pos) { + return (pos / numCols) + 1; + } + + /** + * Converts a linear presentation space position to its corresponding column. + * + * @param pos The position to be converted + * @return The column which corresponds to the position given + * @throws OhioException + */ + private int convertPosToColumn(int pos) { + return (pos % numCols) + 1; + } + + /** + * + * Converts a row and column coordinate to its corresponding linear position. + * + * @param row - The row of the coordinate + * @param col - The column of the coordinate + * @return The linear position which corresponds to the coordinate given. + * @throws OhioException + */ + private int convertRowColToPos(int row, int col) { + return (row - 1) * numCols + col - 1; + } + + + /** + *

+ * GetScreen retrieves the various planes associated with the presentation + * space. The data is returned as a linear array of character values in the + * array provided. The array is not terminated by a null character except + * when data is retrieved from the text plane, in which case a single null + * character is appended. + *

+ *

+ * The application must supply a buffer for the returned data and the length + * of the buffer. Data is returned starting from the beginning of the + * presentation space and continuing until the buffer is full or the entire + * plane has been copied. For text plane data, the buffer must include one + * extra position for the terminating null character. + *

+ * + * @param buffer + * @param bufferLength + * @param plane + * @return The number of characters copied to the buffer + * @throws OhioException + */ + + public synchronized int GetScreen(char buffer[], int bufferLength, int plane) { + return GetScreen(buffer, bufferLength, 0, screenSize, plane); + } + + /** + *

+ * GetScreen retrieves the various planes associated with the presentation + * space. The data is returned as a linear array of character values in the + * array provided. The array is not terminated by a null character except + * when data is retrieved from the text plane, in which case a single null + * character is appended. + *

+ *

+ * The application must supply a buffer for the returned data and the length + * of the buffer. Data is returned starting from the given position and + * continuing until the specified number of characters have been copied, the + * buffer is full or the entire plane has been copied. For text plane data, + * the buffer must include one extra position for the terminating null character. + *

+ * + * @param buffer + * @param bufferLength + * @param from + * @param length + * @param plane + * @return The number of characters copied to the buffer + * @throws OhioException + */ + + public synchronized int GetScreen(char buffer[], int bufferLength, int from, + int length, int plane) { + // if(buffer == null) + // throw new OhioException(sessionVT.getSessionConfiguration(), + // OhioScreen.class.getName(), "osohio.screen.ohio00300", 1); + if (buffer == null) + return 0; + + int min = Math.min(Math.min(buffer.length, bufferLength), screenSize); + + if ((from + min) > screenSize) { + min = screenSize - from; + } + + char[] pd = getPlaneData(from, from + min, plane); + + if (pd != null) { + System.arraycopy(pd, 0, buffer, 0, min); + return pd.length; + } + + return 0; + } + + /** + *

+ * GetScreen retrieves the various planes associated with the presentation + * space. The data is returned as a linear array of character values in the + * array provided. The array is not terminated by a null character except + * when data is retrieved from the text plane, in which case a single null + * character is appended. + *

+ *

+ * The application must supply a buffer for the returned data and the length + * of the buffer. Data is returned starting from the given coordinates and + * continuing until the specified number of characters have been copied, + * the buffer is full, or the entire plane has been copied. For text plane + * data, the buffer must include one extra position for the terminating null + * character. + *

+ * + * @param buffer + * @param bufferLength + * @param row + * @param col + * @param length + * @param plane + * @return The number of characters copied to the buffer. + * @throws OhioException + */ + + public synchronized int GetScreen(char buffer[], int bufferLength, int row, + int col, int length, int plane) + // throws OhioException { + { + // Call GetScreen function after converting row and column to + // a position. + return GetScreen(buffer, bufferLength, convertRowColToPos(row, col), + length, plane); + } + + /** + *

+ * GetScreenRect retrieves data from the various planes associated with the + * presentation space. The data is returned as a linear array of character + * values in the buffer provided. + *

+ * + *

+ * The application supplies two positions that represent opposing corners of + * a rectangle within the presentation space. The starting and ending + * positions can have any spatial relationship to each other. The data + * returned starts from the row containing the upper-most point to the row + * containing the lower-most point, and from the left-most column to the + * right-most column. + *

+ *

+ * The specified buffer must be at least large enough to contain the number + * of characters in the rectangle. If the buffer is too small, no data is + * copied and zero is returned by the method. Otherwise, the method returns + * the number of characters copied. + *

+ * + * @param buffer + * @param bufferLength + * @param startPos + * @param endPos + * @param plane + * @return The number of characters copied to the buffer + * @throws OhioException + */ + protected int GetScreenRect(char buffer[], int bufferLength, + int startPos, int endPos, int plane) + // throws OhioException { + { + // We will use the row,col routine here because it is easier to use + // row colum than it is for position since I wrote the other first and + // am to lazy to implement it here + // Maybe it would be faster to do it the other way? + int startRow = convertPosToRow(startPos); + int startCol = convertPosToColumn(startPos); + int endRow = convertPosToRow(endPos); + int endCol = convertPosToColumn(endPos); + return GetScreenRect(buffer, bufferLength, startRow, startCol, + endRow, endCol, plane); + } + + /** + *

+ * GetScreenRect retrieves data from the various planes associated with the + * presentation space. The data is returned as a linear array of character + * values in the buffer provided. The buffer is not terminated by a null + * character. + *

+ *

+ * The application supplies two coordinates that represent opposing corners + * of a rectangle within the presentation space. The starting and ending + * coordinates can have any spatial relationship to each other. The data + * returned starts from the row containing the upper-most point to the row + * containing the lower-most point, and from the left-most column to the + * right-most column. + *

+ *

+ * The specified buffer must be at least large enough to contain the number + * of characters in the rectangle. If the buffer is too small, no data is + * copied and zero is returned by the method. Otherwise, the method returns + * the number of characters copied. + *

+ * + * @param buffer + * @param bufferLength + * @param startRow + * @param startCol + * @param endRow + * @param endCol + * @param plane + * @return The number characters copied to the buffer + * @throws OhioException + */ + protected int GetScreenRect(char buffer[], int bufferLength, + int startRow, int startCol, + int endRow, int endCol, int plane) + // throws OhioException { + { + // number of bytes obtained + int numBytes = 0; + + // lets check the row range. If they are reversed then we need to + // place them in the correct order. + if (startRow > endRow) { + int r = startRow; + startRow = endRow; + endRow = r; + } + + // lets check the column range. If they are reversed then we need to + // place them in the correct order. + if (startCol > endCol) { + int c = startCol; + startCol = endCol; + endCol = c; + } + + int numCols = (endCol - startCol) + 1; + int numRows = (endRow - startRow) + 1; + + // lets make sure it is within the bounds of the character array passed + // if not the return as zero bytes where read as per documentation. + if (numCols * numRows <= bufferLength) { + // make sure it is one larger. I guess for other languanges to + // reference like in C which is terminated by a zero byte at the end + // of strings. + char cb[] = new char[numCols + 1]; + int charOffset = 0; + int bytes = 0; + + // now let's loop through and get the screen information for + // each row; + for (int row = startRow; row <= endRow;) { + if ((bytes = GetScreen(cb, cb.length, row, startCol, numCols, plane)) != 0) { + System.arraycopy(cb, 0, buffer, charOffset, numCols); + } + + row++; + charOffset += numCols; + // make sure we count the number of bytes returned + numBytes += bytes; + } + } + + return numBytes; + } + + private int isOption(char[] screen, + int x, + int lenScreen, + int numPref, + int numSuff, + char suff) { + boolean hs = true; + int sp = x; + int os = 0; + + // check to the left for option + while (--sp >= 0 && screen[sp] <= ' ') { + if (x - sp > numPref || screen[sp] == suff || + screen[sp] == '.' || + screen[sp] == '*') { + hs = false; + break; + } + } + + // now lets check for how long the option is it has to be numPref or less + os = sp; + + while (hs && --os > 0 && screen[os] > ' ') { + if (sp - os >= numPref || screen[os] == suff || + screen[os] == '.' || + screen[os] == '*') { + hs = false; + break; + } + } + + if (sp - os > 1 && !Character.isDigit(screen[os + 1])) { + hs = false; + } + + sp = x; + + if (Character.isDigit(screen[sp + 1])) + hs = false; + + // now lets make sure there are no more than numSuff spaces after option + while (hs && (++sp < lenScreen && screen[sp] <= ' ' + || screen[sp] == suff)) { + if (sp - x >= numSuff || screen[sp] == suff || + screen[sp] == '.' || + screen[sp] == '*') { + hs = false; + break; + } + } + + if (hs && !Character.isLetterOrDigit(screen[sp])) + hs = false; + + if (hs) { + return os; + } + + return -1; + } + +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/framework/tn5250/Stream5250.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/framework/tn5250/Stream5250.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,164 @@ +/** + * Title: tn5250J + * Copyright: Copyright (c) 2001 + * Company: + * @author Kenneth J. Pouncey + * @version 0.4 + * + * Description: + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.framework.tn5250; + +public class Stream5250 { + + public int streamSize; + public int opCode; + public int dataStart; + public int pos; + public byte buffer[]; + + public Stream5250(byte abyte0[]) { + buffer = abyte0; + // size without end of record 0xFF 0xEF + streamSize = (abyte0[0] & 0xff) << 8 | abyte0[1] & 0xff; + opCode = abyte0[9]; + dataStart = 6 + abyte0[6]; + pos = dataStart; + } + + public Stream5250() { + buffer = null; + streamSize = 0; + opCode = 0; + dataStart = 0; + pos = dataStart; + } + + /** + * This method takes a byte array and initializes the object information + * to be used. + * + * @param abyte0 + */ + public void initialize(byte abyte0[]) { + buffer = abyte0; + // size without end of record 0xFF 0xEF + streamSize = (abyte0[0] & 0xff) << 8 | abyte0[1] & 0xff; + opCode = abyte0[9]; + dataStart = 6 + abyte0[6]; + pos = dataStart; + } + + public final int getOpCode() { + return opCode; + } + + public final byte getNextByte() + throws Exception { + if (buffer == null || pos > buffer.length) + throw new Exception("Buffer length exceeded: " + pos); + else + return buffer[pos++]; + } + + public final void setPrevByte() + throws Exception { + if (pos == 0) { + throw new Exception("Index equals zero."); + } + else { + pos--; + return; + } + } + + /** + * Returns where we are in the buffer + * @return position in the buffer + */ + public final int getCurrentPos() { + return pos; + } + + public final byte getByteOffset(int off) + throws Exception { + if (buffer == null || (pos + off) > buffer.length) + throw new Exception("Buffer length exceeded: " + pos); + else + return buffer[pos + off]; + } + + public final boolean size() { + return pos >= streamSize; + } + + + /** + * Determines if any more bytes are available in the buffer to be processed. + * @return yes or no + */ + public final boolean hasNext() { +// return pos >= buffer.length; + return pos < streamSize; + } + + /** + * This routine will retrieve a segment based on the first two bytes being + * the length of the segment. + * + * @return a new byte array containing the bytes of the segment. + * @throws Exception + */ + public final byte[] getSegment() throws Exception { + // The first two bytes contain the length of the segment. + int length = ((buffer[pos] & 0xff) << 8 | (buffer[pos + 1] & 0xff)); + // allocate space for it. + byte[] segment = new byte[length]; + getSegment(segment, length, true); + return segment; + } + + + /** + * This routine will retrieve a byte array based on the first two bytes being + * the length of the segment. + * + * @param segment - byte array + * @param length - length of segment to return + * @param adjustPos - adjust the position of the buffer to the end of the seg + * ment + * @throws Exception + */ + public final void getSegment(byte[] segment, int length, boolean adjustPos) + throws Exception { + // If the length is larger than what is available throw an exception + if ((pos + length) > buffer.length) + throw new Exception("Buffer length exceeded: start " + pos + + " length " + length); + + // use the system array copy to move the bytes from the buffer + // to the allocated byte array + System.arraycopy(buffer, pos, segment, 0, length); + + // update the offset to be after the segment so the next byte can be read + if (adjustPos) + pos += length; + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/framework/tn5250/WTDSFParser.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/framework/tn5250/WTDSFParser.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,927 @@ +/** + * Title: tn5250J + * Copyright: Copyright (c) 2001 + * Company: + * @author Kenneth J. Pouncey + * @version 0.5 + * + * Description: + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.framework.tn5250; + +import static org.tn5250j.TN5250jConstants.BOTTOM; +import static org.tn5250j.TN5250jConstants.GUI_LEFT; +import static org.tn5250j.TN5250jConstants.GUI_RIGHT; +import static org.tn5250j.TN5250jConstants.LOWER_LEFT; +import static org.tn5250j.TN5250jConstants.LOWER_RIGHT; +import static org.tn5250j.TN5250jConstants.NO_GUI; +import static org.tn5250j.TN5250jConstants.NR_REQUEST_ERROR; +import static org.tn5250j.TN5250jConstants.UPPER; +import static org.tn5250j.TN5250jConstants.UPPER_LEFT; +import static org.tn5250j.TN5250jConstants.UPPER_RIGHT; + +import java.util.ArrayList; +import java.util.List; + +import org.tn5250j.encoding.ICodePage; +import android.util.Log; + + +/** + * + * Write To Display Structured Field: + * + * This module will parse the structured field information for enhanced + * emulation mode. + * + */ +public class WTDSFParser { + private static final String TAG = "WTDSFParser"; + private Screen5250 screen52; + private tnvt vt; + private ICodePage codePage; + int pos; + byte[] segment; + int length; + boolean error; + boolean guiStructsExist; + + + + private final List guiStructs = new ArrayList(3); + private final List choices = new ArrayList(3); + + + WTDSFParser(tnvt vt) { + this.vt = vt; + screen52 = vt.screen52; + codePage = vt.codePage; + } + + protected class ChoiceField { + + int x; + int y; + int row; + int col; + int width; + int height; + char mnemonic; + int fieldId; + int selectIndex; + + ChoiceField(int row, int col, int fldRow, int fldCol) { + x = row; + y = col; + row = fldRow; + col = fldCol; + } + } + + protected class Window { + + byte[] window; + int pos; + + Window(byte[] seg, int pos) { + //Log.i(TAG,"window created at " + pos); + window = seg; + this.pos = pos; + guiStructsExist = true; + } + + } + + protected void addChoiceField(int row, int col, int fldRow, int fldCol, String text) { + ChoiceField cf = new ChoiceField(row, col, fldRow, fldCol); + cf.fieldId = screen52.getScreenFields().getCurrentField().getFieldId(); + choices.add(cf); + } + + protected boolean isGuisExists() { + return guiStructsExist; + } + + protected byte[] getSegmentAtPos(int pos) { + int len = guiStructs.size(); + + for (int x = 0; x < len; x++) { + Window w = guiStructs.get(x); + + if (w.pos == pos) + return w.window; + } + + return null; + } + + protected void clearGuiStructs() { + guiStructs.clear(); + } + + protected boolean parseWriteToDisplayStructuredField(byte[] seg) { +// bk = vt.bk; + error = false; + boolean done = false; + boolean windowDefined = false; +// int nextone; + pos = 0; + segment = seg; +// try { + length = ((segment[pos++] & 0xff) << 8 | (segment[pos++] & 0xff)); + + while (!done) { + int s = segment[pos++] & 0xff; + + switch (s) { + case 0xD9: // Class Type 0xD9 - Create Window + switch (segment[pos++]) { + case 0x50: // Define Selection Field + defineSelectionField(length); + done = true; + break; + + case 0x51: // Create Window + guiStructs.add(new Window(segment, screen52.getLastPos())); + boolean cr = false; + int rows = 0; + int cols = 0; + + // pull down not supported yet + if ((segment[pos++] & 0x80) == 0x80) + cr = true; // restrict cursor + + pos++; // get reserved field pos 6 + pos++; // get reserved field pos 7 + rows = segment[pos++]; // get window depth rows pos 8 + cols = segment[pos++]; // get window width cols pos 9 + length -= 9; + + if (length == 0) { + done = true; +// System.out.println("Create Window"); +// System.out.println(" restrict cursor " + cr); +// System.out.println(" Depth = " + rows + " Width = " + cols); +// screen52.createWindow(rows,cols,1,true,32,58, + createWindow(rows, cols, 1, true, 32, 58, + '.', + '.', + '.', + ':', + ':', + ':', + '.', + ':'); + windowDefined = true; + break; + } + + // pos 10 is Minor Structure + int ml = 0; + int type = 0; + int lastPos = screen52.getLastPos(); +// if (cr) +// screen52.setPendingInsert(true, +// screen52.getCurrentRow(), +// screen52.getCurrentCol()); + int mAttr = 0; + int cAttr = 0; + + while (length > 0) { + // get minor length + ml = (segment[pos++] & 0xff); + length -= ml; + // only normal windows are supported at this time + type = segment[pos++]; + + switch (type) { + case 0x01 : // Border presentation + boolean gui = false; + + if ((segment[pos++] & 0x80) == 0x80) + gui = true; + + mAttr = segment[pos++]; + cAttr = segment[pos++]; + char ul = '.'; + char upper = '.'; + char ur = '.'; + char left = ':'; + char right = ':'; + char ll = ':'; + char bottom = '.'; + char lr = ':'; + + // if minor length is greater than 5 then + // the border characters are specified + if (ml > 5) { + ul = codePage.ebcdic2uni(segment[pos++]); + +// ul = getASCIIChar(segment[pos++]); + if (ul == 0) + ul = '.'; + + upper = codePage.ebcdic2uni(segment[pos++]); + +// upper = getASCIIChar(segment[pos++]); + if (upper == 0) + upper = '.'; + + ur = codePage.ebcdic2uni(segment[pos++]); + +// ur = getASCIIChar(segment[pos++]); + if (ur == 0) + ur = '.'; + + left = codePage.ebcdic2uni(segment[pos++]); + +// left = getASCIIChar(segment[pos++]); + if (left == 0) + left = ':'; + + right = codePage.ebcdic2uni(segment[pos++]); + +// right = getASCIIChar(segment[pos++]); + if (right == 0) + right = ':'; + + ll = codePage.ebcdic2uni(segment[pos++]); + +// ll = getASCIIChar(segment[pos++]); + if (ll == 0) + ll = ':'; + + bottom = codePage.ebcdic2uni(segment[pos++]); + +// bottom = getASCIIChar(segment[pos++]); + if (bottom == 0) + bottom = '.'; + + lr = codePage.ebcdic2uni(segment[pos++]); + +// lr = getASCIIChar(segment[pos++]); + if (lr == 0) + lr = ':'; + } + +// System.out.println("Create Window"); +// System.out.println(" restrict cursor " + cr); +// System.out.println(" Depth = " + rows + " Width = " + cols); +// System.out.println(" type = " + type + " gui = " + gui); +// System.out.println(" mono attr = " + mAttr + " color attr = " + cAttr); +// System.out.println(" ul = " + ul + " upper = " + upper + +// " ur = " + ur + +// " left = " + left + +// " right = " + right + +// " ll = " + ll + +// " bottom = " + bottom + +// " lr = " + lr +// ); +// screen52.createWindow(rows,cols,type,gui,mAttr,cAttr, + createWindow(rows, cols, type, gui, mAttr, cAttr, + ul, + upper, + ur, + left, + right, + ll, + bottom, + lr); + windowDefined = true; + break; + + // + // The following shows the input for window with a title + // + // +0000 019A12A0 00000400 00020411 00200107 .?.?..?...?..?. + // +0010 00000018 00000011 06131500 37D95180 ........?.?..R?? + // +0020 00000A24 0D018023 23404040 40404040 ..??..??? + // +0030 40211000 000000D7 C2C1D9C4 C5D4D67A \uFFFD.....PBARDEMO: + // +0040 40D79996 879985A2 A2408281 99408485 Progress bar de + // +0050 94961108 1520D5A4 94828599 40968640 mo.???Number of + // +0060 8595A399 8985A24B 4B4B4B4B 4B7A2011 entries......:?. + // +0070 082E2040 404040F5 F0F06BF0 F0F02011 ?.? 500,000?. + // +0080 091520C3 A4999985 95A34085 95A399A8 \uFFFD??Current entry + // +0090 4095A494 8285994B 4B4B7A20 11092E20 number...:?.\uFFFD.? + // +00A0 40404040 4040F56B F0F0F020 110A1520 5,000?.??? + // +00B0 D9859481 89958995 87408595 A3998985 Remaining entrie + // +00C0 A24B4B4B 4B4B4B7A 20110A2E 20404040 s......:?.?.? + // +00D0 40F4F9F5 6BF0F0F0 20110C15 20E2A381 495,000?..??Sta + // +00E0 99A340A3 8994854B 4B4B4B4B 4B4B4B4B rt time......... + // +00F0 4B4B4B4B 7A20110C 2F2040F7 7AF5F37A ....:?...? 7:53: + + case 0x10 : // Window title/footer + if (!windowDefined) { +// screen52.createWindow(rows,cols,1,true,32,58, + guiStructs.add(new Window(segment, screen52.getLastPos())); + createWindow(rows, cols, 1, true, 32, 58, + '.', + '.', + '.', + ':', + ':', + ':', + '.', + ':'); + windowDefined = true; + } + + byte orientation = segment[pos++]; + mAttr = segment[pos++]; + cAttr = segment[pos++]; + //reserved + pos++; + ml -= 6; + StringBuffer hfBuffer = new StringBuffer(ml); + + while (ml-- > 0) { + //LDC - 13/02/2003 - Convert it to unicode + hfBuffer.append(codePage.ebcdic2uni(segment[pos++])); +// hfBuffer.append(getASCIIChar(segment[pos++])); + } + + Log.d(TAG, + " orientation " + Integer.toBinaryString(orientation) + + " mAttr " + mAttr + + " cAttr " + cAttr + + " Header/Footer " + hfBuffer); + screen52.writeWindowTitle(lastPos, + rows, + cols, + orientation, + mAttr, + cAttr, + hfBuffer); + break; + + default: + Log.w(TAG, "Invalid Window minor structure"); + length = 0; + done = true; + } + } + + done = true; + break; + + case 0x53: // Scroll Bar + int sblen = 15; + byte sbflag = segment[pos++]; // flag position 5 + pos++; // reserved position 6 + // position 7,8 + int totalRowScrollable = ((segment[pos++] & 0xff) << 8 + | (segment[pos++] & 0xff)); + // position 9,10 + int totalColScrollable = ((segment[pos++] & 0xff) << 8 + | (segment[pos++] & 0xff)); + // position 11,12 + int sliderRowPos = ((segment[pos++] & 0xff) << 8 + | (segment[pos++] & 0xff)); + // position 13,14 + int sliderColPos = ((segment[pos++] & 0xff) << 8 + | (segment[pos++] & 0xff)); + // position 15 + int sliderRC = segment[pos++]; + screen52.createScrollBar(sbflag, totalRowScrollable, + totalColScrollable, + sliderRowPos, + sliderColPos, + sliderRC); + length -= 15; + done = true; + break; + + case 0x5B: // Remove GUI ScrollBar field + pos++; // reserved must be set to off pos 5 + pos++; // reserved must be set to zero pos 6 + done = true; + break; + + case 0x5F: // Remove All GUI Constructs + Log.i(TAG, "remove all gui contructs"); + clearGuiStructs(); + guiStructsExist = false; + int len = 4; + int d = 0; + length -= s; + + while (--len > 0) + d = segment[pos++]; + +// if (length > 0) { +// len = (segment[pos++] & 0xff )<< 8; +// +// while (--len > 0) +// d = segment[pos++]; +// } + screen52.clearGuiStuff(); + // per 14.6.13.4 documentation we should clear the + // format table after this command + screen52.clearTable(); + done = true; + break; + + case 0x59: // remove gui window + Log.i(TAG, " remove window at " + screen52.getCurrentPos()); + done = true; + break; + + case 0x60: // Erase/Draw Grid Lines - not supported + // do not know what they are + // as of 03/11/2002 we should not be getting + // this anymore but I will leave it here + // just in case. +// System.out.println("erase/draw grid lines " + length); + len = 6; + d = 0; + length -= 9; + + while (--len > 0) + d = segment[pos++]; + + if (length > 0) { + len = (segment[pos++] & 0xff) << 8; + + while (--len > 0) { + d = segment[pos++]; + } + } + + done = true; + break; + + default: + vt.sendNegResponse(NR_REQUEST_ERROR, 0x03, 0x01, 0x01, "invalid wtd structured field sub command " + + (pos - 1)); +// + bk.getByteOffset(-1)); + error = true; + break; + } + + break; + + default: + vt.sendNegResponse(NR_REQUEST_ERROR, 0x03, 0x01, 0x01, + "invalid wtd structured field command " + + (pos - 1)); +// + bk.getByteOffset(-1)); + error = true; + break; + } + + if (error) + done = true; + } + +// } +// catch (Exception e) {}; + return error; + } + + /** + * Creates a window on the screen + * + * @param depth + * @param width + * @param type + * @param gui + * @param monoAttr + * @param colorAttr + * @param ul + * @param upper + * @param ur + * @param left + * @param right + * @param ll + * @param bottom + * @param lr + */ + protected void createWindow(int depth, int width, int type, boolean gui, + int monoAttr, int colorAttr, int ul, int upper, int ur, int left, + int right, int ll, int bottom, int lr) { + int lastPos = screen52.getLastPos(); + int numCols = screen52.getColumns(); + int c = screen52.getCol(lastPos); + int w = 0; + width++; + w = width; + char initChar = Screen5250.initChar; + int initAttr = Screen5250.initAttr; + // set leading attribute byte + screen52.setScreenCharAndAttr(initChar, initAttr, true); + + // set upper left + if (gui) { + screen52.setScreenCharAndAttr((char) ul, colorAttr, UPPER_LEFT, false); + } + else { + screen52.setScreenCharAndAttr((char) ul, colorAttr, false); + } + + // draw top row + while (w-- >= 0) { + if (gui) { + screen52.setScreenCharAndAttr((char) upper, colorAttr, UPPER, false); + } + else { + screen52.setScreenCharAndAttr((char) upper, colorAttr, false); + } + } + + // set upper right + if (gui) { + screen52.setScreenCharAndAttr((char) ur, colorAttr, UPPER_RIGHT, false); + } + else { + screen52.setScreenCharAndAttr((char) ur, colorAttr, false); + } + + // set ending attribute byte + screen52.setScreenCharAndAttr(initChar, initAttr, true); + lastPos = ((screen52.getRow(lastPos) + 1) * numCols) + c; + screen52.goto_XY(lastPos); + + // now handle body of window + while (depth-- > 0) { + // set leading attribute byte + screen52.setScreenCharAndAttr(initChar, initAttr, true); + + // set left + if (gui) { + screen52.setScreenCharAndAttr((char) left, colorAttr, GUI_LEFT, false); + } + else { + screen52.setScreenCharAndAttr((char) left, colorAttr, false); + } + + w = width - 2; + screen52.setScreenCharAndAttr(initChar, initAttr, NO_GUI, true); + + // fill it in + while (w-- >= 0) { +// if (!planes.isUseGui(screen52.getLastPos())) + screen52.setScreenCharAndAttr(initChar, initAttr, NO_GUI, false); + } + + screen52.setScreenCharAndAttr(initChar, initAttr, NO_GUI, true); + + // set right + if (gui) { + screen52.setScreenCharAndAttr((char) right, colorAttr, GUI_RIGHT, false); + } + else { + screen52.setScreenCharAndAttr((char) right, colorAttr, false); + } + + screen52.setScreenCharAndAttr(initChar, initAttr, true); + lastPos = ((screen52.getRow(lastPos) + 1) * numCols) + c; + screen52.goto_XY(lastPos); + } + + // set leading attribute byte + screen52.setScreenCharAndAttr(initChar, initAttr, true); + + if (gui) { + screen52.setScreenCharAndAttr((char) ll, colorAttr, LOWER_LEFT, false); + } + else { + screen52.setScreenCharAndAttr((char) ll, colorAttr, false); + } + + w = width; + + // draw bottom row + while (w-- >= 0) { + if (gui) { + screen52.setScreenCharAndAttr((char) bottom, colorAttr, BOTTOM, false); + } + else { + screen52.setScreenCharAndAttr((char) bottom, colorAttr, false); + } + } + + // set lower right + if (gui) { + screen52.setScreenCharAndAttr((char) lr, colorAttr, LOWER_RIGHT, false); + } + else { + screen52.setScreenCharAndAttr((char) lr, colorAttr, false); + } + + // set ending attribute byte + screen52.setScreenCharAndAttr(initChar, initAttr, true); + } + + /* *** NEVER USED LOCALLY ************************************************** */ +// private void clearWindowBody(ScreenPlanes planes, int startPos, int depth, int width) { +// +// int lastPos = startPos; +// char initChar = Screen5250.initChar; +// int initAttr = Screen5250.initAttr; +// +// // now handle body of window +// while (depth-- > 0) { +// +// // set leading attribute byte +//// planes.setScreenCharAndAttr(lastPos,initChar, initAttr, true); +//// setDirty(lastPos); +//// advancePos(); +//// +//// // set left +//// planes.setScreenCharAndAttr(lastPos, (char) left, colorAttr, false); +//// +//// if (gui) { +//// planes.setUseGUI(lastPos,GUI_LEFT); +//// } +//// setDirty(lastPos); +//// advancePos(); +// +// int w = width; +// // fill it in +// while (w-- >= 0) { +//// screen[lastPos].setCharAndAttr(initChar, initAttr, true); +// planes.setScreenCharAndAttr(lastPos,initChar, initAttr, true); +//// screen[lastPos].setUseGUI(NO_GUI); +// planes.setUseGUI(lastPos,NO_GUI); +//// setDirty(lastPos); +// lastPos++; +// advancePos(); +// } +// +//// // set right +//// // screen[lastPos].setCharAndAttr((char) right, colorAttr, false); +//// planes.setScreenCharAndAttr(lastPos,(char) right, colorAttr, false); +//// if (gui) { +//// // screen[lastPos].setUseGUI(RIGHT); +//// planes.setUseGUI(lastPos,GUI_RIGHT); +//// } +//// +//// setDirty(lastPos); +//// advancePos(); +//// +//// // set ending attribute byte +//// // screen[lastPos].setCharAndAttr(initChar, initAttr, true); +//// planes.setScreenCharAndAttr(lastPos,initChar, initAttr, true); +//// setDirty(lastPos); +// +// lastPos = startPos; +// } +// +// } + + /* *** NEVER USED LOCALLY ************************************************** */ +// private void setDirty(int pos) { +// +// screen52.setDirty(pos); +// +// } + + /* *** NEVER USED LOCALLY ************************************************** */ +// private void advancePos() { +// +// screen52.advancePos(); +// } + + private void defineSelectionField(int majLen) { + // 0030: 20 00 2C 3E 00 00 00 69 12 A0 00 00 04 00 00 03 .,>...i........ + // 0040: 04 40 04 11 00 28 01 07 00 00 00 19 00 00 04 11 .@...(.......... + // 0050: 14 19 15 00 48 D9 50 00 60 00 11 01 84 84 00 00 ....H.P.`....... + // 0060: 05 03 01 01 00 00 00 13 01 E0 00 21 00 21 00 3B ...........!.!.; + // 0070: 22 20 20 20 20 3A 24 20 20 3A 0B 10 08 00 E0 00 " :$ :...... + // 0080: D6 95 85 40 40 0B 10 08 00 E0 00 E3 A6 96 40 40 ...@@.........@@ + // 0090: 0B 10 08 00 E0 00 E3 88 99 85 85 04 52 00 00 FF ............R... + // 00A0: EF . + try { + int flag1 = segment[pos++]; // Flag byte 1 - byte 5 + int flag2 = segment[pos++]; // Flag byte 2 - byte 6 + int flag3 = segment[pos++]; // Flag byte 3 - byte 7 + int typeSelection = segment[pos++]; // Type of selection Field - byte 8 + // GUI Device Characteristics: + // This byte is used if the target device is a GUI PWS or a GUI-like + // NWS. If neigher of these WS are the targets, this byte is ignored + int guiDevice = segment[pos++]; // byte 9 + int withMnemonic = segment[pos++]; // byte 10 + int noMnemonic = segment[pos++]; // byte 11 + pos++; // Reserved - byte 12 + pos++; // Reserved - byte 13 + int cols = segment[pos++]; // Text Size - byte 14 + int rows = segment[pos++]; // Rows - byte 15 + int maxColChoice = segment[pos++]; // byte 16 num of column choices + int padding = segment[pos++]; // byte 17 + int numSepChar = segment[pos++]; // byte 18 + int ctySepChar = segment[pos++]; // byte 19 + int cancelAID = segment[pos++]; // byte 20 + int cnt = 0; + int minLen = 0; + majLen -= 21; + Log.d(TAG, " row: " + screen52.getCurrentRow() + + " col: " + screen52.getCurrentCol() + + " type " + typeSelection + + " gui " + guiDevice + + " withMnemonic " + Integer.toHexString(withMnemonic & 0xf0) + + " noMnemonic " + Integer.toHexString(noMnemonic & 0xf0) + + " noMnemonic " + Integer.toBinaryString(noMnemonic) + + " noMnemonicType " + Integer.toBinaryString((noMnemonic & 0xf0)) + + " noMnemonicSel " + Integer.toBinaryString((noMnemonic & 0x0f)) + + " maxcols " + maxColChoice + + " cols " + cols + + " rows " + rows); + int rowCtr = 0; + int colCtr = 0; + int chcRowStart = screen52.getCurrentRow(); + int chcColStart = screen52.getCurrentCol(); + int chcRow = chcRowStart; + int chcCol = chcColStart; + int chcPos = screen52.getPos(chcRow - 1, chcCol); +// client access +//0000 00 04 ac 9e b9 35 00 01 02 32 bb 4e 08 00 45 00 .....5...2.N..E. +//0010 00 3c 4f 8e 40 00 80 06 00 00 c1 a8 33 58 c1 a8 .= maxColChoice) { + rowCtr++; + colCtr = 0; + chcColStart = chcCol; + chcRowStart = chcRow + rowCtr; + + if (rowCtr > rows) { + chcRowStart = chcRow; + rowCtr = 0; + chcColStart = chcColStart + 3 + cols + padding; + } + } + else { + chcColStart = chcColStart + padding + cols + 3; +// + } + + break; + + default: + for (cnt = 2; cnt < minLen; cnt++) { + pos++; + } + } + + majLen -= minLen; + } + while (majLen > 0); + } + catch (Exception exc) { + Log.w(TAG, " defineSelectionField :", exc); + exc.printStackTrace(); + } + } + + // negotiating commands +// private static final byte IAC = (byte)-1; // 255 FF +// private static final byte DONT = (byte)-2; //254 FE +// private static final byte DO = (byte)-3; //253 FD +// private static final byte WONT = (byte)-4; //252 FC +// private static final byte WILL = (byte)-5; //251 FB +// private static final byte SB = (byte)-6; //250 Sub Begin FA +// private static final byte SE = (byte)-16; //240 Sub End F0 +// private static final byte EOR = (byte)-17; //239 End of Record EF +// private static final byte TERMINAL_TYPE = (byte)24; // 18 +// private static final byte OPT_END_OF_RECORD = (byte)25; // 19 +// private static final byte TRANSMIT_BINARY = (byte)0; // 0 +// private static final byte QUAL_IS = (byte)0; // 0 +// private static final byte TIMING_MARK = (byte)6; // 6 +// private static final byte NEW_ENVIRONMENT = (byte)39; // 27 +// private static final byte IS = (byte)0; // 0 +// private static final byte SEND = (byte)1; // 1 +// private static final byte INFO = (byte)2; // 2 +// private static final byte VAR = (byte)0; // 0 +// private static final byte VALUE = (byte)1; // 1 +// private static final byte NEGOTIATE_ESC = (byte)2; // 2 +// private static final byte USERVAR = (byte)3; // 3 + + // miscellaneous +// private static final byte ESC = 0x04; // 04 +// private static final char char0 = 0; + +// private static final byte CMD_READ_IMMEDIATE_ALT = (byte)0x83; // 131 + + +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/framework/tn5250/tnvt.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/framework/tn5250/tnvt.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,2609 @@ +/** + * Title: tnvt.java + * Copyright: Copyright (c) 2001 Company: + * + * @author Kenneth J. Pouncey + * @version 0.5 + * + * Description: + * + * This program 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 2, or (at your option) any later version. + * + * This program 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 + * this software; see the file COPYING. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.framework.tn5250; + +import static java.lang.Math.max; +import static java.lang.Math.min; +import static org.tn5250j.TN5250jConstants.AID_HELP; +import static org.tn5250j.TN5250jConstants.AID_PRINT; +import static org.tn5250j.TN5250jConstants.CMD_CLEAR_FORMAT_TABLE; +import static org.tn5250j.TN5250jConstants.CMD_CLEAR_UNIT; +import static org.tn5250j.TN5250jConstants.CMD_CLEAR_UNIT_ALTERNATE; +import static org.tn5250j.TN5250jConstants.CMD_READ_INPUT_FIELDS; +import static org.tn5250j.TN5250jConstants.CMD_READ_MDT_FIELDS; +import static org.tn5250j.TN5250jConstants.CMD_READ_MDT_IMMEDIATE_ALT; +import static org.tn5250j.TN5250jConstants.CMD_READ_SCREEN_IMMEDIATE; +import static org.tn5250j.TN5250jConstants.CMD_READ_SCREEN_TO_PRINT; +import static org.tn5250j.TN5250jConstants.CMD_RESTORE_SCREEN; +import static org.tn5250j.TN5250jConstants.CMD_ROLL; +import static org.tn5250j.TN5250jConstants.CMD_SAVE_SCREEN; +import static org.tn5250j.TN5250jConstants.CMD_WRITE_ERROR_CODE; +import static org.tn5250j.TN5250jConstants.CMD_WRITE_ERROR_CODE_TO_WINDOW; +import static org.tn5250j.TN5250jConstants.CMD_WRITE_STRUCTURED_FIELD; +import static org.tn5250j.TN5250jConstants.CMD_WRITE_TO_DISPLAY; +import static org.tn5250j.TN5250jConstants.NR_REQUEST_ERROR; +import static org.tn5250j.TN5250jConstants.PF1; +import static org.tn5250j.TN5250jConstants.PF10; +import static org.tn5250j.TN5250jConstants.PF11; +import static org.tn5250j.TN5250jConstants.PF12; +import static org.tn5250j.TN5250jConstants.PF13; +import static org.tn5250j.TN5250jConstants.PF14; +import static org.tn5250j.TN5250jConstants.PF15; +import static org.tn5250j.TN5250jConstants.PF16; +import static org.tn5250j.TN5250jConstants.PF17; +import static org.tn5250j.TN5250jConstants.PF18; +import static org.tn5250j.TN5250jConstants.PF19; +import static org.tn5250j.TN5250jConstants.PF2; +import static org.tn5250j.TN5250jConstants.PF20; +import static org.tn5250j.TN5250jConstants.PF21; +import static org.tn5250j.TN5250jConstants.PF22; +import static org.tn5250j.TN5250jConstants.PF23; +import static org.tn5250j.TN5250jConstants.PF24; +import static org.tn5250j.TN5250jConstants.PF3; +import static org.tn5250j.TN5250jConstants.PF4; +import static org.tn5250j.TN5250jConstants.PF5; +import static org.tn5250j.TN5250jConstants.PF6; +import static org.tn5250j.TN5250jConstants.PF7; +import static org.tn5250j.TN5250jConstants.PF8; +import static org.tn5250j.TN5250jConstants.PF9; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.util.Arrays; +import java.util.Properties; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLSocket; + +import android.content.Intent; +import android.net.Uri; +import android.util.Log; + +import com.five_ten_sg.connectbot.R; +import com.five_ten_sg.connectbot.bean.HostBean; +import com.five_ten_sg.connectbot.service.TerminalBridge; +import com.five_ten_sg.connectbot.service.TerminalManager; +import de.mud.terminal.vt320; + +import org.tn5250j.TN5250jConstants; +import org.tn5250j.encoding.CharMappings; +import org.tn5250j.encoding.ICodePage; +import org.tn5250j.framework.transport.SocketConnector; + + +public final class tnvt implements Runnable { + private static final String TAG = "tnvt"; + // negotiating commands + private static final byte IAC = (byte) - 1; // 255 FF + private static final byte DONT = (byte) - 2; //254 FE + private static final byte DO = (byte) - 3; //253 FD + private static final byte WONT = (byte) - 4; //252 FC + private static final byte WILL = (byte) - 5; //251 FB + private static final byte SB = (byte) - 6; //250 Sub Begin FA + private static final byte SE = (byte) - 16; //240 Sub End F0 + private static final byte EOR = (byte) - 17; //239 End of Record EF + private static final byte TERMINAL_TYPE = (byte) 24; // 18 + private static final byte OPT_END_OF_RECORD = (byte) 25; // 19 + private static final byte TRANSMIT_BINARY = (byte) 0; // 0 + private static final byte QUAL_IS = (byte) 0; // 0 + private static final byte TIMING_MARK = (byte) 6; // 6 + private static final byte NEW_ENVIRONMENT = (byte) 39; // 27 + private static final byte IS = (byte) 0; // 0 + private static final byte SEND = (byte) 1; // 1 + private static final byte INFO = (byte) 2; // 2 + private static final byte VAR = (byte) 0; // 0 + private static final byte VALUE = (byte) 1; // 1 + private static final byte NEGOTIATE_ESC = (byte) 2; // 2 + private static final byte USERVAR = (byte) 3; // 3 + + // miscellaneous + private static final byte ESC = 0x04; // 04 + + private Socket sock; + public BufferedInputStream bin; + public BufferedOutputStream bout; + private final BlockingQueue dsq = new ArrayBlockingQueue(25); + private Stream5250 bk; + private DataStreamProducer producer; + protected Screen5250 screen52; + private boolean waitingForInput; + private boolean invited; + private boolean negotiated = false; + private Thread me; + private Thread pthread; + private int readType; + private boolean enhanced = true; + private boolean cursorOn = false; + private String hostname = ""; + private int port = 23; + private vt320 buffer; + private boolean connected = false; + private boolean support132 = true; + private ByteArrayOutputStream baosp = null; + private ByteArrayOutputStream baosrsp = null; + private int devSeq = -1; + private String devName; + private String devNameUsed; + private KbdTypesCodePages kbdTypesCodePage; + // WVL - LDC : TR.000300 : Callback scenario from 5250 + private boolean scan; // = false; + private static int STRSCAN = 1; + // WVL - LDC : 05/08/2005 : TFX.006253 - support STRPCCMD + private boolean strpccmd; // = false; + private String encryption; + private String user; + private String password = null; + private String library; + private String initialMenu; + private String program; + private boolean keepTrucking = true; + private boolean pendingUnlock = false; + private boolean[] dataIncluded; + protected ICodePage codePage; + private String sslType; + private WTDSFParser sfParser; + private TerminalBridge bridge; + private TerminalManager manager; + + + + /** + * @param screen52 + * @param enhanced + * @param support132 + * @param bridge + * @param manager + */ + public tnvt(Screen5250 screen52, boolean enhanced, boolean support132, TerminalBridge bridge, TerminalManager manager) { + this.screen52 = screen52; + this.support132 = support132; + this.enhanced = enhanced; + this.bridge = bridge; + this.manager = manager; + setCodePage("37"); + dataIncluded = new boolean[24]; + baosp = new ByteArrayOutputStream(); + baosrsp = new ByteArrayOutputStream(); + } + + public void showURL(String url) { + if (url.indexOf("://") < 0) url = "http://" + url; + + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + manager.startActivity(intent); + } + + public String getHostName() { + return hostname; + } + + public void setDeviceName(String name) { + devName = name; + } + + public String getDeviceName() { + return devName; + } + + public String getAllocatedDeviceName() { + return devNameUsed; + } + + public boolean isConnected() { + return connected; + } + + /** + * @return true when SSL is used and socket is connected. + * @see {@link #isConnected()} + */ + public boolean isSslSocket() { + if (this.connected && this.sock != null && this.sock instanceof SSLSocket) { + return true; + } + else { + return false; + } + } + + public final void setProxy(String proxyHost, String proxyPort) { + Properties systemProperties = System.getProperties(); + systemProperties.put("socksProxySet", "true"); + systemProperties.put("socksProxyHost", proxyHost); + systemProperties.put("socksProxyPort", proxyPort); + System.setProperties(systemProperties); + Log.i(TAG, " socks set "); + } + + private String fixer(String value, String def) { + if ((value == null) || (value.length() == 0)) return def; + + return value; + } + + public final boolean connect(HostBean host, String homeDirectory, vt320 buffer) { + try { + this.hostname = host.getHostname(); + this.port = host.getPort(); + this.buffer = buffer; + this.encryption = fixer(host.getEncryption5250(), "NONE"); + this.user = host.getUsername(); + this.library = host.getLibrary(); + this.initialMenu = host.getInitialMenu(); + this.program = host.getProgram(); + + try { + screen52.getOIA().setInputInhibited(ScreenOIA.INPUTINHIBITED_SYSTEM_WAIT, + ScreenOIA.OIA_LEVEL_INPUT_INHIBITED, "X - Connecting"); + } + catch (Exception exc) { + Log.w(TAG, "setStatus(ON) " + exc.getMessage()); + } + + SocketConnector sc = new SocketConnector(); + sock = sc.createSocket(hostname, port, encryption, homeDirectory, bridge, manager); + + if (sock == null) { + Log.w(TAG, "I did not get a socket"); + disconnect(); + return false; + } + + connected = true; + // used for JDK1.3 + sock.setKeepAlive(true); + sock.setTcpNoDelay(true); + sock.setSoLinger(false, 0); + InputStream in = sock.getInputStream(); + OutputStream out = sock.getOutputStream(); + bin = new BufferedInputStream(in, 8192); + bout = new BufferedOutputStream(out); + byte abyte0[]; + + while (negotiate(abyte0 = readNegotiations())); + + negotiated = true; + + try { + screen52.setCursorActive(false); + } + catch (Exception excc) { + Log.w(TAG, "setCursorOff " + excc.getMessage()); + } + + producer = new DataStreamProducer(this, bin, dsq, abyte0); + pthread = new Thread(producer); + // pthread.setPriority(pthread.MIN_PRIORITY); + pthread.setPriority(Thread.NORM_PRIORITY); + // pthread.setPriority(Thread.NORM_PRIORITY / 2); + pthread.start(); + + try { + screen52.getOIA().setInputInhibited(ScreenOIA.INPUTINHIBITED_NOTINHIBITED, + ScreenOIA.OIA_LEVEL_INPUT_INHIBITED); + } + catch (Exception exc) { + Log.w(TAG, "setStatus(OFF) " + exc.getMessage()); + } + + keepTrucking = true; + me = new Thread(this); + me.start(); + } + catch (Exception exception) { + if (exception.getMessage() == null) + exception.printStackTrace(); + + Log.w(TAG, "connect() " + exception.getMessage()); + + if (sock == null) + Log.w(TAG, "I did not get a socket"); + + disconnect(); + return false; + } + + return true; + } + + public final boolean disconnect() { + if (!connected) { + screen52.getOIA().setInputInhibited(ScreenOIA.INPUTINHIBITED_SYSTEM_WAIT, + ScreenOIA.OIA_LEVEL_INPUT_INHIBITED, "X - Disconnected"); + return false; + } + + if (me != null && me.isAlive()) { + me.interrupt(); + keepTrucking = false; + pthread.interrupt(); + } + + screen52.getOIA().setInputInhibited(ScreenOIA.INPUTINHIBITED_SYSTEM_WAIT, + ScreenOIA.OIA_LEVEL_INPUT_INHIBITED, "X - Disconnected"); + screen52.getOIA().setKeyBoardLocked(false); + pendingUnlock = false; + + try { + if (sock != null) { + Log.i(TAG, "Closing socket"); + sock.close(); + } + + if (bin != null) bin.close(); + + if (bout != null) bout.close(); + + connected = false; + // WVL - LDC : TR.000345 : properly disconnect and clear screen + // Is this the right place to set screen realestate on disconnect? + //controller.getScreen().clearAll(); + screen52.goto_XY(0); + screen52.setCursorActive(false); + screen52.clearAll(); + screen52.restoreScreen(); + } + catch (Exception exception) { + Log.w(TAG, exception.getMessage()); + connected = false; + devSeq = -1; + return false; + } + + devSeq = -1; + return true; + } + + private final ByteArrayOutputStream appendByteStream(byte abyte0[]) { + ByteArrayOutputStream bytearrayoutputstream = new ByteArrayOutputStream(); + + for (int i = 0; i < abyte0.length; i++) { + bytearrayoutputstream.write(abyte0[i]); + + if (abyte0[i] == -1) + bytearrayoutputstream.write(-1); + } + + return bytearrayoutputstream; + } + + private final byte[] readNegotiations() throws IOException { + int i = bin.read(); + + if (i < 0) { + throw new IOException("Connection closed."); + } + else { + int j = bin.available(); + byte abyte0[] = new byte[j + 1]; + abyte0[0] = (byte) i; + bin.read(abyte0, 1, j); + return abyte0; + } + } + + private final void writeByte(byte abyte0[]) throws IOException { + bout.write(abyte0); + bout.flush(); + } + + private final void readImmediate(int readType) { + if (screen52.isStatusErrorCode()) { + screen52.restoreErrorLine(); + screen52.setStatus(Screen5250.STATUS_ERROR_CODE, + Screen5250.STATUS_VALUE_OFF, null); + } + + if (!enhanced) { + screen52.setCursorActive(false); + } + + // screen52.setStatus(Screen5250.STATUS_SYSTEM, + // Screen5250.STATUS_VALUE_ON, null); + screen52.getOIA().setInputInhibited(ScreenOIA.INPUTINHIBITED_SYSTEM_WAIT, + ScreenOIA.OIA_LEVEL_INPUT_INHIBITED); + screen52.getOIA().setKeyBoardLocked(true); + pendingUnlock = false; + invited = false; + screen52.getScreenFields().readFormatTable(baosp, readType, codePage); + + try { + Log.i(TAG, "readImmediate() writeGDS()"); + writeGDS(0, 3, baosp.toByteArray()); + } + catch (IOException ioe) { + Log.w(TAG, ioe.getMessage()); + } + + baosp.reset(); + } + + public final boolean sendAidKey(int aid) { + if (screen52.isStatusErrorCode()) { + screen52.restoreErrorLine(); + screen52.setStatus(Screen5250.STATUS_ERROR_CODE, + Screen5250.STATUS_VALUE_OFF, null); + } + + if (!enhanced) { + screen52.setCursorActive(false); + } + + // screen52.setStatus(Screen5250.STATUS_SYSTEM, + // Screen5250.STATUS_VALUE_ON, null); + screen52.getOIA().setInputInhibited(ScreenOIA.INPUTINHIBITED_SYSTEM_WAIT, + ScreenOIA.OIA_LEVEL_INPUT_INHIBITED); + screen52.getOIA().setKeyBoardLocked(true); + pendingUnlock = false; + invited = false; + baosp.write(screen52.getCurrentRow()); + baosp.write(screen52.getCurrentCol()); + baosp.write(aid); + + if (dataIncluded(aid)) + screen52.getScreenFields().readFormatTable(baosp, readType, + codePage); + + try { + Log.i(TAG, "sendAidKey() writeGDS()"); + writeGDS(0, 3, baosp.toByteArray()); + } + catch (IOException ioe) { + Log.w(TAG, ioe.getMessage()); + baosp.reset(); + return false; + } + + baosp.reset(); + return true; + } + + private boolean dataIncluded(int aid) { + switch (aid) { + case PF1: + return !dataIncluded[0]; + + case PF2: + return !dataIncluded[1]; + + case PF3: + return !dataIncluded[2]; + + case PF4: + return !dataIncluded[3]; + + case PF5: + return !dataIncluded[4]; + + case PF6: + return !dataIncluded[5]; + + case PF7: + return !dataIncluded[6]; + + case PF8: + return !dataIncluded[7]; + + case PF9: + return !dataIncluded[8]; + + case PF10: + return !dataIncluded[9]; + + case PF11: + return !dataIncluded[10]; + + case PF12: + return !dataIncluded[11]; + + case PF13: + return !dataIncluded[12]; + + case PF14: + return !dataIncluded[13]; + + case PF15: + return !dataIncluded[14]; + + case PF16: + return !dataIncluded[15]; + + case PF17: + return !dataIncluded[16]; + + case PF18: + return !dataIncluded[17]; + + case PF19: + return !dataIncluded[18]; + + case PF20: + return !dataIncluded[19]; + + case PF21: + return !dataIncluded[20]; + + case PF22: + return !dataIncluded[21]; + + case PF23: + return !dataIncluded[22]; + + case PF24: + return !dataIncluded[23]; + + default: + return true; + } + } + + /** + * Help request - + * + * + * See notes inside method + */ + public final void sendHelpRequest() { + // Client sends header 000D12A0000004000003####F3FFEF + // operation code 3 + // row - first ## + // column - second ## + // F3 - Help Aid Key + // System.out.println("Help request sent"); + baosp.write(screen52.getCurrentRow()); + baosp.write(screen52.getCurrentCol()); + baosp.write(AID_HELP); + + try { + Log.i(TAG, "sendHelpRequest() writeGDS()"); + writeGDS(0, 3, baosp.toByteArray()); + } + catch (IOException ioe) { + Log.w(TAG, ioe.getMessage()); + } + + baosp.reset(); + } + + /** + * Attention Key - + * + * + * See notes inside method + */ + public final void sendAttentionKey() { + // Client sends header 000A12A000004400000FFEF + // 0x40 -> 01000000 + // + // flags + // bit 0 - ERR + // bit 1 - ATN Attention + // bits 2-4 - reserved + // bit 5 - SRQ system request + // bit 6 - TRQ Test request key + // bit 7 - HLP + + // System.out.println("Attention key sent"); + try { + Log.i(TAG, "sendAttentionKey() writeGDS()"); + writeGDS(0x40, 0, null); + } + catch (IOException ioe) { + Log.w(TAG, ioe.getMessage()); + } + } + + /** + * Opens a dialog and asks the user before sending a request + * + * @see {@link #systemRequest(String)} + */ + public final void systemRequest() { + String ask = manager.res.getString(R.string.prompt_sys_request); + String sysreq = bridge.promptHelper.requestStringPrompt(null, ask); + systemRequest(sysreq); + } + + /** + * @param sr - system request option + * @see {@link #systemRequest(String)} + */ + public final void systemRequest(char sr) { + systemRequest(Character.toString(sr)); + } + + /** + * System request, taken from the rfc1205, 5250 Telnet interface section 4.3 + * + * @param sr system request option (allowed to be null, but than nothing happens) + */ + public final void systemRequest(String sr) { + byte[] bytes = null; + + if ((sr != null) && (sr.length() > 0)) { + // XXX: Not sure, if this is a sufficient check for 'clear dataq' + if (sr.charAt(0) == '2') { + dsq.clear(); + } + + for (int i = 0, l = sr.length(); i < l; i++) { + baosp.write(codePage.uni2ebcdic(sr.charAt(i))); + } + + bytes = baosp.toByteArray(); + } + + try { + Log.i(TAG, "systemRequest() writeGDS()"); + writeGDS(4, 0, bytes); + } + catch (IOException ioe) { + Log.i(TAG, ioe.getMessage()); + } + + baosp.reset(); + } + + /** + * Cancel Invite - taken from the rfc1205 - 5250 Telnet interface section + * 4.3 + * + * See notes inside method + */ + public final void cancelInvite() { + // screen52.setStatus(Screen5250.STATUS_SYSTEM, + // Screen5250.STATUS_VALUE_ON, null); + screen52.getOIA().setInputInhibited(ScreenOIA.INPUTINHIBITED_SYSTEM_WAIT, + ScreenOIA.OIA_LEVEL_INPUT_INHIBITED); + + // from rfc1205 section 4.3 + // Server: Sends header with the 000A12A0 00000400 000AFFEF + // Opcode = Cancel Invite. + + // Client: sends header with the 000A12A0 00000400 000AFFEF + // Opcode = Cancel Invite to + // indicate that the work station is + // no longer invited. + try { + Log.i(TAG, "cancelInvite() writeGDS()"); + writeGDS(0, 10, null); + } + catch (IOException ioe) { + Log.w(TAG, ioe.getMessage()); + } + } + + public final void hostPrint(int aid) { + if (screen52.isStatusErrorCode()) { + screen52.restoreErrorLine(); + screen52.setStatus(Screen5250.STATUS_ERROR_CODE, + Screen5250.STATUS_VALUE_OFF, null); + } + + screen52.setCursorActive(false); + // screen52.setStatus(Screen5250.STATUS_SYSTEM, + // Screen5250.STATUS_VALUE_ON, null); + screen52.getOIA().setInputInhibited(ScreenOIA.INPUTINHIBITED_SYSTEM_WAIT, + ScreenOIA.OIA_LEVEL_INPUT_INHIBITED); + // From client access ip capture + // it seems to use an operation code of 3 and 4 + // also note that the flag field that says reserved is being sent as + // well + // with a value of 0x80 + // + // I have tried with not setting these flags and sending with 3 or 1 + // there is no effect and I still get a host print screen. Go figure + //0000: 000D 12A0 0000 0400 8003 1407 F6FFEF + //0000: 000D 12A0 0000 0400 8001 110E F6FFEF + // + // Client sends header 000D12A0000004000003####F6FFEF + // operation code 3 + // row - first ## + // column - second ## + // F6 - Print Aid Key + baosp.write(screen52.getCurrentRow()); + baosp.write(screen52.getCurrentCol()); + baosp.write(AID_PRINT); // aid key + + try { + Log.i(TAG, "hostPrint() writeGDS()"); + writeGDS(0, 3, baosp.toByteArray()); + } + catch (IOException ioe) { + Log.w(TAG, ioe.getMessage()); + } + + baosp.reset(); + } + + public final void toggleDebug() { + producer.toggleDebug(codePage); + } + + // write gerneral data stream + private final void writeGDS(int flags, int opcode, byte abyte0[]) + throws IOException { + // Added to fix for JDK 1.4 this was null coming from another method. + // There was a weird keyRelease event coming from another panel when + // using a key instead of the mouse to select button. + // The other method was fixed as well but this check should be here + // anyway. + if (bout == null) + return; + + int length; + + if (abyte0 != null) + length = abyte0.length + 10; + else + length = 10; + + // refer to rfc1205 - 5250 Telnet interface + // Section 3. Data Stream Format + // Logical Record Length - 16 bits + baosrsp.write(length >> 8); // Length LL + baosrsp.write(length & 0xff); // LL + // Record Type - 16 bits + // It should always be set to '12A0'X to indicate the + // General Data Stream (GDS) record type. + baosrsp.write(18); // 0x12 + baosrsp.write(160); // 0xA0 + // the next 16 bits are not used + baosrsp.write(0); // 0x00 + baosrsp.write(0); // 0x00 + // The second part is meant to be variable in length + // currently this portion is 4 octets long (1 byte or 8 bits for us ;-O) + baosrsp.write(4); // 0x04 + baosrsp.write(flags); // flags + // bit 0 - ERR + // bit 1 - ATN Attention + // bits 2-4 - reserved + // bit 5 - SRQ system request + // bit 6 - TRQ Test request key + // bit 7 - HLP + baosrsp.write(0); // reserved - set to 0x00 + baosrsp.write(opcode); // opcode + + if (abyte0 != null) + baosrsp.write(abyte0, 0, abyte0.length); + + baosrsp = appendByteStream(baosrsp.toByteArray()); + // make sure we indicate no more to be sent + baosrsp.write(IAC); + baosrsp.write(EOR); + baosrsp.writeTo(bout); + bout.flush(); + baosrsp.reset(); + } + + protected final int getOpCode() { + return bk.getOpCode(); + } + + protected boolean[] getActiveAidKeys() { + boolean aids[] = new boolean[dataIncluded.length]; + System.arraycopy(dataIncluded, 0, aids, 0, dataIncluded.length); + return aids; + } + + private final void setInvited() { + Log.d(TAG, "invited"); + if (!screen52.isStatusErrorCode()) + screen52.getOIA().setInputInhibited(ScreenOIA.INPUTINHIBITED_NOTINHIBITED, + ScreenOIA.OIA_LEVEL_INPUT_INHIBITED); + invited = true; + screen52.updateDirty(); + buffer.testChanged(); + } + + // WVL - LDC : 05/08/2005 : TFX.006253 - Support STRPCCMD + private void strpccmd() { + try { + int str = 11; + char c; + ScreenPlanes planes = screen52.getPlanes(); + c = planes.getChar(str); + boolean waitFor = !(c == 'a'); + StringBuffer command = new StringBuffer(); + + for (int i = str + 1; i < 132; i++) { + c = planes.getChar(i); + + if (Character.isISOControl(c)) + c = ' '; + + command.append(c); + } + + String cmd = command.toString().trim(); + run(cmd, waitFor); + } + finally { + strpccmd = false; + screen52.sendKeys(TN5250jConstants.MNEMONIC_ENTER); + } + } + + // WVL - LDC : 05/08/2005 : TFX.006253 - Support STRPCCMD + private void run(String cmd, boolean waitFor) { + try { + Log.d(TAG, "RUN cmd = " + cmd); + Log.d(TAG, "RUN wait = " + waitFor); + Runtime r = Runtime.getRuntime(); + Process p = r.exec(cmd); + + if (waitFor) { + int result = p.waitFor(); + Log.d(TAG, "RUN result = " + result); + } + } + catch (Throwable t) { + Log.e(TAG, "exception", t); + } + } + + + // WVL - LDC : TR.000300 : Callback scenario from 5250 + /** + * Activate or deactivate the command scanning behaviour. + * + * @param scan + * if true, scanning is enabled; disabled otherwise. + * + * @see scan4Cmd() + */ + public void setScanningEnabled(boolean scan) { + this.scan = scan; + } + + // WVL - LDC : TR.000300 : Callback scenario from 5250 + /** + * Checks whether command scanning is enabled. + * + * @return true is command scanning is enabled; false otherwise. + */ + public boolean isScanningEnabled() { + return this.scan; + } + + // WVL - LDC : TR.000300 : Callback scenario from 5250 + /** + * When command scanning is activated, the terminal reads the first and + * second character in the datastream (the zero position allows to + * devisualize the scan stream). If the sequence #! is + * encountered and if this sequence is not followed by a + * blank character, the {@link parseCommand(ScreenChar[])}is called. + */ + private void scan() { + // System.out.println("Checking command : " + + // screen52.screen[1].getChar() + screen52.screen[2].getChar()); + // ScreenChar[] screen = screen52.screen; + ScreenPlanes planes = screen52.getPlanes(); + + if ((planes.getChar(STRSCAN) == '#') + && (planes.getChar(STRSCAN + 1) == '!') + && (planes.getChar(STRSCAN + 2) != ' ')) { + try { + parseCommand(); + } + catch (Throwable t) { + Log.i(TAG, "Exec cmd: " + t.getMessage()); + t.printStackTrace(); + } + } + } + + // WVL - LDC : TR.000300 : Callback scenario from 5250 + /** + * The screen is parsed starting from second position until a white space is + * encountered. When found the Session#execCommand(String, int) is + * called with the parsed string. The position immediately following the + * encountered white space, separating the command from the rest of the + * screen, is passed as starting index. + * + * Note that the character at the starting position can potentially be a + * white space itself. The starting position in execCommand + * provided to make the scanning sequence more flexible. We'd like for + * example to embed also a + or - sign to + * indicate whether the tnvt should trigger a repaint or not. This would + * allow the flashing of command sequences without them becoming visible. + * + *
    + *
  • PRE The screen character at position + * STRSCAN + 2 is not a white space.
  • + *
+ */ + private void parseCommand() { + // Search for the command i.e. the first token in the stream + // after the #! sequence separated by a space from the rest + // of the screen. + char[] screen = screen52.getScreenAsChars(); + + for (int s = STRSCAN + 2, i = s; i < screen.length; i++) { + if (screen[i] == ' ') { + String command = new String(screen, s, i - s); + // Skip all white spaces between the command and the rest of + // the screen. + //for (; (i < screen.length) && (screen[i] == ' '); i++); + String remainder = new String(screen, i + 1, screen.length + - (i + 1)); + //controller.fireScanned(command, remainder); + Log.i(TAG, "trying to run " + command + " " + remainder); + break; + } + } + } + + public void run() { + if (enhanced) sfParser = new WTDSFParser(this); + + bk = new Stream5250(); + + while (keepTrucking) { + try { + Object e = dsq.take(); + + if ((e instanceof Integer) && ((Integer)e == 0)) { + screen52.updateDirty(); + buffer.testChanged(); + continue; + }; + + bk.initialize((byte[])e); + } + catch (InterruptedException ie) { + Log.w(TAG, " vt thread interrupted and stopping "); + keepTrucking = false; + continue; + } + + invited = false; + screen52.setCursorActive(false); + if (bk == null) continue; + + switch (bk.getOpCode()) { + case 00: + Log.d(TAG, "No operation"); + break; + + case 1: + Log.d(TAG, "Invite Operation"); + parseIncoming(); + pendingUnlock = true; + cursorOn = true; + setInvited(); + break; + + case 2: + Log.d(TAG, "Output Only"); + parseIncoming(); + screen52.updateDirty(); + break; + + case 3: + Log.d(TAG, "Put/Get Operation"); + parseIncoming(); + setInvited(); + break; + + case 4: + Log.d(TAG, "Save Screen Operation"); + parseIncoming(); + break; + + case 5: + Log.d(TAG, "Restore Screen Operation"); + parseIncoming(); + break; + + case 6: + Log.d(TAG, "Read Immediate"); + sendAidKey(0); + break; + + case 7: + Log.d(TAG, "Reserved"); + break; + + case 8: + Log.d(TAG, "Read Screen Operation"); + + try { + readScreen(); + } + catch (IOException ex) { + Log.w(TAG, ex.getMessage()); + } + + break; + + case 9: + Log.d(TAG, "Reserved"); + break; + + case 10: + Log.d(TAG, "Cancel Invite Operation"); + cancelInvite(); + break; + + case 11: + Log.d(TAG, "Turn on message light"); + screen52.getOIA().setMessageLightOn(); + screen52.setCursorActive(true); + break; + + case 12: + Log.d(TAG, "Turn off Message light"); + screen52.getOIA().setMessageLightOff(); + screen52.setCursorActive(true); + break; + + default: + break; + } + + if (screen52.isUsingGuiInterface()) + screen52.drawFields(); + + try { + if (!strpccmd) { + screen52.updateDirty(); + } + else { + strpccmd(); + } + } + catch (Exception exd) { + Log.w(TAG, " tnvt.run: " + exd.getMessage()); + exd.printStackTrace(); + } + + if (pendingUnlock && !screen52.isStatusErrorCode()) { + screen52.getOIA().setKeyBoardLocked(false); + pendingUnlock = false; + } + + if (cursorOn && !screen52.getOIA().isKeyBoardLocked()) { + screen52.setCursorActive(true); + cursorOn = false; + } + } + } + + public void dumpStuff() { + Log.d(TAG, " Pending unlock " + pendingUnlock); + Log.d(TAG, " Status Error " + screen52.isStatusErrorCode()); + Log.d(TAG, " Keyboard Locked " + screen52.getOIA().isKeyBoardLocked()); + Log.d(TAG, " Cursor On " + cursorOn); + Log.d(TAG, " Cursor Active " + screen52.cursorActive); + } + + + private final void readScreen() throws IOException { + int rows = screen52.getRows(); + int cols = screen52.getColumns(); + byte abyte0[] = new byte[rows * cols]; + fillScreenArray(abyte0, rows, cols); + Log.i(TAG, "readScreen() writeGDS()"); + writeGDS(0, 0, abyte0); + abyte0 = null; + } + + private final void fillScreenArray(byte[] sa, int rows, int cols) { + int la = 32; + int sac = 0; + int len = rows * cols; + ScreenPlanes planes = screen52.planes; + + try { + for (int y = 0; y < len; y++) { // save the screen data + if (planes.isAttributePlace(y)) { + la = planes.getCharAttr(y); + sa[sac++] = (byte) la; + } + else { + // The characters on screen are in unicode + char ch = planes.getChar(y); + byte byteCh; + if (isDataUnicode(ch)) byteCh = codePage.uni2ebcdic(ch); + else byteCh = (byte) ch; + sa[sac++] = byteCh; + } + } + } + catch (Exception exc) { + Log.i(TAG, exc.getMessage()); + exc.printStackTrace(); + } + } + + private final void fillRegenerationBuffer(ByteArrayOutputStream sc, int rows, int cols) + throws IOException { + int la = 32; + int sac = 0; + int len = rows * cols; + ScreenPlanes planes = screen52.planes; + byte[] sa = new byte[len]; + + try { + boolean guiExists = sfParser != null && sfParser.isGuisExists(); + + for (int y = 0; y < len; y++) { // save the screen data + if (guiExists) { + byte[] guiSeg = sfParser.getSegmentAtPos(y); + + if (guiSeg != null) { + //Log.i(TAG," gui saved at " + y + " - " + screen52.getRow(y) + "," + + // screen52.getCol(y)); + byte[] gsa = new byte[sa.length + guiSeg.length + 2]; + System.arraycopy(sa, 0, gsa, 0, sa.length); + System.arraycopy(guiSeg, 0, gsa, sac + 2, guiSeg.length); + sa = new byte[gsa.length]; + System.arraycopy(gsa, 0, sa, 0, gsa.length); + sa[sac++] = (byte)0x04; + sa[sac++] = (byte)0x11; + sac += guiSeg.length; + //y--; + // continue; + } + } + + if (planes.isAttributePlace(y)) { + la = planes.getCharAttr(y); + sa[sac++] = (byte) la; + } + else { + // The characters on screen are in unicode + char ch = planes.getChar(y); + byte byteCh; + if (isDataUnicode(ch)) byteCh = codePage.uni2ebcdic(ch); + else byteCh = (byte) ch; + sa[sac++] = byteCh; + } + } + } + catch (Exception exc) { + Log.i(TAG, exc.getMessage()); + exc.printStackTrace(); + } + + sc.write(sa); + } + + public final void saveScreen() throws IOException { + ByteArrayOutputStream sc = new ByteArrayOutputStream(); + sc.write(4); + sc.write(0x12); // 18 + sc.write(0); // 18 + sc.write(0); // 18 + sc.write((byte) screen52.getRows()); // store the current size + sc.write((byte) screen52.getColumns()); // "" + int cp = screen52.getCurrentPos(); // save off current position + // fix below submitted by Mitch Blevins + //int cp = screen52.getScreenFields().getCurrentFieldPos(); + // save off current position + sc.write((byte)(cp >> 8 & 0xff)); // "" + sc.write((byte)(cp & 0xff)); // "" + sc.write((byte)(screen52.homePos >> 8 & 0xff)); // save home pos + sc.write((byte)(screen52.homePos & 0xff)); // "" + int rows = screen52.getRows(); // store the current size + int cols = screen52.getColumns(); // "" + // byte[] sa = new byte[rows * cols]; + fillRegenerationBuffer(sc, rows, cols); + // fillScreenArray(sa, rows, cols); + // + // sc.write(sa); + // sa = null; + int sizeFields = screen52.getScreenFields().getSize(); + sc.write((byte)(sizeFields >> 8 & 0xff)); // "" + sc.write((byte)(sizeFields & 0xff)); // "" + + if (sizeFields > 0) { + int x = 0; + int s = screen52.getScreenFields().getSize(); + ScreenField sf = null; + + while (x < s) { + sf = screen52.getScreenFields().getField(x); + sc.write((byte) sf.getAttr()); // attribute + int sp = sf.startPos(); + sc.write((byte)(sp >> 8 & 0xff)); // "" + sc.write((byte)(sp & 0xff)); // "" + + if (sf.mdt) + sc.write((byte) 1); + else + sc.write((byte) 0); + + sc.write((byte)(sf.getLength() >> 8 & 0xff)); // "" + sc.write((byte)(sf.getLength() & 0xff)); // "" + sc.write((byte) sf.getFFW1() & 0xff); + sc.write((byte) sf.getFFW2() & 0xff); + sc.write((byte) sf.getFCW1() & 0xff); + sc.write((byte) sf.getFCW2() & 0xff); + Log.d(TAG, "Saved "); + Log.d(TAG, sf.toString()); + x++; + } + + sf = null; + } + + // The following two lines of code looks to have caused all sorts of + // problems so for now we have commented them out. + // screen52.getScreenFields().setCurrentField(null); // set it to null + // for GC ? + // screen52.clearTable(); + + try { + Log.i(TAG, "saveScreen() writeGDS()"); + writeGDS(0, 3, sc.toByteArray()); + } + catch (IOException ioe) { + Log.w(TAG, ioe.getMessage()); + } + + sc = null; + Log.d(TAG, "Save Screen end "); + } + + /** + * + * @throws IOException + */ + public final void restoreScreen() throws IOException { + int which = 0; + ScreenPlanes planes = screen52.planes; + + try { + Log.d(TAG, "Restore "); + bk.getNextByte(); + bk.getNextByte(); + int rows = bk.getNextByte() & 0xff; + int cols = bk.getNextByte() & 0xff; + int pos = bk.getNextByte() << 8 & 0xff00; // current position + pos |= bk.getNextByte() & 0xff; + int hPos = bk.getNextByte() << 8 & 0xff00; // home position + hPos |= bk.getNextByte() & 0xff; + + if ((rows != screen52.getRows()) || (cols != screen52.getColumns())) + screen52.setRowsCols(rows, cols); + + screen52.clearAll(); // initialize what we currenty have + + if (sfParser != null && sfParser.isGuisExists()) + sfParser.clearGuiStructs(); + + int b = 32; + int la = 32; + int len = rows * cols; + + for (int y = 0; y < len; y++) { + b = bk.getNextByte(); + + if (b == 0x04) { + Log.i(TAG, " gui restored at " + y + " - " + screen52.getRow(y) + "," + + screen52.getCol(y)); + int command = bk.getNextByte(); + byte[] seg = bk.getSegment(); + + if (seg.length > 0) { + screen52.goto_XY(y); + sfParser.parseWriteToDisplayStructuredField(seg); + } + + y--; + // screen52.goto_XY(y); + } + else { + // b = bk.getNextByte(); + if (planes.isUseGui(y)) + continue; + + if (isAttribute(b)) { + planes.setScreenCharAndAttr(y, planes.getChar(y), b, true); + la = b; + } + else { + // The characters on screen are in unicode + char ch; + if (isDataEBCDIC(b)) ch = codePage.ebcdic2uni(b); + else ch = (char) b; + planes.setScreenCharAndAttr(y, ch, la, false); + } + } + } + + int numFields = bk.getNextByte() << 8 & 0xff00; + numFields |= bk.getNextByte() & 0xff; + Log.d(TAG, "number of fields " + numFields); + + if (numFields > 0) { + int x = 0; + int attr = 0; + int fPos = 0; + int fLen = 0; + int ffw1 = 0; + int ffw2 = 0; + int fcw1 = 0; + int fcw2 = 0; + boolean mdt = false; + ScreenField sf = null; + + while (x < numFields) { + attr = bk.getNextByte(); + fPos = bk.getNextByte() << 8 & 0xff00; + fPos |= bk.getNextByte() & 0xff; + + if (bk.getNextByte() == 1) + mdt = true; + else + mdt = false; + + fLen = bk.getNextByte() << 8 & 0xff00; + fLen |= bk.getNextByte() & 0xff; + ffw1 = bk.getNextByte(); + ffw2 = bk.getNextByte(); + fcw1 = bk.getNextByte(); + fcw2 = bk.getNextByte(); + sf = screen52.getScreenFields().setField(attr, + screen52.getRow(fPos), screen52.getCol(fPos), fLen, + ffw1, ffw2, fcw1, fcw2); + + while (fLen-- > 0) { + // now we set the field plane attributes + planes.setScreenFieldAttr(fPos++, ffw1); + } + + if (mdt) { + sf.setMDT(); + screen52.getScreenFields().setMasterMDT(); + } + + Log.d(TAG, "Restored "); + Log.d(TAG, sf.toString()); + x++; + } + } + + // Redraw the gui fields if we are in gui mode + if (screen52.isUsingGuiInterface()) + screen52.drawFields(); + + screen52.restoreScreen(); // display the screen + // The position was saved with currentPos which 1,1 offset of the + // screen position. + // The setPendingInsert is the where the cursor position will be + // displayed after the restore. + screen52.setPendingInsert(true, screen52.getRow(pos + cols), screen52 + .getCol(pos + cols)); + // We need to offset the pos by -1 since the position is 1,1 based + // and the goto_XY is 0,0 based. + screen52.goto_XY(pos - 1); + screen52.isInField(); + // // Redraw the gui fields if we are in gui mode + // if (screen52.isUsingGuiInterface()) + // screen52.drawFields(); + } + catch (Exception e) { + Log.w(TAG, "error restoring screen " + which + " with " + + e.getMessage()); + } + } + + public final boolean waitingForInput() { + return waitingForInput; + } + + private void parseIncoming() { + boolean done = false; + boolean error = false; + + try { + while (bk.hasNext() && !done) { + byte b = bk.getNextByte(); + + switch (b) { + case 0: + case 1: + break; + + case CMD_SAVE_SCREEN: // 0x02 2 Save Screen + case 3: // 0x03 3 Save Partial Screen + Log.d(TAG, "save screen partial"); + saveScreen(); + break; + + case ESC: // ESCAPE + break; + + case 7: // audible bell + manager.playBeep(); + bk.getNextByte(); + bk.getNextByte(); + break; + + case CMD_WRITE_TO_DISPLAY: // 0x11 17 write to display + error = writeToDisplay(true); + + // WVL - LDC : TR.000300 : Callback scenario from 5250 + // Only scan when WRITE_TO_DISPLAY operation (i.e. refill + // screen buffer) + // has been issued! + if (scan) + scan(); + + break; + + case CMD_RESTORE_SCREEN: // 0x12 18 Restore Screen + case 13: // 0x13 19 Restore Partial Screen + Log.d(TAG, "restore screen partial"); + restoreScreen(); + break; + + case CMD_CLEAR_UNIT_ALTERNATE: // 0x20 32 clear unit alternate + int param = bk.getNextByte(); + + if (param != 0) { + Log.d(TAG, " clear unit alternate error " + + Integer.toHexString(param)); + sendNegResponse(NR_REQUEST_ERROR, 03, 01, 05, + " clear unit alternate not supported"); + done = true; + } + else { + if (screen52.getRows() != 27) + screen52.setRowsCols(27, 132); + + screen52.clearAll(); + + if (sfParser != null && sfParser.isGuisExists()) + sfParser.clearGuiStructs(); + } + + break; + + case CMD_WRITE_ERROR_CODE: // 0x21 33 Write Error Code + writeErrorCode(); + error = writeToDisplay(false); + break; + + case CMD_WRITE_ERROR_CODE_TO_WINDOW: // 0x22 34 + // Write Error Code to window + writeErrorCodeToWindow(); + error = writeToDisplay(false); + break; + + case CMD_READ_SCREEN_IMMEDIATE: // 0x62 98 + case CMD_READ_SCREEN_TO_PRINT: // 0x66 102 read screen to print + readScreen(); + break; + + case CMD_CLEAR_UNIT: // 64 0x40 clear unit + if (screen52.getRows() != 24) + screen52.setRowsCols(24, 80); + + screen52.clearAll(); + + if (sfParser != null && sfParser.isGuisExists()) + sfParser.clearGuiStructs(); + + break; + + case CMD_CLEAR_FORMAT_TABLE: // 80 0x50 Clear format table + screen52.clearTable(); + break; + + case CMD_READ_INPUT_FIELDS: //0x42 66 read input fields + case CMD_READ_MDT_FIELDS: // 0x52 82 read MDT Fields + bk.getNextByte(); + bk.getNextByte(); + readType = b; + screen52.goHome(); + // do nothing with the cursor here it is taken care of + // in the main loop. + //////////////// screen52.setCursorOn(); + waitingForInput = true; + pendingUnlock = true; + // screen52.setKeyboardLocked(false); + break; + + case CMD_READ_MDT_IMMEDIATE_ALT: // 0x53 83 + readType = b; + // screen52.goHome(); + // waitingForInput = true; + // screen52.setKeyboardLocked(false); + readImmediate(readType); + break; + + case CMD_WRITE_STRUCTURED_FIELD: // 243 0xF3 -13 Write + // structured field + writeStructuredField(); + break; + + case CMD_ROLL: // 0x23 35 Roll Not sure what it does right now + int updown = bk.getNextByte(); + int topline = bk.getNextByte(); + int bottomline = bk.getNextByte(); + screen52.rollScreen(updown, topline, bottomline); + break; + + default: + done = true; + sendNegResponse(NR_REQUEST_ERROR, 03, 01, 01, + "parseIncoming"); + break; + } + + if (error) + done = true; + } + } + catch (Exception exc) { + Log.w(TAG, "incoming " + exc.getMessage()); + } + } + + /** + * This routine handles sending negative responses back to the host. + * + * You can find a description of the types of responses to be sent back by + * looking at section 12.4 of the 5250 Functions Reference manual + * + * + * @param cat + * @param modifier + * @param uByte1 + * @param uByte2 + * @param from + * + */ + protected void sendNegResponse(int cat, int modifier, int uByte1, + int uByte2, String from) { + try { + int os = bk.getByteOffset(-1) & 0xf0; + int cp = (bk.getCurrentPos() - 1); + Log.i(TAG, "invalid " + from + " command " + os + + " at pos " + cp); + } + catch (Exception e) { + Log.w(TAG, "Send Negative Response error " + e.getMessage()); + } + + baosp.write(cat); + baosp.write(modifier); + baosp.write(uByte1); + baosp.write(uByte2); + + try { + Log.i(TAG, "sendNegResponse() writeGDS()"); + writeGDS(128, 0, baosp.toByteArray()); + } + catch (IOException ioe) { + Log.w(TAG, ioe.getMessage()); + } + + baosp.reset(); + } + + public void sendNegResponse2(int ec) { + screen52.setPrehelpState(true, true, false); + baosp.write(0x00); + baosp.write(ec); + + try { + Log.i(TAG, "sendNegResponse2() writeGDS()"); + writeGDS(1, 0, baosp.toByteArray()); + } + catch (IOException ioe) { + Log.w(TAG, ioe.getMessage()); + } + + baosp.reset(); + } + + private boolean writeToDisplay(boolean controlsExist) { + boolean error = false; + boolean done = false; + int attr; + byte control0 = 0; + byte control1 = 0; + int saRows = screen52.getRows(); + int saCols = screen52.getColumns(); + + try { + if (controlsExist) { + control0 = bk.getNextByte(); + control1 = bk.getNextByte(); + processCC0(control0); + } + + while (bk.hasNext() && !done) { + // pos = bk.getCurrentPos(); + // int rowc = screen52.getCurrentRow(); + // int colc = screen52.getCurrentCol(); + byte bytebk = bk.getNextByte(); + + switch (bytebk) { + case 1: // SOH - Start of Header Order + Log.d(TAG, "SOH - Start of Header Order"); + error = processSOH(); + break; + + case 02: // RA - Repeat to address + Log.d(TAG, "RA - Repeat to address"); + int row = screen52.getCurrentRow(); + int col = screen52.getCurrentCol(); + int toRow = bk.getNextByte(); + int toCol = bk.getNextByte() & 0xff; + + if (toRow >= row) { + int repeat = bk.getNextByte(); + + // a little intelligence here I hope + if (row == 1 && col == 2 && toRow == screen52.getRows() + && toCol == screen52.getColumns()) + screen52.clearScreen(); + else { + if (repeat != 0) { + //LDC - 13/02/2003 - convert it to unicode + repeat = codePage.ebcdic2uni(repeat); + //repeat = getASCIIChar(repeat); + } + + int times = ((toRow * screen52.getColumns()) + toCol) + - ((row * screen52.getColumns()) + col); + + while (times-- >= 0) { + screen52.setChar(repeat); + } + } + } + else { + sendNegResponse(NR_REQUEST_ERROR, 0x05, 0x01, 0x23, + " RA invalid"); + error = true; + } + + break; + + case 03: // EA - Erase to address + Log.d(TAG, "EA - Erase to address"); + int EArow = screen52.getCurrentRow(); + int EAcol = screen52.getCurrentCol(); + int toEARow = bk.getNextByte(); + int toEACol = bk.getNextByte() & 0xff; + int EALength = bk.getNextByte() & 0xff; + + while (--EALength > 0) { + bk.getNextByte(); + } + + char EAAttr = (char) 0; + + // a little intelligence here I hope + if (EArow == 1 && EAcol == 2 + && toEARow == screen52.getRows() + && toEACol == screen52.getColumns()) + screen52.clearScreen(); + else { + int times = ((toEARow * screen52.getColumns()) + toEACol) + - ((EArow * screen52.getColumns()) + EAcol); + + while (times-- >= 0) { + screen52.setChar(EAAttr); + } + } + + break; + + case 04: // Command - Escape + Log.d(TAG, "Command - Escape"); + done = true; + break; + + case 16: // TD - Transparent Data + Log.d(TAG, "TD - Transparent Data"); + int j = (bk.getNextByte() & 0xff) << 8 | bk.getNextByte() + & 0xff; // length + break; + + case 17: // SBA - set buffer address order (row column) + //Log.d(TAG,"SBA - set buffer address order (row column)"); + int saRow = bk.getNextByte(); + int saCol = bk.getNextByte() & 0xff; + + // make sure it is in bounds + if (saRow >= 0 && saRow <= screen52.getRows() && saCol >= 0 + && saCol <= screen52.getColumns()) { + screen52.setCursor(saRow, saCol); // now set screen + // position for output + } + else { + sendNegResponse(NR_REQUEST_ERROR, 0x05, 0x01, 0x22, + "invalid row/col order" + " saRow = " + saRow + + " saRows = " + screen52.getRows() + + " saCol = " + saCol); + error = true; + } + + break; + + case 18: // WEA - Extended Attribute + Log.d(TAG, "WEA - Extended Attribute"); + bk.getNextByte(); + bk.getNextByte(); + break; + + case 19: // IC - Insert Cursor + Log.d(TAG, "IC - Insert Cursor"); + int icX = bk.getNextByte(); + int icY = bk.getNextByte() & 0xff; + + if (icX >= 0 && icX <= saRows && icY >= 0 && icY <= saCols) { + Log.d(TAG, " IC " + icX + " " + icY); + screen52.setPendingInsert(true, icX, icY); + } + else { + sendNegResponse(NR_REQUEST_ERROR, 0x05, 0x01, 0x22, + " IC/IM position invalid "); + error = true; + } + + break; + + case 20: // MC - Move Cursor + Log.d(TAG, "MC - Move Cursor"); + int imcX = bk.getNextByte(); + int imcY = bk.getNextByte() & 0xff; + + if (imcX >= 0 && imcX <= saRows && imcY >= 0 + && imcY <= saCols) { + Log.d(TAG, " MC " + imcX + " " + imcY); + screen52.setPendingInsert(false, imcX, imcY); + } + else { + sendNegResponse(NR_REQUEST_ERROR, 0x05, 0x01, 0x22, + " IC/IM position invalid "); + error = true; + } + + break; + + case 21: // WTDSF - Write To Display Structured Field order + Log.d(TAG, "WTDSF - Write To Display Structured Field order"); + byte[] seg = bk.getSegment(); + error = sfParser.parseWriteToDisplayStructuredField(seg); + break; + + case 29: // SF - Start of Field + Log.d(TAG, "SF - Start of Field"); + int fcw1 = 0; + int fcw2 = 0; + int ffw1 = 0; + int ffw0 = bk.getNextByte() & 0xff; // FFW + + if ((ffw0 & 0x40) == 0x40) { + ffw1 = bk.getNextByte() & 0xff; // FFW 1 + fcw1 = bk.getNextByte() & 0xff; // check for field + + // control word + + // check if the first fcw1 is an 0x81 if it is then get + // the next pair for checking + if (fcw1 == 0x81) { + bk.getNextByte(); + fcw1 = bk.getNextByte() & 0xff; // check for field + // control word + } + + if (!isAttribute(fcw1)) { + fcw2 = bk.getNextByte() & 0xff; // FCW 2 + attr = bk.getNextByte() & 0xff; // attribute field + + while (!isAttribute(attr)) { + Log.i(TAG, Integer.toHexString(fcw1) + " " + + Integer.toHexString(fcw2) + + " "); + Log.i(TAG, Integer.toHexString(attr) + + " " + + Integer.toHexString(bk + .getNextByte() & 0xff)); + attr = bk.getNextByte() & 0xff; // attribute field + } + } + else { + attr = fcw1; // attribute of field + fcw1 = 0; + } + } + else { + attr = ffw0; + } + + int fLength = (bk.getNextByte() & 0xff) << 8 + | bk.getNextByte() & 0xff; + screen52.addField(attr, fLength, ffw0, ffw1, fcw1, fcw2); + break; + + // WVL - LDC : 05/08/2005 : TFX.006253 - Support STRPCCMD + case -128: //STRPCCMD + // if (screen52.getCurrentPos() == 82) { + Log.d(TAG, "STRPCCMD got a -128 command at " + screen52.getCurrentPos()); + StringBuffer value = new StringBuffer(); + int b; + char c; + int[] pco = new int[9]; + int[] pcoOk = {0xfc, 0xd7, 0xc3, 0xd6, 0x40, 0x83, 0x80, 0xa1, 0x80}; + + for (int i = 0; i < 9; i++) { + b = bk.getNextByte(); + pco[i] = ((b & 0xff)); + c = codePage.ebcdic2uni(b); + value.append(c); + } + + // Check "PCO-String" + if (Arrays.equals(pco, pcoOk)) { + strpccmd = true; + } + + // we return in the stream to have all chars + // arrive at the screen for later processing + for (int i = 0; i < 9; i++) + bk.setPrevByte(); + + //} + // no break: so every chars arrives + // on the screen for later parsing + //break; + + default: // all others must be output to screen + //Log.d(TAG,"all others must be output to screen"); + byte byte0 = bk.getByteOffset(-1); + + if (isAttribute(byte0)) { + screen52.setAttr(byte0); + } + else { + if (!screen52.isStatusErrorCode()) { + if (!isDataEBCDIC(byte0)) { + // if (byte0 == 255) { + // sendNegResponse(NR_REQUEST_ERROR,0x05,0x01,0x42, + // " Attempt to send FF to screen"); + // } + // else + screen52.setChar(byte0); + } + else + //LDC - 13/02/2003 - Convert it to unicode + //screen52.setChar(getASCIIChar(byte0)); + screen52.setChar(codePage.ebcdic2uni(byte0)); + } + else { + if (byte0 == 0) + screen52.setChar(byte0); + else + //LDC - 13/02/2003 - Convert it to unicode + //screen52.setChar(getASCIIChar(byte0)); + screen52.setChar(codePage.ebcdic2uni(byte0)); + } + } + + break; + } + + if (error) + done = true; + } + } + catch (Exception e) { + Log.w(TAG, "write to display " + e.getMessage()); + e.printStackTrace(); + } + + ; + + processCC1(control1); + + return error; + } + + private boolean processSOH() throws Exception { + int l = bk.getNextByte(); // length + Log.d(TAG, " byte 0 " + l); + + if (l > 0 && l <= 7) { + bk.getNextByte(); // flag byte 2 + bk.getNextByte(); // reserved + bk.getNextByte(); // resequence fields + screen52.clearTable(); + + // well that is the first time I have seen this. This fixes a + // problem with S/36 command line. Finally got it. + if (l <= 3) return false; + + screen52.setErrorLine(bk.getNextByte()); // error row + int byte1 = 0; + + if (l >= 5) { + byte1 = bk.getNextByte(); + dataIncluded[23] = (byte1 & 0x80) == 0x80; + dataIncluded[22] = (byte1 & 0x40) == 0x40; + dataIncluded[21] = (byte1 & 0x20) == 0x20; + dataIncluded[20] = (byte1 & 0x10) == 0x10; + dataIncluded[19] = (byte1 & 0x8) == 0x8; + dataIncluded[18] = (byte1 & 0x4) == 0x4; + dataIncluded[17] = (byte1 & 0x2) == 0x2; + dataIncluded[16] = (byte1 & 0x1) == 0x1; + } + + if (l >= 6) { + byte1 = bk.getNextByte(); + dataIncluded[15] = (byte1 & 0x80) == 0x80; + dataIncluded[14] = (byte1 & 0x40) == 0x40; + dataIncluded[13] = (byte1 & 0x20) == 0x20; + dataIncluded[12] = (byte1 & 0x10) == 0x10; + dataIncluded[11] = (byte1 & 0x8) == 0x8; + dataIncluded[10] = (byte1 & 0x4) == 0x4; + dataIncluded[9] = (byte1 & 0x2) == 0x2; + dataIncluded[8] = (byte1 & 0x1) == 0x1; + } + + if (l >= 7) { + byte1 = bk.getNextByte(); + dataIncluded[7] = (byte1 & 0x80) == 0x80; + dataIncluded[6] = (byte1 & 0x40) == 0x40; + dataIncluded[5] = (byte1 & 0x20) == 0x20; + dataIncluded[4] = (byte1 & 0x10) == 0x10; + dataIncluded[3] = (byte1 & 0x8) == 0x8; + dataIncluded[2] = (byte1 & 0x4) == 0x4; + dataIncluded[1] = (byte1 & 0x2) == 0x2; + dataIncluded[0] = (byte1 & 0x1) == 0x1; + } + + return false; + } + else { + sendNegResponse(NR_REQUEST_ERROR, 0x05, 0x01, 0x2B, + "invalid SOH length"); + return true; + } + } + + private void processCC0(byte byte0) { + Log.d(TAG, " Control byte0 " + Integer.toBinaryString(byte0 & 0xff)); + boolean lockKeyboard = true; + boolean resetMDT = false; + boolean resetMDTAll = false; + boolean nullMDT = false; + boolean nullAll = false; + + // Bits 3 to 6 are reserved and should be set to '0000' + // 0xE0 = '11100000' - only the first 3 bits are tested + if ((byte0 & 0xE0) == 0x00) { + lockKeyboard = false; + } + + // '00100000' = 0x20 /32 -- just lock keyboard + // '01000000' = 0x40 /64 + // '01100000' = 0x60 /96 + // '10000000' = 0x80 /128 + // '10100000' = 0xA0 /160 + // '11000000' = 0xC0 /192 + // '11100000' = 0xE0 /224 + + switch (byte0 & 0xE0) { + case 0x40: + resetMDT = true; + break; + + case 0x60: + resetMDTAll = true; + break; + + case 0x80: + nullMDT = true; + break; + + case 0xA0: + resetMDT = true; + nullAll = true; + break; + + case 0xC0: + resetMDT = true; + nullMDT = true; + break; + + case 0xE0: + resetMDTAll = true; + nullAll = true; + break; + } + + if (lockKeyboard) { + screen52.getOIA().setKeyBoardLocked(true); + pendingUnlock = false; + } + else + pendingUnlock = false; + + if (resetMDT || resetMDTAll || nullMDT || nullAll) { + ScreenField sf; + int f = screen52.getScreenFields().getSize(); + + for (int x = 0; x < f; x++) { + sf = screen52.getScreenFields().getField(x); + + if (!sf.isBypassField()) { + if ((nullMDT && sf.mdt) || nullAll) { + sf.setFieldChar((char) 0x0); + screen52.drawField(sf); + } + } + + if (resetMDTAll || (resetMDT && !sf.isBypassField())) + sf.resetMDT(); + } + + sf = null; + } + } + + private void processCC1(byte byte1) { + Log.d(TAG, " Control byte1 " + Integer.toBinaryString(byte1 & 0xff)); + + if ((byte1 & 0x04) == 0x04) { + manager.playBeep(); + } + + if ((byte1 & 0x02) == 0x02) { + screen52.getOIA().setMessageLightOff(); + } + + if ((byte1 & 0x01) == 0x01) { + screen52.getOIA().setMessageLightOn(); + } + + if ((byte1 & 0x01) == 0x01 && (byte1 & 0x02) == 0x02) { + screen52.getOIA().setMessageLightOn(); + } + + // reset blinking cursor seems to control whether to set or not set the + // the cursor position. No documentation for this just testing and + // looking at the bit settings of this field. This was a pain in the + // ass! + // + // if it is off '0' then keep existing cursor positioning information + // if it is on '1' then reset the cursor positioning information + // *** Note *** unless we receive bit 4 on at the same time + // this seems to work so far + if ((byte1 & 0x20) == 0x20 && (byte1 & 0x08) == 0x00) { + screen52.setPendingInsert(false); + Log.d(TAG, " WTD position no move"); + } + else { + screen52.setPendingInsert(true); + Log.d(TAG, " WTD position move to home" + screen52.homePos + " row " + + screen52.getRow(screen52.homePos) + " col " + + screen52.getCol(screen52.homePos)); + } + + // in enhanced mode we sometimes only receive bit 6 turned on which + // is reset blinking cursor + if ((byte1 & 0x20) == 0x20 && enhanced) { + cursorOn = true; + } + + if (!screen52.isStatusErrorCode() && (byte1 & 0x08) == 0x08) { + // screen52.setStatus(screen52.STATUS_SYSTEM,screen52.STATUS_VALUE_OFF,null); + cursorOn = true; + } + + if ((byte1 & 0x20) == 0x20 && (byte1 & 0x08) == 0x00) { + screen52.setPendingInsert(false, 1, 1); + } + } + + private boolean isAttribute(int byte0) { + int byte1 = byte0 & 0xff; + return (byte1 & 0xe0) == 0x20; + } + + //LDC - 12/02/2003 - Function name changed from isData to isDataEBCDIC + private boolean isDataEBCDIC(int byte0) { + int byte1 = byte0 & 0xff; + + // here it should always be less than 255 + if (byte1 >= 64 && byte1 < 255) + return true; + else + return false; + } + + //LDC - 12/02/2003 - Test if the unicode character is a displayable + // character. + // The first 32 characters are non displayable characters + // This is normally the inverse of isDataEBCDIC (That's why there is a + // check on 255 -> 0xFFFF + private boolean isDataUnicode(int byte0) { + return (((byte0 < 0) || (byte0 >= 32)) && (byte0 != 0xFFFF)); + } + + private void writeStructuredField() { + boolean done = false; + + try { + int length = ((bk.getNextByte() & 0xff) << 8 | (bk.getNextByte() & 0xff)); + + while (bk.hasNext() && !done) { + switch (bk.getNextByte()) { + case -39: // SOH - Start of Header Order + switch (bk.getNextByte()) { + case 112: // 5250 Query + bk.getNextByte(); // get null required field + sendQueryResponse(); + break; + + default: + Log.d(TAG, "invalid structured field sub command " + + bk.getByteOffset(-1)); + break; + } + + break; + + default: + Log.d(TAG, "invalid structured field command " + + bk.getByteOffset(-1)); + break; + } + } + } + catch (Exception e) { + } + + ; + } + + private final void writeErrorCode() throws Exception { + screen52.setCursor(screen52.getErrorLine(), 1); // Skip the control byte + screen52.setStatus(Screen5250.STATUS_ERROR_CODE, + Screen5250.STATUS_VALUE_ON, null); + screen52.saveErrorLine(); + cursorOn = true; + } + + private final void writeErrorCodeToWindow() throws Exception { + int fromCol = bk.getNextByte() & 0xff; // from column + int toCol = bk.getNextByte() & 0xff; // to column + screen52.setCursor(screen52.getErrorLine(), fromCol); // Skip the control byte + screen52.setStatus(Screen5250.STATUS_ERROR_CODE, + Screen5250.STATUS_VALUE_ON, null); + screen52.saveErrorLine(); + cursorOn = true; + } + + /** + * Method sendQueryResponse + * + * The query command is used to obtain information about the capabilities of + * the 5250 display. + * + * The Query command must follow an Escape (0x04) and Write Structured Field + * command (0xF3). + * + * This section is modeled after the rfc1205 - 5250 Telnet Interface section + * 5.3 + * + * @throws IOException + */ + private final void sendQueryResponse() throws IOException { + Log.i(TAG, "sending query response"); + byte abyte0[] = new byte[64]; + abyte0[0] = 0; // Cursor Row/column (set to zero) + abyte0[1] = 0; // "" + abyte0[2] = -120; // X'88' inbound write structure Field aid + + if (enhanced) { + abyte0[3] = 0; // 0x003D (61) length of query response + abyte0[4] = 64; // "" see note below ????????? + } + else { + abyte0[3] = 0; // 0x003A (58) length of query response + abyte0[4] = 58; // "" + // the length between 58 and 64 seems to cause + // different formatting codes to be sent from + // the host ???????????????? why ??????? + // Well the why can be found in the manual if + // read a little more ;-) + } + + abyte0[5] = -39; // command class 0xD9 + abyte0[6] = 112; // Command type query 0x70 + abyte0[7] = -128; // 0x80 Flag byte + abyte0[8] = 6; // Controller Hardware Class + abyte0[9] = 0; // 0x0600 - Other WSF or another 5250 Emulator + abyte0[10] = 1; // Controller Code Level + abyte0[11] = 1; // Version 1 Rel 1.0 + abyte0[12] = 0; // "" + abyte0[13] = 0; // 13 - 28 are reserved so set to 0x00 + abyte0[14] = 0; // "" + abyte0[15] = 0; // "" + abyte0[16] = 0; // "" + abyte0[17] = 0; // "" + abyte0[18] = 0; // "" + abyte0[19] = 0; // "" + abyte0[20] = 0; // "" + abyte0[21] = 0; // "" + abyte0[22] = 0; // "" + abyte0[23] = 0; // "" + abyte0[24] = 0; // "" + abyte0[25] = 0; // "" + abyte0[26] = 0; // "" + abyte0[27] = 0; // "" + abyte0[28] = 0; // "" + abyte0[29] = 1; // Device type - 0x01 5250 Emulator + abyte0[30] = codePage.uni2ebcdic('5'); // Device type character + abyte0[31] = codePage.uni2ebcdic('2'); // "" + abyte0[32] = codePage.uni2ebcdic('5'); // "" + abyte0[33] = codePage.uni2ebcdic('1'); // "" + abyte0[34] = codePage.uni2ebcdic('0'); // "" + abyte0[35] = codePage.uni2ebcdic('1'); // "" + abyte0[36] = codePage.uni2ebcdic('1'); // "" + abyte0[37] = 2; // Keyboard Id - 0x02 Standard Keyboard + abyte0[38] = 0; // extended keyboard id + abyte0[39] = 0; // reserved + abyte0[40] = 0; // 40 - 43 Display Serial Number + abyte0[41] = 36; // + abyte0[42] = 36; // + abyte0[43] = 0; // + abyte0[44] = 1; // Maximum number of display fields - 256 + abyte0[45] = 0; // 0x0100 + abyte0[46] = 0; // 46 -48 Reserved set to 0x00 + abyte0[47] = 0; + abyte0[48] = 0; + abyte0[49] = 1; // 49 - 53 Controller Display Capability + abyte0[50] = 17; // see rfc - tired of typing :-) + abyte0[51] = 0; // "" + abyte0[52] = 0; // "" + + // 53 + // Bit 0-2: B'000' - no graphics capability + // B'001' - 5292-2 style graphics + // Bit 3-7: B '00000' = reserved (it seems for Client access) + + if (enhanced) { + // abyte0[53] = 0x5E; // 0x5E turns on enhanced mode + // abyte0[53] = 0x27; // 0x5E turns on enhanced mode + abyte0[53] = 0x7; // 0x5E turns on ehnhanced mode + Log.i(TAG, "enhanced options"); + } + else + abyte0[53] = 0x0; // 0x0 is normal emulation + + abyte0[54] = 24; // 54 - 60 Reserved set to 0x00 + // 54 - I found out is used for enhanced user + // interface level 3. Bit 4 allows headers + // and footers for windows + abyte0[54] = 8; // 54 - 60 Reserved set to 0x00 + // 54 - I found out is used for enhanced user + // interface level 3. Bit 4 allows headers + // and footers for windows + abyte0[55] = 0; + abyte0[56] = 0; + abyte0[57] = 0; + abyte0[58] = 0; + abyte0[59] = 0; + abyte0[60] = 0; + abyte0[61] = 0; // gridlines are not supported + abyte0[62] = 0; // gridlines are not supported + abyte0[63] = 0; + Log.i(TAG, "sendQueryResponse() writeGDS()"); + writeGDS(0, 0, abyte0); // now tell them about us + abyte0 = null; + } + + protected final boolean negotiate(byte abyte0[]) throws IOException { + int i = 0; + + // from server negotiations + if (abyte0[i] == IAC) { // -1 + while (i < abyte0.length && abyte0[i++] == -1) + + // while(i < abyte0.length && (abyte0[i] == -1 || abyte0[i++] == 0x20)) + switch (abyte0[i++]) { + // we will not worry about what it WONT do + case WONT: // -4 + default: + break; + + case DO: //-3 + + // not sure why but since moving to V5R2 we are receiving a + // DO with no option when connecting a second session with + // device name. Can not find the cause at all. If anybody + // is interested please debug this until then this works. + if (i < abyte0.length) { + switch (abyte0[i]) { + case TERMINAL_TYPE: // 24 + baosp.write(IAC); + baosp.write(WILL); + baosp.write(TERMINAL_TYPE); + writeByte(baosp.toByteArray()); + baosp.reset(); + break; + + case OPT_END_OF_RECORD: // 25 + baosp.write(IAC); + baosp.write(WILL); + baosp.write(OPT_END_OF_RECORD); + writeByte(baosp.toByteArray()); + baosp.reset(); + break; + + case TRANSMIT_BINARY: // 0 + baosp.write(IAC); + baosp.write(WILL); + baosp.write(TRANSMIT_BINARY); + writeByte(baosp.toByteArray()); + baosp.reset(); + break; + + case TIMING_MARK: // 6 rfc860 + // System.out.println("Timing Mark Received and notifying " + + // "the server that we will not do it"); + baosp.write(IAC); + baosp.write(WONT); + baosp.write(TIMING_MARK); + writeByte(baosp.toByteArray()); + baosp.reset(); + break; + + case NEW_ENVIRONMENT: // 39 rfc1572, rfc4777 + // allways send new environment vars ... + baosp.write(IAC); + baosp.write(WILL); + baosp.write(NEW_ENVIRONMENT); + writeByte(baosp.toByteArray()); + baosp.reset(); + break; + + default: // every thing else we will not do at this time + baosp.write(IAC); + baosp.write(WONT); + baosp.write(abyte0[i]); // either + writeByte(baosp.toByteArray()); + baosp.reset(); + break; + } + } + + i++; + break; + + case WILL: + switch (abyte0[i]) { + case OPT_END_OF_RECORD: // 25 + baosp.write(IAC); + baosp.write(DO); + baosp.write(OPT_END_OF_RECORD); + writeByte(baosp.toByteArray()); + baosp.reset(); + break; + + case TRANSMIT_BINARY: // '\0' + baosp.write(IAC); + baosp.write(DO); + baosp.write(TRANSMIT_BINARY); + writeByte(baosp.toByteArray()); + baosp.reset(); + break; + } + + i++; + break; + + case SB: // -6 + if (abyte0[i] == NEW_ENVIRONMENT && abyte0[i + 1] == 1) { + negNewEnvironment(); + + while (++i < abyte0.length && abyte0[i + 1] != IAC); + } + + if (abyte0[i] == TERMINAL_TYPE && abyte0[i + 1] == 1) { + baosp.write(IAC); + baosp.write(SB); + baosp.write(TERMINAL_TYPE); + baosp.write(QUAL_IS); + + if (!support132) + baosp.write("IBM-3179-2".getBytes()); + else + baosp.write("IBM-3477-FC".getBytes()); + + baosp.write(IAC); + baosp.write(SE); + writeByte(baosp.toByteArray()); + baosp.reset(); + i++; + } + + i++; + break; + } + + return true; + } + else { + return false; + } + } + + /** + * Negotiate new environment string for device name + * + * @throws IOException + */ + private void negNewEnvironment() throws IOException { + baosp.write(IAC); + baosp.write(SB); + baosp.write(NEW_ENVIRONMENT); + baosp.write(IS); + + // http://tools.ietf.org/html/rfc4777 + + if (kbdTypesCodePage != null) { + baosp.write(USERVAR); + baosp.write("KBDTYPE".getBytes()); + baosp.write(VALUE); + baosp.write(kbdTypesCodePage.kbdType.getBytes()); + baosp.write(USERVAR); + baosp.write("CODEPAGE".getBytes()); + baosp.write(VALUE); + baosp.write(kbdTypesCodePage.codepage.getBytes()); + baosp.write(USERVAR); + baosp.write("CHARSET".getBytes()); + baosp.write(VALUE); + baosp.write(kbdTypesCodePage.charset.getBytes()); + } + + if (devName != null) { + baosp.write(USERVAR); + baosp.write("DEVNAME".getBytes()); + baosp.write(VALUE); + baosp.write(negDeviceName().getBytes()); + } + + if (user != null) { + baosp.write(VAR); + baosp.write("USER".getBytes()); + baosp.write(VALUE); + baosp.write(user.getBytes()); + + if (password != null) { + baosp.write(USERVAR); + baosp.write("IBMRSEED".getBytes()); + baosp.write(VALUE); + baosp.write(NEGOTIATE_ESC); + baosp.write(0x0); + baosp.write(0x0); + baosp.write(0x0); + baosp.write(0x0); + baosp.write(0x0); + baosp.write(0x0); + baosp.write(0x0); + baosp.write(0x0); + baosp.write(USERVAR); + baosp.write("IBMSUBSPW".getBytes()); + baosp.write(VALUE); + baosp.write(password.getBytes()); + } + + if (library != null) { + baosp.write(USERVAR); + baosp.write("IBMCURLIB".getBytes()); + baosp.write(VALUE); + baosp.write(library.getBytes()); + } + + if (initialMenu != null) { + baosp.write(USERVAR); + baosp.write("IBMIMENU".getBytes()); + baosp.write(VALUE); + baosp.write(initialMenu.getBytes()); + } + + if (program != null) { + baosp.write(USERVAR); + baosp.write("IBMPROGRAM".getBytes()); + baosp.write(VALUE); + baosp.write(program.getBytes()); + } + } + + baosp.write(IAC); + baosp.write(SE); + writeByte(baosp.toByteArray()); + baosp.reset(); + } + + /** + * This will negotiate a device name with controller. if the sequence is + * less than zero then it will send the device name as specified. On each + * unsuccessful attempt a sequential number is appended until we find one or + * the controller says no way. + * + * @return String + */ + private String negDeviceName() { + if (devSeq++ == -1) { + devNameUsed = devName; + return devName; + } + else { + StringBuffer sb = new StringBuffer(devName + devSeq); + int ei = 1; + + while (sb.length() > 10) { + sb.setLength(0); + sb.append(devName.substring(0, devName.length() - ei++)); + sb.append(devSeq); + } + + devNameUsed = sb.toString(); + return devNameUsed; + } + } + + public final void setCodePage(String cp) { + codePage = CharMappings.getCodePage(cp); + cp = cp.toLowerCase(); + + for (KbdTypesCodePages kbdtyp : KbdTypesCodePages.values()) { + if (("cp" + kbdtyp.codepage).equals(cp) || kbdtyp.ccsid.equals(cp)) { + kbdTypesCodePage = kbdtyp; + break; + } + } + + Log.i(TAG, "Chose keyboard mapping " + kbdTypesCodePage.toString() + " for code page " + cp); + } + + public final ICodePage getCodePage() { + return codePage; + } + + public void signalBell() { + manager.playBeep(); + } + +} diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/framework/transport/SSL/SSLImplementation.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/framework/transport/SSL/SSLImplementation.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,213 @@ +package org.tn5250j.framework.transport.SSL; + +/* + * @(#)SSLImplementation.java + * @author Stephen M. Kennedy + * + * Copyright: Copyright (c) 2001 + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.net.Socket; +import java.security.KeyStore; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import com.five_ten_sg.connectbot.R; +import com.five_ten_sg.connectbot.service.TerminalBridge; +import com.five_ten_sg.connectbot.service.TerminalManager; + +import org.tn5250j.framework.transport.SSLInterface; +import android.util.Log; + + +/** + *

+ * This class implements the SSLInterface and is used to create SSL socket + * instances. + *

+ * + * @author Stephen M. Kennedy + * + */ +public class SSLImplementation implements SSLInterface, X509TrustManager { + private static final String TAG = "SSLImplementation"; + SSLContext sslContext = null; + KeyStore userks = null; + private String userKsPath; + private char[] userksPassword = "changeit".toCharArray(); + + TerminalBridge bridge = null; + TerminalManager manager = null; + String target = null; // destination:port + + KeyManagerFactory userkmf = null; + + TrustManagerFactory usertmf = null; + + TrustManager[] userTrustManagers = null; + + X509Certificate[] acceptedIssuers; + + public SSLImplementation(TerminalBridge bridge, TerminalManager manager) { + this.bridge = bridge; + this.manager = manager; + } + + public void init(String sslType, String homeDirectory) { + try { + Log.d(TAG, "Initializing User KeyStore"); + userKsPath = homeDirectory + File.separator + "keystore"; + File userKsFile = new File(userKsPath); + userks = KeyStore.getInstance(KeyStore.getDefaultType()); + userks.load(userKsFile.exists() ? new FileInputStream(userKsFile) + : null, userksPassword); + Log.d(TAG, "Initializing User Key Manager Factory"); + userkmf = KeyManagerFactory.getInstance(KeyManagerFactory + .getDefaultAlgorithm()); + userkmf.init(userks, userksPassword); + Log.d(TAG, "Initializing User Trust Manager Factory"); + usertmf = TrustManagerFactory.getInstance(TrustManagerFactory + .getDefaultAlgorithm()); + usertmf.init(userks); + userTrustManagers = usertmf.getTrustManagers(); + Log.d(TAG, "Initializing SSL Context"); + sslContext = SSLContext.getInstance(sslType); + sslContext.init(userkmf.getKeyManagers(), new TrustManager[] {this}, null); + } + catch (Exception ex) { + Log.e(TAG, "Error initializing SSL [" + ex.getMessage() + "]"); + } + } + + public Socket createSSLSocket(String destination, int port) { + if (sslContext == null) + throw new IllegalStateException("SSL Context Not Initialized"); + + SSLSocket socket = null; + + try { + target = destination + ":" + String.valueOf(port); + socket = (SSLSocket) sslContext.getSocketFactory().createSocket( + destination, port); + } + catch (Exception e) { + Log.e(TAG, "Error creating ssl socket [" + e.getMessage() + "]"); + } + + return socket; + } + + // X509TrustManager Methods + + /* + * (non-Javadoc) + * + * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers() + */ + public X509Certificate[] getAcceptedIssuers() { + return acceptedIssuers; + } + + /* + * (non-Javadoc) + * + * @see + * javax.net.ssl.X509TrustManager#checkClientTrusted(java.security.cert. + * X509Certificate[], java.lang.String) + */ + public void checkClientTrusted(X509Certificate[] arg0, String arg1) + throws CertificateException { + throw new SecurityException("checkClientTrusted unsupported"); + } + + /* + * (non-Javadoc) + * + * @see + * javax.net.ssl.X509TrustManager#checkServerTrusted(java.security.cert. + * X509Certificate[], java.lang.String) + */ + public void checkServerTrusted(X509Certificate[] chain, String type) + throws CertificateException { + try { + for (int i = 0; i < userTrustManagers.length; i++) { + if (userTrustManagers[i] instanceof X509TrustManager) { + X509TrustManager trustManager = (X509TrustManager) userTrustManagers[i]; + X509Certificate[] calist = trustManager + .getAcceptedIssuers(); + + if (calist.length > 0) { + trustManager.checkServerTrusted(chain, type); + } + else { + throw new CertificateException( + "Empty list of accepted issuers (a.k.a. root CA list)."); + } + } + } + + return; + } + catch (CertificateException ce) { + X509Certificate cert = chain[0]; + String certInfo = manager.res.getString(R.string.host_cert_version) + cert.getVersion() + "\r\n"; + certInfo = certInfo.concat(manager.res.getString(R.string.host_cert_serial) + cert.getSerialNumber() + "\r\n"); + certInfo = certInfo.concat(manager.res.getString(R.string.host_cert_algorithm) + cert.getSigAlgName() + "\r\n"); + certInfo = certInfo.concat(manager.res.getString(R.string.host_cert_issuer) + cert.getIssuerDN().getName() + "\r\n"); + certInfo = certInfo.concat(manager.res.getString(R.string.host_cert_from) + cert.getNotBefore() + "\r\n"); + certInfo = certInfo.concat(manager.res.getString(R.string.host_cert_to) + cert.getNotAfter() + "\r\n"); + certInfo = certInfo.concat(manager.res.getString(R.string.host_cert_dn) + cert.getSubjectDN().getName() + "\r\n"); + certInfo = certInfo.concat(manager.res.getString(R.string.host_cert_publickey) + cert.getPublicKey().getFormat() + "\r\n"); + bridge.outputLine(manager.res.getString(R.string.host_authenticity_warning, target)); + bridge.outputLine(manager.res.getString(R.string.host_certificate, certInfo)); + Boolean result = bridge.promptHelper.requestBooleanPrompt(null, manager.res.getString(R.string.prompt_accept_certificate)); + + if ((result == null) || (!result.booleanValue())) { + throw new java.security.cert.CertificateException( + "Certificate Rejected"); + } + + result = bridge.promptHelper.requestBooleanPrompt(null, manager.res.getString(R.string.prompt_save_certificate)); + + if ((result != null) && (result.booleanValue())) { + try { + userks.setCertificateEntry(cert.getSubjectDN().getName(), + cert); + userks.store(new FileOutputStream(userKsPath), + userksPassword); + } + catch (Exception e) { + Log.e(TAG, "Error saving certificate [" + e.getMessage() + + "]"); + e.printStackTrace(); + } + } + } + } +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/framework/transport/SSL/X509CertificateTrustManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/framework/transport/SSL/X509CertificateTrustManager.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,112 @@ +package org.tn5250j.framework.transport.SSL; + +/* + * @(#)X509CertificateTrustManager.java + * + * Copyright: Copyright (c) 2001 + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.security.cert.X509Certificate; +import java.security.KeyStore; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.Arrays; + +import com.five_ten_sg.connectbot.R; +import com.five_ten_sg.connectbot.service.TerminalBridge; +import com.five_ten_sg.connectbot.service.TerminalManager; + + +/** + * This class is used to trust certificates exchanged during an SSL socket + * handshake. It allows the user to accept the certificate so that connections + * can be made without requiring the server to have a certificate signed by a + * CA (Verisign, Thawte, etc.). + * + * @author Stephen M. Kennedy + * @deprecated. no longer used. + * + */ +public class X509CertificateTrustManager implements X509TrustManager { + + KeyStore ks = null; + TrustManager[] trustManagers; + TerminalBridge bridge = null; + TerminalManager manager = null; + + public X509CertificateTrustManager(TrustManager[] managers, KeyStore keyStore, TerminalBridge bridge, TerminalManager manager) { + this.bridge = bridge; + this.manager = manager; + trustManagers = managers; + ks = keyStore; + } + + public void checkClientTrusted(X509Certificate[] chain, String type) throws CertificateException { + throw new SecurityException("checkClientTrusted unsupported"); + } + + + /** + * Checks the server certificate. If it isn't trusted by the trust manager + * passed to the constructor, then the user will be prompted to accept the + * certificate. + */ + public void checkServerTrusted(X509Certificate[] chain, String type) + throws CertificateException { + try { + for (int i = 0; i < trustManagers.length; i++) { + if (trustManagers[i] instanceof X509TrustManager) + ((X509TrustManager)trustManagers[i]).checkServerTrusted(chain, type); + } + + return; + } + catch (CertificateException ce) { + X509Certificate cert = chain[0]; + String certInfo = "Version: " + cert.getVersion() + "\n"; + certInfo = certInfo.concat("Serial Number: " + cert.getSerialNumber() + "\n"); + certInfo = certInfo.concat("Signature Algorithm: " + cert.getSigAlgName() + "\n"); + certInfo = certInfo.concat("Issuer: " + cert.getIssuerDN().getName() + "\n"); + certInfo = certInfo.concat("Valid From: " + cert.getNotBefore() + "\n"); + certInfo = certInfo.concat("Valid To: " + cert.getNotAfter() + "\n"); + certInfo = certInfo.concat("Subject DN: " + cert.getSubjectDN().getName() + "\n"); + certInfo = certInfo.concat("Public Key: " + cert.getPublicKey().getFormat() + "\n"); + bridge.outputLine(manager.res.getString(R.string.host_certificate, certInfo)); + Boolean result = bridge.promptHelper.requestBooleanPrompt(null, manager.res.getString(R.string.prompt_accept_certificate)); + + if ((result == null) || (!result.booleanValue())) { + throw new java.security.cert.CertificateException("Certificate Not Accepted"); + } + } + } + + public X509Certificate[] getAcceptedIssuers() { + ArrayList list = new ArrayList(10); + + for (int i = 0; i < trustManagers.length; i++) { + if (trustManagers[i] instanceof X509TrustManager) + list.addAll(Arrays.asList(((X509TrustManager)trustManagers[i]).getAcceptedIssuers())); + } + + X509Certificate[] acceptedIssuers = new X509Certificate[list.size()]; + acceptedIssuers = list.toArray(acceptedIssuers); + return acceptedIssuers; + } +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/framework/transport/SSLInterface.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/framework/transport/SSLInterface.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,46 @@ +package org.tn5250j.framework.transport; + +/* + * @(#)SSLInterface.java + * + * Copyright: Copyright (c) 2001 + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +import java.net.Socket; + +public interface SSLInterface { + + /** + * Initialize the components required to create a new client socket + * when createSSLSocket is called. + * @param sslType The ssl socket type (NONE, SSLv2, SSLv3, TLS) + * @param homeDirectory location of the .tn5250j subdirectory containing + * the keystore + * @see org.tn5250j.framework.transport.SSLConstants + */ + public abstract void init(String sslType, String homeDirectory); + + /** + * Create a new socket + * @param destination + * @param port + * @return new socket, or null if none could be created. + */ + public abstract Socket createSSLSocket(String destination, int port); + +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/java/org/tn5250j/framework/transport/SocketConnector.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/org/tn5250j/framework/transport/SocketConnector.java Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,98 @@ + +/** + * @(#)SocketConnector.java + * @author Stephen M. Kennedy + * + * Copyright: Copyright (c) 2001 + * + * This program 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 2, or (at your option) + * any later version. + * + * This program 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 this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + * + */ +package org.tn5250j.framework.transport; + +import java.net.Socket; + +import com.five_ten_sg.connectbot.service.TerminalBridge; +import com.five_ten_sg.connectbot.service.TerminalManager; + +import org.tn5250j.framework.transport.SSL.SSLImplementation; +import org.tn5250j.TN5250jConstants; +import android.util.Log; + + +public class SocketConnector { + private static final String TAG = "SocketConnector"; + + /** + * Creates a new instance that creates a plain socket by default. + */ + public SocketConnector() { + } + + /** + * Create a new client Socket to the given destination, port and sslType of + * encryption. + * @param destination + * @param port + * @return a new client socket, or null if + */ + public Socket createSocket(String destination, int port, String sslType, String homeDirectory, TerminalBridge bridge, TerminalManager manager) { + Socket socket = null; + Exception ex = null; + + if (sslType == null || sslType.trim().length() == 0 || + sslType.toUpperCase().equals(TN5250jConstants.SSL_TYPE_NONE)) { + Log.i(TAG, "Creating Plain Socket"); + + try { + // Use Socket Constructor!!! SocketFactory for jdk 1.4 + socket = new Socket(destination, port); + } + catch (Exception e) { + ex = e; + } + } + else { //SSL SOCKET + Log.i(TAG, "Creating SSL [" + sslType + "] Socket"); + SSLInterface sslIf = null; + + try { + sslIf = (SSLInterface) new SSLImplementation(bridge, manager); + } + catch (Exception e) { + ex = new Exception("Failed to create SSLInterface Instance. " + + "Message is [" + e.getMessage() + "]"); + } + + if (sslIf != null) { + sslIf.init(sslType, homeDirectory); + socket = sslIf.createSSLSocket(destination, port); + } + } + + if (ex != null) { + Log.e(TAG, "exception", ex); + } + + if (socket == null) { + Log.w(TAG, "No socket was created"); + } + + return socket; + } + + +} \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/jni/Android.mk --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/jni/Android.mk Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,1 @@ +include $(call all-subdir-makefiles) diff -r 208b31032318 -r d29cce60f393 app/src/main/jni/Application.mk --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/jni/Application.mk Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,2 @@ +# Build both ARMv5TE and x86-32 machine code. +APP_ABI := armeabi x86 diff -r 208b31032318 -r d29cce60f393 app/src/main/jni/Exec/Android.mk --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/jni/Exec/Android.mk Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,10 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := com_google_ase_Exec +LOCAL_CFLAGS := -Werror +LOCAL_SRC_FILES := com_google_ase_Exec.cpp +LOCAL_LDLIBS := -llog + +include $(BUILD_SHARED_LIBRARY) diff -r 208b31032318 -r d29cce60f393 app/src/main/jni/Exec/com_google_ase_Exec.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/jni/Exec/com_google_ase_Exec.cpp Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "com_google_ase_Exec.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "android/log.h" + +#define LOG_TAG "Exec" +#define LOG(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) + +void JNU_ThrowByName(JNIEnv * env, const char *name, const char *msg) { + jclass clazz = env->FindClass(name); + if (clazz != NULL) { + env->ThrowNew(clazz, msg); + } + env->DeleteLocalRef(clazz); +} + +char *JNU_GetStringNativeChars(JNIEnv * env, jstring jstr) { + if (jstr == NULL) { + return NULL; + } + jbyteArray bytes = 0; + jthrowable exc; + char *result = 0; + if (env->EnsureLocalCapacity(2) < 0) { + return 0; /* out of memory error */ + } + jclass Class_java_lang_String = env->FindClass("java/lang/String"); + jmethodID MID_String_getBytes = env->GetMethodID(Class_java_lang_String, "getBytes", "()[B"); + bytes = (jbyteArray) env->CallObjectMethod(jstr, MID_String_getBytes); + exc = env->ExceptionOccurred(); + if (!exc) { + jint len = env->GetArrayLength(bytes); + result = (char *) malloc(len + 1); + if (result == 0) { + JNU_ThrowByName(env, "java/lang/OutOfMemoryError", 0); + env->DeleteLocalRef(bytes); + return 0; + } + env->GetByteArrayRegion(bytes, 0, len, (jbyte *) result); + result[len] = 0; /* NULL-terminate */ + } else { + env->DeleteLocalRef(exc); + } + env->DeleteLocalRef(bytes); + return result; +} + +int jniGetFDFromFileDescriptor(JNIEnv * env, jobject fileDescriptor) { + jclass Class_java_io_FileDescriptor = env->FindClass("java/io/FileDescriptor"); + jfieldID descriptor = env->GetFieldID(Class_java_io_FileDescriptor, + "descriptor", "I"); + return env->GetIntField(fileDescriptor, descriptor); +} + +static int create_subprocess(const char *cmd, const char *arg0, const char *arg1, int *pProcessId) { + char *devname; + int ptm; + pid_t pid; + + ptm = open("/dev/ptmx", O_RDWR); // | O_NOCTTY); + if (ptm < 0) { + LOG("[ cannot open /dev/ptmx - %s ]\n", strerror(errno)); + return -1; + } + fcntl(ptm, F_SETFD, FD_CLOEXEC); + + if (grantpt(ptm) || unlockpt(ptm) || ((devname = (char *) ptsname(ptm)) == 0)) { + LOG("[ trouble with /dev/ptmx - %s ]\n", strerror(errno)); + return -1; + } + + pid = fork(); + if (pid < 0) { + LOG("- fork failed: %s -\n", strerror(errno)); + return -1; + } + + if (pid == 0) { + int pts; + + setsid(); + + pts = open(devname, O_RDWR); + if (pts < 0) + exit(-1); + + dup2(pts, 0); + dup2(pts, 1); + dup2(pts, 2); + + close(ptm); + + execl(cmd, cmd, arg0, arg1, NULL); + exit(-1); + } else { + *pProcessId = (int) pid; + return ptm; + } +} + +JNIEXPORT jobject JNICALL Java_com_google_ase_Exec_createSubprocess(JNIEnv * env, jclass clazz, + jstring cmd, jstring arg0, + jstring arg1, + jintArray processIdArray) { + char *cmd_8 = JNU_GetStringNativeChars(env, cmd); + char *arg0_8 = JNU_GetStringNativeChars(env, arg0); + char *arg1_8 = JNU_GetStringNativeChars(env, arg1); + + int procId; + int ptm = create_subprocess(cmd_8, arg0_8, arg1_8, &procId); + + if (processIdArray) { + int procIdLen = env->GetArrayLength(processIdArray); + if (procIdLen > 0) { + jboolean isCopy; + int *pProcId = (int *) env->GetPrimitiveArrayCritical(processIdArray, &isCopy); + if (pProcId) { + *pProcId = procId; + env->ReleasePrimitiveArrayCritical(processIdArray, pProcId, 0); + } + } + } + + jclass Class_java_io_FileDescriptor = env->FindClass("java/io/FileDescriptor"); + jmethodID init = env->GetMethodID(Class_java_io_FileDescriptor, + "", "()V"); + jobject result = env->NewObject(Class_java_io_FileDescriptor, init); + + if (!result) { + LOG("Couldn't create a FileDescriptor."); + } else { + jfieldID descriptor = env->GetFieldID(Class_java_io_FileDescriptor, + "descriptor", "I"); + env->SetIntField(result, descriptor, ptm); + } + + return result; +} + +JNIEXPORT void Java_com_google_ase_Exec_setPtyWindowSize(JNIEnv * env, jclass clazz, + jobject fileDescriptor, jint row, jint col, + jint xpixel, jint ypixel) { + int fd; + struct winsize sz; + + fd = jniGetFDFromFileDescriptor(env, fileDescriptor); + + if (env->ExceptionOccurred() != NULL) { + return; + } + + sz.ws_row = row; + sz.ws_col = col; + sz.ws_xpixel = xpixel; + sz.ws_ypixel = ypixel; + + ioctl(fd, TIOCSWINSZ, &sz); +} + +JNIEXPORT jint Java_com_google_ase_Exec_waitFor(JNIEnv * env, jclass clazz, jint procId) { + int status; + waitpid(procId, &status, 0); + int result = 0; + if (WIFEXITED(status)) { + result = WEXITSTATUS(status); + } + return result; +} diff -r 208b31032318 -r d29cce60f393 app/src/main/jni/Exec/com_google_ase_Exec.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/jni/Exec/com_google_ase_Exec.h Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,43 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_google_ase_Exec */ + +#ifndef _Included_com_google_ase_Exec +#define _Included_com_google_ase_Exec +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_google_ase_Exec + * Method: createSubprocess + * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[I)Ljava/io/FileDescriptor; + */ + JNIEXPORT jobject JNICALL Java_com_google_ase_Exec_createSubprocess + (JNIEnv *, jclass, jstring, jstring, jstring, jintArray); + +/* + * Class: com_google_ase_Exec + * Method: setPtyWindowSize + * Signature: (Ljava/io/FileDescriptor;IIII)V + */ + JNIEXPORT void JNICALL Java_com_google_ase_Exec_setPtyWindowSize + (JNIEnv *, jclass, jobject, jint, jint, jint, jint); + +/* + * Class: com_google_ase_Exec + * Method: waitFor + * Signature: (I)I + */ + JNIEXPORT jint JNICALL Java_com_google_ase_Exec_waitFor(JNIEnv *, jclass, jint); + +/* + * Class: com_google_ase_Exec + * Method: register + * Signature: ()I + */ + JNIEXPORT jint JNICALL Java_com_google_ase_Exec_register(JNIEnv *, jclass); + +#ifdef __cplusplus +} +#endif +#endif diff -r 208b31032318 -r d29cce60f393 app/src/main/res/anim/fade_out_delayed.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/anim/fade_out_delayed.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,27 @@ + + + + diff -r 208b31032318 -r d29cce60f393 app/src/main/res/anim/fade_stay_hidden.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/anim/fade_stay_hidden.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,26 @@ + + + + diff -r 208b31032318 -r d29cce60f393 app/src/main/res/anim/keyboard_fade_in.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/anim/keyboard_fade_in.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,25 @@ + + + + diff -r 208b31032318 -r d29cce60f393 app/src/main/res/anim/keyboard_fade_out.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/anim/keyboard_fade_out.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,25 @@ + + + + diff -r 208b31032318 -r d29cce60f393 app/src/main/res/anim/slide_left_in.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/anim/slide_left_in.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,24 @@ + + + + + + + diff -r 208b31032318 -r d29cce60f393 app/src/main/res/anim/slide_left_out.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/anim/slide_left_out.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,24 @@ + + + + + + + diff -r 208b31032318 -r d29cce60f393 app/src/main/res/anim/slide_right_in.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/anim/slide_right_in.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,24 @@ + + + + + + + diff -r 208b31032318 -r d29cce60f393 app/src/main/res/anim/slide_right_out.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/anim/slide_right_out.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,24 @@ + + + + + + + diff -r 208b31032318 -r d29cce60f393 app/src/main/res/color/blue.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/color/blue.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,26 @@ + + + + + + + + + diff -r 208b31032318 -r d29cce60f393 app/src/main/res/color/green.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/color/green.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,26 @@ + + + + + + + + + diff -r 208b31032318 -r d29cce60f393 app/src/main/res/color/red.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/color/red.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,26 @@ + + + + + + + + + diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-hdpi/btn_close_normal.png Binary file app/src/main/res/drawable-hdpi/btn_close_normal.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-hdpi/btn_close_pressed.png Binary file app/src/main/res/drawable-hdpi/btn_close_pressed.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-hdpi/btn_close_selected.png Binary file app/src/main/res/drawable-hdpi/btn_close_selected.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-hdpi/icon.png Binary file app/src/main/res/drawable-hdpi/icon.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-hdpi/keyboard_popup_panel_trans_background.9.png Binary file app/src/main/res/drawable-hdpi/keyboard_popup_panel_trans_background.9.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-hdpi/notification_icon.png Binary file app/src/main/res/drawable-hdpi/notification_icon.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-ldpi/btn_close_normal.png Binary file app/src/main/res/drawable-ldpi/btn_close_normal.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-ldpi/btn_close_pressed.png Binary file app/src/main/res/drawable-ldpi/btn_close_pressed.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-ldpi/btn_close_selected.png Binary file app/src/main/res/drawable-ldpi/btn_close_selected.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-ldpi/icon.png Binary file app/src/main/res/drawable-ldpi/icon.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-ldpi/keyboard_popup_panel_trans_background.9.png Binary file app/src/main/res/drawable-ldpi/keyboard_popup_panel_trans_background.9.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-ldpi/notification_icon.png Binary file app/src/main/res/drawable-ldpi/notification_icon.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-mdpi/btn_close_normal.png Binary file app/src/main/res/drawable-mdpi/btn_close_normal.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-mdpi/btn_close_pressed.png Binary file app/src/main/res/drawable-mdpi/btn_close_pressed.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-mdpi/btn_close_selected.png Binary file app/src/main/res/drawable-mdpi/btn_close_selected.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-mdpi/button_ctrl.png Binary file app/src/main/res/drawable-mdpi/button_ctrl.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-mdpi/button_esc.png Binary file app/src/main/res/drawable-mdpi/button_esc.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-mdpi/button_input.png Binary file app/src/main/res/drawable-mdpi/button_input.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-mdpi/button_keyboard.png Binary file app/src/main/res/drawable-mdpi/button_keyboard.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-mdpi/button_sym.png Binary file app/src/main/res/drawable-mdpi/button_sym.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-mdpi/file.png Binary file app/src/main/res/drawable-mdpi/file.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-mdpi/folder.png Binary file app/src/main/res/drawable-mdpi/folder.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-mdpi/highlight_disabled_pressed.9.png Binary file app/src/main/res/drawable-mdpi/highlight_disabled_pressed.9.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-mdpi/ic_btn_back.png Binary file app/src/main/res/drawable-mdpi/ic_btn_back.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-mdpi/ic_btn_next.png Binary file app/src/main/res/drawable-mdpi/ic_btn_next.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-mdpi/icon.png Binary file app/src/main/res/drawable-mdpi/icon.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-mdpi/keyboard_popup_panel_trans_background.9.png Binary file app/src/main/res/drawable-mdpi/keyboard_popup_panel_trans_background.9.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-mdpi/notification_icon.png Binary file app/src/main/res/drawable-mdpi/notification_icon.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-mdpi/pubkey_locked.png Binary file app/src/main/res/drawable-mdpi/pubkey_locked.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-mdpi/pubkey_unlocked.png Binary file app/src/main/res/drawable-mdpi/pubkey_unlocked.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-xhdpi/btn_close_normal.png Binary file app/src/main/res/drawable-xhdpi/btn_close_normal.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-xhdpi/btn_close_pressed.png Binary file app/src/main/res/drawable-xhdpi/btn_close_pressed.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-xhdpi/btn_close_selected.png Binary file app/src/main/res/drawable-xhdpi/btn_close_selected.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-xhdpi/icon.png Binary file app/src/main/res/drawable-xhdpi/icon.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-xhdpi/keyboard_popup_panel_trans_background.9.png Binary file app/src/main/res/drawable-xhdpi/keyboard_popup_panel_trans_background.9.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-xhdpi/notification_icon.png Binary file app/src/main/res/drawable-xhdpi/notification_icon.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-xxhdpi/icon.png Binary file app/src/main/res/drawable-xxhdpi/icon.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-xxhdpi/notification_icon.png Binary file app/src/main/res/drawable-xxhdpi/notification_icon.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-xxxhdpi/icon.png Binary file app/src/main/res/drawable-xxxhdpi/icon.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable-xxxhdpi/notification_icon.png Binary file app/src/main/res/drawable-xxxhdpi/notification_icon.png has changed diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable/btn_close.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/drawable/btn_close.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,27 @@ + + + + + + + + + + + diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable/connected.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/drawable/connected.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,34 @@ + + + + + + + + + + + diff -r 208b31032318 -r d29cce60f393 app/src/main/res/drawable/pubkey.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/drawable/pubkey.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/res/layout-land/item_host.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/layout-land/item_host.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/res/layout-port/item_host.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/layout-port/item_host.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/res/layout/act_colors.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/layout/act_colors.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + diff -r 208b31032318 -r d29cce60f393 app/src/main/res/layout/act_console.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/layout/act_console.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 208b31032318 -r d29cce60f393 app/src/main/res/layout/file_dialog_row.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/layout/file_dialog_row.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 app/src/main/res/layout/item_portforward.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/layout/item_portforward.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,48 @@ + + + + + + + + + + diff -r 208b31032318 -r d29cce60f393 app/src/main/res/layout/item_pubkey.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/layout/item_pubkey.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,54 @@ + + + + + + + + + + + + diff -r 208b31032318 -r d29cce60f393 app/src/main/res/layout/item_terminal.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/layout/item_terminal.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,37 @@ + + + + + + + + diff -r 208b31032318 -r d29cce60f393 app/src/main/res/layout/string_picker.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/layout/string_picker.xml Thu Dec 03 11:23:55 2015 -0800 @@ -0,0 +1,45 @@ + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - diff -r 208b31032318 -r d29cce60f393 res/layout/file_dialog_row.xml --- a/res/layout/file_dialog_row.xml Fri Jun 19 13:41:57 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ - - - - - - - - \ No newline at end of file diff -r 208b31032318 -r d29cce60f393 res/layout/item_portforward.xml --- a/res/layout/item_portforward.xml Fri Jun 19 13:41:57 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,48 +0,0 @@ - - - - - - - - - - diff -r 208b31032318 -r d29cce60f393 res/layout/item_pubkey.xml --- a/res/layout/item_pubkey.xml Fri Jun 19 13:41:57 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ - - - - - - - - - - - - diff -r 208b31032318 -r d29cce60f393 res/layout/item_terminal.xml --- a/res/layout/item_terminal.xml Fri Jun 19 13:41:57 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ - - - - - - - - diff -r 208b31032318 -r d29cce60f393 res/layout/string_picker.xml --- a/res/layout/string_picker.xml Fri Jun 19 13:41:57 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,45 +0,0 @@ - - - - - - - - -