├── .classpath ├── .gitignore ├── .project ├── .settings ├── org.eclipse.jdt.core.prefs └── org.eclipse.jdt.ui.prefs ├── AndroidManifest.xml ├── README.md ├── libs ├── RootTools-1.7.jar └── android-support-v4.jar ├── lint.xml ├── proguard-project.txt ├── project.properties ├── res ├── drawable-hdpi │ └── ic_launcher.png ├── drawable-ldpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png ├── drawable-xhdpi │ └── ic_launcher.png ├── drawable │ ├── startbtn.png │ └── stopbtn.png ├── layout │ ├── log.xml │ ├── main.xml │ ├── manual.xml │ └── status.xml ├── menu │ └── main.xml ├── values │ ├── resources.xml │ └── strings.xml └── xml │ └── preferences.xml ├── server ├── config53.js └── element53.js └── src ├── android └── util │ └── Base64.java └── biz └── nijhof └── e53 ├── Base32.java ├── Element53Activity.java ├── Element53DNS.java ├── Element53PagerAdapter.java ├── Element53Service.java ├── Element53Signal.java └── Preferences.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | 15 | # Local configuration file (sdk path, etc) 16 | local.properties 17 | 18 | # Eclipse project files 19 | #.classpath 20 | #.project 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Intellij project files 26 | *.iml 27 | *.ipr 28 | *.iws 29 | .idea/ 30 | 31 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | Element53 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.formatter.align_type_members_on_columns=false 3 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 4 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 5 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 6 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 7 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 8 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 9 | org.eclipse.jdt.core.formatter.alignment_for_assignment=0 10 | org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 11 | org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 12 | org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 13 | org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 14 | org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 15 | org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 16 | org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 17 | org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 18 | org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 19 | org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 20 | org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 21 | org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 22 | org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 23 | org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 24 | org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 25 | org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 26 | org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 27 | org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 28 | org.eclipse.jdt.core.formatter.blank_lines_after_package=1 29 | org.eclipse.jdt.core.formatter.blank_lines_before_field=0 30 | org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 31 | org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 32 | org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 33 | org.eclipse.jdt.core.formatter.blank_lines_before_method=1 34 | org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 35 | org.eclipse.jdt.core.formatter.blank_lines_before_package=0 36 | org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 37 | org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 38 | org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line 39 | org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line 40 | org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line 41 | org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line 42 | org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line 43 | org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line 44 | org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line 45 | org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line 46 | org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line 47 | org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line 48 | org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line 49 | org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false 50 | org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false 51 | org.eclipse.jdt.core.formatter.comment.format_block_comments=true 52 | org.eclipse.jdt.core.formatter.comment.format_header=false 53 | org.eclipse.jdt.core.formatter.comment.format_html=true 54 | org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true 55 | org.eclipse.jdt.core.formatter.comment.format_line_comments=true 56 | org.eclipse.jdt.core.formatter.comment.format_source_code=true 57 | org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true 58 | org.eclipse.jdt.core.formatter.comment.indent_root_tags=true 59 | org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert 60 | org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert 61 | org.eclipse.jdt.core.formatter.comment.line_length=80 62 | org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true 63 | org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true 64 | org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false 65 | org.eclipse.jdt.core.formatter.compact_else_if=true 66 | org.eclipse.jdt.core.formatter.continuation_indentation=2 67 | org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 68 | org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off 69 | org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on 70 | org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false 71 | org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true 72 | org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true 73 | org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true 74 | org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true 75 | org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true 76 | org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true 77 | org.eclipse.jdt.core.formatter.indent_empty_lines=false 78 | org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true 79 | org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true 80 | org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true 81 | org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false 82 | org.eclipse.jdt.core.formatter.indentation.size=4 83 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert 84 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert 85 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert 86 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert 87 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert 88 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert 89 | org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert 90 | org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert 91 | org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert 92 | org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert 93 | org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert 94 | org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert 95 | org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert 96 | org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert 97 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert 98 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert 99 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert 100 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert 101 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert 102 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert 103 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert 104 | org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert 105 | org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert 106 | org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert 107 | org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert 108 | org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert 109 | org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert 110 | org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert 111 | org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert 112 | org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert 113 | org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert 114 | org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert 115 | org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert 116 | org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert 117 | org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert 118 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert 119 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert 120 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert 121 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert 122 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert 123 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert 124 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert 125 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert 126 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert 127 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert 128 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert 129 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert 130 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert 131 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert 132 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert 133 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert 134 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert 135 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert 136 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert 137 | org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert 138 | org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert 139 | org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert 140 | org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert 141 | org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert 142 | org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert 143 | org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert 144 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert 145 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert 146 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert 147 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert 148 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert 149 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert 150 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert 151 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert 152 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert 153 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert 154 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert 155 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert 156 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert 157 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert 158 | org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert 159 | org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert 160 | org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert 161 | org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert 162 | org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert 163 | org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert 164 | org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert 165 | org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert 166 | org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert 167 | org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert 168 | org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert 169 | org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert 170 | org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert 171 | org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert 172 | org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert 173 | org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert 174 | org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert 175 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert 176 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert 177 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert 178 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert 179 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert 180 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert 181 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert 182 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert 183 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert 184 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert 185 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert 186 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert 187 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert 188 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert 189 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert 190 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert 191 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert 192 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert 193 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert 194 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert 195 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert 196 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert 197 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert 198 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert 199 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert 200 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert 201 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert 202 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert 203 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert 204 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert 205 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert 206 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert 207 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert 208 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert 209 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert 210 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert 211 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert 212 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert 213 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert 214 | org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert 215 | org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert 216 | org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert 217 | org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert 218 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert 219 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert 220 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert 221 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert 222 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert 223 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert 224 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert 225 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert 226 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert 227 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert 228 | org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert 229 | org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert 230 | org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert 231 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert 232 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert 233 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert 234 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert 235 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert 236 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert 237 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert 238 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert 239 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert 240 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert 241 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert 242 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert 243 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert 244 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert 245 | org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert 246 | org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert 247 | org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert 248 | org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert 249 | org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert 250 | org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert 251 | org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert 252 | org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert 253 | org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert 254 | org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert 255 | org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert 256 | org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert 257 | org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert 258 | org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert 259 | org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert 260 | org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert 261 | org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert 262 | org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert 263 | org.eclipse.jdt.core.formatter.join_lines_in_comments=true 264 | org.eclipse.jdt.core.formatter.join_wrapped_lines=true 265 | org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false 266 | org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false 267 | org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false 268 | org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false 269 | org.eclipse.jdt.core.formatter.lineSplit=160 270 | org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false 271 | org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false 272 | org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 273 | org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 274 | org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true 275 | org.eclipse.jdt.core.formatter.tabulation.char=tab 276 | org.eclipse.jdt.core.formatter.tabulation.size=4 277 | org.eclipse.jdt.core.formatter.use_on_off_tags=true 278 | org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false 279 | org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true 280 | org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true 281 | org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true 282 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.ui.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | formatter_profile=_Long lines 3 | formatter_settings_version=12 4 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Element53 2 | ========= 3 | 4 | Client is in the [Play Store](https://play.google.com/store/apps/details?id=biz.nijhof.e53) 5 | 6 | Some background info is [here](http://blog.bokhorst.biz/7681/computers-and-internet/android-dns-tunnel-element53/) 7 | 8 | License 9 | ------- 10 | 11 | GNU General Public License version 3 12 | 13 | Copyright (c) 2013 [Marcel Bokhorst](http://blog.bokhorst.biz/about/) 14 | 15 | This program is free software; you can redistribute it and/or modify 16 | it under the terms of the GNU General Public License as published by 17 | the Free Software Foundation; either version 3 of the License, or 18 | (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | You should have received a copy of the GNU General Public License 26 | along with this program; if not, write to the Free Software 27 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 28 | -------------------------------------------------------------------------------- /libs/RootTools-1.7.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M66B/element53/0ed8722548e66cc3bf70a66f6d36de3c56202781/libs/RootTools-1.7.jar -------------------------------------------------------------------------------- /libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M66B/element53/0ed8722548e66cc3bf70a66f6d36de3c56202781/libs/android-support-v4.jar -------------------------------------------------------------------------------- /lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-7 15 | android.library=false 16 | -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M66B/element53/0ed8722548e66cc3bf70a66f6d36de3c56202781/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M66B/element53/0ed8722548e66cc3bf70a66f6d36de3c56202781/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M66B/element53/0ed8722548e66cc3bf70a66f6d36de3c56202781/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M66B/element53/0ed8722548e66cc3bf70a66f6d36de3c56202781/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable/startbtn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M66B/element53/0ed8722548e66cc3bf70a66f6d36de3c56202781/res/drawable/startbtn.png -------------------------------------------------------------------------------- /res/drawable/stopbtn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M66B/element53/0ed8722548e66cc3bf70a66f6d36de3c56202781/res/drawable/stopbtn.png -------------------------------------------------------------------------------- /res/layout/log.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | 22 | 23 | -------------------------------------------------------------------------------- /res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | 20 | 21 | -------------------------------------------------------------------------------- /res/layout/manual.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | 22 | 23 | -------------------------------------------------------------------------------- /res/layout/status.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 21 | 22 | 23 | 24 | 28 | 29 | 35 | 36 | 37 | 42 | 43 | 44 | 45 | 49 | 50 | 56 | 57 | 58 | 63 | 64 | 65 | 66 | 70 | 71 | 77 | 78 | 79 | 84 | 85 | 86 | 87 | 91 | 92 | 98 | 99 | 100 | 105 | 106 | 107 | 108 | 112 | 113 | 119 | 120 | 121 | 126 | 127 | 128 | 129 | 133 | 134 | 140 | 141 | 142 | 147 | 148 | 149 | 150 | 154 | 155 | 161 | 162 | 163 | 168 | 169 | 170 | 171 | 175 | 176 | 182 | 183 | 184 | 189 | 190 | 191 | 192 | 196 | 197 | 203 | 204 | 205 | 210 | 211 | 212 | 213 | 217 | 218 | 224 | 225 | 226 | 231 | 232 | 233 | 234 | 238 | 239 | 245 | 246 | 247 | 252 | 253 | 254 | 255 | 259 | 260 | 266 | 267 | 268 | 273 | 274 | 275 | 276 | 280 | 281 | 287 | 288 | 289 | 294 | 295 | 296 | 297 | 302 | 303 | 312 | 313 | 314 | -------------------------------------------------------------------------------- /res/menu/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /res/values/resources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Wifi 6 | Mobile 7 | 8 | 9 | Random 10 | CNAME 11 | NULL 12 | TEXT 13 | 14 | 15 | 0 16 | 5 17 | 10 18 | 16 19 | 20 | 21 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Element53 5 | Preferences 6 | Domain: 7 | Network: 8 | DNS server: 9 | Gateway: 10 | Type/size: 11 | Seq/requests: 12 | Requests: 13 | Idle wait: 14 | Latency: 15 | Errors/resets: 16 | Sent: 17 | Received: 18 | Channels: 19 | Queued: 20 | n/a 21 | Start/stop 22 | Clear 23 | Logging 24 | Main 25 | Manual 26 | Settings 27 | About 28 | Version %s (%d)\nCopyright © 2012 M. Bokhorst 29 | Quit? 30 | Running 31 | up: %.2f kBps - down: %.2f kBps 32 | Element53 is a ready to use DNS-tunnel. It can be used for penetration testing (other uses are not supported). 33 | 34 | Element53 creates a TCP-tunnel to a proxy server. The client side of the tunnel is localhost:3128. You can use ProxyDroid to connect to the proxy. 35 | 36 | You can try the lite version for 15 minutes. 37 | 38 | -------------------------------------------------------------------------------- /res/xml/preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 16 | 17 | 21 | 22 | 26 | 27 | 31 | 32 | 36 | 40 | 41 | 45 | 49 | 50 | 56 | 57 | 61 | 65 | 69 | 73 | 77 | 81 | 82 | 86 | 90 | 94 | 95 | 99 | 100 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /server/config53.js: -------------------------------------------------------------------------------- 1 | exports.config = 2 | { 3 | domain: 'example.com', 4 | port: 53, 5 | server_ip: 'localhost', 6 | server_port: 1194, 7 | max_client_inactive_ms: 5 * 60 * 1000, 8 | status_interval_ms: 3000 9 | } 10 | -------------------------------------------------------------------------------- /server/element53.js: -------------------------------------------------------------------------------- 1 | var modpath = '/usr/local/lib/node_modules/'; 2 | 3 | //require(modpath + 'nodetime').profile(); 4 | 5 | var sys = require('util'), 6 | Buffer = require('buffer').Buffer, 7 | dgram = require('dgram'), 8 | net = require('net'), 9 | base32 = require(modpath + 'base32'), 10 | crc = require(modpath + 'crc'), 11 | //mongo = require(modpath + 'mongodb'), 12 | //microtime = require(modpath + 'microtime'); 13 | config = require('./config53.js').config; 14 | 15 | // INSTALLING 16 | 17 | // MongoDB 18 | // http://docs.mongodb.org/manual/tutorial/install-mongodb-on-debian-or-ubuntu-linux/ 19 | // (SysV style init process) 20 | 21 | // node.js 22 | // https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager 23 | // (Debian Squeeze) 24 | 25 | // Packages 26 | // npm install base32 -g 27 | // npm install crc -g 28 | // npm install mongodb -g 29 | // (npm install microtime -g) 30 | 31 | // Stop/start 32 | // killall node 33 | // (assuming one node process) 34 | // nano /etc/rc.local 35 | // /usr/local/bin/node /root/element53.js >>/root/element53.log 2>&1 & 36 | // ssh -f -N -D 0.0.0.0:1080 localhost -p 22022 37 | 38 | 39 | // TO DO 40 | // optimize find recursive server 41 | // group small messages (client and server) 42 | // probe random record type with cname 43 | // move updateNotification to Element53Signal 44 | // get DNS/gateway mobile/WiMAX 45 | 46 | // Data structures 47 | var _id = {}; // [unique_id] -> client 48 | var _clientid = {}; // [client] -> unique_id 49 | var _seq = {}; // [client] 50 | var _socket = {}; // [client][channel] 51 | var _queue = {} // [client] 52 | var _msgsize = {}; // [client] 53 | var _lastmsg = {}; // [client] 54 | var _clientclose = {}; // [client][channel] 55 | var _lastactivity = {}; // [client] 56 | var _laststatus = {}; // [client] 57 | var _nocrc = {}; // [client] 58 | var _debug = {}; // [client] 59 | 60 | // Constants 61 | 62 | var MONGO_LOG = 0; 63 | 64 | var PROTOCOL_VERSION = 1; 65 | 66 | var OOB_RESET = 1; 67 | var OOB_QUIT = 2; 68 | var OOB_PROBE_CLIENT = 3; 69 | var OOB_PROBE_SERVER = 4; 70 | var OOB_SET_SIZE = 5; 71 | 72 | var CONTROL_DATA = 0; 73 | var CONTROL_CLOSE = 1; 74 | var CONTROL_OPEN = 2; 75 | var CONTROL_STATUS = 3; 76 | 77 | var RECORD_RANDOM = 0; 78 | var RECORD_CNAME = 5; 79 | var RECORD_NULL = 10; 80 | var RECORD_TEXT = 16; 81 | 82 | Object.size = function(obj) { 83 | var size = 0, key; 84 | for (key in obj) 85 | if (obj.hasOwnProperty(key)) 86 | size++; 87 | return size; 88 | }; 89 | 90 | var eLog = function(msg) { 91 | console.log(new Date() + ' ' + msg); 92 | }; 93 | 94 | var sliceBits = function(b, off, len) { 95 | var s = 7 - (off + len - 1); 96 | b = b >>> s; 97 | return b & ~(0xff << len); 98 | }; 99 | 100 | var decodeRequest = function(buffer) { 101 | var query = {}; 102 | query.header = {}; 103 | query.question = {}; 104 | 105 | var index = 0; 106 | query.header.id = buffer[index++] * 256 + buffer[index++]; 107 | 108 | query.header.qr = sliceBits(buffer[index], 0, 1); 109 | query.header.opcode = sliceBits(buffer[index], 1, 4); 110 | query.header.aa = sliceBits(buffer[index], 5, 1); 111 | query.header.tc = sliceBits(buffer[index], 6, 1); 112 | query.header.rd = sliceBits(buffer[index], 7, 1); 113 | index++; 114 | 115 | query.header.ra = sliceBits(buffer[index], 0, 1); 116 | query.header.z = sliceBits(buffer[index], 1, 3); 117 | query.header.rcode = sliceBits(buffer[index], 4, 4); 118 | index++; 119 | 120 | query.header.qdcount = buffer[index++] * 256 + buffer[index++]; 121 | query.header.ancount = buffer[index++] * 256 + buffer[index++]; 122 | query.header.nscount = buffer[index++] * 256 + buffer[index++]; 123 | query.header.arcount = buffer[index++] * 256 + buffer[index++]; 124 | 125 | // One question 126 | var len = buffer[index++]; 127 | if ((len & 0xC0) == 0) { 128 | query.question.qname = decodeName(buffer, --index); 129 | index += query.question.qname.length + 1; 130 | } else { 131 | // Compression 132 | eLog('Compression receive'); 133 | var poffset = (len & 0x3F) * 256 + buffer[index++]; 134 | query.question.qname = decodeName(buffer, poffset); 135 | } 136 | query.question.qtype = buffer[index++] * 256 + buffer[index++]; 137 | query.question.qclass = buffer[index++] * 256 + buffer[index++]; 138 | 139 | // Sanity checks 140 | if (query.header.qr != 0) 141 | throw new Error('Expected query'); 142 | if (query.header.opcode != 0) 143 | throw new Error('Expected opcode query'); 144 | if (query.header.tc != 0) 145 | throw new Error('Packet truncated'); 146 | if (query.header.rcode != 0) 147 | throw new Error('rcode=' + query.header.rcode); 148 | if (query.question.qtype != RECORD_CNAME && query.question.qtype != RECORD_NULL && query.question.qtype != RECORD_TEXT) 149 | throw new Error('Invalid qtype=' + query.question.qtype); 150 | if (query.question.qclass != 1) // INternet 151 | throw new Error('Invalid qclass=' + query.question.qclass); 152 | 153 | return query; 154 | }; 155 | 156 | var decodeName = function(buffer, index) { 157 | var name = ''; 158 | var len = buffer[index++]; 159 | while (len > 0) { 160 | name += buffer.toString('binary', index, index + len) + '.'; 161 | index += len; 162 | len = buffer[index++]; 163 | } 164 | return name; 165 | }; 166 | 167 | var encodeResponse = function(query, rcode, record) { 168 | var response = {}; 169 | response.header = {}; 170 | 171 | response.header.id = query.header.id; 172 | 173 | response.header.qr = 1; 174 | response.header.opcode = 0; 175 | response.header.aa = 1; 176 | response.header.tc = 0; 177 | response.header.rd = query.header.rd; 178 | 179 | response.header.ra = 1; 180 | response.header.z = 0; 181 | response.header.rcode = rcode; 182 | 183 | response.header.qdcount = 1; 184 | response.header.ancount = (record == null ? 0 : 1); 185 | response.header.nscount = 0; 186 | response.header.arcount = 0; 187 | 188 | response.question = {}; 189 | response.question.qname = query.question.qname; 190 | response.question.qtype = query.question.qtype; 191 | response.question.qclass = query.question.qclass; 192 | 193 | response.record = record; 194 | return response; 195 | }; 196 | 197 | var buildResponseBuffer = function(response) { 198 | var index = 0; 199 | var buf = new Buffer(512); 200 | 201 | numToBuffer(buf, index, response.header.id, 2); index += 2; 202 | 203 | buf[index++] = response.header.qr << 7 | response.header.opcode << 3 | response.header.aa << 2 | response.header.tc << 1 | response.header.rd; 204 | buf[index++] = response.header.ra << 7 | response.header.z << 4 | response.header.rcode; 205 | 206 | numToBuffer(buf, index, response.header.qdcount, 2); index += 2; 207 | numToBuffer(buf, index, response.header.ancount, 2); index += 2; 208 | numToBuffer(buf, index, response.header.nscount, 2); index += 2; 209 | numToBuffer(buf, index, response.header.arcount, 2); index += 2; 210 | 211 | var qname = encodeName(response.question.qname); 212 | qname.copy(buf, index, 0, qname.length); index += qname.length; 213 | numToBuffer(buf, index, response.question.qtype, 2); index += 2; 214 | numToBuffer(buf, index, response.question.qclass, 2); index += 2; 215 | 216 | if (response.record != null) { 217 | var rname = encodeName(response.record.qname); 218 | if (response.question.qname == response.record.qname) { 219 | numToBuffer(buf, index, 0xC0 * 256 + 12, 2); 220 | index += 2; 221 | } 222 | else { 223 | eLog('No compression!'); 224 | rname.copy(buf, index, 0, rname.length); 225 | index += rname.length; 226 | } 227 | numToBuffer(buf, index, response.record.qtype, 2); index += 2; 228 | numToBuffer(buf, index, response.record.qclass, 2); index += 2; 229 | numToBuffer(buf, index, response.record.ttl, 4); index += 4; 230 | numToBuffer(buf, index, response.record.rdata.length, 2); index += 2; 231 | 232 | var rlen = index + response.record.rdata.length; 233 | if (rlen > 512) 234 | throw new Error('Response too long length=' + rlen); 235 | 236 | response.record.rdata.copy(buf, index, 0, response.record.rdata.length); index += response.record.rdata.length; 237 | } 238 | 239 | var buffer = new Buffer(index); 240 | buf.copy(buffer, 0, 0, index); 241 | 242 | return buffer; 243 | }; 244 | 245 | var numToBuffer = function(buf, offset, num, len, debug) { 246 | // Sanity check 247 | if (typeof num != 'number') 248 | throw new Error('Num must be a number'); 249 | 250 | for (var i = offset; i < offset + len; i++) { 251 | var shift = 8 * ((len - 1) - (i - offset)); 252 | var insert = (num >> shift) & 255; 253 | buf[i] = insert; 254 | } 255 | return buf; 256 | }; 257 | 258 | var encodeName = function(name) { 259 | var qname = new Buffer(name.length + 1); 260 | var index = 0; 261 | var parts = name.split('.'); 262 | for (var i = 0; i < parts.length; i++) { 263 | qname[index++] = parts[i].length; 264 | for(var j = 0; j < parts[i].length; j++) 265 | qname[index++] = parts[i].charCodeAt(j); 266 | } 267 | qname[index] = 0; 268 | return qname; 269 | }; 270 | 271 | var getMsg = function(client) { 272 | if (new Date().getTime() - _laststatus[client].getTime() > config.status_interval_ms) { 273 | _laststatus[client] = new Date(); 274 | var status = new Buffer(4); 275 | var channels = Object.size(_socket[client]); 276 | status[0] = channels >> 8; 277 | status[1] = channels & 0xFF; 278 | status[2] = _queue[client].length >> 8; 279 | status[3] = _queue[client].length & 0xFF; 280 | return { channel: 0, control: CONTROL_STATUS, data: status }; 281 | } 282 | else if (client in _queue) { 283 | var msg = _queue[client].shift(); 284 | while (msg != undefined && _clientclose[client][msg.channel]) { 285 | eLog('Discarding message for closed channel client=' + client + ' channel=' + msg.channel); 286 | var msg = _queue[client].shift(); 287 | } 288 | if (msg != undefined) 289 | return msg; 290 | } 291 | return { channel: 0, control: CONTROL_DATA, data: new Buffer(0) }; 292 | }; 293 | 294 | var crc8 = function(data) { 295 | return crc.crc8(data.toString('binary')); 296 | }; 297 | 298 | var decodeMessage = function(query) { 299 | // Strip domain name 300 | var index = query.question.qname.lastIndexOf(config.domain); 301 | if (index < 0) 302 | throw new Error('Invalid qname=' + query.question.qname); 303 | var payload = query.question.qname.substring(0, index).replace('.', ''); 304 | 305 | // Decode message 306 | var buf = base32.decode(payload); 307 | var message = new Buffer(buf, 'binary'); 308 | 309 | // Extract message 310 | var rclient = message[0]; 311 | var rseq = message[1]; 312 | // message[2] = nonce 313 | var rchan = message[3]; 314 | var rctl = message[4]; 315 | var rcrc8 = message[5] 316 | var rdata = new Buffer(message.length - 6); 317 | message.copy(rdata, 0, 6, message.length); 318 | if (rseq != 0 && !_nocrc[rclient] && rcrc8 != crc8(rdata)) 319 | throw new Error('CRC mismatch'); 320 | 321 | return { client: rclient, seq: rseq, channel: rchan, control: rctl, data: rdata }; 322 | }; 323 | 324 | var encodeMessage = function(rmsg) { 325 | // Build response 326 | var message = new Buffer(5 + _lastmsg[rmsg.client].data.length); 327 | message[0] = rmsg.client; 328 | message[1] = _seq[rmsg.client]; 329 | message[2] = _lastmsg[rmsg.client].channel; // possible different channel 330 | message[3] = _lastmsg[rmsg.client].control; 331 | message[4] = (_nocrc[rmsg.client] ? 0 : crc8(_lastmsg[rmsg.client].data)); 332 | _lastmsg[rmsg.client].data.copy(message, 5, 0, _lastmsg[rmsg.client].data.length); 333 | return message; 334 | }; 335 | 336 | var encodeOOB = function(rmsg, data) { 337 | var message = new Buffer(5 + data.length); 338 | message[0] = rmsg.client; 339 | message[1] = 0; // sequence OOB = 0 340 | message[2] = 0; // channel unused 341 | message[3] = rmsg.control; 342 | message[4] = (_nocrc[rmsg.client] ? 0 : crc8(data)); 343 | data.copy(message, 5, 0, data.length); 344 | return message; 345 | } 346 | 347 | var handleReset = function(rmsg) { 348 | // Get client ID for unique ID 349 | var clientid = rmsg.data.toString('ascii', 16, 16 + rmsg.data[15]); 350 | if (!(clientid in _id)) 351 | _id[clientid] = getFreeClient(); 352 | rmsg.client = _id[clientid]; 353 | _clientid[rmsg.client] = clientid; 354 | 355 | // Reset sequence 356 | _seq[rmsg.client] = 0; 357 | 358 | // Get client data 359 | var protocol = rmsg.data[0]; 360 | var servermsgsize = rmsg.data[1] * 256 + rmsg.data[2]; 361 | var clientmsgsize = rmsg.data[3] * 256 + rmsg.data[4]; 362 | var recordtype = rmsg.data[5]; 363 | var nocrc = (rmsg.data[6] == 0 ? false : true); 364 | var debug = (rmsg.data[7] == 0 ? false : true); 365 | var istrial = (rmsg.data[8] == 0 ? false : true); 366 | // bytes 9..15 are reserved for future use 367 | 368 | // Process client data 369 | _msgsize[rmsg.client] = servermsgsize; 370 | _nocrc[rmsg.client] = nocrc; 371 | _debug[rmsg.client] = debug; 372 | 373 | eLog('Reset id=' + clientid + ' client=' + rmsg.client + ' protocol=' + protocol + ' trial=' + istrial + ' clients=' + Object.size(_seq)); 374 | 375 | // Set empty last message 376 | _lastmsg[rmsg.client] = { client: rmsg.client, seq: 0, channel: 0, control: CONTROL_DATA, data: new Buffer(0) }; 377 | 378 | // Close sockets 379 | if (rmsg.client in _socket) 380 | for (var channel in _socket[rmsg.client]) { 381 | eLog('End socket client=' + rmsg.client + ' channel=' + channel); 382 | _clientclose[rmsg.client][channel] = true; 383 | _socket[rmsg.client][channel].end(); 384 | } 385 | _socket[rmsg.client] = {}; 386 | if (!(rmsg.client in _clientclose)) 387 | _clientclose[rmsg.client] = {}; 388 | _queue[rmsg.client] = new Array(); 389 | _lastactivity[rmsg.client] = new Date(); 390 | _laststatus[rmsg.client] = new Date(); 391 | 392 | // Register client 393 | if (MONGO_LOG) 394 | try { 395 | var server = new mongo.Server('localhost', 27017, { auto_reconnect: true }); 396 | var db = new mongo.Db('element53', server); 397 | db.open(function(err, db) { 398 | if (err) 399 | eLog('Mongo DB open (reset) error=' + err); 400 | else { 401 | db.createCollection('client', function(err, collection) { 402 | if (err) 403 | eLog('Mongo DB create (reset) error=' + err); 404 | else { 405 | var document = { 406 | clientid: clientid, 407 | protocol: protocol, 408 | servermsgsize: servermsgsize, 409 | clientmsgsize: clientmsgsize, 410 | recordtype: recordtype, 411 | nocrc: nocrc, 412 | debug: debug, 413 | istrial: istrial, 414 | time: new Date() 415 | }; 416 | collection.update( 417 | { clientid: clientid }, 418 | document, 419 | { upsert: true, multi: false }, 420 | function(err) { 421 | eLog('Upserted client (size) id=' + clientid + ' err=' + err); 422 | } 423 | ); 424 | } 425 | }); 426 | db.close(); 427 | } 428 | }); 429 | } 430 | catch (e) { 431 | eLog('Error upserting: ' + e.message); 432 | } 433 | 434 | // Build response 435 | var data = new Buffer(1); 436 | data[0] = PROTOCOL_VERSION; 437 | 438 | // Encode response 439 | return encodeOOB(rmsg, data); 440 | }; 441 | 442 | var handleQuit = function(rmsg) { 443 | eLog('Quit client=' + rmsg.client); 444 | 445 | // Close sockets 446 | if (rmsg.client in _socket) 447 | for (var channel in _socket[rmsg.client]) { 448 | eLog('End socket client=' + rmsg.client + ' channel=' + channel); 449 | _clientclose[rmsg.client][channel] = true; 450 | _socket[rmsg.client][channel].end(); 451 | } 452 | 453 | // Send response 454 | var data = new Buffer(0); 455 | return encodeOOB(rmsg, data); 456 | }; 457 | 458 | var handleProbeClient = function(rmsg) { 459 | var size = rmsg.data[0] * 256 + rmsg.data[1]; 460 | eLog('Probe client size client=' + rmsg.client + ' size=' + size); 461 | 462 | // Send response 463 | var data = new Buffer(0); 464 | return encodeOOB(rmsg, data); 465 | }; 466 | 467 | var handleProbeServer = function(rmsg) { 468 | var size = rmsg.data[0] * 256 + rmsg.data[1]; 469 | eLog('Probe server size client=' + rmsg.client + ' size=' + size); 470 | 471 | // Build response 472 | var data = new Buffer(size); 473 | for (var i = 0; i < size; i++) 474 | data[i] = (i & 0xFF); 475 | 476 | // Send response 477 | return encodeOOB(rmsg, data); 478 | }; 479 | 480 | var handleSetSize = function(rmsg) { 481 | var servermsgsize = rmsg.data[0] * 256 + rmsg.data[1]; 482 | var clientmsgsize = rmsg.data[2] * 256 + rmsg.data[3]; 483 | _msgsize[rmsg.client] = servermsgsize; 484 | eLog('Set size client=' + rmsg.client + ' server=' + servermsgsize + ' client=' + clientmsgsize); 485 | 486 | // Register size 487 | if (MONGO_LOG) 488 | try { 489 | var server = new mongo.Server('localhost', 27017, { auto_reconnect: true }); 490 | var db = new mongo.Db('element53', server); 491 | db.open(function(err, db) { 492 | if (err) 493 | eLog('Mongo DB open (size) error=' + err); 494 | else { 495 | db.createCollection('client', function(err, collection) { 496 | if (err) 497 | eLog('Mongo DB create (size) error=' + err); 498 | else { 499 | var document = { 500 | clientid: _clientid[rmsg.client], 501 | servermsgsize: servermsgsize, 502 | clientmsgsize: clientmsgsize, 503 | time: new Date() 504 | }; 505 | collection.update( 506 | { clientid: _clientid[rmsg.client] }, 507 | { $set : document }, 508 | { upsert: true, multi: false }, 509 | function(err) { 510 | eLog('Upserted client (size) id=' + rmsg.client + ' err=' + err); 511 | } 512 | ); 513 | } 514 | }); 515 | db.close(); 516 | } 517 | }); 518 | } 519 | catch (e) { 520 | eLog('Error upserting: ' + e.message); 521 | } 522 | 523 | // Send response 524 | var data = new Buffer(0); 525 | return encodeOOB(rmsg, data); 526 | } 527 | 528 | var getFreeClient = function() { 529 | // Check for inactive clients 530 | for (client = 1; client <= 255; client++) 531 | if (client in _lastactivity) { 532 | var delta = new Date().getTime() - _lastactivity[client].getTime(); 533 | eLog('Client=' + client + ' inactive ' + delta + ' ms'); 534 | if (delta >= config.max_client_inactive_ms) { 535 | eLog('Delete client=' + client); 536 | 537 | // Close channels 538 | for (var channel in _socket[client]) 539 | _socket[client][channel].end(); 540 | 541 | // Delete client 542 | delete _seq[client]; 543 | delete _socket[client]; 544 | delete _queue[client]; 545 | delete _msgsize[client]; 546 | delete _lastmsg[client]; 547 | delete _clientclose[client]; 548 | delete _lastactivity[client]; 549 | delete _laststatus [client]; 550 | delete _nocrc[client]; 551 | delete _debug [client]; 552 | } 553 | } 554 | 555 | // Find free client 556 | for (client = 1; client <= 255; client++) 557 | if (!(client in _lastactivity)) 558 | return client; 559 | 560 | throw new Error('No free clients'); 561 | }; 562 | 563 | var sendMessage = function(rinfo, query, message) { 564 | // Build record 565 | var record = {}; 566 | record.qname = query.question.qname; 567 | record.qclass = 1; // INternet 568 | record.qtype = query.question.qtype; 569 | record.ttl = 1; 570 | 571 | // Encode data 572 | if (query.question.qtype == RECORD_CNAME) { 573 | // base32 encoded CNAME record 574 | var cname = base32.encode(message.toString('binary')); 575 | var p = Math.floor(cname.length / 4); 576 | cname = cname.substr(0 * p, p) + '.' + cname.substr(1 * p, p) + '.' + cname.substr(2 * p, p) + '.' + cname.substr(3 * p) + '.'; 577 | if (cname.length > 255) 578 | throw new Error('Message too long length=' + cname.length); 579 | record.rdata = encodeName(cname); 580 | } 581 | else if (query.question.qtype == RECORD_TEXT) { 582 | // base64 encoded TEXT record 583 | var payload = new Buffer('~' + message.toString('base64')); 584 | if (payload.length - 1 > 255) 585 | throw new Error('Message too long length=' + payload.length); 586 | payload[0] = payload.length - 1; 587 | record.rdata = payload; 588 | } 589 | else if (query.question.qtype == RECORD_NULL) { 590 | // binary encoded NULL record 591 | if (message.length > 255) 592 | throw new Error('Message too long length=' + message.length); 593 | record.rdata = new Buffer(message.length + 1); 594 | record.rdata[0] = message.length; 595 | // buf.copy(targetBuffer, [targetStart], [sourceStart], [sourceEnd]) 596 | message.copy(record.rdata, 1, 0, message.length); 597 | } 598 | else 599 | throw new Error('Unknown record type=' + query.question.qtype); 600 | 601 | // Send response 602 | var response = encodeResponse(query, 0, record); 603 | var buffer = buildResponseBuffer(response); 604 | server.send(buffer, 0, buffer.length, rinfo.port, rinfo.address, function (err, sent) { }); 605 | }; 606 | 607 | var server = dgram.createSocket('udp4'); 608 | 609 | server.on('message', function (smsg, rinfo) { 610 | //var start = microtime.now(); 611 | var query = null; 612 | var rmsg = null; 613 | try { 614 | // Decode query 615 | query = decodeRequest(smsg); 616 | 617 | // Decode message 618 | rmsg = decodeMessage(query); 619 | 620 | // Handle message 621 | if (rmsg.seq == 0) { 622 | // Out of band message 623 | eLog('OOB=' + rmsg.control + ' client=' + rmsg.client); 624 | 625 | // Reset 626 | if (rmsg.control == OOB_RESET) { 627 | var message = handleReset(rmsg); 628 | sendMessage(rinfo, query, message); 629 | } 630 | 631 | // Quit 632 | else if (rmsg.control == OOB_QUIT) { 633 | var message = handleQuit(rmsg); 634 | sendMessage(rinfo, query, message); 635 | } 636 | 637 | // Client probe 638 | else if (rmsg.control == OOB_PROBE_CLIENT) { 639 | var message = handleProbeClient(rmsg); 640 | sendMessage(rinfo, query, message); 641 | } 642 | 643 | // Server probe 644 | else if (rmsg.control == OOB_PROBE_SERVER) { 645 | var message = handleProbeServer(rmsg); 646 | sendMessage(rinfo, query, message); 647 | } 648 | 649 | // Set size 650 | else if (OOB_SET_SIZE) { 651 | var message = handleSetSize(rmsg); 652 | sendMessage(rinfo, query, message); 653 | } 654 | } 655 | else { 656 | // In band message 657 | 658 | // Check client ID 659 | if (!(rmsg.client in _socket)) 660 | throw new Error('Unknown client=' + rmsg.client); 661 | 662 | // Check sequence 663 | var nextseq = (_seq[rmsg.client] == 255 ? 1 : _seq[rmsg.client] + 1); 664 | if (rmsg.seq == nextseq) { 665 | _seq[rmsg.client] = nextseq; 666 | 667 | // Handle open 668 | if (rmsg.control == CONTROL_OPEN) { 669 | eLog('Socket open by client=' + rmsg.client + ' channel=' + rmsg.channel); 670 | 671 | // Create new socket 672 | _clientclose[rmsg.client][rmsg.channel] = false; 673 | _socket[rmsg.client][rmsg.channel] = new net.Socket({ type: 'tcp4' }); 674 | 675 | // Data from socket 676 | _socket[rmsg.client][rmsg.channel].on('data', function(data) { 677 | try { 678 | if (rmsg.client in _queue && !_clientclose[rmsg.client][rmsg.channel]) { 679 | var start = 0; 680 | while (start < data.length) { 681 | var end = start + _msgsize[rmsg.client]; 682 | if (end > data.length) 683 | end = data.length; 684 | var msg = { channel: rmsg.channel, control: CONTROL_DATA, data: data.slice(start, end) }; 685 | _queue[rmsg.client].push(msg); 686 | start += _msgsize[rmsg.client]; 687 | } 688 | } 689 | else 690 | eLog('Socket data discard client=' + rmsg.client + ' channel=' + rmsg.channel + ' len=' + data.length); 691 | } 692 | catch (e) { 693 | eLog('Error processing data: ' + e.message + ' stack=' + e.stack); 694 | } 695 | }); 696 | 697 | // Socket timeout 698 | _socket[rmsg.client][rmsg.channel].on('timeout', function() { 699 | eLog('Socket timeout client=' + rmsg.client + ' channel=' + rmsg.channel) 700 | }); 701 | 702 | // Socket end 703 | _socket[rmsg.client][rmsg.channel].on('end', function() { 704 | eLog('Socket end client=' + rmsg.client + ' channel=' + rmsg.channel) 705 | }); 706 | 707 | // Socket close 708 | _socket[rmsg.client][rmsg.channel].on('close', function() { 709 | if (_clientclose[rmsg.client][rmsg.channel]) 710 | eLog('Socket closed by client client=' + rmsg.client + ' channel=' + rmsg.channel) 711 | else { 712 | eLog('Socket closed by server client=' + rmsg.client + ' channel=' + rmsg.channel) 713 | var msg = { channel: rmsg.channel, control: CONTROL_CLOSE, data: new Buffer(0) }; 714 | _queue[rmsg.client].push(msg); 715 | } 716 | delete _socket[rmsg.client][rmsg.channel]; 717 | delete _clientclose[rmsg.client][rmsg.channel]; 718 | }); 719 | 720 | // Sokcet error 721 | _socket[rmsg.client][rmsg.channel].on('error', function(error) { 722 | eLog('Socket error client=' + rmsg.client + ' channel=' + rmsg.channel + ': ' + error); 723 | _socket[rmsg.client][rmsg.channel].end(); 724 | }); 725 | 726 | // Connect socket 727 | _socket[rmsg.client][rmsg.channel].connect(config.server_port, config.server_ip, function() { 728 | eLog('Socket connected client=' + rmsg.client + ' channel=' + rmsg.channel); 729 | }); 730 | } 731 | 732 | // Handle data 733 | else if (rmsg.control == CONTROL_DATA) { 734 | if (rmsg.data.length > 0) { 735 | if (rmsg.channel in _socket[rmsg.client]) 736 | _socket[rmsg.client][rmsg.channel].write(rmsg.data); 737 | else 738 | eLog('Data for closed channel client=' + rmsg.client + ' channel=' + rmsg.channel + ' len=' + rmsg.data.length); 739 | } 740 | } 741 | 742 | // Handle close 743 | else if (rmsg.control == CONTROL_CLOSE) { 744 | eLog('Socket close by client=' + rmsg.client + ' channel=' + rmsg.channel); 745 | if (rmsg.channel in _socket[rmsg.client]) { 746 | _clientclose[rmsg.client][rmsg.channel] = true; 747 | _socket[rmsg.client][rmsg.channel].end(); 748 | for (var i = 0; i < _queue[rmsg.client].length; i++) 749 | if (_queue[rmsg.client][i].channel == rmsg.channel) { 750 | eLog('Discarding message for closed channel=' + rmsg.channel); 751 | delete _queue[rmsg.client][i]; 752 | } 753 | } 754 | else 755 | eLog('Socket to close not found client=' + rmsg.client + ' channel=' + rmsg.channel); 756 | } 757 | 758 | // Get data to send 759 | delete _lastmsg[rmsg.client]; 760 | _lastmsg[rmsg.client] = getMsg(rmsg.client); 761 | msg = _lastmsg[rmsg.client]; 762 | } 763 | else 764 | eLog('Retransmit seq=' + _seq[rmsg.client] + ' client=' + rmsg.client + ' chan=' + _lastmsg[rmsg.client].channel + ' ctrl=' + _lastmsg[rmsg.client].control + ' len=' + _lastmsg[rmsg.client].data.length + ' rseq=' + rmsg.seq); 765 | 766 | // Build response 767 | var message = encodeMessage(rmsg); 768 | 769 | // Send response 770 | sendMessage(rinfo, query, message); 771 | _lastactivity[rmsg.client] = new Date(); 772 | 773 | // Cleanup 774 | delete query; 775 | delete rmsg; 776 | delete message; 777 | } 778 | } 779 | catch (e) { 780 | if (rmsg == null || rmsg.seq != 0 || !(rmsg.control == OOB_PROBE_CLIENT || rmsg.control == OOB_PROBE_SERVER)) 781 | eLog('Error processing message: ' + e.message + ' stack=' + e.stack); 782 | 783 | // Send error response 784 | if (query != null) 785 | try { 786 | eLog('Sending error response because: ' + e.message); 787 | var response = encodeResponse(query, 3, null); 788 | var buffer = buildResponseBuffer(response); 789 | server.send(buffer, 0, buffer.length, rinfo.port, rinfo.address, function (err, sent) { }); 790 | } 791 | catch (ee) { 792 | eLog('Error sending error response: ' + ee.message); 793 | } 794 | } 795 | //var elapse = microtime.now() - start; 796 | //console.log('Elapse: ' + elapse + ' us'); 797 | }); 798 | 799 | server.addListener('error', function (ex) { 800 | eLog('Server error: ' + ex.message); 801 | }); 802 | 803 | server.bind(config.port); 804 | 805 | eLog('Started element53 DNS server port=' + config.port); 806 | eLog('Tunnel domain ' + config.domain); 807 | eLog('Using server ' + config.server_ip + ':' + config.server_port); 808 | -------------------------------------------------------------------------------- /src/android/util/Base64.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package android.util; 18 | 19 | import java.io.UnsupportedEncodingException; 20 | 21 | /** 22 | * Utilities for encoding and decoding the Base64 representation of 23 | * binary data. See RFCs 2045 and 3548. 26 | */ 27 | public class Base64 { 28 | /** 29 | * Default values for encoder/decoder flags. 30 | */ 31 | public static final int DEFAULT = 0; 32 | 33 | /** 34 | * Encoder flag bit to omit the padding '=' characters at the end 35 | * of the output (if any). 36 | */ 37 | public static final int NO_PADDING = 1; 38 | 39 | /** 40 | * Encoder flag bit to omit all line terminators (i.e., the output 41 | * will be on one long line). 42 | */ 43 | public static final int NO_WRAP = 2; 44 | 45 | /** 46 | * Encoder flag bit to indicate lines should be terminated with a 47 | * CRLF pair instead of just an LF. Has no effect if {@code 48 | * NO_WRAP} is specified as well. 49 | */ 50 | public static final int CRLF = 4; 51 | 52 | /** 53 | * Encoder/decoder flag bit to indicate using the "URL and 54 | * filename safe" variant of Base64 (see RFC 3548 section 4) where 55 | * {@code -} and {@code _} are used in place of {@code +} and 56 | * {@code /}. 57 | */ 58 | public static final int URL_SAFE = 8; 59 | 60 | /** 61 | * Flag to pass to {@link Base64OutputStream} to indicate that it 62 | * should not close the output stream it is wrapping when it 63 | * itself is closed. 64 | */ 65 | public static final int NO_CLOSE = 16; 66 | 67 | // -------------------------------------------------------- 68 | // shared code 69 | // -------------------------------------------------------- 70 | 71 | /* package */ static abstract class Coder { 72 | public byte[] output; 73 | public int op; 74 | 75 | /** 76 | * Encode/decode another block of input data. this.output is 77 | * provided by the caller, and must be big enough to hold all 78 | * the coded data. On exit, this.opwill be set to the length 79 | * of the coded data. 80 | * 81 | * @param finish true if this is the final call to process for 82 | * this object. Will finalize the coder state and 83 | * include any final bytes in the output. 84 | * 85 | * @return true if the input so far is good; false if some 86 | * error has been detected in the input stream.. 87 | */ 88 | public abstract boolean process(byte[] input, int offset, int len, boolean finish); 89 | 90 | /** 91 | * @return the maximum number of bytes a call to process() 92 | * could produce for the given number of input bytes. This may 93 | * be an overestimate. 94 | */ 95 | public abstract int maxOutputSize(int len); 96 | } 97 | 98 | // -------------------------------------------------------- 99 | // decoding 100 | // -------------------------------------------------------- 101 | 102 | /** 103 | * Decode the Base64-encoded data in input and return the data in 104 | * a new byte array. 105 | * 106 | *

