├── .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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------