The padding '=' characters at the end are considered optional, but 107 | * if any are present, there must be the correct number of them. 108 | * 109 | * @param str the input String to decode, which is converted to 110 | * bytes using the default charset 111 | * @param flags controls certain features of the decoded output. 112 | * Pass {@code DEFAULT} to decode standard Base64. 113 | * 114 | * @throws IllegalArgumentException if the input contains 115 | * incorrect padding 116 | */ 117 | public static byte[] decode(String str, int flags) { 118 | return decode(str.getBytes(), flags); 119 | } 120 | 121 | /** 122 | * Decode the Base64-encoded data in input and return the data in 123 | * a new byte array. 124 | * 125 | *

The padding '=' characters at the end are considered optional, but 126 | * if any are present, there must be the correct number of them. 127 | * 128 | * @param input the input array to decode 129 | * @param flags controls certain features of the decoded output. 130 | * Pass {@code DEFAULT} to decode standard Base64. 131 | * 132 | * @throws IllegalArgumentException if the input contains 133 | * incorrect padding 134 | */ 135 | public static byte[] decode(byte[] input, int flags) { 136 | return decode(input, 0, input.length, flags); 137 | } 138 | 139 | /** 140 | * Decode the Base64-encoded data in input and return the data in 141 | * a new byte array. 142 | * 143 | *

The padding '=' characters at the end are considered optional, but 144 | * if any are present, there must be the correct number of them. 145 | * 146 | * @param input the data to decode 147 | * @param offset the position within the input array at which to start 148 | * @param len the number of bytes of input to decode 149 | * @param flags controls certain features of the decoded output. 150 | * Pass {@code DEFAULT} to decode standard Base64. 151 | * 152 | * @throws IllegalArgumentException if the input contains 153 | * incorrect padding 154 | */ 155 | public static byte[] decode(byte[] input, int offset, int len, int flags) { 156 | // Allocate space for the most data the input could represent. 157 | // (It could contain less if it contains whitespace, etc.) 158 | Decoder decoder = new Decoder(flags, new byte[len*3/4]); 159 | 160 | if (!decoder.process(input, offset, len, true)) { 161 | throw new IllegalArgumentException("bad base-64"); 162 | } 163 | 164 | // Maybe we got lucky and allocated exactly enough output space. 165 | if (decoder.op == decoder.output.length) { 166 | return decoder.output; 167 | } 168 | 169 | // Need to shorten the array, so allocate a new one of the 170 | // right size and copy. 171 | byte[] temp = new byte[decoder.op]; 172 | System.arraycopy(decoder.output, 0, temp, 0, decoder.op); 173 | return temp; 174 | } 175 | 176 | /* package */ static class Decoder extends Coder { 177 | /** 178 | * Lookup table for turning bytes into their position in the 179 | * Base64 alphabet. 180 | */ 181 | private static final int DECODE[] = { 182 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 183 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 184 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 185 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, 186 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 187 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, 188 | -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 189 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, 190 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 191 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 192 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 193 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 194 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 195 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 196 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 197 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 198 | }; 199 | 200 | /** 201 | * Decode lookup table for the "web safe" variant (RFC 3548 202 | * sec. 4) where - and _ replace + and /. 203 | */ 204 | private static final int DECODE_WEBSAFE[] = { 205 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 206 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 207 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, 208 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, 209 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 210 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, 211 | -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 212 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, 213 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 214 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 215 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 216 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 217 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 218 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 219 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 220 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 221 | }; 222 | 223 | /** Non-data values in the DECODE arrays. */ 224 | private static final int SKIP = -1; 225 | private static final int EQUALS = -2; 226 | 227 | /** 228 | * States 0-3 are reading through the next input tuple. 229 | * State 4 is having read one '=' and expecting exactly 230 | * one more. 231 | * State 5 is expecting no more data or padding characters 232 | * in the input. 233 | * State 6 is the error state; an error has been detected 234 | * in the input and no future input can "fix" it. 235 | */ 236 | private int state; // state number (0 to 6) 237 | private int value; 238 | 239 | final private int[] alphabet; 240 | 241 | public Decoder(int flags, byte[] output) { 242 | this.output = output; 243 | 244 | alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE; 245 | state = 0; 246 | value = 0; 247 | } 248 | 249 | /** 250 | * @return an overestimate for the number of bytes {@code 251 | * len} bytes could decode to. 252 | */ 253 | public int maxOutputSize(int len) { 254 | return len * 3/4 + 10; 255 | } 256 | 257 | /** 258 | * Decode another block of input data. 259 | * 260 | * @return true if the state machine is still healthy. false if 261 | * bad base-64 data has been detected in the input stream. 262 | */ 263 | public boolean process(byte[] input, int offset, int len, boolean finish) { 264 | if (this.state == 6) return false; 265 | 266 | int p = offset; 267 | len += offset; 268 | 269 | // Using local variables makes the decoder about 12% 270 | // faster than if we manipulate the member variables in 271 | // the loop. (Even alphabet makes a measurable 272 | // difference, which is somewhat surprising to me since 273 | // the member variable is final.) 274 | int state = this.state; 275 | int value = this.value; 276 | int op = 0; 277 | final byte[] output = this.output; 278 | final int[] alphabet = this.alphabet; 279 | 280 | while (p < len) { 281 | // Try the fast path: we're starting a new tuple and the 282 | // next four bytes of the input stream are all data 283 | // bytes. This corresponds to going through states 284 | // 0-1-2-3-0. We expect to use this method for most of 285 | // the data. 286 | // 287 | // If any of the next four bytes of input are non-data 288 | // (whitespace, etc.), value will end up negative. (All 289 | // the non-data values in decode are small negative 290 | // numbers, so shifting any of them up and or'ing them 291 | // together will result in a value with its top bit set.) 292 | // 293 | // You can remove this whole block and the output should 294 | // be the same, just slower. 295 | if (state == 0) { 296 | while (p+4 <= len && 297 | (value = ((alphabet[input[p] & 0xff] << 18) | 298 | (alphabet[input[p+1] & 0xff] << 12) | 299 | (alphabet[input[p+2] & 0xff] << 6) | 300 | (alphabet[input[p+3] & 0xff]))) >= 0) { 301 | output[op+2] = (byte) value; 302 | output[op+1] = (byte) (value >> 8); 303 | output[op] = (byte) (value >> 16); 304 | op += 3; 305 | p += 4; 306 | } 307 | if (p >= len) break; 308 | } 309 | 310 | // The fast path isn't available -- either we've read a 311 | // partial tuple, or the next four input bytes aren't all 312 | // data, or whatever. Fall back to the slower state 313 | // machine implementation. 314 | 315 | int d = alphabet[input[p++] & 0xff]; 316 | 317 | switch (state) { 318 | case 0: 319 | if (d >= 0) { 320 | value = d; 321 | ++state; 322 | } else if (d != SKIP) { 323 | this.state = 6; 324 | return false; 325 | } 326 | break; 327 | 328 | case 1: 329 | if (d >= 0) { 330 | value = (value << 6) | d; 331 | ++state; 332 | } else if (d != SKIP) { 333 | this.state = 6; 334 | return false; 335 | } 336 | break; 337 | 338 | case 2: 339 | if (d >= 0) { 340 | value = (value << 6) | d; 341 | ++state; 342 | } else if (d == EQUALS) { 343 | // Emit the last (partial) output tuple; 344 | // expect exactly one more padding character. 345 | output[op++] = (byte) (value >> 4); 346 | state = 4; 347 | } else if (d != SKIP) { 348 | this.state = 6; 349 | return false; 350 | } 351 | break; 352 | 353 | case 3: 354 | if (d >= 0) { 355 | // Emit the output triple and return to state 0. 356 | value = (value << 6) | d; 357 | output[op+2] = (byte) value; 358 | output[op+1] = (byte) (value >> 8); 359 | output[op] = (byte) (value >> 16); 360 | op += 3; 361 | state = 0; 362 | } else if (d == EQUALS) { 363 | // Emit the last (partial) output tuple; 364 | // expect no further data or padding characters. 365 | output[op+1] = (byte) (value >> 2); 366 | output[op] = (byte) (value >> 10); 367 | op += 2; 368 | state = 5; 369 | } else if (d != SKIP) { 370 | this.state = 6; 371 | return false; 372 | } 373 | break; 374 | 375 | case 4: 376 | if (d == EQUALS) { 377 | ++state; 378 | } else if (d != SKIP) { 379 | this.state = 6; 380 | return false; 381 | } 382 | break; 383 | 384 | case 5: 385 | if (d != SKIP) { 386 | this.state = 6; 387 | return false; 388 | } 389 | break; 390 | } 391 | } 392 | 393 | if (!finish) { 394 | // We're out of input, but a future call could provide 395 | // more. 396 | this.state = state; 397 | this.value = value; 398 | this.op = op; 399 | return true; 400 | } 401 | 402 | // Done reading input. Now figure out where we are left in 403 | // the state machine and finish up. 404 | 405 | switch (state) { 406 | case 0: 407 | // Output length is a multiple of three. Fine. 408 | break; 409 | case 1: 410 | // Read one extra input byte, which isn't enough to 411 | // make another output byte. Illegal. 412 | this.state = 6; 413 | return false; 414 | case 2: 415 | // Read two extra input bytes, enough to emit 1 more 416 | // output byte. Fine. 417 | output[op++] = (byte) (value >> 4); 418 | break; 419 | case 3: 420 | // Read three extra input bytes, enough to emit 2 more 421 | // output bytes. Fine. 422 | output[op++] = (byte) (value >> 10); 423 | output[op++] = (byte) (value >> 2); 424 | break; 425 | case 4: 426 | // Read one padding '=' when we expected 2. Illegal. 427 | this.state = 6; 428 | return false; 429 | case 5: 430 | // Read all the padding '='s we expected and no more. 431 | // Fine. 432 | break; 433 | } 434 | 435 | this.state = state; 436 | this.op = op; 437 | return true; 438 | } 439 | } 440 | 441 | // -------------------------------------------------------- 442 | // encoding 443 | // -------------------------------------------------------- 444 | 445 | /** 446 | * Base64-encode the given data and return a newly allocated 447 | * String with the result. 448 | * 449 | * @param input the data to encode 450 | * @param flags controls certain features of the encoded output. 451 | * Passing {@code DEFAULT} results in output that 452 | * adheres to RFC 2045. 453 | */ 454 | public static String encodeToString(byte[] input, int flags) { 455 | try { 456 | return new String(encode(input, flags), "US-ASCII"); 457 | } catch (UnsupportedEncodingException e) { 458 | // US-ASCII is guaranteed to be available. 459 | throw new AssertionError(e); 460 | } 461 | } 462 | 463 | /** 464 | * Base64-encode the given data and return a newly allocated 465 | * String with the result. 466 | * 467 | * @param input the data to encode 468 | * @param offset the position within the input array at which to 469 | * start 470 | * @param len the number of bytes of input to encode 471 | * @param flags controls certain features of the encoded output. 472 | * Passing {@code DEFAULT} results in output that 473 | * adheres to RFC 2045. 474 | */ 475 | public static String encodeToString(byte[] input, int offset, int len, int flags) { 476 | try { 477 | return new String(encode(input, offset, len, flags), "US-ASCII"); 478 | } catch (UnsupportedEncodingException e) { 479 | // US-ASCII is guaranteed to be available. 480 | throw new AssertionError(e); 481 | } 482 | } 483 | 484 | /** 485 | * Base64-encode the given data and return a newly allocated 486 | * byte[] with the result. 487 | * 488 | * @param input the data to encode 489 | * @param flags controls certain features of the encoded output. 490 | * Passing {@code DEFAULT} results in output that 491 | * adheres to RFC 2045. 492 | */ 493 | public static byte[] encode(byte[] input, int flags) { 494 | return encode(input, 0, input.length, flags); 495 | } 496 | 497 | /** 498 | * Base64-encode the given data and return a newly allocated 499 | * byte[] with the result. 500 | * 501 | * @param input the data to encode 502 | * @param offset the position within the input array at which to 503 | * start 504 | * @param len the number of bytes of input to encode 505 | * @param flags controls certain features of the encoded output. 506 | * Passing {@code DEFAULT} results in output that 507 | * adheres to RFC 2045. 508 | */ 509 | public static byte[] encode(byte[] input, int offset, int len, int flags) { 510 | Encoder encoder = new Encoder(flags, null); 511 | 512 | // Compute the exact length of the array we will produce. 513 | int output_len = len / 3 * 4; 514 | 515 | // Account for the tail of the data and the padding bytes, if any. 516 | if (encoder.do_padding) { 517 | if (len % 3 > 0) { 518 | output_len += 4; 519 | } 520 | } else { 521 | switch (len % 3) { 522 | case 0: break; 523 | case 1: output_len += 2; break; 524 | case 2: output_len += 3; break; 525 | } 526 | } 527 | 528 | // Account for the newlines, if any. 529 | if (encoder.do_newline && len > 0) { 530 | output_len += (((len-1) / (3 * Encoder.LINE_GROUPS)) + 1) * 531 | (encoder.do_cr ? 2 : 1); 532 | } 533 | 534 | encoder.output = new byte[output_len]; 535 | encoder.process(input, offset, len, true); 536 | 537 | assert encoder.op == output_len; 538 | 539 | return encoder.output; 540 | } 541 | 542 | /* package */ static class Encoder extends Coder { 543 | /** 544 | * Emit a new line every this many output tuples. Corresponds to 545 | * a 76-character line length (the maximum allowable according to 546 | * RFC 2045). 547 | */ 548 | public static final int LINE_GROUPS = 19; 549 | 550 | /** 551 | * Lookup table for turning Base64 alphabet positions (6 bits) 552 | * into output bytes. 553 | */ 554 | private static final byte ENCODE[] = { 555 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 556 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 557 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 558 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', 559 | }; 560 | 561 | /** 562 | * Lookup table for turning Base64 alphabet positions (6 bits) 563 | * into output bytes. 564 | */ 565 | private static final byte ENCODE_WEBSAFE[] = { 566 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 567 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 568 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 569 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', 570 | }; 571 | 572 | final private byte[] tail; 573 | /* package */ int tailLen; 574 | private int count; 575 | 576 | final public boolean do_padding; 577 | final public boolean do_newline; 578 | final public boolean do_cr; 579 | final private byte[] alphabet; 580 | 581 | public Encoder(int flags, byte[] output) { 582 | this.output = output; 583 | 584 | do_padding = (flags & NO_PADDING) == 0; 585 | do_newline = (flags & NO_WRAP) == 0; 586 | do_cr = (flags & CRLF) != 0; 587 | alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE; 588 | 589 | tail = new byte[2]; 590 | tailLen = 0; 591 | 592 | count = do_newline ? LINE_GROUPS : -1; 593 | } 594 | 595 | /** 596 | * @return an overestimate for the number of bytes {@code 597 | * len} bytes could encode to. 598 | */ 599 | public int maxOutputSize(int len) { 600 | return len * 8/5 + 10; 601 | } 602 | 603 | public boolean process(byte[] input, int offset, int len, boolean finish) { 604 | // Using local variables makes the encoder about 9% faster. 605 | final byte[] alphabet = this.alphabet; 606 | final byte[] output = this.output; 607 | int op = 0; 608 | int count = this.count; 609 | 610 | int p = offset; 611 | len += offset; 612 | int v = -1; 613 | 614 | // First we need to concatenate the tail of the previous call 615 | // with any input bytes available now and see if we can empty 616 | // the tail. 617 | 618 | switch (tailLen) { 619 | case 0: 620 | // There was no tail. 621 | break; 622 | 623 | case 1: 624 | if (p+2 <= len) { 625 | // A 1-byte tail with at least 2 bytes of 626 | // input available now. 627 | v = ((tail[0] & 0xff) << 16) | 628 | ((input[p++] & 0xff) << 8) | 629 | (input[p++] & 0xff); 630 | tailLen = 0; 631 | }; 632 | break; 633 | 634 | case 2: 635 | if (p+1 <= len) { 636 | // A 2-byte tail with at least 1 byte of input. 637 | v = ((tail[0] & 0xff) << 16) | 638 | ((tail[1] & 0xff) << 8) | 639 | (input[p++] & 0xff); 640 | tailLen = 0; 641 | } 642 | break; 643 | } 644 | 645 | if (v != -1) { 646 | output[op++] = alphabet[(v >> 18) & 0x3f]; 647 | output[op++] = alphabet[(v >> 12) & 0x3f]; 648 | output[op++] = alphabet[(v >> 6) & 0x3f]; 649 | output[op++] = alphabet[v & 0x3f]; 650 | if (--count == 0) { 651 | if (do_cr) output[op++] = '\r'; 652 | output[op++] = '\n'; 653 | count = LINE_GROUPS; 654 | } 655 | } 656 | 657 | // At this point either there is no tail, or there are fewer 658 | // than 3 bytes of input available. 659 | 660 | // The main loop, turning 3 input bytes into 4 output bytes on 661 | // each iteration. 662 | while (p+3 <= len) { 663 | v = ((input[p] & 0xff) << 16) | 664 | ((input[p+1] & 0xff) << 8) | 665 | (input[p+2] & 0xff); 666 | output[op] = alphabet[(v >> 18) & 0x3f]; 667 | output[op+1] = alphabet[(v >> 12) & 0x3f]; 668 | output[op+2] = alphabet[(v >> 6) & 0x3f]; 669 | output[op+3] = alphabet[v & 0x3f]; 670 | p += 3; 671 | op += 4; 672 | if (--count == 0) { 673 | if (do_cr) output[op++] = '\r'; 674 | output[op++] = '\n'; 675 | count = LINE_GROUPS; 676 | } 677 | } 678 | 679 | if (finish) { 680 | // Finish up the tail of the input. Note that we need to 681 | // consume any bytes in tail before any bytes 682 | // remaining in input; there should be at most two bytes 683 | // total. 684 | 685 | if (p-tailLen == len-1) { 686 | int t = 0; 687 | v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4; 688 | tailLen -= t; 689 | output[op++] = alphabet[(v >> 6) & 0x3f]; 690 | output[op++] = alphabet[v & 0x3f]; 691 | if (do_padding) { 692 | output[op++] = '='; 693 | output[op++] = '='; 694 | } 695 | if (do_newline) { 696 | if (do_cr) output[op++] = '\r'; 697 | output[op++] = '\n'; 698 | } 699 | } else if (p-tailLen == len-2) { 700 | int t = 0; 701 | v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) | 702 | (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2); 703 | tailLen -= t; 704 | output[op++] = alphabet[(v >> 12) & 0x3f]; 705 | output[op++] = alphabet[(v >> 6) & 0x3f]; 706 | output[op++] = alphabet[v & 0x3f]; 707 | if (do_padding) { 708 | output[op++] = '='; 709 | } 710 | if (do_newline) { 711 | if (do_cr) output[op++] = '\r'; 712 | output[op++] = '\n'; 713 | } 714 | } else if (do_newline && op > 0 && count != LINE_GROUPS) { 715 | if (do_cr) output[op++] = '\r'; 716 | output[op++] = '\n'; 717 | } 718 | 719 | assert tailLen == 0; 720 | assert p == len; 721 | } else { 722 | // Save the leftovers in tail to be consumed on the next 723 | // call to encodeInternal. 724 | 725 | if (p == len-1) { 726 | tail[tailLen++] = input[p]; 727 | } else if (p == len-2) { 728 | tail[tailLen++] = input[p]; 729 | tail[tailLen++] = input[p+1]; 730 | } 731 | } 732 | 733 | this.op = op; 734 | this.count = count; 735 | 736 | return true; 737 | } 738 | } 739 | 740 | private Base64() { } // don't instantiate 741 | } 742 | -------------------------------------------------------------------------------- /src/biz/nijhof/e53/Base32.java: -------------------------------------------------------------------------------- 1 | package biz.nijhof.e53; 2 | 3 | import java.util.HashMap; 4 | 5 | /** 6 | * Encodes arbitrary byte arrays as case-insensitive base-32 strings 7 | * 8 | * @author sweis@google.com (Steve Weis) 9 | * @author Neal Gafter 10 | */ 11 | public class Base32 { 12 | // singleton 13 | private static final Base32 INSTANCE = new Base32("0123456789abcdefghjkmnpqrtuvwxyz"); 14 | 15 | static Base32 getInstance() { 16 | return INSTANCE; 17 | } 18 | 19 | // 32 alpha-numeric characters. 20 | private String ALPHABET; 21 | private char[] DIGITS; 22 | private int MASK; 23 | private int SHIFT; 24 | private HashMap CHAR_MAP; 25 | 26 | static final String SEPARATOR = "-"; 27 | 28 | protected Base32(String alphabet) { 29 | this.ALPHABET = alphabet; 30 | DIGITS = ALPHABET.toCharArray(); 31 | MASK = DIGITS.length - 1; 32 | SHIFT = Integer.numberOfTrailingZeros(DIGITS.length); 33 | CHAR_MAP = new HashMap(); 34 | for (int i = 0; i < DIGITS.length; i++) { 35 | CHAR_MAP.put(DIGITS[i], i); 36 | } 37 | } 38 | 39 | public static byte[] decode(String encoded) throws DecodingException { 40 | return getInstance().decodeInternal(encoded); 41 | } 42 | 43 | protected byte[] decodeInternal(String encoded) throws DecodingException { 44 | // Remove whitespace and separators 45 | encoded = encoded.trim().replaceAll(SEPARATOR, "").replaceAll(" ", ""); 46 | // Canonicalize to all upper case 47 | // encoded = encoded.toUpperCase(); 48 | if (encoded.length() == 0) { 49 | return new byte[0]; 50 | } 51 | int encodedLength = encoded.length(); 52 | int outLength = encodedLength * SHIFT / 8; 53 | byte[] result = new byte[outLength]; 54 | int buffer = 0; 55 | int next = 0; 56 | int bitsLeft = 0; 57 | for (char c : encoded.toCharArray()) { 58 | if (!CHAR_MAP.containsKey(c)) { 59 | throw new DecodingException("Illegal character: " + c); 60 | } 61 | buffer <<= SHIFT; 62 | buffer |= CHAR_MAP.get(c) & MASK; 63 | bitsLeft += SHIFT; 64 | if (bitsLeft >= 8) { 65 | result[next++] = (byte) (buffer >> (bitsLeft - 8)); 66 | bitsLeft -= 8; 67 | } 68 | } 69 | // We'll ignore leftover bits for now. 70 | // 71 | // if (next != outLength || bitsLeft >= SHIFT) { 72 | // throw new DecodingException("Bits left: " + bitsLeft); 73 | // } 74 | return result; 75 | } 76 | 77 | public static String encode(byte[] data) { 78 | return getInstance().encodeInternal(data); 79 | } 80 | 81 | protected String encodeInternal(byte[] data) { 82 | if (data.length == 0) { 83 | return ""; 84 | } 85 | 86 | // SHIFT is the number of bits per output character, so the length of 87 | // the 88 | // output is the length of the input multiplied by 8/SHIFT, rounded up. 89 | if (data.length >= (1 << 28)) { 90 | // The computation below will fail, so don't do it. 91 | throw new IllegalArgumentException(); 92 | } 93 | 94 | int outputLength = (data.length * 8 + SHIFT - 1) / SHIFT; 95 | StringBuilder result = new StringBuilder(outputLength); 96 | 97 | int buffer = data[0]; 98 | int next = 1; 99 | int bitsLeft = 8; 100 | while (bitsLeft > 0 || next < data.length) { 101 | if (bitsLeft < SHIFT) { 102 | if (next < data.length) { 103 | buffer <<= 8; 104 | buffer |= (data[next++] & 0xff); 105 | bitsLeft += 8; 106 | } else { 107 | int pad = SHIFT - bitsLeft; 108 | buffer <<= pad; 109 | bitsLeft += pad; 110 | } 111 | } 112 | int index = MASK & (buffer >> (bitsLeft - SHIFT)); 113 | bitsLeft -= SHIFT; 114 | result.append(DIGITS[index]); 115 | } 116 | return result.toString(); 117 | } 118 | 119 | @Override 120 | // enforce that this class is a singleton 121 | public Object clone() throws CloneNotSupportedException { 122 | throw new CloneNotSupportedException(); 123 | } 124 | 125 | static class DecodingException extends Exception { 126 | /** 127 | * 128 | */ 129 | private static final long serialVersionUID = 1L; 130 | 131 | public DecodingException(String message) { 132 | super(message); 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /src/biz/nijhof/e53/Element53Activity.java: -------------------------------------------------------------------------------- 1 | package biz.nijhof.e53; 2 | 3 | import java.text.DateFormat; 4 | import java.text.SimpleDateFormat; 5 | import java.util.ArrayList; 6 | import java.util.Date; 7 | import java.util.List; 8 | import java.util.TimeZone; 9 | import java.util.Timer; 10 | import java.util.TimerTask; 11 | 12 | import android.app.Activity; 13 | import android.app.AlertDialog; 14 | import android.content.ComponentName; 15 | import android.content.Context; 16 | import android.content.DialogInterface; 17 | import android.content.Intent; 18 | import android.content.ServiceConnection; 19 | import android.content.SharedPreferences; 20 | import android.content.pm.ApplicationInfo; 21 | import android.content.pm.PackageManager; 22 | import android.net.Uri; 23 | import android.os.Bundle; 24 | import android.os.IBinder; 25 | import android.preference.PreferenceManager; 26 | import android.support.v4.view.ViewPager; 27 | import android.text.method.ScrollingMovementMethod; 28 | import android.view.Menu; 29 | import android.view.MenuInflater; 30 | import android.view.MenuItem; 31 | import android.view.View; 32 | import android.view.View.OnClickListener; 33 | import android.widget.ImageView; 34 | import android.widget.TextView; 35 | import android.widget.Toast; 36 | 37 | public class Element53Activity extends Activity { 38 | private TextView _txtDomain; 39 | private TextView _txtNetwork; 40 | private TextView _txtDNSServer; 41 | private TextView _txtGateway; 42 | private TextView _txtTypeSize; 43 | private TextView _txtSeqReq; 44 | private TextView _txtWait; 45 | private TextView _txtLatency; 46 | private TextView _txtErrorReset; 47 | private TextView _txtSent; 48 | private TextView _txtReceived; 49 | private TextView _txtChannels; 50 | private TextView _txtQueued; 51 | private TextView _txtLog; 52 | private TextView _txtManual; 53 | private ImageView _btnStartStop; 54 | 55 | private List _lstViews; 56 | private ViewPager _viewPager; 57 | 58 | private boolean _isBound; 59 | private Element53Service _boundService; 60 | 61 | private Timer _blinkTimer = null; 62 | 63 | boolean _started = false; 64 | boolean _connected = false; 65 | private int _loglines = 50; 66 | private int _clientmsgsize = 0; 67 | private int _servermsgsize = 0; 68 | 69 | private ServiceConnection mConnection = new ServiceConnection() { 70 | public void onServiceConnected(ComponentName className, IBinder service) { 71 | Element53Service.LocalBinder binder = ((Element53Service.LocalBinder) service); 72 | _boundService = binder.getService(); 73 | binder.setPI(createPendingResult(0, new Intent(), 0)); 74 | } 75 | 76 | public void onServiceDisconnected(ComponentName className) { 77 | _boundService = null; 78 | } 79 | }; 80 | 81 | @Override 82 | protected void onDestroy() { 83 | if (_isBound) { 84 | unbindService(mConnection); 85 | _isBound = false; 86 | } 87 | super.onDestroy(); 88 | } 89 | 90 | @Override 91 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 92 | try { 93 | if (resultCode == Element53Signal.RESULT_DOMAIN) { 94 | if (data.getBooleanExtra("Trial", false)) 95 | Toast.makeText(getApplicationContext(), "Element53 trial version", Toast.LENGTH_LONG).show(); 96 | _txtDomain.setText(String.format("%s:%d", data.getStringExtra("TunnelDomain"), data.getIntExtra("LocalPort", -1))); 97 | 98 | } else if (resultCode == Element53Signal.RESULT_NETWORK) { 99 | _txtNetwork.setText(data.getStringExtra("Network")); 100 | _txtDNSServer.setText(data.getStringExtra("DNS")); 101 | _txtGateway.setText(data.getStringExtra("Gateway")); 102 | 103 | } else if (resultCode == Element53Signal.RESULT_SIZE) { 104 | _clientmsgsize = data.getIntExtra("Client", -1); 105 | _servermsgsize = data.getIntExtra("Server", -1); 106 | _txtTypeSize.setText(String.format("%s %d / %d bytes", data.getStringExtra("Type"), _clientmsgsize, _servermsgsize)); 107 | 108 | } else if (resultCode == Element53Signal.RESULT_STATUS) { 109 | Date elapse = new Date(data.getLongExtra("Elapse", -1)); 110 | DateFormat formatter = new SimpleDateFormat("HH:mm:ss"); 111 | formatter.setTimeZone(TimeZone.getTimeZone("GMT")); 112 | 113 | float latency = data.getFloatExtra("Latency", -1); 114 | float maxclient = (1000 / latency * _clientmsgsize) / 1000; 115 | float maxserver = (1000 / latency * _servermsgsize) / 1000; 116 | 117 | _txtSeqReq.setText(String.format("%d / %d %s", data.getIntExtra("Seq", -1), data.getIntExtra("Req", -1), formatter.format(elapse))); 118 | _txtWait.setText(String.format("%d / %d ms", data.getIntExtra("DNSWait", -1), data.getIntExtra("RXWait", -1))); 119 | _txtLatency.setText(String.format("%.0f ms %.1f / %.1f kBps", latency, maxclient, maxserver)); 120 | _txtErrorReset.setText(String.format("%d / %d", data.getIntExtra("Error", -1), data.getIntExtra("Reset", -1))); 121 | 122 | float tx = (data.getIntExtra("Sent", -1) / 10) / 100f; 123 | float rx = (data.getIntExtra("Received", -1) / 10) / 100f; 124 | float ttx = (data.getIntExtra("TotalSent", -1) / 10) / 100f; 125 | float trx = (data.getIntExtra("TotalReceived", -1) / 10) / 100f; 126 | 127 | _txtSent.setText(String.format("%.2f kBps / %.2f kB", tx, ttx)); 128 | _txtReceived.setText(String.format("%.2f kBps / %.2f kB", rx, trx)); 129 | _txtChannels.setText(String.format("%d / %d", data.getIntExtra("TXChan", -1), data.getIntExtra("RXChan", -1))); 130 | _txtQueued.setText(String.format("%d / %d", data.getIntExtra("TXQueue", -1), data.getIntExtra("RXQueue", -1))); 131 | 132 | } else if (resultCode == Element53Signal.RESULT_LOG) { 133 | String text = _txtLog.getText().toString(); 134 | if (text.split("\\n").length >= _loglines) 135 | text = text.substring(0, text.lastIndexOf("\n")); 136 | text = data.getStringExtra("Log") + "\n" + text; 137 | _txtLog.setText(text); 138 | 139 | } else if (resultCode == Element53Signal.RESULT_RESET) 140 | Toast.makeText(getApplicationContext(), "Element53 reset", Toast.LENGTH_SHORT).show(); 141 | 142 | else if (resultCode == Element53Signal.RESULT_STARTED) { 143 | _started = true; 144 | _connected = false; 145 | _btnStartStop.setImageResource(R.drawable.stopbtn); 146 | } 147 | 148 | else if (resultCode == Element53Signal.RESULT_CONNECTED) 149 | _connected = true; 150 | 151 | else if (resultCode == Element53Signal.RESULT_ERROR) { 152 | } 153 | 154 | else if (resultCode == Element53Signal.RESULT_STOPPED) { 155 | _started = false; 156 | _connected = false; 157 | _btnStartStop.setImageResource(R.drawable.startbtn); 158 | } 159 | 160 | else if (resultCode == Element53Signal.RESULT_TRIALOVER) { 161 | showTrailOverMessage(); 162 | } 163 | } catch (Exception ex) { 164 | ex.printStackTrace(); 165 | } 166 | super.onActivityResult(requestCode, resultCode, data); 167 | } 168 | 169 | @Override 170 | public void onCreate(Bundle savedInstanceState) { 171 | super.onCreate(savedInstanceState); 172 | // Main layout 173 | setContentView(R.layout.main); 174 | 175 | // Adding views to view pager 176 | _lstViews = new ArrayList(); 177 | _lstViews.add(View.inflate(getApplicationContext(), R.layout.manual, null)); 178 | _lstViews.add(View.inflate(getApplicationContext(), R.layout.status, null)); 179 | _lstViews.add(View.inflate(getApplicationContext(), R.layout.log, null)); 180 | 181 | // Set up view pager. 182 | _viewPager = (ViewPager) findViewById(R.id.element53Pager); 183 | _viewPager.setAdapter(new Element53PagerAdapter(_lstViews)); 184 | _viewPager.setCurrentItem(1); 185 | 186 | // Setup controls 187 | View manualView = _lstViews.get(0); 188 | View statusView = _lstViews.get(1); 189 | View logView = _lstViews.get(2); 190 | _txtDomain = (TextView) statusView.findViewById(R.id.txtDomain); 191 | _txtNetwork = (TextView) statusView.findViewById(R.id.txtNetwork); 192 | _txtDNSServer = (TextView) statusView.findViewById(R.id.txtDNSServer); 193 | _txtGateway = (TextView) statusView.findViewById(R.id.txtGateway); 194 | _txtTypeSize = (TextView) statusView.findViewById(R.id.txtTypeSize); 195 | _txtSeqReq = (TextView) statusView.findViewById(R.id.txtSeqReq); 196 | _txtWait = (TextView) statusView.findViewById(R.id.txtWait); 197 | _txtLatency = (TextView) statusView.findViewById(R.id.txtLatency); 198 | _txtErrorReset = (TextView) statusView.findViewById(R.id.txtErrorReset); 199 | _txtSent = (TextView) statusView.findViewById(R.id.txtSent); 200 | _txtReceived = (TextView) statusView.findViewById(R.id.txtReceived); 201 | _txtQueued = (TextView) statusView.findViewById(R.id.txtQueued); 202 | _txtChannels = (TextView) statusView.findViewById(R.id.txtChannels); 203 | _txtLog = (TextView) logView.findViewById(R.id.txtLog); 204 | _txtManual = (TextView) manualView.findViewById(R.id.txtManual); 205 | _btnStartStop = (ImageView) statusView.findViewById(R.id.startStopButton); 206 | 207 | _txtLog.setMovementMethod(new ScrollingMovementMethod()); 208 | _txtManual.setText(R.string.Description); 209 | 210 | _btnStartStop.setOnClickListener(new OnClickListener() { 211 | public void onClick(View v) { 212 | if (_started) { 213 | _boundService.stop(); 214 | _started = false; 215 | _connected = false; 216 | if (_blinkTimer != null) 217 | _blinkTimer.cancel(); 218 | _btnStartStop.setVisibility(View.VISIBLE); 219 | _btnStartStop.setImageResource(R.drawable.startbtn); 220 | } else { 221 | _txtDomain.setText(R.string.na); 222 | _txtNetwork.setText(R.string.na); 223 | _txtDNSServer.setText(R.string.na); 224 | _txtGateway.setText(R.string.na); 225 | _txtTypeSize.setText(R.string.na); 226 | _txtSeqReq.setText(R.string.na); 227 | _txtWait.setText(R.string.na); 228 | _txtLatency.setText(R.string.na); 229 | _txtErrorReset.setText(R.string.na); 230 | _txtSent.setText(R.string.na); 231 | _txtReceived.setText(R.string.na); 232 | _txtQueued.setText(R.string.na); 233 | _txtChannels.setText(R.string.na); 234 | _txtLog.setText(""); 235 | _txtLog.scrollTo(0, 0); 236 | 237 | SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); 238 | String sLogLines = preferences.getString(Preferences.PREF_LOGLINES, ""); 239 | _loglines = (sLogLines.equals("") ? 50 : Integer.parseInt(sLogLines)); 240 | 241 | if (_boundService != null) { 242 | if (_boundService.checkTrialLimit(0)) { 243 | _boundService.start(); 244 | _started = true; 245 | _connected = false; 246 | _blinkTimer = new Timer(); 247 | _blinkTimer.schedule(new TimerTask() { 248 | @Override 249 | public void run() { 250 | Element53Activity.this.runOnUiThread(new Runnable() { 251 | public void run() { 252 | if (_started) { 253 | if (_btnStartStop.getVisibility() == View.VISIBLE && !_connected) 254 | _btnStartStop.setVisibility(View.INVISIBLE); 255 | else 256 | _btnStartStop.setVisibility(View.VISIBLE); 257 | } 258 | } 259 | }); 260 | } 261 | }, 500, 500); 262 | } else 263 | showTrailOverMessage(); 264 | } 265 | } 266 | } 267 | }); 268 | 269 | // Attach to service 270 | bindService(new Intent(Element53Activity.this, Element53Service.class), mConnection, Context.BIND_AUTO_CREATE); 271 | _isBound = true; 272 | 273 | // Check ProxyDroid 274 | checkProxyDroid(); 275 | } 276 | 277 | // Wire options menu 278 | @Override 279 | public boolean onCreateOptionsMenu(Menu menu) { 280 | MenuInflater inflater = getMenuInflater(); 281 | inflater.inflate(R.menu.main, menu); 282 | return true; 283 | } 284 | 285 | // Handle option selection 286 | @Override 287 | public boolean onOptionsItemSelected(MenuItem item) { 288 | switch (item.getItemId()) { 289 | case R.id.menuSettings: 290 | Intent preferencesIntent = new Intent(getBaseContext(), Preferences.class); 291 | startActivity(preferencesIntent); 292 | return true; 293 | case R.id.menuClear: 294 | _txtLog.setText(""); 295 | _txtLog.scrollTo(0, 0); 296 | return true; 297 | case R.id.menuAbout: 298 | // Get version 299 | String version = null; 300 | try { 301 | String versionName = getBaseContext().getPackageManager().getPackageInfo(getPackageName(), 0).versionName; 302 | int versionCode = getBaseContext().getPackageManager().getPackageInfo(getPackageName(), 0).versionCode; 303 | String format = getText(R.string.Version).toString(); 304 | version = String.format(format, versionName, versionCode); 305 | } catch (Exception ex) { 306 | version = ex.getMessage(); 307 | } 308 | 309 | AlertDialog.Builder aboutDialog = new AlertDialog.Builder(this); 310 | aboutDialog.setTitle(R.string.app_name); 311 | aboutDialog.setMessage(version); 312 | aboutDialog.setPositiveButton("Ok", new DialogInterface.OnClickListener() { 313 | public void onClick(DialogInterface dialog, int which) { 314 | } 315 | }); 316 | aboutDialog.create().show(); 317 | return true; 318 | default: 319 | return super.onOptionsItemSelected(item); 320 | } 321 | } 322 | 323 | // Handle back button 324 | @Override 325 | public void onBackPressed() { 326 | if (_started) { 327 | AlertDialog.Builder b = new AlertDialog.Builder(Element53Activity.this); 328 | b.setTitle(getString(R.string.app_name)); 329 | b.setMessage(getString(R.string.Quit)); 330 | b.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { 331 | public void onClick(DialogInterface dialog, int whichButton) { 332 | if (_boundService != null) 333 | _boundService.stop(); 334 | Element53Activity.super.onBackPressed(); 335 | } 336 | }); 337 | b.setNegativeButton(android.R.string.no, null); 338 | b.show(); 339 | } else 340 | Element53Activity.super.onBackPressed(); 341 | } 342 | 343 | private void showTrailOverMessage() { 344 | // Inform user 345 | AlertDialog.Builder trailOverDialog = new AlertDialog.Builder(this); 346 | trailOverDialog.setTitle(R.string.app_name); 347 | trailOverDialog.setMessage("Sorry, trial over. You have reached the free 10 MB limit."); 348 | trailOverDialog.setPositiveButton("Yes, I want to buy", new DialogInterface.OnClickListener() { 349 | public void onClick(DialogInterface dialog, int which) { 350 | Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=org.proxydroid")); 351 | startActivity(browserIntent); 352 | } 353 | }); 354 | trailOverDialog.setNegativeButton("No, thanks", new DialogInterface.OnClickListener() { 355 | public void onClick(DialogInterface dialog, int which) { 356 | } 357 | }); 358 | trailOverDialog.create().show(); 359 | } 360 | 361 | private void checkProxyDroid() { 362 | if (isInstalled("org.proxydroid") == null) { 363 | AlertDialog.Builder proxyDroidDialog = new AlertDialog.Builder(this); 364 | proxyDroidDialog.setTitle(R.string.app_name); 365 | proxyDroidDialog.setMessage("ProxyDroid is not installed\n(ProxyDroid should connect to localhost:3128 / SOCKS5)"); 366 | proxyDroidDialog.setPositiveButton("Install now", new DialogInterface.OnClickListener() { 367 | public void onClick(DialogInterface dialog, int which) { 368 | Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=org.proxydroid")); 369 | startActivity(browserIntent); 370 | } 371 | }); 372 | proxyDroidDialog.setNegativeButton("No, thanks", new DialogInterface.OnClickListener() { 373 | public void onClick(DialogInterface dialog, int which) { 374 | } 375 | }); 376 | proxyDroidDialog.create().show(); 377 | } 378 | } 379 | 380 | private ApplicationInfo isInstalled(String uri) { 381 | try { 382 | return getPackageManager().getApplicationInfo(uri, 0); 383 | } catch (PackageManager.NameNotFoundException e) { 384 | return null; 385 | } 386 | } 387 | } -------------------------------------------------------------------------------- /src/biz/nijhof/e53/Element53DNS.java: -------------------------------------------------------------------------------- 1 | package biz.nijhof.e53; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.net.Inet4Address; 5 | import java.net.Inet6Address; 6 | import java.net.InetAddress; 7 | import java.net.UnknownHostException; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Random; 11 | 12 | import android.app.PendingIntent; 13 | import android.content.Context; 14 | import android.text.TextUtils; 15 | import android.util.Base64; 16 | import biz.nijhof.e53.Base32.DecodingException; 17 | 18 | public class Element53DNS { 19 | 20 | public static final byte RECORD_RANDOM = 0; 21 | public static final byte RECORD_A = 1; 22 | public static final byte RECORD_NS = 2; 23 | public static final byte RECORD_CNAME = 5; 24 | public static final byte RECORD_NULL = 10; 25 | public static final byte RECORD_TEXT = 16; 26 | public static final byte RECORD_AAAA = 28; 27 | 28 | private final String[] _rcodes = { "", "FORMERR", "SERVFAIL", "NXDOMAIN", "NOTIMP", "REFUSED", "YXDOMAIN", "YXRRSET", "NXRRSET", "NOTAUTH", "NOTZONE" }; 29 | 30 | private byte _recordtype = RECORD_TEXT; 31 | private boolean _nostrictchecking = false; 32 | private boolean _debug = false; 33 | 34 | private Element53Signal _signal = null; 35 | private final Random _random = new Random(); 36 | 37 | public Element53DNS(Context context) { 38 | _signal = new Element53Signal(context); 39 | } 40 | 41 | public void setPI(PendingIntent pi) { 42 | _signal.setPI(pi); 43 | } 44 | 45 | public void setRecordType(byte recordtype) { 46 | _recordtype = recordtype; 47 | } 48 | 49 | public void setNoStrict(boolean nostrict) { 50 | _nostrictchecking = nostrict; 51 | } 52 | 53 | public void setDebug(boolean debug) { 54 | _debug = debug; 55 | } 56 | 57 | public byte getRecordType(boolean randomize) throws E53DNSException { 58 | if (randomize && _recordtype == RECORD_RANDOM) { 59 | int x = _random.nextInt(3); 60 | if (x == 0) 61 | return RECORD_CNAME; 62 | else if (x == 1) 63 | return RECORD_NULL; 64 | else if (x == 2) 65 | return RECORD_TEXT; 66 | else 67 | throw new E53DNSException("Random error"); 68 | } 69 | return (byte) _recordtype; 70 | } 71 | 72 | public String getRecordName() throws E53DNSException { 73 | if (_recordtype == RECORD_RANDOM) 74 | return "Random"; 75 | else if (_recordtype == RECORD_CNAME) 76 | return "CNAME"; 77 | else if (_recordtype == RECORD_NULL) 78 | return "NULL"; 79 | else if (_recordtype == RECORD_TEXT) 80 | return "TEXT"; 81 | else 82 | throw new E53DNSException(String.format("Unknown record type: %d", _recordtype)); 83 | } 84 | 85 | public byte[] encodeData(int id, byte[] message, String domain, boolean recursion) throws UnsupportedEncodingException, UnknownHostException, 86 | E53DNSException, InterruptedException { 87 | // Create payload 88 | String payload = Base32.encode(message); 89 | 90 | // Divide into parts 91 | int pparts = payload.length() / 63 + 1; 92 | int plen = payload.length() / pparts; 93 | int pindex = 0; 94 | String ppayload = ""; 95 | while (pindex < payload.length()) { 96 | if (pindex > 0) 97 | ppayload += "."; 98 | ppayload += payload.substring(pindex, Math.min(pindex + plen, payload.length())); 99 | pindex += plen; 100 | } 101 | 102 | // Build domain name 103 | String domainName = (ppayload.equals("") ? domain : String.format("%s.%s", ppayload, domain)); 104 | if (domainName.length() > 253) 105 | throw new E53DNSException(String.format("Domain name too long: %d bytes, reduce message size", domainName.length())); 106 | if (_debug) 107 | _signal.UILog(String.format("Sending ID %s query %s", id, domainName)); 108 | 109 | byte qr = 0; // 0 = query; 1 = response 110 | byte opcode = 0; // 0 = query 111 | byte aa = 0; // authorative answer 112 | byte tc = 0; // truncation flag 113 | byte rd = (byte) (recursion ? 1 : 0); // recursion desired 114 | byte ra = 0; // recursion available 115 | byte z = 0; // zero reserved 116 | byte rcode = 0; // 0 = no error 117 | 118 | byte[] sbuffer = new byte[512]; 119 | int index = 0; 120 | sbuffer[index++] = (byte) ((id >> 8) & 0xFF); 121 | sbuffer[index++] = (byte) (id & 0xFF); 122 | sbuffer[index++] = (byte) (qr << 7 | opcode << 3 | aa << 2 | tc << 1 | rd); 123 | sbuffer[index++] = (byte) (ra << 7 | z << 4 | rcode); 124 | 125 | // Question count 126 | sbuffer[index++] = 0; 127 | sbuffer[index++] = 1; 128 | 129 | // Answer count 130 | sbuffer[index++] = 0; 131 | sbuffer[index++] = 0; 132 | 133 | // NS count 134 | sbuffer[index++] = 0; 135 | sbuffer[index++] = 0; 136 | 137 | // Additional count 138 | sbuffer[index++] = 0; 139 | sbuffer[index++] = 0; 140 | 141 | // Query name 142 | String[] qname = domainName.split("\\."); 143 | for (int i = 0; i < qname.length; i++) { 144 | byte[] b = qname[i].getBytes("US-ASCII"); 145 | sbuffer[index++] = (byte) b.length; 146 | System.arraycopy(b, 0, sbuffer, index, b.length); 147 | index += b.length; 148 | } 149 | sbuffer[index++] = 0; 150 | 151 | // Query type 152 | sbuffer[index++] = 0; 153 | sbuffer[index++] = getRecordType(true); 154 | 155 | // Query class 156 | sbuffer[index++] = 0; 157 | sbuffer[index++] = 1; // 1 = INternet 158 | 159 | byte[] result = new byte[index]; 160 | System.arraycopy(sbuffer, 0, result, 0, index); 161 | return result; 162 | } 163 | 164 | public byte[] decodeData(int eid, byte[] data) throws UnsupportedEncodingException, E53DNSException, DecodingException, UnknownHostException { 165 | // Decode response 166 | DNSResult result = decodeResponse(eid, data, false); 167 | 168 | List lstMessages = new ArrayList(); 169 | 170 | // Sanity checks 171 | if (result.qr != 1) 172 | lstMessages.add("Expected response"); 173 | 174 | if (result.opcode != 0) 175 | lstMessages.add("Expected opcode query"); 176 | 177 | if (result.tc != 0) 178 | lstMessages.add("Packet truncated"); 179 | 180 | if (result.ra == 0) 181 | lstMessages.add("No recursion available"); 182 | 183 | String messages = TextUtils.join(", ", lstMessages); 184 | 185 | if (result.rcode == 0) { 186 | if (!messages.equals("")) 187 | if (_nostrictchecking) 188 | _signal.UILog(messages); 189 | else 190 | throw new E53DNSException(messages); 191 | } else 192 | throw new E53DNSException(String.format("rcode=%d (%s) %s", result.rcode, result.rtext, messages)); 193 | 194 | if (result.id != eid) { 195 | _signal.UILog(String.format("Received ID %d, expected ID %d", result.id, eid)); 196 | return null; 197 | } 198 | 199 | if (_debug) 200 | _signal.UILog(String.format("Received ID %d Q=%d A=%d NS=%d AR=%d", result.id, result.QRecord.size(), result.ANRecord.size(), 201 | result.NSRecord.size(), result.ARRecord.size())); 202 | 203 | // Check query 204 | if (result.QRecord.size() != 1) 205 | throw new E53DNSException(String.format("Expected one query response count=%d", result.QRecord.size())); 206 | 207 | if (!(result.QRecord.get(0).Prop.Type == RECORD_CNAME || result.QRecord.get(0).Prop.Type == RECORD_NULL || result.QRecord.get(0).Prop.Type == RECORD_TEXT) 208 | || result.QRecord.get(0).Prop.Class != 1) 209 | throw new E53DNSException(String.format("Question name=%s type=%d:%d", result.QRecord.get(0).Name.Name, result.QRecord.get(0).Prop.Class, 210 | result.QRecord.get(0).Prop.Type)); 211 | 212 | // Check answer 213 | if (result.ANRecord.size() != 1) 214 | throw new E53DNSException(String.format("Expected one answer count=%d", result.ANRecord.size())); 215 | if (result.ANRecord.get(0).Prop.Class != 1) 216 | throw new E53DNSException(String.format("Answer name=%s type=%d:%d", result.ANRecord.get(0).Name.Name, result.ANRecord.get(0).Prop.Class, 217 | result.ANRecord.get(0).Prop.Type)); 218 | 219 | if (!result.QRecord.get(0).Name.Name.equals(result.ANRecord.get(0).Name.Name)) 220 | throw new E53DNSException(String.format("Wrong answer name: %s <> %s", result.QRecord.get(0).Name.Name, result.ANRecord.get(0).Name.Name)); 221 | 222 | if (!(result.ANRecord.get(0).Prop.Type == RECORD_NULL || result.ANRecord.get(0).Prop.Type == RECORD_TEXT || result.ANRecord.get(0).Prop.Type == RECORD_CNAME)) 223 | throw new E53DNSException(String.format("Unknown answer name=%s type=%d:%d", result.ANRecord.get(0).Name.Name, result.ANRecord.get(0).Prop.Class, 224 | result.ANRecord.get(0).Prop.Type)); 225 | 226 | // Return decode data 227 | return result.ANRecord.get(0).Data; 228 | } 229 | 230 | public DNSResult decodeResponse(int eid, byte[] data, boolean all) throws UnsupportedEncodingException, E53DNSException, DecodingException, 231 | UnknownHostException { 232 | DNSResult result = new DNSResult(); 233 | 234 | int index = 0; 235 | result.id = (data[index++] & 0xFF) * 256 + (data[index++] & 0xFF); 236 | 237 | result.qr = (data[index] >> 7) & 1; 238 | result.opcode = (data[index] >> 3) & 0x0F; 239 | result.aa = (data[index] >> 2) & 1; 240 | result.tc = (data[index] >> 1) & 1; 241 | result.rd = data[index] & 1; 242 | index++; 243 | 244 | result.ra = (data[index] >> 7) & 1; 245 | result.rcode = data[index] & 0x0F; 246 | index++; 247 | 248 | if (result.rcode <= _rcodes.length) 249 | result.rtext = _rcodes[result.rcode]; 250 | else 251 | result.rtext = String.format("Error %d", result.rcode); 252 | 253 | int qdcount = (data[index++] & 0xFF) * 256 + (data[index++] & 0xFF); 254 | int ancount = (data[index++] & 0xFF) * 256 + (data[index++] & 0xFF); 255 | int nscount = (data[index++] & 0xFF) * 256 + (data[index++] & 0xFF); 256 | int arcount = (data[index++] & 0xFF) * 256 + (data[index++] & 0xFF); 257 | 258 | result.QRecord = new ArrayList(); 259 | result.ANRecord = new ArrayList(); 260 | result.NSRecord = new ArrayList(); 261 | result.ARRecord = new ArrayList(); 262 | 263 | // Question 264 | for (int i = 0; i < qdcount; i++) { 265 | DNSRecord qRecord = new DNSRecord(); 266 | qRecord.Name = decodeName(data, index); 267 | index = qRecord.Name.Index; 268 | qRecord.Prop = new DNSProp(); 269 | qRecord.Prop.Type = (data[index++] & 0xFF) * 256 + (data[index++] & 0xFF); 270 | qRecord.Prop.Class = (data[index++] & 0xFF) * 256 + (data[index++] & 0xFF); 271 | result.QRecord.add(qRecord); 272 | } 273 | 274 | // Decode answer section 275 | for (int i = 0; i < ancount; i++) { 276 | DNSRecord anRecord = new DNSRecord(); 277 | anRecord.Name = decodeName(data, index); 278 | index = anRecord.Name.Index; 279 | anRecord.Prop = decodeType(data, index); 280 | index = anRecord.Prop.Index; 281 | 282 | // Decode data 283 | if (anRecord.Prop.Type == RECORD_NULL) { 284 | int rdatalen = (data[index] & 0xFF); 285 | anRecord.Data = new byte[rdatalen]; 286 | System.arraycopy(data, index + 1, anRecord.Data, 0, rdatalen); 287 | } else if (anRecord.Prop.Type == RECORD_TEXT) { 288 | int rdatalen = (data[index] & 0xFF); 289 | String r_data = new String(data, index + 1, rdatalen, "US-ASCII"); 290 | anRecord.Data = Base64.decode(r_data, Base64.DEFAULT | Base64.NO_WRAP); 291 | } else if (anRecord.Prop.Type == RECORD_CNAME) { 292 | String r_data = decodeName(data, index).Name; 293 | r_data = r_data.replace(".", ""); 294 | anRecord.Data = Base32.decode(r_data); 295 | } 296 | 297 | result.ANRecord.add(anRecord); 298 | index += anRecord.Prop.Len; 299 | } 300 | 301 | if (all) { 302 | // Name servers 303 | for (int ns = 0; ns < nscount; ns++) { 304 | DNSRecord nsRecord = new DNSRecord(); 305 | nsRecord.Name = decodeName(data, index); 306 | index = nsRecord.Name.Index; 307 | nsRecord.Prop = decodeType(data, index); 308 | index = nsRecord.Prop.Index; 309 | 310 | if (nsRecord.Prop.Type == RECORD_NS && nsRecord.Prop.Class == 1) 311 | nsRecord.Content = decodeName(data, index).Name; 312 | else 313 | _signal.UILog(String.format("Skipping NS record name=%s type=%d:%d", nsRecord.Name.Name, nsRecord.Prop.Class, nsRecord.Prop.Type)); 314 | 315 | result.NSRecord.add(nsRecord); 316 | index += nsRecord.Prop.Len; 317 | } 318 | 319 | // Additional 320 | for (int ar = 0; ar < arcount; ar++) { 321 | DNSRecord arRecord = new DNSRecord(); 322 | arRecord.Name = decodeName(data, index); 323 | index = arRecord.Name.Index; 324 | arRecord.Prop = decodeType(data, index); 325 | index = arRecord.Prop.Index; 326 | 327 | if (arRecord.Prop.Type == RECORD_A && arRecord.Prop.Class == 1) { 328 | byte[] rawAddress = new byte[4]; 329 | System.arraycopy(data, index, rawAddress, 0, 4); 330 | arRecord.Address = Inet4Address.getByAddress(rawAddress); 331 | 332 | } else if (arRecord.Prop.Type == RECORD_AAAA && arRecord.Prop.Class == 1) { 333 | byte[] rawAddress = new byte[16]; 334 | System.arraycopy(data, index, rawAddress, 0, 16); 335 | arRecord.Address = Inet6Address.getByAddress(rawAddress); 336 | } else 337 | _signal.UILog(String.format("Skipping AR record name=%s type=%d:%d", arRecord.Name.Name, arRecord.Prop.Class, arRecord.Prop.Type)); 338 | 339 | result.ARRecord.add(arRecord); 340 | index += arRecord.Prop.Len; 341 | } 342 | } 343 | 344 | return result; 345 | } 346 | 347 | private DNSName decodeName(byte[] data, int index) throws UnsupportedEncodingException { 348 | DNSName name = new DNSName(); 349 | name.Name = ""; 350 | int len = (data[index++] & 0xFF); 351 | while (len > 0) { 352 | if (!name.Name.equals("")) 353 | name.Name += "."; 354 | if ((len & 0xC0) == 0) { 355 | name.Name += new String(data, index, len, "US-ASCII"); 356 | index += len; 357 | len = (data[index++] & 0xFF); 358 | } else { 359 | // Compression 360 | int ptr = (len & 0x3F) * 256 + (data[index++] & 0xFF); 361 | name.Name += decodeName(data, ptr).Name; 362 | break; 363 | } 364 | } 365 | name.Index = index; 366 | return name; 367 | } 368 | 369 | private DNSProp decodeType(byte[] data, int index) { 370 | DNSProp type = new DNSProp(); 371 | type.Type = (data[index++] & 0xFF) * 256 + (data[index++] & 0xFF); 372 | type.Class = (data[index++] & 0xFF) * 256 + (data[index++] & 0xFF); 373 | type.TTL = (data[index++] & 0xFF) * 256 * 256 * 256 + (data[index++] & 0xFF) * 256 * 256 + (data[index++] & 0xFF) * 256 + (data[index++] & 0xFF); 374 | type.Len = (data[index++] & 0xFF) * 256 + (data[index++] & 0xFF); 375 | type.Index = index; 376 | return type; 377 | } 378 | 379 | public class DNSName { 380 | public String Name; 381 | public int Index; 382 | } 383 | 384 | public class DNSProp { 385 | public int Type; 386 | public int Class; 387 | public int TTL; 388 | public int Len; 389 | public int Index; 390 | } 391 | 392 | public class DNSRecord { 393 | public DNSName Name; 394 | public DNSProp Prop; 395 | public byte[] Data; 396 | public InetAddress Address; 397 | public String Content; 398 | } 399 | 400 | public class DNSResult { 401 | public int id; 402 | public int qr; 403 | public int opcode; 404 | public int aa; 405 | public int tc; 406 | public int rd; 407 | public int ra; 408 | public int rcode; 409 | public String rtext; 410 | public List QRecord; 411 | public List ANRecord; 412 | public List NSRecord; 413 | public List ARRecord; 414 | } 415 | 416 | public class E53DNSException extends Exception { 417 | private static final long serialVersionUID = 8123L; 418 | 419 | public E53DNSException(String message) { 420 | super(message); 421 | } 422 | } 423 | } 424 | -------------------------------------------------------------------------------- /src/biz/nijhof/e53/Element53PagerAdapter.java: -------------------------------------------------------------------------------- 1 | package biz.nijhof.e53; 2 | 3 | import java.util.List; 4 | import android.os.Parcelable; 5 | import android.support.v4.view.PagerAdapter; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | public class Element53PagerAdapter extends PagerAdapter { 10 | 11 | private List mListView; 12 | 13 | Element53PagerAdapter(List list) { 14 | this.mListView = list; 15 | } 16 | 17 | @Override 18 | public void destroyItem(View arg0, int arg1, Object arg2) { 19 | ((ViewGroup) arg0).removeView(mListView.get(arg1)); 20 | } 21 | 22 | @Override 23 | public void finishUpdate(View arg0) { 24 | } 25 | 26 | @Override 27 | public int getCount() { 28 | return mListView.size(); 29 | } 30 | 31 | @Override 32 | public Object instantiateItem(View arg0, int arg1) { 33 | ((ViewGroup) arg0).addView(mListView.get(arg1), 0); 34 | return mListView.get(arg1); 35 | } 36 | 37 | @Override 38 | public boolean isViewFromObject(View arg0, Object arg1) { 39 | return arg0 == (arg1); 40 | } 41 | 42 | @Override 43 | public void restoreState(Parcelable arg0, ClassLoader arg1) { 44 | } 45 | 46 | @Override 47 | public Parcelable saveState() { 48 | return null; 49 | } 50 | 51 | @Override 52 | public void startUpdate(View arg0) { 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/biz/nijhof/e53/Element53Service.java: -------------------------------------------------------------------------------- 1 | package biz.nijhof.e53; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.BufferedReader; 5 | import java.io.DataInputStream; 6 | import java.io.IOException; 7 | import java.io.InputStreamReader; 8 | import java.net.DatagramPacket; 9 | import java.net.DatagramSocket; 10 | import java.net.Inet4Address; 11 | import java.net.InetAddress; 12 | import java.net.ServerSocket; 13 | import java.net.Socket; 14 | import java.net.SocketTimeoutException; 15 | import java.net.UnknownHostException; 16 | import java.util.ArrayList; 17 | import java.util.Date; 18 | import java.util.HashMap; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.Queue; 22 | import java.util.Random; 23 | import java.util.concurrent.ConcurrentHashMap; 24 | import java.util.concurrent.ConcurrentLinkedQueue; 25 | import java.util.concurrent.TimeoutException; 26 | 27 | import com.stericson.RootTools.RootTools; 28 | import com.stericson.RootTools.RootToolsException; 29 | 30 | import biz.nijhof.e53.Base32.DecodingException; 31 | import biz.nijhof.e53.Element53DNS.DNSRecord; 32 | import biz.nijhof.e53.Element53DNS.DNSResult; 33 | import biz.nijhof.e53.Element53DNS.E53DNSException; 34 | import biz.nijhof.e53.Preferences; 35 | 36 | import android.app.Notification; 37 | import android.app.NotificationManager; 38 | import android.app.PendingIntent; 39 | import android.app.PendingIntent.CanceledException; 40 | import android.app.Service; 41 | import android.content.BroadcastReceiver; 42 | import android.content.Context; 43 | import android.content.Intent; 44 | import android.content.IntentFilter; 45 | import android.content.SharedPreferences; 46 | import android.net.ConnectivityManager; 47 | import android.net.DhcpInfo; 48 | import android.net.NetworkInfo; 49 | import android.net.wifi.WifiInfo; 50 | import android.net.wifi.WifiManager; 51 | import android.os.Binder; 52 | import android.os.Build; 53 | import android.os.IBinder; 54 | import android.os.PowerManager; 55 | import android.preference.PreferenceManager; 56 | import android.util.Log; 57 | import android.provider.Settings.Secure; 58 | 59 | public class Element53Service extends Service { 60 | private static final int PROTOCOL_VERSION = 1; 61 | 62 | private int _client = 0; 63 | private String _tunnelDomain = null; 64 | private String _networkType = "Wifi"; 65 | private String _dnsServer = null; 66 | private String _gateway = null; 67 | private boolean _routedns = false; 68 | private boolean _probeclient = false; 69 | private boolean _probeserver = false; 70 | private int _maxprobes = 3; 71 | private int _clientmsgsize = 128; 72 | private int _servermsgsize = 128; 73 | private int _maxresends = 20; 74 | private int _minidlewait = 100; 75 | private int _maxidlewait = 3200; 76 | private int _requestwait = 800; // milliseconds 77 | private int _receivetimeout = 1500; // milliseconds 78 | private int _receivewait = 800; // milliseconds 79 | private int _errorwait = 1500; // milliseconds 80 | private int _localPort = 3128; 81 | private int _notifyinterval = 1000; // milliseconds 82 | private int _roottimeout = 10000; // milliseconds 83 | private boolean _rootaccess = false; 84 | private boolean _nocrcchecking = false; 85 | private boolean _experimental = false; 86 | private boolean _debug = false; 87 | private boolean _checkconnection = false; 88 | private int _rxchan = 0; 89 | private int _rxqueue = 0; 90 | 91 | private SharedPreferences _preferences = null; 92 | private ConnectivityManager _connectivityManager = null; 93 | private WifiManager _wifiManager = null; 94 | private NotificationManager _notificationManager = null; 95 | private PowerManager _powerManager = null; 96 | private PowerManager.WakeLock _wakeLock = null; 97 | private Element53Signal _signal = null; 98 | 99 | private final Random _random = new Random(); 100 | 101 | private Thread _dnsThread = null; 102 | private Thread _serverThread = null; 103 | private ServerSocket _serverSocket = null; 104 | private int _lastChannel = 0; 105 | 106 | private PendingIntent _intentBack = null; 107 | private Notification _notification = null; 108 | private PendingIntent _pi = null; 109 | 110 | private Element53DNS _dnsHelper = null; 111 | 112 | // Pro/lit 113 | private boolean _isTrial = false; 114 | private static final int MAXTRIALBYTES = 10 * 1024 * 1024; // 10 Megabytes 115 | 116 | private class Msg { 117 | public int Client; 118 | public int Seq; 119 | public int Channel; 120 | public int Control; 121 | public byte[] Data; 122 | 123 | public static final int OOB_RESET = 1; 124 | public static final int OOB_QUIT = 2; 125 | public static final int OOB_PROBE_CLIENT = 3; 126 | public static final int OOB_PROBE_SERVER = 4; 127 | public static final int OOB_SET_SIZE = 5; 128 | 129 | public static final int CONTROL_DATA = 0; 130 | public static final int CONTROL_CLOSE = 1; 131 | public static final int CONTROL_OPEN = 2; 132 | public static final int CONTROL_STATUS = 3; 133 | 134 | Msg() { 135 | this.Client = _client; 136 | this.Seq = 0; 137 | this.Channel = 0; 138 | this.Control = 0; 139 | this.Data = new byte[0]; 140 | } 141 | 142 | Msg(int seq, int channel, int control, byte[] data) { 143 | this.Client = _client; 144 | this.Seq = seq; 145 | this.Channel = channel; 146 | this.Control = control; 147 | this.Data = data; 148 | } 149 | } 150 | 151 | public class E53Exception extends Exception { 152 | private static final long serialVersionUID = 8123L; 153 | 154 | public E53Exception(String message) { 155 | super(message); 156 | } 157 | } 158 | 159 | private Map _mapChannelSocket = new ConcurrentHashMap(); 160 | private Queue _queue = new ConcurrentLinkedQueue(); 161 | 162 | private Msg getMsg() { 163 | return (_queue.isEmpty() ? new Msg() : _queue.remove()); 164 | } 165 | 166 | private int GetChannel() throws E53Exception { 167 | int i = 0; 168 | while (true) { 169 | _lastChannel++; 170 | if (_lastChannel > 255) 171 | _lastChannel = 1; 172 | if (!_mapChannelSocket.containsKey(Integer.toString(_lastChannel))) 173 | return _lastChannel; 174 | if (++i > 255) 175 | throw new E53Exception("No channels available"); 176 | } 177 | } 178 | 179 | public class LocalBinder extends Binder { 180 | Element53Service getService() { 181 | return Element53Service.this; 182 | } 183 | 184 | void setPI(PendingIntent pi) { 185 | _pi = pi; 186 | _dnsHelper.setPI(pi); 187 | _signal.setPI(pi); 188 | } 189 | } 190 | 191 | @Override 192 | public IBinder onBind(Intent intent) { 193 | return mBinder; 194 | } 195 | 196 | private final IBinder mBinder = new LocalBinder(); 197 | 198 | private boolean _broadcastReceiverRegistered = false; 199 | 200 | private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 201 | @Override 202 | public void onReceive(Context context, Intent intent) { 203 | 204 | if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { 205 | _checkconnection = true; 206 | NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); 207 | if (info != null) 208 | _signal.UILog(String.format("Wifi state: %s", info.getDetailedState())); 209 | } 210 | 211 | else if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) { 212 | _checkconnection = true; 213 | NetworkInfo info = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); 214 | if (info != null) 215 | _signal.UILog(String.format("Network %s %s state: %s", info.getTypeName(), info.getSubtypeName(), info.getDetailedState())); 216 | } 217 | } 218 | }; 219 | 220 | @Override 221 | public void onCreate() { 222 | // References 223 | _notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); 224 | _preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); 225 | _wifiManager = (WifiManager) getSystemService(WIFI_SERVICE); 226 | _connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); 227 | _powerManager = (PowerManager) getSystemService(POWER_SERVICE); 228 | _dnsHelper = new Element53DNS(getApplicationContext()); 229 | _signal = new Element53Signal(getApplicationContext()); 230 | } 231 | 232 | @Override 233 | public int onStartCommand(Intent intent, int flags, int startId) { 234 | return START_STICKY; 235 | } 236 | 237 | @Override 238 | public void onDestroy() { 239 | stop(); 240 | } 241 | 242 | public void start() { 243 | // Acquire wake lock 244 | _wakeLock = _powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "e53"); 245 | _wakeLock.acquire(); 246 | 247 | // Build notification 248 | Intent toLaunch = new Intent(getApplicationContext(), Element53Activity.class); 249 | toLaunch.setAction("android.intent.action.MAIN"); 250 | toLaunch.addCategory("android.intent.category.LAUNCHER"); 251 | toLaunch.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); 252 | 253 | _intentBack = PendingIntent.getActivity(getApplicationContext(), 0, toLaunch, PendingIntent.FLAG_UPDATE_CURRENT); 254 | 255 | String text = getText(R.string.Running).toString(); 256 | _notification = new Notification(R.drawable.ic_launcher, text, System.currentTimeMillis()); 257 | _notification.setLatestEventInfo(getApplicationContext(), getText(R.string.app_name), getText(R.string.Running), _intentBack); 258 | 259 | IntentFilter iff = new IntentFilter(); 260 | iff.addAction("android.net.wifi.STATE_CHANGE"); 261 | iff.addAction("android.net.conn.CONNECTIVITY_CHANGE"); 262 | this.registerReceiver(this.mBroadcastReceiver, iff); 263 | _broadcastReceiverRegistered = true; 264 | 265 | // Start foreground service 266 | startForeground(8123, _notification); 267 | 268 | // Start tunnel 269 | new Thread(new Runnable() { 270 | public void run() { 271 | startTunnel(); 272 | } 273 | }).start(); 274 | } 275 | 276 | public void stop() { 277 | // Start shutdown 278 | new Thread(new Runnable() { 279 | public void run() { 280 | stopTunnel(); 281 | } 282 | }).start(); 283 | } 284 | 285 | private void startTunnel() { 286 | try { 287 | // Get (default) settings 288 | getSettings(); 289 | 290 | // Start server thread 291 | _serverThread = new Thread(new Runnable() { 292 | public void run() { 293 | try { 294 | // Wait for DNS reset to complete 295 | synchronized (_serverThread) { 296 | _serverThread.wait(); 297 | } 298 | 299 | // Start accepting connections 300 | _serverSocket = new ServerSocket(_localPort); 301 | String serverType = (_tunnelDomain.endsWith(".nijhof.biz") ? "SOCKS5" : "client"); 302 | _signal.UILog(String.format("Accepting %s connections on localhost:%d", serverType, _localPort)); 303 | _signal.reportConnected(); 304 | 305 | while (Thread.currentThread() == _serverThread) 306 | try { 307 | // Wait for new client to connect 308 | final Socket clientsocket = _serverSocket.accept(); 309 | if (clientsocket.isConnected()) { 310 | // Assign channel 311 | final int channel = GetChannel(); 312 | _signal.UILog(String.format("Client connection from %s:%d, channel=%d", clientsocket.getInetAddress().getHostAddress(), 313 | clientsocket.getPort(), channel)); 314 | 315 | // Start receiving from socket/channel 316 | new Thread(new Runnable() { 317 | public void run() { 318 | try { 319 | // Create channel 320 | final String ch = Integer.toString(channel); 321 | _mapChannelSocket.put(ch, clientsocket); 322 | _queue.add(new Msg(0, channel, Msg.CONTROL_OPEN, new byte[0])); 323 | 324 | byte[] buffer = new byte[_clientmsgsize]; 325 | DataInputStream ds = new DataInputStream(_mapChannelSocket.get(ch).getInputStream()); 326 | BufferedInputStream in = new BufferedInputStream(ds); 327 | while (true) { 328 | // Exit when channel removed 329 | if (!_mapChannelSocket.containsKey(ch)) { 330 | _signal.UILog(String.format("Stopping receive for channel %d", channel)); 331 | break; 332 | } 333 | 334 | // Check if socket was 335 | // closed 336 | if (_mapChannelSocket.get(ch).isClosed()) 337 | throw new E53Exception(String.format("Socket for channel %d was closed", channel)); 338 | 339 | // Receive data 340 | if (in.available() > 0) { 341 | int bytes = in.read(buffer); 342 | 343 | // Queue data to 344 | // transmit 345 | byte[] data = new byte[bytes]; 346 | System.arraycopy(buffer, 0, data, 0, bytes); 347 | _queue.add(new Msg(0, channel, Msg.CONTROL_DATA, data)); 348 | 349 | // Wake-up DNS thread 350 | if (_dnsThread != null) 351 | synchronized (_dnsThread) { 352 | _dnsThread.notify(); 353 | } 354 | 355 | _receivewait /= 2; 356 | if (_receivewait < _minidlewait) 357 | _receivewait = _minidlewait; 358 | } else { 359 | _receivewait *= 2; 360 | if (_receivewait > _maxidlewait) 361 | _receivewait = _maxidlewait; 362 | Thread.sleep(_receivewait); 363 | } 364 | } 365 | } catch (Exception ex) { 366 | _signal.UILog(String.format("Error receiving from channel %d: %s", channel, ex)); 367 | stackTrace(ex); 368 | clientCloseChannel(channel); 369 | } 370 | } 371 | }).start(); 372 | } else 373 | throw new E53Exception("Client socket not connected"); 374 | } catch (Exception ex) { 375 | _signal.UILog(String.format("Error accepting socket connection: %s", ex)); 376 | stackTrace(ex); 377 | Thread.sleep(_errorwait); 378 | } 379 | } catch (Exception ex) { 380 | _signal.UILog(String.format("Error creating server socket: %s", ex)); 381 | stackTrace(ex); 382 | } 383 | } 384 | }); 385 | _serverThread.start(); 386 | 387 | // Start DNS thread 388 | _dnsThread = new Thread(new Runnable() { 389 | public void run() { 390 | int seq = 0; 391 | int resends = 0; 392 | Msg msg = new Msg(); 393 | boolean confirmed = true; 394 | boolean sent = false; 395 | boolean received = false; 396 | boolean control = false; 397 | long latencyCumm = 0; 398 | long latencyCount = 0; 399 | int requests = 0; 400 | int bytesSent = 0; 401 | int bytesReceived = 0; 402 | int totalBytesSent = 0; 403 | int totalBytesReceived = 0; 404 | int errors = 0; 405 | int resets = 0; 406 | Date start = new Date(); 407 | Date lastNotify = new Date(); 408 | 409 | try { 410 | // Create UDP DNS socket 411 | DatagramSocket dgs = new DatagramSocket(); 412 | dgs.setSoTimeout(_receivetimeout); 413 | 414 | // Wait for network state events 415 | Thread.sleep(_receivetimeout); 416 | 417 | // Resolve DNS server address 418 | InetAddress dnsAddress = checkConnectivity(); 419 | 420 | // Reset tunnel 421 | int retry = 1; 422 | while (true) 423 | try { 424 | retry++; 425 | resetTunnel(dgs, dnsAddress); 426 | break; 427 | } catch (Exception ex) { 428 | _signal.UILog(String.format("Reset: %s retry=%d", ex, retry)); 429 | if (_debug) 430 | ex.printStackTrace(); 431 | if (retry <= _maxresends) { 432 | if (!ex.getClass().equals(SocketTimeoutException.class)) 433 | Thread.sleep(_errorwait); 434 | } else { 435 | _signal.UILog("Giving up"); 436 | _signal.reportError(); 437 | throw ex; 438 | } 439 | } 440 | 441 | // Probe message sizes 442 | probeTunnel(dgs, dnsAddress); 443 | _signal.reportSize(_dnsHelper.getRecordName(), _clientmsgsize, _servermsgsize); 444 | 445 | // Enable server thread 446 | synchronized (_serverThread) { 447 | _serverThread.notify(); 448 | } 449 | 450 | // Start communicating 451 | while (Thread.currentThread() == _dnsThread) 452 | try { 453 | if (_checkconnection) { 454 | dnsAddress = checkConnectivity(); 455 | probeTunnel(dgs, dnsAddress); 456 | _signal.reportSize(_dnsHelper.getRecordName(), _clientmsgsize, _servermsgsize); 457 | } 458 | 459 | // Nothing happened, wait increasing time 460 | if (!sent && !received && !control && confirmed) { 461 | _requestwait *= 2; 462 | if (_requestwait > _maxidlewait) 463 | _requestwait = _maxidlewait; 464 | synchronized (_dnsThread) { 465 | _dnsThread.wait(_requestwait); 466 | } 467 | } else { 468 | _requestwait /= 2; 469 | if (_requestwait < _minidlewait) 470 | _requestwait = _minidlewait; 471 | } 472 | 473 | if (confirmed) { 474 | // Get next sequence 475 | seq = (seq == 255 ? 1 : seq + 1); 476 | 477 | // Get next message 478 | msg = getMsg(); 479 | confirmed = false; 480 | resends = 0; 481 | } else { 482 | // Not confirmed 483 | resends++; 484 | if (resends <= _maxresends) 485 | _signal.UILog(String.format("Resending seq=%d try=%d", seq, resends)); 486 | else { 487 | // Too many resends: reset tunnel 488 | _signal.reportReset(); 489 | resetTunnel(dgs, dnsAddress); 490 | 491 | // Probe again, network could be changed 492 | probeTunnel(dgs, dnsAddress); 493 | _signal.reportSize(_dnsHelper.getRecordName(), _clientmsgsize, _servermsgsize); 494 | 495 | // Reset data 496 | seq = 0; 497 | resends = 0; 498 | confirmed = true; 499 | sent = false; 500 | received = false; 501 | control = false; 502 | bytesSent = 0; 503 | bytesReceived = 0; 504 | resets++; 505 | start = new Date(); 506 | continue; 507 | } 508 | } 509 | 510 | // Generate random request ID 511 | int id = _random.nextInt() & 0xFFFF; 512 | 513 | // Encode request 514 | msg.Seq = seq; 515 | byte[] rawMessage = composeRawMessage(msg); 516 | byte[] pdata = _dnsHelper.encodeData(id, rawMessage, _tunnelDomain, true); 517 | DatagramPacket packet = new DatagramPacket(pdata, pdata.length, dnsAddress, 53); 518 | 519 | // Send request 520 | dgs.send(packet); 521 | sent = (msg.Data.length > 0); 522 | requests++; 523 | Date sentTime = new Date(); 524 | 525 | // Receive response 526 | int seqErrors = 0; 527 | while (true) { 528 | // Receive packet 529 | byte[] rbuffer = new byte[512]; 530 | DatagramPacket rpacket = new DatagramPacket(rbuffer, rbuffer.length); 531 | dgs.receive(rpacket); 532 | 533 | // Decode response 534 | byte[] rmessage = _dnsHelper.decodeData(id, rpacket.getData()); 535 | if (rmessage == null) 536 | continue; // Invalid ID 537 | 538 | // Calculate latency 539 | latencyCount++; 540 | latencyCumm += new Date().getTime() - sentTime.getTime(); 541 | 542 | // Decode message 543 | Msg rmsg = decomposeRawMessage(rmessage); 544 | 545 | // Update state 546 | confirmed = (rmsg.Seq == seq); 547 | received = (rmsg.Data.length > 0); 548 | control = (rmsg.Control > 0); 549 | 550 | // Sanity check 551 | if (rmsg.Client != _client) 552 | throw new E53Exception(String.format("Message for client %d, we are %d", rmsg.Client, _client)); 553 | 554 | // Check state 555 | if (confirmed) { 556 | // Update statistics 557 | bytesSent += msg.Data.length; 558 | bytesReceived += rmsg.Data.length; 559 | totalBytesSent += msg.Data.length; 560 | totalBytesReceived += rmsg.Data.length; 561 | 562 | // Process received data 563 | if (rmsg.Control == Msg.CONTROL_DATA) { 564 | serverData(rmsg); 565 | } else if (rmsg.Control == Msg.CONTROL_CLOSE) 566 | serverCloseChannel(rmsg.Channel); 567 | else if (rmsg.Control == Msg.CONTROL_STATUS) { 568 | _rxchan = (rmsg.Data[0] & 0xFF) * 256 + (rmsg.Data[1] & 0xFF); 569 | _rxqueue = (rmsg.Data[2] & 0xFF) * 256 + (rmsg.Data[3] & 0xFF); 570 | } else 571 | throw new E53Exception(String.format("Unknown control message %d", rmsg.Control)); 572 | 573 | // Successfully received response 574 | break; 575 | } else { 576 | seqErrors++; 577 | _signal.UILog(String.format("Sequence error, received %d expected %d try %d", rmsg.Seq, seq, seqErrors)); 578 | 579 | // Too many sequence errors? 580 | if (seqErrors >= _maxresends) 581 | throw new E53Exception("Too many sequence errors"); 582 | 583 | // Retry 584 | continue; 585 | } 586 | } 587 | 588 | // Update notification 589 | int notifyDelta = (int) (new Date().getTime() - lastNotify.getTime()); 590 | if (notifyDelta > _notifyinterval) { 591 | lastNotify = new Date(); 592 | bytesSent = bytesSent * 1000 / notifyDelta; 593 | bytesReceived = bytesReceived * 1000 / notifyDelta; 594 | float latency = (latencyCount == 0 ? 0f : ((float) latencyCumm / latencyCount)); 595 | updateNotification(start, seq, requests, latency, errors, resets, bytesSent, bytesReceived, totalBytesSent, 596 | totalBytesReceived); 597 | bytesSent = 0; 598 | bytesReceived = 0; 599 | } 600 | 601 | } catch (Exception ex) { 602 | errors++; 603 | _signal.UILog(String.format("Error communicating: %s", ex)); 604 | stackTrace(ex); 605 | 606 | if (!ex.getClass().equals(SocketTimeoutException.class)) 607 | Thread.sleep(_errorwait); 608 | } 609 | 610 | // Send quit message 611 | quitTunnel(dgs, dnsAddress); 612 | } catch (Exception ex) { 613 | _signal.UILog(String.format("Connectivity error: %s", ex)); 614 | stackTrace(ex); 615 | } 616 | } 617 | }); 618 | _dnsThread.start(); 619 | 620 | } catch (Exception ex) { 621 | _signal.UILog(String.format("Error: %s", ex)); 622 | stackTrace(ex); 623 | _signal.reportError(); 624 | } finally { 625 | _signal.reportStarted(); 626 | } 627 | } 628 | 629 | private void stopTunnel() { 630 | try { 631 | // Unregister broadcast receiver 632 | if (_broadcastReceiverRegistered) 633 | try { 634 | this.unregisterReceiver(this.mBroadcastReceiver); 635 | } catch (Exception ex) { 636 | } finally { 637 | _broadcastReceiverRegistered = false; 638 | } 639 | 640 | // Terminate DNS loop 641 | if (_dnsThread != null) { 642 | Thread tmp = _dnsThread; 643 | _dnsThread = null; 644 | try { 645 | tmp.interrupt(); 646 | } catch (Exception ex) { 647 | } 648 | } 649 | 650 | // Close server socket 651 | if (_serverSocket != null) 652 | try { 653 | _serverSocket.close(); 654 | } catch (Exception ex) { 655 | } finally { 656 | _serverSocket = null; 657 | } 658 | 659 | // Terminate server loop 660 | if (_serverThread != null) { 661 | Thread tmp = _serverThread; 662 | _serverThread = null; 663 | try { 664 | tmp.interrupt(); 665 | } catch (Exception ex) { 666 | } 667 | } 668 | 669 | // Close sockets/channels 670 | try { 671 | closeAllChannels(); 672 | } catch (Exception ex) { 673 | } 674 | 675 | // Undo routing 676 | if (_routedns) 677 | try { 678 | delRoute(_gateway, _dnsServer); 679 | } catch (Exception ex) { 680 | } 681 | _gateway = null; 682 | _dnsServer = null; 683 | 684 | // Dispose notification 685 | _notification = null; 686 | 687 | // End foreground service 688 | stopForeground(true); 689 | 690 | // Release wake lock 691 | if (_wakeLock != null) { 692 | _wakeLock.release(); 693 | _wakeLock = null; 694 | } 695 | } finally { 696 | _signal.reportStopped(); 697 | } 698 | 699 | } 700 | 701 | private void getSettings() throws E53Exception, CanceledException, DecodingException, E53DNSException { 702 | // Get tunnel domain 703 | _tunnelDomain = _preferences.getString(Preferences.PREF_TUNNELDOMAIN, ""); 704 | if (_tunnelDomain.equals("")) 705 | _tunnelDomain = "t.nijhof.biz"; 706 | _signal.UILog(String.format("Tunnel domain: %s", _tunnelDomain)); 707 | 708 | // Get network type 709 | _networkType = _preferences.getString(Preferences.PREF_NETWORKTYPE, ""); 710 | if (_networkType.equals("")) 711 | _networkType = "Wifi"; 712 | _signal.UILog(String.format("Network type: %s", _networkType)); 713 | 714 | // Get DNS record type 715 | byte recordtype = (byte) Integer.parseInt(_preferences.getString(Preferences.PREF_RECORDTYPE, Integer.toString(Element53DNS.RECORD_TEXT))); 716 | _dnsHelper.setRecordType(recordtype); 717 | _signal.UILog(String.format("Record type: %s", _dnsHelper.getRecordName())); 718 | 719 | // Calculate maximum message sizes 720 | int clientheadersize = 6; 721 | int serverheadersize = 5; 722 | int subdomainlength = 255 - _tunnelDomain.length() - 1; 723 | int maxclientmsgsize = (subdomainlength - subdomainlength / 64 - 1) * 5 / 8 - clientheadersize; 724 | 725 | int packetleft = 512 - (6 * 2 + 255 + 5 * 2 + 4 + 2); 726 | 727 | if (recordtype == Element53DNS.RECORD_RANDOM || recordtype == Element53DNS.RECORD_CNAME) 728 | packetleft = Math.min(255 - 5, packetleft - 5) * 5 / 8; 729 | else if (recordtype == Element53DNS.RECORD_NULL) 730 | packetleft = Math.min(255, packetleft - 1) * 8 / 8; 731 | else if (recordtype == Element53DNS.RECORD_TEXT) 732 | packetleft = Math.min(255, packetleft - 1) * 6 / 8; 733 | int maxservermsgsize = packetleft - serverheadersize; 734 | 735 | _signal.UILog(String.format("Max message size client/server %d/%d bytes", maxclientmsgsize, maxservermsgsize)); 736 | 737 | // Get message sizes 738 | String cmsg = _preferences.getString(Preferences.PREF_CLIENTMSGSIZE, ""); 739 | String smsg = _preferences.getString(Preferences.PREF_SERVERMSGSIZE, ""); 740 | _clientmsgsize = (cmsg.equals("") ? 0 : Integer.parseInt(cmsg)); 741 | _servermsgsize = (smsg.equals("") ? (recordtype == Element53DNS.RECORD_RANDOM ? 100 : 0) : Integer.parseInt(smsg)); 742 | _probeclient = (_clientmsgsize <= 0); 743 | _probeserver = (_servermsgsize <= 0); 744 | if (!_probeclient && !_probeserver) { 745 | _signal.UILog(String.format("Client message size is %d bytes", _clientmsgsize)); 746 | _signal.UILog(String.format("Server message size is %d bytes", _servermsgsize)); 747 | 748 | // Check message sizes 749 | if (_clientmsgsize < 1 || _clientmsgsize > maxclientmsgsize) 750 | throw new E53Exception("Client message size too small/large"); 751 | if (_servermsgsize < 1 || _servermsgsize > maxservermsgsize) 752 | throw new E53Exception("Server message size too small/large"); 753 | } 754 | // Get maximum number of re-sends 755 | String sMaxResends = _preferences.getString(Preferences.PREF_MAXRESENDS, ""); 756 | _maxresends = (sMaxResends.equals("") ? 20 : Integer.parseInt(sMaxResends)); 757 | _signal.UILog(String.format("Maximum number of resends is %d", _maxresends)); 758 | 759 | // Get idle wait range 760 | String sMinIdleWait = _preferences.getString(Preferences.PREF_MINIDLEWAIT, ""); 761 | String sMaxIdleWait = _preferences.getString(Preferences.PREF_MAXIDLEWAIT, ""); 762 | _minidlewait = (sMinIdleWait.equals("") ? 100 : Integer.parseInt(sMinIdleWait)); 763 | _maxidlewait = (sMaxIdleWait.equals("") ? 3200 : Integer.parseInt(sMaxIdleWait)); 764 | _signal.UILog(String.format("Idle wait min %d max %d ms", _minidlewait, _maxidlewait)); 765 | _requestwait = _minidlewait; 766 | 767 | // Get local port 768 | String pport = _preferences.getString(Preferences.PREF_LOCALPORT, ""); 769 | _localPort = (pport.equals("") ? 3128 : Integer.parseInt(pport)); 770 | if (_localPort < 1024 || _localPort > 65535) 771 | throw new E53Exception("Invalid local port number"); 772 | _signal.UILog(String.format("Local port number is %d", _localPort)); 773 | 774 | // Get receive time-out 775 | String rtimeout = _preferences.getString(Preferences.PREF_RECEIVETIMEOUT, ""); 776 | _receivetimeout = (rtimeout.equals("") ? 1500 : Integer.parseInt(rtimeout)); 777 | _signal.UILog(String.format("Receive time-out is %d ms", _receivetimeout)); 778 | 779 | // Get error wait time 780 | String ewait = _preferences.getString(Preferences.PREF_ERRORWAIT, ""); 781 | _errorwait = (ewait.equals("") ? 1500 : Integer.parseInt(ewait)); 782 | _signal.UILog(String.format("Error wait is %d ms", _errorwait)); 783 | 784 | // Get flags 785 | _routedns = _preferences.getBoolean(Preferences.PREF_ROUTEDNS, false); 786 | boolean nostrictchecking = _preferences.getBoolean(Preferences.PREF_NOSTRICTCHECKING, false); 787 | _nocrcchecking = _preferences.getBoolean(Preferences.PREF_NOCRCCHECKING, false); 788 | _experimental = _preferences.getBoolean(Preferences.PREF_EXPERIMENTAL, false); 789 | _signal.UILog(String.format("Route DNS to gateway: %s", _routedns ? "Yes" : "No")); 790 | _signal.UILog(String.format("No strick checking: %s", nostrictchecking ? "Yes" : "No")); 791 | _signal.UILog(String.format("No CRC checking: %s", _nocrcchecking ? "Yes" : "No")); 792 | _signal.UILog(String.format("Experimental features: %s", _experimental ? "Yes" : "No")); 793 | _dnsHelper.setNoStrict(nostrictchecking); 794 | 795 | // Debug mode? 796 | _debug = _preferences.getBoolean(Preferences.PREF_DEBUG, false); 797 | RootTools.debugMode = _debug; 798 | _signal.UILog(String.format("Debug mode: %s", _debug ? "Yes" : "No")); 799 | _dnsHelper.setDebug(_debug); 800 | 801 | // Check root 802 | _rootaccess = RootTools.isAccessGiven(); 803 | _signal.UILog(String.format("Root access: %s", _rootaccess ? "Yes" : "No")); 804 | 805 | // Show tunnel domain 806 | _signal.reportDomain(_isTrial, _tunnelDomain, _localPort); 807 | } 808 | 809 | private InetAddress checkConnectivity() throws E53Exception, IOException, RootToolsException, TimeoutException, CanceledException, InterruptedException { 810 | 811 | // Get DNS server 812 | String dnsServer = _preferences.getString(Preferences.PREF_DNSSERVER, ""); 813 | boolean useDNSServer = _preferences.getBoolean(Preferences.PREF_USEDNSSERVER, false); 814 | 815 | // Check Wifi 816 | if (Build.PRODUCT.contains("sdk")) { 817 | Process proc = Runtime.getRuntime().exec("getprop net.dns1"); 818 | _dnsServer = new BufferedReader(new InputStreamReader(proc.getInputStream())).readLine(); 819 | _checkconnection = false; 820 | } else { 821 | // Check if network connected/available 822 | NetworkInfo nwInfo = null; 823 | if (_networkType.equals("Wifi")) 824 | nwInfo = _connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); 825 | else if (_networkType.equals("Mobile")) 826 | nwInfo = _connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE); 827 | else 828 | throw new E53Exception(String.format("Unknown network type: %s", _networkType)); 829 | if (nwInfo == null || !nwInfo.isConnected()) 830 | throw new E53Exception(String.format("%s not connected/available", _networkType)); 831 | 832 | // Get network info 833 | String gateway = null; 834 | String name = null; 835 | if (_networkType.equals("Wifi")) { 836 | // Get connection info 837 | DhcpInfo dhcpInfo = _wifiManager.getDhcpInfo(); 838 | WifiInfo wifiInfo = _wifiManager.getConnectionInfo(); 839 | 840 | // Gateway 841 | gateway = _preferences.getString(Preferences.PREF_ALTGATEWAY, ""); 842 | boolean useAltGateway = _preferences.getBoolean(Preferences.PREF_USEALTGATEWAY, false); 843 | if (gateway.equals("") || !useAltGateway) 844 | gateway = intToIp(dhcpInfo.gateway); 845 | else 846 | gateway = resolveIPv4(gateway).getHostAddress(); 847 | 848 | // DNS server 849 | if (dnsServer.equals("") || !useDNSServer) 850 | dnsServer = intToIp(dhcpInfo.dns1); 851 | else 852 | dnsServer = resolveIPv4(dnsServer).getHostAddress(); 853 | 854 | name = wifiInfo.getSSID(); 855 | } else { 856 | NetworkInfo networkInfo = null; 857 | if (dnsServer.equals("") || !useDNSServer) { 858 | Process proc = Runtime.getRuntime().exec("getprop net.dns1"); 859 | dnsServer = new BufferedReader(new InputStreamReader(proc.getInputStream())).readLine(); 860 | } else 861 | dnsServer = resolveIPv4(dnsServer).getHostAddress(); 862 | 863 | gateway = null; 864 | 865 | if (_networkType.equals("Mobile")) 866 | networkInfo = _connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE); 867 | name = String.format("%s %s", networkInfo.getTypeName(), networkInfo.getSubtypeName()); 868 | } 869 | 870 | // Undo previous routing 871 | delRoute(_gateway, _dnsServer); 872 | _gateway = gateway; 873 | 874 | // Find recursive DNS servers 875 | if (_experimental) { 876 | // Create socket 877 | DatagramSocket dgs = new DatagramSocket(); 878 | dgs.setSoTimeout(_receivetimeout); 879 | 880 | // Find recursive 881 | List lstSearched = new ArrayList(); 882 | Map mapServers = searchRecursiveServers(dgs, dnsServer, resolveIPv4(dnsServer), lstSearched); 883 | 884 | if (mapServers.size() > 0) { 885 | // Show found servers 886 | for (String rname : mapServers.keySet()) 887 | _signal.UILog(String.format("Recursive server %s", rname)); 888 | 889 | // Pick random recursive server 890 | int server = _random.nextInt(mapServers.size()); 891 | String rname = (String) mapServers.keySet().toArray()[server]; 892 | dnsServer = mapServers.get(rname).getHostAddress(); 893 | } 894 | } 895 | 896 | // Set new servers 897 | _dnsServer = dnsServer; 898 | _signal.reportNetwork(name, dnsServer, gateway); 899 | 900 | // Route DNS server to gateway 901 | addRoute(_gateway, _dnsServer); 902 | 903 | _checkconnection = false; 904 | } 905 | 906 | return resolveIPv4(_dnsServer); 907 | } 908 | 909 | private void addRoute(String gateway, String dnsServer) throws IOException, RootToolsException, TimeoutException { 910 | if (_routedns && gateway != null && !dnsServer.equals(gateway)) 911 | if (_rootaccess) { 912 | String command = String.format("ip route add %s via %s", dnsServer, gateway); 913 | _signal.UILog(command); 914 | List lstRoot = RootTools.sendShell(command, _roottimeout); 915 | if (_debug) 916 | for (String line : lstRoot) 917 | _signal.UILog(line); 918 | } 919 | } 920 | 921 | private void delRoute(String gateway, String dnsServer) throws IOException, RootToolsException, TimeoutException { 922 | if (_routedns && gateway != null && dnsServer != null && !dnsServer.equals(gateway)) 923 | if (_rootaccess) { 924 | String command = String.format("ip route del %s via %s", dnsServer, gateway); 925 | _signal.UILog(command); 926 | List lstRoot = RootTools.sendShell(command, _roottimeout); 927 | if (_debug) 928 | for (String line : lstRoot) 929 | _signal.UILog(line); 930 | } 931 | } 932 | 933 | public boolean checkTrialLimit(int bytesToAdd) { 934 | if (_isTrial) { 935 | SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); 936 | int trialTotalBytes = (sharedPreferences.contains("E53TotalBytes") ? sharedPreferences.getInt("E53TotalBytes", 0) : 0); 937 | trialTotalBytes += bytesToAdd; 938 | SharedPreferences.Editor editor = sharedPreferences.edit(); 939 | editor.putInt("E53TotalBytes", trialTotalBytes); 940 | editor.commit(); 941 | return (trialTotalBytes < MAXTRIALBYTES); 942 | } 943 | return true; 944 | } 945 | 946 | private void updateNotification(Date start, int seq, int requests, float latency, int errors, int resets, int bytesSent, int bytesReceived, 947 | int totalBytesSent, int totalBytesReceived) { 948 | // Update notification 949 | if (_notification != null) { 950 | String format = getText(R.string.Speed).toString(); 951 | float tx = (bytesSent / 10) / 100f; 952 | float rx = (bytesReceived / 10) / 100f; 953 | String text = String.format(format, tx, rx); 954 | _notification.setLatestEventInfo(getApplicationContext(), getText(R.string.app_name), text, _intentBack); 955 | _notificationManager.notify(8123, _notification); 956 | } 957 | 958 | // Update user interface 959 | if (_pi != null) 960 | try { 961 | long elapse = new Date().getTime() - start.getTime(); 962 | Intent intent = new Intent(); 963 | intent.putExtra("Seq", seq); 964 | intent.putExtra("Req", requests); 965 | intent.putExtra("Elapse", elapse); 966 | intent.putExtra("Latency", latency); 967 | intent.putExtra("DNSWait", _requestwait); 968 | intent.putExtra("RXWait", _receivewait); 969 | intent.putExtra("Error", errors); 970 | intent.putExtra("Reset", resets); 971 | intent.putExtra("Sent", bytesSent); 972 | intent.putExtra("Received", bytesReceived); 973 | intent.putExtra("TotalSent", totalBytesSent); 974 | intent.putExtra("TotalReceived", totalBytesReceived); 975 | intent.putExtra("TXChan", _mapChannelSocket.size()); 976 | intent.putExtra("RXChan", _rxchan); 977 | intent.putExtra("TXQueue", _queue.size()); 978 | intent.putExtra("RXQueue", _rxqueue); 979 | _pi.send(getApplicationContext(), Element53Signal.RESULT_STATUS, intent); 980 | } catch (CanceledException ex) { 981 | } 982 | 983 | // Check trial limit 984 | if (!checkTrialLimit(bytesSent + bytesReceived)) { 985 | stopTunnel(); 986 | _signal.reportTrialOver(); 987 | } 988 | } 989 | 990 | private void resetTunnel(DatagramSocket dgs, InetAddress dnsAddress) throws IOException, InterruptedException, E53Exception, DecodingException, 991 | E53DNSException { 992 | // This leads to data loss :-( 993 | closeAllChannels(); 994 | 995 | // Get unique ID 996 | String android_id = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID); 997 | if (android_id == null || android_id.equals("")) 998 | android_id = _wifiManager.getConnectionInfo().getMacAddress(); 999 | if (android_id == null && Build.PRODUCT.contains("sdk")) 1000 | android_id = "SDK"; 1001 | byte[] id = android_id.getBytes("US-ASCII"); 1002 | 1003 | // Build reset OOB data 1004 | byte[] data = new byte[16 + id.length]; 1005 | data[0] = PROTOCOL_VERSION; 1006 | data[1] = (byte) ((_servermsgsize >> 8) & 0xFF); 1007 | data[2] = (byte) (_servermsgsize & 255); 1008 | data[3] = (byte) ((_clientmsgsize >> 8) & 0xFF); 1009 | data[4] = (byte) (_clientmsgsize & 255); 1010 | data[5] = (byte) _dnsHelper.getRecordType(false); 1011 | data[6] = (byte) (_nocrcchecking ? 1 : 0); 1012 | data[7] = (byte) (_debug ? 1 : 0); 1013 | data[8] = (byte) (_isTrial ? 1 : 0); 1014 | data[9] = 0; // reserved 1015 | data[10] = 0; // reserved 1016 | data[11] = 0; // reserved 1017 | data[12] = 0; // reserved 1018 | data[13] = 0; // reserved 1019 | data[14] = 0; // reserved 1020 | data[15] = (byte) id.length; 1021 | System.arraycopy(id, 0, data, 16, id.length); 1022 | 1023 | // Send OOB reset 1024 | _signal.UILog(String.format("Reset %s", android_id)); 1025 | Msg response = sendOOB(dgs, dnsAddress, Msg.OOB_RESET, data); 1026 | byte protocol = response.Data[0]; 1027 | if (protocol > PROTOCOL_VERSION) 1028 | throw new E53Exception(String.format("Unsupported protocol version %d, please update", protocol)); 1029 | _client = response.Client; 1030 | _signal.UILog(String.format("Protocol %d client %d", protocol, _client)); 1031 | } 1032 | 1033 | private Map searchRecursiveServers(DatagramSocket dgs, String name, InetAddress dnsAddress, List lstSearched) { 1034 | if (lstSearched.contains(name)) 1035 | return new HashMap(); 1036 | 1037 | lstSearched.add(name); 1038 | _signal.UILog(String.format("Recursive search %s", name)); 1039 | 1040 | int tries = 1; 1041 | Map mapServers = new HashMap(); 1042 | while (true) 1043 | try { 1044 | DNSResult result = null; 1045 | try { 1046 | // Route DNS server to gateway 1047 | addRoute(_gateway, dnsAddress.getHostAddress()); 1048 | 1049 | // Send request 1050 | int rid = _random.nextInt() & 0xFFFF; 1051 | byte[] pdata = _dnsHelper.encodeData(rid, new byte[6], _tunnelDomain, false); 1052 | DatagramPacket cpacket = new DatagramPacket(pdata, pdata.length, dnsAddress, 53); 1053 | dgs.send(cpacket); 1054 | 1055 | // Wait for response 1056 | byte[] rbuffer = new byte[512]; 1057 | DatagramPacket rpacket = new DatagramPacket(rbuffer, rbuffer.length); 1058 | dgs.receive(rpacket); 1059 | result = _dnsHelper.decodeResponse(rid, rpacket.getData(), true); 1060 | } finally { 1061 | // Undo routing 1062 | delRoute(_gateway, dnsAddress.getHostAddress()); 1063 | } 1064 | 1065 | // Check response 1066 | if (result.rcode != 0) 1067 | throw new E53Exception(result.rtext); 1068 | 1069 | // Process response 1070 | if (result.ra == 1) { 1071 | if (dnsAddress.getClass().equals(Inet4Address.class) && !mapServers.containsKey(name)) 1072 | mapServers.put(name, dnsAddress); 1073 | } else { 1074 | for (DNSRecord nsrecord : result.NSRecord) 1075 | if (nsrecord.Content != null) { 1076 | // Find additional record 1077 | InetAddress address = null; 1078 | for (DNSRecord arrecord : result.ARRecord) 1079 | if (nsrecord.Prop.Type == Element53DNS.RECORD_A && nsrecord.Content.equals(arrecord.Name.Name)) { 1080 | address = arrecord.Address; 1081 | break; 1082 | } 1083 | 1084 | // Resolve address 1085 | if (address == null && !lstSearched.contains(nsrecord.Content)) { 1086 | _signal.UILog(String.format("Resolving %s", nsrecord.Content)); 1087 | try { 1088 | address = resolveIPv4(nsrecord.Content); 1089 | } catch (UnknownHostException ex) { 1090 | _signal.UILog(String.format("Could not resolve %s: %s", nsrecord.Content, ex)); 1091 | } 1092 | } 1093 | 1094 | // Recursive search 1095 | if (address != null) 1096 | mapServers.putAll(searchRecursiveServers(dgs, nsrecord.Content, address, lstSearched)); 1097 | } 1098 | } 1099 | 1100 | break; 1101 | } catch (Exception ex) { 1102 | _signal.UILog(String.format("Error searching: %s", ex)); 1103 | stackTrace(ex); 1104 | if (++tries <= _maxprobes) 1105 | mapServers.clear(); 1106 | else { 1107 | _signal.UILog("Giving up"); 1108 | break; 1109 | } 1110 | } 1111 | return mapServers; 1112 | } 1113 | 1114 | private void quitTunnel(final DatagramSocket dgs, final InetAddress dnsAddress) throws IOException, InterruptedException, DecodingException, 1115 | E53DNSException, E53Exception { 1116 | _signal.UILog("Quit"); 1117 | byte[] data = new byte[0]; 1118 | sendOOB(dgs, dnsAddress, Msg.OOB_QUIT, data); 1119 | } 1120 | 1121 | private void clientProbe(final DatagramSocket dgs, final InetAddress dnsAddress, int size) throws IOException, InterruptedException, E53Exception, 1122 | DecodingException, E53DNSException { 1123 | byte[] data = new byte[size]; 1124 | data[0] = (byte) ((size >> 8) & 0xFF); 1125 | data[1] = (byte) (size & 255); 1126 | for (int i = 2; i < size; i++) 1127 | data[i] = (byte) (i & 0xFF); 1128 | sendOOB(dgs, dnsAddress, Msg.OOB_PROBE_CLIENT, data); 1129 | } 1130 | 1131 | private void serverProbe(final DatagramSocket dgs, final InetAddress dnsAddress, int clientsize, int serversize) throws IOException, InterruptedException, 1132 | E53Exception, DecodingException, E53DNSException { 1133 | byte[] data = new byte[clientsize]; 1134 | data[0] = (byte) ((serversize >> 8) & 0xFF); 1135 | data[1] = (byte) (serversize & 255); 1136 | sendOOB(dgs, dnsAddress, Msg.OOB_PROBE_SERVER, data); 1137 | } 1138 | 1139 | private void setSize(final DatagramSocket dgs, final InetAddress dnsAddress, int clientsize, int serversize) throws IOException, InterruptedException, 1140 | E53Exception, DecodingException, E53DNSException { 1141 | byte[] data = new byte[4]; 1142 | data[0] = (byte) ((serversize >> 8) & 0xFF); 1143 | data[1] = (byte) (serversize & 255); 1144 | data[2] = (byte) ((clientsize >> 8) & 0xFF); 1145 | data[3] = (byte) (clientsize & 255); 1146 | sendOOB(dgs, dnsAddress, Msg.OOB_SET_SIZE, data); 1147 | _clientmsgsize = clientsize; 1148 | _servermsgsize = serversize; 1149 | } 1150 | 1151 | private void probeTunnel(DatagramSocket dgs, InetAddress dnsAddress) throws Exception { 1152 | int csize = 0; 1153 | int ssize = 0; 1154 | 1155 | // Probe client message size 1156 | if (_probeclient) { 1157 | int cmin = 1; 1158 | int cmax = 255; 1159 | while (cmax - cmin > 0) { 1160 | int tries = 1; 1161 | boolean ok = false; 1162 | csize = (int) Math.ceil((cmin + cmax) / 2f); 1163 | while (true) 1164 | try { 1165 | clientProbe(dgs, dnsAddress, csize); 1166 | ok = true; 1167 | break; 1168 | } catch (Exception ex) { 1169 | if (_debug) 1170 | _signal.UILog(String.format("Probe client: %s", ex)); 1171 | stackTrace(ex); 1172 | if (++tries > _maxprobes) { 1173 | if (_debug) 1174 | _signal.UILog("Giving up"); 1175 | break; 1176 | } 1177 | } 1178 | _signal.UILog(String.format("Client size probe %d %s", csize, (ok ? "ok" : "fail"))); 1179 | if (ok) 1180 | cmin = csize; 1181 | else 1182 | cmax = csize - 1; 1183 | } 1184 | csize = cmax; 1185 | } else 1186 | csize = _clientmsgsize; 1187 | 1188 | // Probe server message size 1189 | if (_probeserver) { 1190 | int smin = 1; 1191 | int smax = 255; 1192 | while (smax - smin > 0) { 1193 | int tries = 1; 1194 | boolean ok = false; 1195 | ssize = (int) Math.ceil((smin + smax) / 2f); 1196 | while (true) 1197 | try { 1198 | serverProbe(dgs, dnsAddress, csize, ssize); 1199 | ok = true; 1200 | break; 1201 | } catch (Exception ex) { 1202 | if (_debug) 1203 | _signal.UILog(String.format("Probe server: %s", ex)); 1204 | if (++tries > _maxprobes) { 1205 | if (_debug) 1206 | _signal.UILog("Giving up"); 1207 | break; 1208 | } 1209 | } 1210 | _signal.UILog(String.format("Server size probe %d %s", ssize, (ok ? "ok" : "fail"))); 1211 | if (ok) 1212 | smin = ssize; 1213 | else 1214 | smax = ssize - 1; 1215 | } 1216 | ssize = smax; 1217 | } else 1218 | ssize = _servermsgsize; 1219 | 1220 | _signal.UILog(String.format("Probed size client=%d, server=%d", csize, ssize)); 1221 | 1222 | // Set probed message sizes 1223 | int tries = 0; 1224 | while (true) 1225 | try { 1226 | tries++; 1227 | setSize(dgs, dnsAddress, csize, ssize); 1228 | break; 1229 | } catch (Exception ex) { 1230 | _signal.UILog(String.format("Set size: %s", ex)); 1231 | if (tries <= _maxresends) { 1232 | if (!ex.getClass().equals(SocketTimeoutException.class)) 1233 | Thread.sleep(_errorwait); 1234 | } else { 1235 | _signal.UILog("Giving up"); 1236 | throw ex; 1237 | } 1238 | } 1239 | } 1240 | 1241 | private Msg sendOOB(final DatagramSocket dgs, final InetAddress dnsAddress, int command, byte[] data) throws IOException, E53Exception, 1242 | InterruptedException, DecodingException, E53DNSException { 1243 | // Encode OOB message 1244 | Msg msg = new Msg(); 1245 | msg.Client = _client; 1246 | msg.Seq = 0; // = OOB 1247 | msg.Channel = 0; // Unused 1248 | msg.Control = command; // OOB code 1249 | msg.Data = data; 1250 | byte[] rawMessage = composeRawMessage(msg); 1251 | 1252 | // Send message 1253 | int id = _random.nextInt() & 0xFFFF; 1254 | 1255 | byte[] pdata = _dnsHelper.encodeData(id, rawMessage, _tunnelDomain, true); 1256 | DatagramPacket cpacket = new DatagramPacket(pdata, pdata.length, dnsAddress, 53); 1257 | dgs.send(cpacket); 1258 | 1259 | // Wait for response 1260 | while (true) { 1261 | byte[] rbuffer = new byte[512]; 1262 | DatagramPacket rpacket = new DatagramPacket(rbuffer, rbuffer.length); 1263 | dgs.receive(rpacket); 1264 | 1265 | // Decode response 1266 | byte[] rmessage = _dnsHelper.decodeData(id, rpacket.getData()); 1267 | if (rmessage == null) 1268 | continue; 1269 | 1270 | Msg response = decomposeRawMessage(rmessage); 1271 | 1272 | // Sanity checks 1273 | if (response.Seq != 0) 1274 | throw new E53Exception("Invalid control sequence"); 1275 | if (response.Channel != 0) 1276 | throw new E53Exception("Invalid control channel"); 1277 | if (response.Control != msg.Control) 1278 | throw new E53Exception("Invalid control command"); 1279 | 1280 | return response; 1281 | } 1282 | } 1283 | 1284 | private void serverData(Msg rmsg) throws IOException { 1285 | String ch = Integer.toString(rmsg.Channel); 1286 | if (rmsg.Data.length > 0) { 1287 | if (_mapChannelSocket.containsKey(ch)) 1288 | try { 1289 | _mapChannelSocket.get(ch).getOutputStream().write(rmsg.Data); 1290 | } catch (IOException ex) { 1291 | clientCloseChannel(rmsg.Channel); 1292 | throw ex; 1293 | } 1294 | else 1295 | _signal.UILog(String.format("Data for unknown channel %d", rmsg.Channel)); 1296 | } 1297 | } 1298 | 1299 | private void clientCloseChannel(int channel) { 1300 | String ch = Integer.toString(channel); 1301 | if (_mapChannelSocket.containsKey(ch)) { 1302 | // Queue close command 1303 | _signal.UILog(String.format("Sending close for channel %d to server", channel)); 1304 | _queue.add(new Msg(0, channel, Msg.CONTROL_CLOSE, new byte[0])); 1305 | 1306 | // Always close socket 1307 | Socket socket = _mapChannelSocket.get(ch); 1308 | _mapChannelSocket.remove(ch); 1309 | if (!socket.isClosed()) 1310 | try { 1311 | socket.close(); 1312 | } catch (Exception ex) { 1313 | _signal.UILog(String.format("Error closing socket of channel %d: %s", channel, ex)); 1314 | stackTrace(ex); 1315 | } 1316 | } 1317 | } 1318 | 1319 | private void serverCloseChannel(int channel) throws IOException, E53Exception { 1320 | String ch = Integer.toString(channel); 1321 | if (_mapChannelSocket.containsKey(ch)) { 1322 | _signal.UILog(String.format("Received server close for channel %d", channel)); 1323 | Socket socket = _mapChannelSocket.get(ch); 1324 | _mapChannelSocket.remove(ch); 1325 | socket.close(); 1326 | for (Msg qmsg : _queue) 1327 | if (qmsg.Channel == channel) { 1328 | _signal.UILog(String.format("Discarding message for closed channel %d", channel)); 1329 | _queue.remove(qmsg); 1330 | } 1331 | } else 1332 | _signal.UILog(String.format("Server close for unknown channel %d", channel)); 1333 | } 1334 | 1335 | private void closeAllChannels() { 1336 | for (String ch : _mapChannelSocket.keySet()) 1337 | try { 1338 | Socket socket = _mapChannelSocket.get(ch); 1339 | // Prevent sending close to server 1340 | _mapChannelSocket.remove(ch); 1341 | socket.close(); 1342 | } catch (Exception ex) { 1343 | _signal.UILog(String.format("Closing channel %s: %s", ch, ex)); 1344 | stackTrace(ex); 1345 | } 1346 | 1347 | // Clear queue 1348 | _queue.clear(); 1349 | } 1350 | 1351 | private String intToIp(int i) { 1352 | return (i & 0xFF) + "." + ((i >> 8) & 0xFF) + "." + ((i >> 16) & 0xFF) + "." + ((i >> 24) & 0xFF); 1353 | } 1354 | 1355 | private InetAddress resolveIPv4(String name) throws UnknownHostException { 1356 | for (InetAddress addr : InetAddress.getAllByName(name)) 1357 | if (addr.getClass().equals(Inet4Address.class)) 1358 | return addr; 1359 | throw new UnknownHostException(); 1360 | } 1361 | 1362 | private byte[] composeRawMessage(Msg msg) { 1363 | int nonce = _random.nextInt() & 0xFF; 1364 | byte[] message = new byte[6 + msg.Data.length]; 1365 | message[0] = (byte) msg.Client; 1366 | message[1] = (byte) msg.Seq; 1367 | message[2] = (byte) nonce; 1368 | message[3] = (byte) msg.Channel; 1369 | message[4] = (byte) msg.Control; 1370 | message[5] = (_nocrcchecking ? 0 : crc8(msg.Data)); 1371 | System.arraycopy(msg.Data, 0, message, 6, msg.Data.length); 1372 | if (_debug) 1373 | _signal.UILog(String.format("Compose seq=%d nonce=%d ch=%d ctl=%d crc=%d len=%d", msg.Seq, nonce, msg.Channel, msg.Control, message[5], 1374 | msg.Data.length)); 1375 | return message; 1376 | } 1377 | 1378 | private Msg decomposeRawMessage(byte[] rmessage) throws E53Exception { 1379 | Msg rmsg = new Msg(); 1380 | rmsg.Client = (rmessage[0] & 0xFF); 1381 | rmsg.Seq = (rmessage[1] & 0xFF); 1382 | rmsg.Channel = (rmessage[2] & 0xFF); 1383 | rmsg.Control = (rmessage[3] & 0xFF); 1384 | rmsg.Data = new byte[rmessage.length - 5]; 1385 | System.arraycopy(rmessage, 5, rmsg.Data, 0, rmessage.length - 5); 1386 | byte rcrc8 = rmessage[4]; 1387 | if (!_nocrcchecking && rcrc8 != crc8(rmsg.Data)) 1388 | throw new E53Exception("CRC mismatch"); 1389 | if (_debug) 1390 | _signal.UILog(String.format("Decompose seq=%d ch=%d ctl=%d crc=%d len=%d", rmsg.Seq, rmsg.Channel, rmsg.Control, rcrc8, rmsg.Data.length)); 1391 | return rmsg; 1392 | } 1393 | 1394 | private static final int _crc8_table[] = { 0x00, 0x1B, 0x36, 0x2D, 0x6C, 0x77, 0x5A, 0x41, 0xD8, 0xC3, 0xEE, 0xF5, 0xB4, 0xAF, 0x82, 0x99, 0xD3, 0xC8, 1395 | 0xE5, 0xFE, 0xBF, 0xA4, 0x89, 0x92, 0x0B, 0x10, 0x3D, 0x26, 0x67, 0x7C, 0x51, 0x4A, 0xC5, 0xDE, 0xF3, 0xE8, 0xA9, 0xB2, 0x9F, 0x84, 0x1D, 0x06, 1396 | 0x2B, 0x30, 0x71, 0x6A, 0x47, 0x5C, 0x16, 0x0D, 0x20, 0x3B, 0x7A, 0x61, 0x4C, 0x57, 0xCE, 0xD5, 0xF8, 0xE3, 0xA2, 0xB9, 0x94, 0x8F, 0xE9, 0xF2, 1397 | 0xDF, 0xC4, 0x85, 0x9E, 0xB3, 0xA8, 0x31, 0x2A, 0x07, 0x1C, 0x5D, 0x46, 0x6B, 0x70, 0x3A, 0x21, 0x0C, 0x17, 0x56, 0x4D, 0x60, 0x7B, 0xE2, 0xF9, 1398 | 0xD4, 0xCF, 0x8E, 0x95, 0xB8, 0xA3, 0x2C, 0x37, 0x1A, 0x01, 0x40, 0x5B, 0x76, 0x6D, 0xF4, 0xEF, 0xC2, 0xD9, 0x98, 0x83, 0xAE, 0xB5, 0xFF, 0xE4, 1399 | 0xC9, 0xD2, 0x93, 0x88, 0xA5, 0xBE, 0x27, 0x3C, 0x11, 0x0A, 0x4B, 0x50, 0x7D, 0x66, 0xB1, 0xAA, 0x87, 0x9C, 0xDD, 0xC6, 0xEB, 0xF0, 0x69, 0x72, 1400 | 0x5F, 0x44, 0x05, 0x1E, 0x33, 0x28, 0x62, 0x79, 0x54, 0x4F, 0x0E, 0x15, 0x38, 0x23, 0xBA, 0xA1, 0x8C, 0x97, 0xD6, 0xCD, 0xE0, 0xFB, 0x74, 0x6F, 1401 | 0x42, 0x59, 0x18, 0x03, 0x2E, 0x35, 0xAC, 0xB7, 0x9A, 0x81, 0xC0, 0xDB, 0xF6, 0xED, 0xA7, 0xBC, 0x91, 0x8A, 0xCB, 0xD0, 0xFD, 0xE6, 0x7F, 0x64, 1402 | 0x49, 0x52, 0x13, 0x08, 0x25, 0x3E, 0x58, 0x43, 0x6E, 0x75, 0x34, 0x2F, 0x02, 0x19, 0x80, 0x9B, 0xB6, 0xAD, 0xEC, 0xF7, 0xDA, 0xC1, 0x8B, 0x90, 1403 | 0xBD, 0xA6, 0xE7, 0xFC, 0xD1, 0xCA, 0x53, 0x48, 0x65, 0x7E, 0x3F, 0x24, 0x09, 0x12, 0x9D, 0x86, 0xAB, 0xB0, 0xF1, 0xEA, 0xC7, 0xDC, 0x45, 0x5E, 1404 | 0x73, 0x68, 0x29, 0x32, 0x1F, 0x04, 0x4E, 0x55, 0x78, 0x63, 0x22, 0x39, 0x14, 0x0F, 0x96, 0x8D, 0xA0, 0xBB, 0xFA, 0xE1, 0xCC, 0xD7 }; 1405 | 1406 | private byte crc8(byte[] data) { 1407 | byte crc = 0; 1408 | for (byte c : data) 1409 | crc = (byte) _crc8_table[(crc ^ c) & 0xFF]; 1410 | return crc; 1411 | } 1412 | 1413 | private void stackTrace(Exception ex) { 1414 | if (_debug) 1415 | _signal.UILog(Log.getStackTraceString(ex)); 1416 | } 1417 | } 1418 | -------------------------------------------------------------------------------- /src/biz/nijhof/e53/Element53Signal.java: -------------------------------------------------------------------------------- 1 | package biz.nijhof.e53; 2 | 3 | import android.app.PendingIntent; 4 | import android.app.PendingIntent.CanceledException; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.util.Log; 8 | 9 | public class Element53Signal { 10 | private Context _context = null; 11 | private PendingIntent _pi = null; 12 | 13 | public static final int RESULT_CLIENT = 1; 14 | public static final int RESULT_DOMAIN = 2; 15 | public static final int RESULT_NETWORK = 3; 16 | public static final int RESULT_SIZE = 4; 17 | public static final int RESULT_STATUS = 5; 18 | public static final int RESULT_LOG = 6; 19 | public static final int RESULT_RESET = 7; 20 | public static final int RESULT_STARTED = 8; 21 | public static final int RESULT_CONNECTED = 9; 22 | public static final int RESULT_ERROR = 10; 23 | public static final int RESULT_STOPPED = 11; 24 | public static final int RESULT_TRIALOVER = 12; 25 | 26 | public Element53Signal(Context context) { 27 | _context = context; 28 | } 29 | 30 | public void setPI(PendingIntent pi) { 31 | _pi = pi; 32 | } 33 | 34 | public void UILog(String msg) { 35 | Log.v("e53", msg); 36 | try { 37 | if (_pi != null) { 38 | Intent intent = new Intent(); 39 | intent.putExtra("Log", msg); 40 | _pi.send(_context, RESULT_LOG, intent); 41 | } 42 | } catch (CanceledException ex) { 43 | } 44 | } 45 | 46 | public void reportDomain(boolean isTrail, String tunnelDomain, int localPort) { 47 | if (_pi != null) 48 | try { 49 | Intent intent = new Intent(); 50 | intent.putExtra("Trial", isTrail); 51 | intent.putExtra("TunnelDomain", tunnelDomain); 52 | intent.putExtra("LocalPort", localPort); 53 | _pi.send(_context, RESULT_DOMAIN, intent); 54 | } catch (CanceledException ex) { 55 | } 56 | } 57 | 58 | public void reportNetwork(String name, String dnsServer, String gateway) { 59 | if (_pi != null) 60 | try { 61 | Intent intent = new Intent(); 62 | intent.putExtra("Network", name); 63 | intent.putExtra("DNS", dnsServer); 64 | intent.putExtra("Gateway", gateway); 65 | _pi.send(_context, RESULT_NETWORK, intent); 66 | } catch (CanceledException ex) { 67 | } 68 | } 69 | 70 | public void reportSize(String recordName, int clientmsgsize, int servermsgsize) { 71 | if (_pi != null) 72 | try { 73 | Intent intent = new Intent(); 74 | intent.putExtra("Type", recordName); 75 | intent.putExtra("Client", clientmsgsize); 76 | intent.putExtra("Server", servermsgsize); 77 | _pi.send(_context, RESULT_SIZE, intent); 78 | } catch (CanceledException ex) { 79 | } 80 | } 81 | 82 | public void reportStarted() { 83 | if (_pi != null) 84 | try { 85 | Intent intent = new Intent(); 86 | _pi.send(_context, RESULT_STARTED, intent); 87 | } catch (CanceledException ex) { 88 | } 89 | } 90 | 91 | public void reportConnected() { 92 | if (_pi != null) 93 | try { 94 | Intent intent = new Intent(); 95 | _pi.send(_context, RESULT_CONNECTED, intent); 96 | } catch (CanceledException ex) { 97 | } 98 | } 99 | 100 | public void reportError() { 101 | if (_pi != null) 102 | try { 103 | Intent intent = new Intent(); 104 | _pi.send(_context, RESULT_ERROR, intent); 105 | } catch (CanceledException ex) { 106 | } 107 | } 108 | 109 | public void reportStopped() { 110 | if (_pi != null) 111 | try { 112 | Intent intent = new Intent(); 113 | _pi.send(_context, RESULT_STOPPED, intent); 114 | } catch (CanceledException ex) { 115 | } 116 | } 117 | 118 | public void reportReset() { 119 | try { 120 | Intent intent = new Intent(); 121 | _pi.send(_context, RESULT_RESET, intent); 122 | } catch (CanceledException ex) { 123 | } 124 | } 125 | 126 | public void reportTrialOver() { 127 | try { 128 | Intent intent = new Intent(); 129 | _pi.send(_context, RESULT_TRIALOVER, intent); 130 | } catch (CanceledException ex) { 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/biz/nijhof/e53/Preferences.java: -------------------------------------------------------------------------------- 1 | package biz.nijhof.e53; 2 | 3 | import android.os.Bundle; 4 | import android.preference.PreferenceActivity; 5 | 6 | public class Preferences extends PreferenceActivity { 7 | // Constants 8 | public static final String PREF_TUNNELDOMAIN = "TunnelDomain"; 9 | public static final String PREF_NETWORKTYPE = "NetworkType"; 10 | public static final String PREF_DNSSERVER = "DNSServer"; 11 | public static final String PREF_USEDNSSERVER = "UseDNSServer"; 12 | public static final String PREF_ALTGATEWAY = "AltGateway"; 13 | public static final String PREF_USEALTGATEWAY = "UseAltGateway"; 14 | public static final String PREF_ROUTEDNS = "RouteDNS"; 15 | public static final String PREF_CLIENTMSGSIZE = "ClientMsgSize"; 16 | public static final String PREF_SERVERMSGSIZE = "ServerMsgSize"; 17 | public static final String PREF_RECORDTYPE = "RecordType"; 18 | public static final String PREF_LOCALPORT = "LocalPort"; 19 | public static final String PREF_RECEIVETIMEOUT = "ReceiveTimeout"; 20 | public static final String PREF_ERRORWAIT = "ErrorWait"; 21 | public static final String PREF_MAXRESENDS = "MaxResends"; 22 | public static final String PREF_MINIDLEWAIT = "MinIdleWait"; 23 | public static final String PREF_MAXIDLEWAIT = "MaxIdleWait"; 24 | public static final String PREF_NOSTRICTCHECKING = "NoStrictChecking"; 25 | public static final String PREF_NOCRCCHECKING = "NoCRCChecking"; 26 | public static final String PREF_EXPERIMENTAL = "Experimental"; 27 | public static final String PREF_LOGLINES = "LogLines"; 28 | public static final String PREF_DEBUG = "Debug"; 29 | 30 | public void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | addPreferencesFromResource(R.xml.preferences); 33 | } 34 | } 35 | --------------------------------------------------------------------------------