├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── rustfmt.toml └── src ├── common ├── bytes.rs ├── dialer.rs ├── hex.rs ├── mod.rs ├── reader.rs └── utils.rs ├── crypto ├── cipher.rs ├── hash.rs └── mod.rs ├── drcom ├── mod.rs ├── pppoe │ ├── heartbeater.rs │ └── mod.rs ├── tests.rs └── wired │ ├── dialer.rs │ ├── heartbeater.rs │ └── mod.rs ├── ghca ├── dialer.rs ├── mod.rs └── tests.rs ├── ipclient ├── dialer.rs ├── mod.rs └── tests.rs ├── lib.rs ├── netkeeper ├── dialer.rs ├── heartbeater.rs ├── mod.rs └── tests.rs ├── netkeeper4 ├── dialer.rs ├── mod.rs └── tests.rs ├── singlenet ├── attributes.rs ├── dialer.rs ├── heartbeater.rs ├── mod.rs └── tests.rs └── srun3k ├── dialer.rs ├── mod.rs └── tests.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 7 | Cargo.lock 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | rust: 4 | - stable 5 | - nightly-2019-06-01 6 | 7 | before_install: 8 | - sudo apt-get -qq update 9 | - sudo apt-get install -y openssl 10 | 11 | matrix: 12 | allow_failures: 13 | - script: 14 | - cargo install clippy --verbose --force 15 | - cargo clippy -- -D clippy 16 | include: 17 | - rust: "nightly-2019-06-01" 18 | script: 19 | - cargo install clippy --verbose --force 20 | - cargo clippy -- -D clippy 21 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["realityone "] 3 | name = "libnetkeeper" 4 | version = "0.2.1" 5 | 6 | [dependencies] 7 | aes_frast = "0.1.5" 8 | byteorder = "1.4.3" 9 | chrono = "0.4.19" 10 | digest = "0.10.1" 11 | linked-hash-map = "0.5.4" 12 | md4 = "0.10.0" 13 | md5 = "0.7.0" 14 | rand = "0.8.4" 15 | sha1 = "0.10.0" 16 | 17 | [features] 18 | default = [ 19 | "netkeeper", 20 | "netkeeper4", 21 | "singlenet", 22 | "drcom", 23 | "ghca", 24 | "srun3k", 25 | "ipclient", 26 | ] 27 | drcom = [] 28 | ghca = [] 29 | ipclient = [] 30 | netkeeper = [] 31 | netkeeper4 = [] 32 | singlenet = [] 33 | srun3k = [] 34 | 35 | [lib] 36 | name = "netkeeper" 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libnetkeeper 2 | 3 | [![Build Status](https://travis-ci.org/realityone/libnetkeeper.svg?branch=master)](https://travis-ci.org/realityone/libnetkeeper) 4 | 5 | The netkeeper toolkits write in rust. 6 | 7 | We want integrate more algorithms in rust to avoid suffering memory management in C/C++. 8 | And rust can be happy to cross compile to another platform, such as `MIPS` or `ARM`. 9 | 10 | ## State 11 | 12 | Current we support these algorithms with fully test case: 13 | 14 | - [SingleNet](https://github.com/singlenet/Anti_teNelgniS) 15 | - Netkeeper 16 | - [DrCOM](https://github.com/drcoms/drcom-generic) 17 | 18 | And some not tested algorithms: 19 | 20 | - SRun3k 21 | - GHCA 22 | - IPClient 23 | 24 | ## Documents 25 | 26 | > TBD 27 | 28 | ## Develop 29 | 30 | First of all, you have to install rust and use nightly build, [rustup](https://www.rustup.rs) is recommended. 31 | 32 | ### Run Test 33 | 34 | ```bash 35 | $ cargo test 36 | ... 37 | test singlenet::dialer::test_hash_key ... ok 38 | test netkeeper_tests::test_netkeeper_heartbeat_parse ... ok 39 | test singlenet::heartbeater::test_authenticator ... ok 40 | test singlenet::heartbeater::test_calc_seq ... ok 41 | test singlenet_tests::test_bubble_request ... ok 42 | test singlenet_tests::test_real_time_bubble_request ... ok 43 | test singlenet_tests::test_register_request ... ok 44 | test singlenet_tests::test_singlenet_username_encrypt ... ok 45 | test srun3k_tests::test_srun3k_v20_username_encrypt ... ok 46 | test singlenet_tests::test_keepalive_request_generate_and_parse ... ok 47 | 48 | test result: ok. 36 passed; 0 failed; 0 ignored; 0 measured 49 | 50 | Doc-tests netkeeper 51 | 52 | running 0 tests 53 | 54 | test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured 55 | ``` 56 | 57 | ### Work With Stable Rust 58 | 59 | `libnetkeeper` should be compatible with stable rust in `default` feature. 60 | 61 | If you are using stable rust, everything will be fine except `clippy`. 62 | 63 | ```bash 64 | $ cargo build --features=default --release 65 | Compiling libnetkeeper v0.1.0 (file:///Users/realityone/Documents/Softs/libnetkeeper) 66 | Finished release [optimized] target(s) in 5.50 secs 67 | ``` 68 | 69 | ### Issue or Pull Request 70 | 71 | Please fell free to open an issue or create a pull request if you have any question. 72 | 73 | ### License 74 | 75 | `libnetkeeper` is under GPLv3 License. -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | error_on_line_overflow = true 3 | error_on_unformatted = true 4 | struct_field_align_threshold = 20 5 | tab_spaces = 4 6 | use_try_shorthand = true 7 | # format_doc_comments = true 8 | -------------------------------------------------------------------------------- /src/common/bytes.rs: -------------------------------------------------------------------------------- 1 | use std::net::Ipv4Addr; 2 | 3 | use byteorder::{ByteOrder, NativeEndian, NetworkEndian}; 4 | 5 | pub trait BytesAble { 6 | fn as_bytes(&self) -> Vec; 7 | 8 | #[inline] 9 | fn write_bytes(&self, dst: &mut [u8]) { 10 | dst.copy_from_slice(&self.as_bytes()); 11 | } 12 | } 13 | 14 | pub trait BytesAbleNum { 15 | fn as_bytes_be(&self) -> Vec; 16 | fn as_bytes_le(&self) -> Vec; 17 | 18 | #[inline] 19 | fn write_bytes_be(&self, dst: &mut [u8]) { 20 | dst.copy_from_slice(&self.as_bytes_be()); 21 | } 22 | 23 | #[inline] 24 | fn write_bytes_le(&self, dst: &mut [u8]) { 25 | dst.copy_from_slice(&self.as_bytes_le()); 26 | } 27 | } 28 | 29 | impl BytesAble for Ipv4Addr { 30 | #[inline] 31 | fn as_bytes(&self) -> Vec { 32 | self.octets().to_vec() 33 | } 34 | } 35 | 36 | impl BytesAble for String { 37 | #[inline] 38 | fn as_bytes(&self) -> Vec { 39 | self.as_bytes().to_vec() 40 | } 41 | } 42 | 43 | macro_rules! impl_bytes_able_for_num_type { 44 | ($ty:ty, $size:expr) => { 45 | impl BytesAble for $ty { 46 | #[inline] 47 | fn as_bytes(&self) -> Vec { 48 | self.as_bytes_be().to_vec() 49 | } 50 | } 51 | 52 | impl BytesAbleNum for $ty { 53 | #[inline] 54 | fn as_bytes_be(&self) -> Vec { 55 | let mut bytes = [0u8; $size]; 56 | NetworkEndian::write_uint(&mut bytes, u64::from(*self), $size); 57 | bytes.to_vec() 58 | } 59 | 60 | #[inline] 61 | fn as_bytes_le(&self) -> Vec { 62 | let mut bytes = [0u8; $size]; 63 | NativeEndian::write_uint(&mut bytes, u64::from(*self), $size); 64 | bytes.to_vec() 65 | } 66 | } 67 | }; 68 | } 69 | 70 | impl_bytes_able_for_num_type!(u64, 8); 71 | impl_bytes_able_for_num_type!(u32, 4); 72 | impl_bytes_able_for_num_type!(u16, 2); 73 | -------------------------------------------------------------------------------- /src/common/dialer.rs: -------------------------------------------------------------------------------- 1 | pub trait Dialer { 2 | type C; 3 | 4 | fn load_from_config(config: Self::C) -> Self; 5 | } 6 | 7 | pub fn load_dialer(config: D::C) -> D 8 | where 9 | D: Dialer, 10 | { 11 | D::load_from_config(config) 12 | } 13 | -------------------------------------------------------------------------------- /src/common/hex.rs: -------------------------------------------------------------------------------- 1 | pub trait ToHex { 2 | fn to_hex(&self) -> String; 3 | } 4 | 5 | static CHARS: &'static [u8] = b"0123456789abcdef"; 6 | 7 | impl ToHex for [u8] { 8 | fn to_hex(&self) -> String { 9 | let mut v = Vec::with_capacity(self.len() * 2); 10 | for &byte in self.iter() { 11 | v.push(CHARS[(byte >> 4) as usize]); 12 | v.push(CHARS[(byte & 0xf) as usize]); 13 | } 14 | 15 | unsafe { String::from_utf8_unchecked(v) } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/common/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bytes; 2 | pub mod dialer; 3 | pub mod hex; 4 | pub mod reader; 5 | pub mod utils; 6 | -------------------------------------------------------------------------------- /src/common/reader.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | #[derive(Debug)] 4 | pub enum ReadBytesError { 5 | // Expect length {}, got {} 6 | LengthMismatch(usize, usize), 7 | // IO Error from std library 8 | IOError(io::Error), 9 | } 10 | 11 | pub trait ReaderHelper: io::Read { 12 | fn read_bytes(&mut self, required_length: usize) -> Result, ReadBytesError> { 13 | let mut bytes_container: Vec = vec![0; required_length]; 14 | match self.read(&mut bytes_container) { 15 | Ok(length) => { 16 | if length != required_length { 17 | Err(ReadBytesError::LengthMismatch(required_length, length)) 18 | } else { 19 | Ok(bytes_container) 20 | } 21 | } 22 | Err(e) => Err(ReadBytesError::IOError(e)), 23 | } 24 | } 25 | } 26 | 27 | impl ReaderHelper for io::BufReader {} 28 | 29 | #[test] 30 | fn test_read_bytes() { 31 | let bytes: Vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 32 | let mut buffer = io::BufReader::new(&bytes as &[u8]); 33 | 34 | let bytes03 = buffer.read_bytes(3).unwrap(); 35 | assert_eq!(bytes03, vec![1, 2, 3]); 36 | 37 | let bytes45 = buffer.read_bytes(2).unwrap(); 38 | assert_eq!(bytes45, vec![4, 5]); 39 | 40 | let bytes611 = buffer.read_bytes(6); 41 | assert!(bytes611.is_err()); 42 | } 43 | -------------------------------------------------------------------------------- /src/common/utils.rs: -------------------------------------------------------------------------------- 1 | use chrono::offset::Utc; 2 | 3 | pub fn current_timestamp() -> u32 { 4 | let now = Utc::now(); 5 | now.timestamp() as u32 6 | } 7 | -------------------------------------------------------------------------------- /src/crypto/cipher.rs: -------------------------------------------------------------------------------- 1 | use aes_frast::aes_core; 2 | use aes_frast::aes_with_operation_mode::{ecb_dec, ecb_enc}; 3 | use aes_frast::padding_128bit::{de_ansix923_pkcs7, pa_pkcs7}; 4 | 5 | #[derive(Debug)] 6 | pub enum CipherError { 7 | // Expect length {}, got {} 8 | KeyLengthMismatch(usize, usize), 9 | BufferOverflow, 10 | } 11 | 12 | pub trait SimpleCipher { 13 | fn encrypt(&self, plain_bytes: &[u8]) -> Result, CipherError>; 14 | fn decrypt(&self, encrypted_bytes: &[u8]) -> Result, CipherError>; 15 | } 16 | 17 | #[allow(non_camel_case_types)] 18 | #[derive(Debug)] 19 | pub struct AES_128_ECB { 20 | key: [u8; 16], 21 | } 22 | 23 | impl AES_128_ECB { 24 | pub fn from_key(key: &[u8]) -> Result { 25 | if key.len() != 16 { 26 | return Err(CipherError::KeyLengthMismatch(16usize, key.len())); 27 | } 28 | let mut fixed_key = [0u8; 16]; 29 | fixed_key.clone_from_slice(key); 30 | 31 | Ok(AES_128_ECB { key: fixed_key }) 32 | } 33 | } 34 | 35 | impl SimpleCipher for AES_128_ECB { 36 | fn encrypt(&self, plain_bytes: &[u8]) -> Result, CipherError> { 37 | let mut data = plain_bytes.to_vec(); 38 | pa_pkcs7(&mut data); 39 | let mut result = vec![0u8; data.len()]; 40 | let mut scheduled_keys: [u32; 44] = [0; 44]; 41 | aes_core::setkey_enc_k128(&self.key, &mut scheduled_keys); 42 | ecb_enc(&data, &mut result, &scheduled_keys); 43 | Ok(result) 44 | } 45 | 46 | fn decrypt(&self, encrypted_bytes: &[u8]) -> Result, CipherError> { 47 | let data = encrypted_bytes.to_vec(); 48 | let mut result = vec![0u8; data.len()]; 49 | let mut scheduled_keys: [u32; 44] = [0; 44]; 50 | aes_core::setkey_dec_k128(&self.key, &mut scheduled_keys); 51 | ecb_dec(&data, &mut result, &scheduled_keys); 52 | de_ansix923_pkcs7(&mut result); 53 | Ok(result) 54 | } 55 | } 56 | 57 | #[test] 58 | fn test_aes_128_ecb_cipher() { 59 | let message = b"Hello, World"; 60 | let key = b"1234567887654321"; 61 | let aes = AES_128_ECB::from_key(key).unwrap(); 62 | let encrypted = aes.encrypt(message).unwrap(); 63 | let decrypted = aes.decrypt(encrypted.as_slice()).unwrap(); 64 | assert_eq!( 65 | vec![208, 217, 45, 21, 237, 39, 220, 119, 98, 164, 86, 69, 76, 172, 126, 5,], 66 | encrypted 67 | ); 68 | assert_eq!(message.to_vec(), decrypted); 69 | } 70 | -------------------------------------------------------------------------------- /src/crypto/hash.rs: -------------------------------------------------------------------------------- 1 | use md4; 2 | use md4::Digest as Md4Digest; 3 | use md5; 4 | use sha1; 5 | 6 | #[derive(Debug, Clone, Copy)] 7 | pub enum HasherType { 8 | MD4, 9 | MD5, 10 | SHA1, 11 | } 12 | 13 | pub struct HasherBuilder; 14 | 15 | pub trait Hasher { 16 | fn update(&mut self, bytes: &[u8]); 17 | fn finish(&mut self) -> Vec; 18 | } 19 | 20 | struct MD4(md4::Md4); 21 | 22 | struct MD5(md5::Context); 23 | 24 | struct SHA1(sha1::Sha1); 25 | 26 | impl MD4 { 27 | fn new() -> Self { 28 | MD4(md4::Md4::new()) 29 | } 30 | } 31 | 32 | impl Hasher for MD4 { 33 | fn update(&mut self, bytes: &[u8]) { 34 | self.0.update(bytes); 35 | } 36 | 37 | fn finish(&mut self) -> Vec { 38 | self.0.clone().finalize().to_vec() 39 | } 40 | } 41 | 42 | impl MD5 { 43 | fn new() -> Self { 44 | MD5(md5::Context::new()) 45 | } 46 | } 47 | 48 | impl Hasher for MD5 { 49 | fn update(&mut self, bytes: &[u8]) { 50 | self.0.consume(bytes) 51 | } 52 | 53 | fn finish(&mut self) -> Vec { 54 | self.0.clone().compute().to_vec() 55 | } 56 | } 57 | 58 | impl SHA1 { 59 | fn new() -> Self { 60 | SHA1(sha1::Sha1::new()) 61 | } 62 | } 63 | 64 | impl Hasher for SHA1 { 65 | fn update(&mut self, bytes: &[u8]) { 66 | self.0.update(bytes) 67 | } 68 | 69 | fn finish(&mut self) -> Vec { 70 | self.0.clone().finalize().to_vec() 71 | } 72 | } 73 | 74 | impl HasherBuilder { 75 | pub fn build(type_: HasherType) -> Box { 76 | match type_ { 77 | HasherType::MD4 => Box::new(MD4::new()) as Box, 78 | HasherType::MD5 => Box::new(MD5::new()) as Box, 79 | HasherType::SHA1 => Box::new(SHA1::new()) as Box, 80 | } 81 | } 82 | } 83 | 84 | fn hash_bytes(bytes: &[u8], type_: HasherType) -> Vec { 85 | let mut hasher = HasherBuilder::build(type_); 86 | hasher.update(bytes); 87 | hasher.finish() 88 | } 89 | 90 | #[test] 91 | fn test_hash_bytes() { 92 | assert_eq!( 93 | vec![249, 212, 4, 157, 214, 164, 220, 53, 212, 14, 82, 101, 149, 75, 42, 70,], 94 | hash_bytes(b"admin", HasherType::MD4) 95 | ); 96 | assert_eq!( 97 | vec![33, 35, 47, 41, 122, 87, 165, 167, 67, 137, 74, 14, 74, 128, 31, 195,], 98 | hash_bytes(b"admin", HasherType::MD5) 99 | ); 100 | assert_eq!( 101 | vec![ 102 | 208, 51, 226, 42, 227, 72, 174, 181, 102, 15, 194, 20, 10, 236, 53, 133, 12, 77, 169, 103 | 151, 104 | ], 105 | hash_bytes(b"admin", HasherType::SHA1) 106 | ); 107 | } 108 | -------------------------------------------------------------------------------- /src/crypto/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cipher; 2 | pub mod hash; 3 | -------------------------------------------------------------------------------- /src/drcom/mod.rs: -------------------------------------------------------------------------------- 1 | // copy from https://github.com/drcoms/drcom-generic 2 | use std::fmt::Debug; 3 | use std::io; 4 | 5 | use common::reader::{ReadBytesError, ReaderHelper}; 6 | 7 | pub mod pppoe; 8 | pub mod wired; 9 | 10 | #[cfg(test)] 11 | mod tests; 12 | 13 | const PASSWORD_MAX_LEN: usize = 16; 14 | const USERNAME_MAX_LEN: usize = 16; 15 | const PACKET_MAGIC_NUMBER: u16 = 0x0103u16; 16 | 17 | pub trait DrCOMFlag: Debug { 18 | fn as_u32(&self) -> u32; 19 | } 20 | 21 | #[derive(Debug)] 22 | pub enum DrCOMValidateError { 23 | CodeMismatch(u8), 24 | PacketReadError(ReadBytesError), 25 | } 26 | 27 | pub trait DrCOMCommon { 28 | fn code() -> u8 { 29 | 7u8 30 | } 31 | } 32 | 33 | pub trait DrCOMResponseCommon { 34 | fn validate_stream( 35 | input: &mut io::BufReader, 36 | validator: V, 37 | ) -> Result<(), DrCOMValidateError> 38 | where 39 | R: io::Read, 40 | V: FnOnce(u8) -> bool, 41 | { 42 | let code_bytes = input 43 | .read_bytes(1) 44 | .map_err(DrCOMValidateError::PacketReadError)?; 45 | let code = code_bytes[0]; 46 | if !validator(code) { 47 | return Err(DrCOMValidateError::CodeMismatch(code)); 48 | } 49 | Ok(()) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/drcom/pppoe/heartbeater.rs: -------------------------------------------------------------------------------- 1 | use std::net::Ipv4Addr; 2 | use std::num::Wrapping; 3 | use std::{io, marker, result}; 4 | 5 | use byteorder::{ByteOrder, NativeEndian, NetworkEndian}; 6 | 7 | use common::bytes::BytesAbleNum; 8 | use common::reader::{ReadBytesError, ReaderHelper}; 9 | use crypto::hash::{Hasher, HasherBuilder, HasherType}; 10 | use drcom::{DrCOMCommon, DrCOMFlag, DrCOMResponseCommon, DrCOMValidateError}; 11 | 12 | #[derive(Debug)] 13 | pub enum DrCOMHeartbeatError { 14 | ValidateError(DrCOMValidateError), 15 | CRCHashError(CRCHashError), 16 | PacketReadError(ReadBytesError), 17 | UnexpectedBytes(Vec), 18 | } 19 | 20 | type PacketResult = result::Result; 21 | 22 | #[derive(Debug)] 23 | pub enum HeartbeatFlag { 24 | First, 25 | NotFirst, 26 | } 27 | 28 | #[derive(Debug)] 29 | pub enum KeepAliveRequestFlag { 30 | First, 31 | NotFirst, 32 | } 33 | 34 | #[derive(Debug, PartialEq)] 35 | pub enum KeepAliveResponseType { 36 | KeepAliveSucceed, 37 | FileResponse, 38 | UnrecognizedResponse, 39 | } 40 | 41 | #[derive(Debug)] 42 | pub enum CRCHasherType { 43 | NONE, 44 | MD5, 45 | MD4, 46 | SHA1, 47 | } 48 | 49 | #[derive(Debug)] 50 | pub enum CRCHashError { 51 | ModeNotExist, 52 | InputLengthInvalid, 53 | } 54 | 55 | struct NoneHasher; 56 | 57 | #[derive(Debug)] 58 | pub struct ChallengeRequest { 59 | sequence: u8, 60 | } 61 | 62 | #[derive(Debug)] 63 | pub struct ChallengeResponse { 64 | pub challenge_seed: u32, 65 | pub source_ip: Ipv4Addr, 66 | } 67 | 68 | #[derive(Debug)] 69 | pub struct HeartbeatRequest<'a> { 70 | sequence: u8, 71 | type_id: u8, 72 | uid_length: u8, 73 | mac_address: [u8; 6], 74 | source_ip: Ipv4Addr, 75 | flag: &'a (dyn DrCOMFlag + 'a), 76 | challenge_seed: u32, 77 | } 78 | 79 | #[derive(Debug)] 80 | pub struct KeepAliveRequest<'a> { 81 | sequence: u8, 82 | type_id: u8, 83 | source_ip: Ipv4Addr, 84 | flag: &'a (dyn DrCOMFlag + 'a), 85 | keep_alive_seed: u32, 86 | } 87 | 88 | #[derive(Debug)] 89 | pub struct KeepAliveResponse { 90 | pub response_type: KeepAliveResponseType, 91 | } 92 | 93 | trait CRCHasher { 94 | fn hasher(&self) -> Box; 95 | fn retain_postions(&self) -> [usize; 8]; 96 | 97 | fn hash(&self, bytes: &[u8]) -> [u8; 8] { 98 | let mut hasher = self.hasher(); 99 | let retain_postions = self.retain_postions(); 100 | 101 | hasher.update(bytes); 102 | let hashed_bytes = hasher.finish(); 103 | 104 | let mut hashed = Vec::::with_capacity(retain_postions.len()); 105 | for i in &retain_postions { 106 | if *i > hashed_bytes.len() { 107 | continue; 108 | } 109 | hashed.push(hashed_bytes[*i]); 110 | } 111 | 112 | let mut result = [0u8; 8]; 113 | result.clone_from_slice(hashed.as_slice()); 114 | result 115 | } 116 | } 117 | 118 | trait CRCHasherBuilder { 119 | fn from_mode(mode: u8) -> Result 120 | where 121 | Self: marker::Sized; 122 | } 123 | 124 | impl Hasher for NoneHasher { 125 | #[allow(unused_variables)] 126 | fn update(&mut self, bytes: &[u8]) {} 127 | fn finish(&mut self) -> Vec { 128 | const DRCOM_DIAL_EXT_PROTO_CRC_INIT: u32 = 20_000_711; 129 | const UNKNOW_MAGIC_NUMBER: u32 = 126; 130 | 131 | let mut result = Vec::with_capacity(8); 132 | result.extend(DRCOM_DIAL_EXT_PROTO_CRC_INIT.as_bytes_le()); 133 | result.extend(UNKNOW_MAGIC_NUMBER.as_bytes_le()); 134 | 135 | result 136 | } 137 | } 138 | 139 | impl CRCHasher for CRCHasherType { 140 | fn hasher(&self) -> Box { 141 | match *self { 142 | CRCHasherType::NONE => Box::new(NoneHasher {}) as Box, 143 | CRCHasherType::MD5 => HasherBuilder::build(HasherType::MD5), 144 | CRCHasherType::MD4 => HasherBuilder::build(HasherType::MD4), 145 | CRCHasherType::SHA1 => HasherBuilder::build(HasherType::SHA1), 146 | } 147 | } 148 | 149 | fn retain_postions(&self) -> [usize; 8] { 150 | match *self { 151 | CRCHasherType::NONE => [0, 1, 2, 3, 4, 5, 6, 7], 152 | CRCHasherType::MD5 => [2, 3, 8, 9, 5, 6, 13, 14], 153 | CRCHasherType::MD4 => [1, 2, 8, 9, 4, 5, 11, 12], 154 | CRCHasherType::SHA1 => [2, 3, 9, 10, 5, 6, 15, 16], 155 | } 156 | } 157 | } 158 | 159 | impl CRCHasherBuilder for CRCHasherType { 160 | fn from_mode(mode: u8) -> Result 161 | where 162 | Self: marker::Sized, 163 | { 164 | match mode { 165 | 0 => Ok(CRCHasherType::NONE), 166 | 1 => Ok(CRCHasherType::MD5), 167 | 2 => Ok(CRCHasherType::MD4), 168 | 3 => Ok(CRCHasherType::SHA1), 169 | 170 | _ => Err(CRCHashError::ModeNotExist), 171 | } 172 | } 173 | } 174 | 175 | impl DrCOMCommon for ChallengeRequest {} 176 | 177 | impl DrCOMResponseCommon for ChallengeResponse {} 178 | 179 | impl ChallengeRequest { 180 | pub fn new(sequence: Option) -> Self { 181 | ChallengeRequest { 182 | sequence: sequence.unwrap_or(1u8), 183 | } 184 | } 185 | 186 | #[inline] 187 | fn magic_number() -> u32 { 188 | 65_544u32 189 | } 190 | 191 | #[inline] 192 | fn header_length() -> usize { 193 | // code + sequence 194 | 1 + 1 195 | } 196 | 197 | #[inline] 198 | fn packet_length() -> usize { 199 | // header + magic number + padding? 200 | Self::header_length() + 4 + 2 201 | } 202 | 203 | pub fn as_bytes(&self) -> Vec { 204 | let mut result = vec![0u8; Self::packet_length()]; 205 | 206 | result[0] = Self::code(); 207 | result[1] = self.sequence; 208 | Self::magic_number().write_bytes_le(&mut result[2..6]); 209 | 210 | result 211 | } 212 | } 213 | 214 | impl ChallengeResponse { 215 | pub fn from_bytes(input: &mut io::BufReader) -> PacketResult 216 | where 217 | R: io::Read, 218 | { 219 | // validate packet and consume 1 byte 220 | Self::validate_stream(input, |c| c != 0x4d).map_err(DrCOMHeartbeatError::ValidateError)?; 221 | // drain unknow bytes 222 | input 223 | .read_bytes(7) 224 | .map_err(DrCOMHeartbeatError::PacketReadError)?; 225 | 226 | let challenge_seed; 227 | { 228 | let challenge_seed_bytes = input 229 | .read_bytes(4) 230 | .map_err(DrCOMHeartbeatError::PacketReadError)?; 231 | challenge_seed = NativeEndian::read_u32(&challenge_seed_bytes); 232 | } 233 | 234 | let source_ip; 235 | { 236 | let source_ip_bytes = input 237 | .read_bytes(4) 238 | .map_err(DrCOMHeartbeatError::PacketReadError)?; 239 | source_ip = Ipv4Addr::from(NetworkEndian::read_u32(&source_ip_bytes)); 240 | } 241 | 242 | Ok(ChallengeResponse { 243 | challenge_seed, 244 | source_ip, 245 | }) 246 | } 247 | } 248 | 249 | impl DrCOMFlag for HeartbeatFlag { 250 | fn as_u32(&self) -> u32 { 251 | match *self { 252 | HeartbeatFlag::First => 0x2a00_6200u32, 253 | HeartbeatFlag::NotFirst => 0x2a00_6300u32, 254 | } 255 | } 256 | } 257 | 258 | impl DrCOMFlag for KeepAliveRequestFlag { 259 | fn as_u32(&self) -> u32 { 260 | match *self { 261 | KeepAliveRequestFlag::First => 0x122f_270fu32, 262 | KeepAliveRequestFlag::NotFirst => 0x122f_02dcu32, 263 | } 264 | } 265 | } 266 | 267 | impl<'a> DrCOMCommon for HeartbeatRequest<'a> {} 268 | 269 | impl<'a> HeartbeatRequest<'a> { 270 | pub fn new( 271 | sequence: u8, 272 | source_ip: Ipv4Addr, 273 | flag: &'a F, 274 | challenge_seed: u32, 275 | type_id: Option, 276 | uid_length: Option, 277 | mac_address: Option<[u8; 6]>, 278 | ) -> Self 279 | where 280 | F: DrCOMFlag, 281 | { 282 | HeartbeatRequest { 283 | sequence, 284 | type_id: type_id.unwrap_or(3u8), 285 | uid_length: uid_length.unwrap_or(0u8), 286 | mac_address: mac_address.unwrap_or([0u8; 6]), 287 | source_ip, 288 | flag, 289 | challenge_seed, 290 | } 291 | } 292 | 293 | #[inline] 294 | fn header_length() -> usize { 295 | // code + sequence + packet_length 296 | 1 + 1 + 2 297 | } 298 | 299 | #[inline] 300 | fn content_length() -> usize { 301 | // type_id + uid_length + mac_address + source_ip + pppoe_flag + challenge_seed 302 | 1 + 1 + 6 + 4 + 4 + 4 // 303 | } 304 | 305 | #[inline] 306 | fn footer_length() -> usize { 307 | // crc_hash + padding? 308 | 8 + 16 * 4 309 | } 310 | 311 | #[inline] 312 | fn packet_length() -> usize { 313 | Self::header_length() + Self::content_length() + Self::footer_length() 314 | } 315 | 316 | pub fn as_bytes(&self) -> Vec { 317 | let mut header_bytes = Vec::with_capacity(Self::header_length()); 318 | { 319 | header_bytes.push(Self::code()); 320 | header_bytes.push(self.sequence); 321 | header_bytes.extend((Self::packet_length() as u16).as_bytes_le()); 322 | } 323 | 324 | let challenge_seed_bytes = self.challenge_seed.as_bytes_le(); 325 | let mut content_bytes = Vec::with_capacity(Self::content_length()); 326 | { 327 | content_bytes.push(self.type_id); 328 | content_bytes.push(self.uid_length); 329 | content_bytes.extend_from_slice(&self.mac_address); 330 | content_bytes.extend_from_slice(&self.source_ip.octets()); 331 | 332 | let flag_bytes = self.flag.as_u32().as_bytes_le(); 333 | content_bytes.extend_from_slice(&flag_bytes); 334 | content_bytes.extend(&challenge_seed_bytes); 335 | } 336 | 337 | let mut footer_bytes = Vec::with_capacity(Self::footer_length()); 338 | { 339 | let hash_mode = CRCHasherType::from_mode((self.challenge_seed % 3) as u8).unwrap(); 340 | let crc_hash_bytes = hash_mode.hash(&challenge_seed_bytes); 341 | footer_bytes.extend_from_slice(&crc_hash_bytes); 342 | 343 | if let CRCHasherType::NONE = hash_mode { 344 | let mut rehash_bytes: Vec = Vec::with_capacity(Self::packet_length()); 345 | rehash_bytes.extend(&header_bytes); 346 | rehash_bytes.extend(&content_bytes); 347 | rehash_bytes.extend(&footer_bytes); 348 | let rehash = Wrapping(calculate_drcom_crc32(&rehash_bytes, None).unwrap()) 349 | * Wrapping(19_680_126); 350 | 351 | rehash.0.write_bytes_le(&mut footer_bytes[0..4]); 352 | 0u32.write_bytes_le(&mut footer_bytes[4..8]); 353 | } 354 | // padding? 355 | footer_bytes.extend_from_slice(&[0u8; 16 * 4]); 356 | } 357 | 358 | let mut packet_bytes = Vec::with_capacity(Self::packet_length()); 359 | packet_bytes.extend(header_bytes); 360 | packet_bytes.extend(content_bytes); 361 | packet_bytes.extend(footer_bytes); 362 | packet_bytes 363 | } 364 | } 365 | 366 | impl<'a> DrCOMCommon for KeepAliveRequest<'a> {} 367 | 368 | impl<'a> KeepAliveRequest<'a> { 369 | pub fn new( 370 | sequence: u8, 371 | flag: &'a F, 372 | type_id: Option, 373 | source_ip: Option, 374 | keep_alive_seed: Option, 375 | ) -> Self 376 | where 377 | F: DrCOMFlag, 378 | { 379 | let type_id = type_id.unwrap_or(1u8); 380 | let source_ip = source_ip.unwrap_or_else(|| Ipv4Addr::from(0x0)); 381 | let keep_alive_seed = keep_alive_seed.unwrap_or_default(); 382 | KeepAliveRequest { 383 | sequence, 384 | type_id, 385 | source_ip, 386 | flag, 387 | keep_alive_seed, 388 | } 389 | } 390 | 391 | #[inline] 392 | fn packet_length() -> usize { 393 | // code + sequence + packet length + uid length + keep alive seed 394 | 1 + 1 + 2 + 1 + Self::uid_length() + 4 + Self::footer_length() 395 | } 396 | 397 | #[inline] 398 | fn uid_length() -> usize { 399 | // type id + keep alive flag + padding? 400 | 1 + 4 + 6 401 | } 402 | 403 | #[inline] 404 | fn footer_length() -> usize { 405 | // crc + source ip + padding? 406 | 8 + 4 + 8 407 | } 408 | 409 | pub fn as_bytes(&self) -> Vec { 410 | let mut packet_bytes = Vec::with_capacity(Self::packet_length()); 411 | 412 | packet_bytes.push(Self::code()); 413 | packet_bytes.push(self.sequence); 414 | packet_bytes.extend((Self::packet_length() as u16).as_bytes_le()); 415 | packet_bytes.push(Self::uid_length() as u8); 416 | packet_bytes.push(self.type_id); 417 | packet_bytes.extend(self.flag.as_u32().as_bytes_le()); 418 | 419 | // padding? 420 | packet_bytes.extend_from_slice(&[0u8; 6]); 421 | 422 | packet_bytes.extend(self.keep_alive_seed.as_bytes_le()); 423 | 424 | let footer_bytes = match self.type_id { 425 | 3 => { 426 | let mut result = Vec::with_capacity(Self::footer_length()); 427 | let hash_mode = CRCHasherType::from_mode((self.keep_alive_seed & 3) as u8).unwrap(); 428 | let crc_hash_bytes = hash_mode.hash(&packet_bytes); 429 | result.extend_from_slice(&crc_hash_bytes); 430 | 431 | result.extend_from_slice(&self.source_ip.octets()); 432 | result.extend_from_slice(&[0u8; 8]); 433 | result 434 | } 435 | _ => vec![0u8; Self::footer_length()], 436 | }; 437 | packet_bytes.extend(footer_bytes); 438 | 439 | packet_bytes 440 | } 441 | } 442 | 443 | impl DrCOMResponseCommon for KeepAliveResponse {} 444 | 445 | impl KeepAliveResponse { 446 | pub fn from_bytes(input: &mut io::BufReader) -> PacketResult 447 | where 448 | R: io::Read, 449 | { 450 | // validate packet and consume 1 byte 451 | Self::validate_stream(input, |c| c != 0x4d).map_err(DrCOMHeartbeatError::ValidateError)?; 452 | // drain unknow bytes 453 | input 454 | .read_bytes(1) 455 | .map_err(DrCOMHeartbeatError::PacketReadError)?; 456 | 457 | let type_flag_byte; 458 | { 459 | type_flag_byte = input 460 | .read_bytes(1) 461 | .map_err(DrCOMHeartbeatError::PacketReadError)?[0]; 462 | } 463 | 464 | let response_type = match type_flag_byte { 465 | 0x28 => KeepAliveResponseType::KeepAliveSucceed, 466 | 0x10 => KeepAliveResponseType::FileResponse, 467 | _ => KeepAliveResponseType::UnrecognizedResponse, 468 | }; 469 | 470 | Ok(KeepAliveResponse { response_type }) 471 | } 472 | } 473 | 474 | fn calculate_drcom_crc32(bytes: &[u8], initial: Option) -> Result { 475 | if bytes.len() % 4 != 0 { 476 | return Err(CRCHashError::InputLengthInvalid); 477 | } 478 | 479 | let mut result = initial.unwrap_or(0u32); 480 | for c in 0..(bytes.len() / 4usize) { 481 | result ^= NativeEndian::read_u32(&bytes[c * 4..c * 4 + 4]); 482 | } 483 | Ok(result) 484 | } 485 | 486 | #[test] 487 | fn test_generate_crc_hash() { 488 | let crc_hash_none = CRCHasherType::NONE.hash(b"1234567890"); 489 | let crc_hash_md5 = CRCHasherType::MD5.hash(b"1234567890"); 490 | let crc_hash_md4 = CRCHasherType::MD4.hash(b"1234567890"); 491 | let crc_hash_sha1 = CRCHasherType::SHA1.hash(b"1234567890"); 492 | assert_eq!(crc_hash_md5, [241, 252, 155, 176, 45, 19, 56, 161]); 493 | assert_eq!(crc_hash_sha1, [7, 172, 175, 195, 79, 84, 246, 202]); 494 | assert_eq!(crc_hash_none, [199, 47, 49, 1, 126, 0, 0, 0]); 495 | assert_eq!(crc_hash_md4, [177, 150, 28, 171, 227, 148, 144, 95]); 496 | } 497 | 498 | #[test] 499 | fn test_calculate_drcom_crc32() { 500 | let crc32 = calculate_drcom_crc32(b"1234567899999999", None).unwrap(); 501 | assert_eq!(crc32, 201_589_764); 502 | } 503 | -------------------------------------------------------------------------------- /src/drcom/pppoe/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod heartbeater; 2 | -------------------------------------------------------------------------------- /src/drcom/tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod pppoe_tests { 3 | use drcom::pppoe::heartbeater::{ 4 | ChallengeRequest, ChallengeResponse, HeartbeatFlag, HeartbeatRequest, KeepAliveRequest, 5 | KeepAliveRequestFlag, KeepAliveResponse, KeepAliveResponseType, 6 | }; 7 | use std::io::BufReader; 8 | use std::net::Ipv4Addr; 9 | use std::str::FromStr; 10 | 11 | #[test] 12 | fn test_drcom_pppoe_challenge() { 13 | let c = ChallengeRequest::new(Some(1)); 14 | assert_eq!(vec![7, 1, 8, 0, 1, 0, 0, 0], c.as_bytes()); 15 | 16 | let fake_response: Vec = vec![ 17 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 18 | 24, 25, 26, 27, 28, 29, 30, 31, 19 | ]; 20 | let mut buffer = BufReader::new(&fake_response as &[u8]); 21 | let cr = ChallengeResponse::from_bytes(&mut buffer).unwrap(); 22 | assert_eq!(cr.challenge_seed, 185_207_048); 23 | assert_eq!(cr.source_ip, Ipv4Addr::from_str("12.13.14.15").unwrap()); 24 | } 25 | 26 | #[test] 27 | fn test_drcom_pppoe_heartbeat() { 28 | let flag_first = HeartbeatFlag::First; 29 | let flag_not_first = HeartbeatFlag::NotFirst; 30 | 31 | let hr1 = HeartbeatRequest::new( 32 | 1, 33 | Ipv4Addr::from_str("1.2.3.4").unwrap(), 34 | &flag_first, 35 | 0x04030201u32, 36 | None, 37 | None, 38 | None, 39 | ); 40 | let hr2 = HeartbeatRequest::new( 41 | 1, 42 | Ipv4Addr::from_str("1.2.3.4").unwrap(), 43 | &flag_not_first, 44 | 0x04030201u32, 45 | None, 46 | None, 47 | None, 48 | ); 49 | let hr3 = HeartbeatRequest::new( 50 | 1, 51 | Ipv4Addr::from_str("1.2.3.4").unwrap(), 52 | &flag_not_first, 53 | 0x04030200u32, 54 | None, 55 | None, 56 | None, 57 | ); 58 | 59 | assert_eq!( 60 | hr1.as_bytes(), 61 | vec![ 62 | 7, 1, 96, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 0, 98, 0, 42, 1, 2, 3, 4, 192, 90, 63 | 161, 223, 81, 42, 143, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66 | ] 67 | ); 68 | assert_eq!( 69 | hr2.as_bytes(), 70 | vec![ 71 | 7, 1, 96, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 0, 99, 0, 42, 1, 2, 3, 4, 192, 90, 72 | 161, 223, 81, 42, 143, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 73 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 75 | ] 76 | ); 77 | assert_eq!( 78 | hr3.as_bytes(), 79 | vec![ 80 | 7, 1, 96, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 0, 99, 0, 42, 0, 2, 3, 4, 136, 86, 81 | 26, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 83 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84 | ] 85 | ); 86 | } 87 | 88 | #[test] 89 | fn test_drcom_pppoe_keep_alive() { 90 | let flag_first = KeepAliveRequestFlag::First; 91 | let flag_not_first = KeepAliveRequestFlag::NotFirst; 92 | 93 | let ka1 = KeepAliveRequest::new(1u8, &flag_first, None, None, None); 94 | let ka2 = KeepAliveRequest::new(1u8, &flag_first, Some(3), None, None); 95 | let ka3 = KeepAliveRequest::new(1u8, &flag_not_first, Some(3), None, None); 96 | let ka4 = KeepAliveRequest::new( 97 | 1u8, 98 | &flag_not_first, 99 | Some(3), 100 | Some(Ipv4Addr::from_str("1.2.3.4").unwrap()), 101 | Some(0x22221111u32), 102 | ); 103 | 104 | assert_eq!( 105 | ka1.as_bytes(), 106 | vec![ 107 | 7, 1, 40, 0, 11, 1, 15, 39, 47, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 108 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 109 | ] 110 | ); 111 | assert_eq!( 112 | ka2.as_bytes(), 113 | vec![ 114 | 7, 1, 40, 0, 11, 3, 15, 39, 47, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 199, 47, 49, 1, 115 | 126, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 116 | ] 117 | ); 118 | assert_eq!( 119 | ka3.as_bytes(), 120 | vec![ 121 | 7, 1, 40, 0, 11, 3, 220, 2, 47, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 199, 47, 49, 1, 122 | 126, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123 | ] 124 | ); 125 | assert_eq!( 126 | ka4.as_bytes(), 127 | vec![ 128 | 7, 1, 40, 0, 11, 3, 220, 2, 47, 18, 0, 0, 0, 0, 0, 0, 17, 17, 34, 34, 82, 139, 161, 129 | 42, 71, 175, 94, 167, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 130 | ] 131 | ); 132 | 133 | let fake_response1: Vec = vec![7, 0, 0x28]; 134 | let mut buffer1 = BufReader::new(&fake_response1 as &[u8]); 135 | let kar1 = KeepAliveResponse::from_bytes(&mut buffer1).unwrap(); 136 | assert_eq!(kar1.response_type, KeepAliveResponseType::KeepAliveSucceed); 137 | 138 | let fake_response2: Vec = vec![7, 0, 0x10]; 139 | let mut buffer2 = BufReader::new(&fake_response2 as &[u8]); 140 | let kar2 = KeepAliveResponse::from_bytes(&mut buffer2).unwrap(); 141 | assert_eq!(kar2.response_type, KeepAliveResponseType::FileResponse); 142 | 143 | let fake_response3: Vec = vec![7, 0, 0x11]; 144 | let mut buffer3 = BufReader::new(&fake_response3 as &[u8]); 145 | let kar3 = KeepAliveResponse::from_bytes(&mut buffer3).unwrap(); 146 | assert_eq!( 147 | kar3.response_type, 148 | KeepAliveResponseType::UnrecognizedResponse 149 | ); 150 | } 151 | } 152 | 153 | #[cfg(test)] 154 | mod wired_tests { 155 | use drcom::wired::dialer::{ChallengeRequest, ChallengeResponse, LoginAccount, LoginResponse}; 156 | use drcom::wired::heartbeater::{ 157 | HeartbeatFlag, PhaseOneRequest, PhaseOneResponse, PhaseTwoRequest, PhaseTwoResponse, 158 | }; 159 | use std::io::BufReader; 160 | use std::net::Ipv4Addr; 161 | use std::str::FromStr; 162 | 163 | #[test] 164 | fn test_drcom_wired_challenge() { 165 | let c = ChallengeRequest::new(Some(1)); 166 | assert_eq!( 167 | c.as_bytes(), 168 | vec![1, 2, 1, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 169 | ); 170 | 171 | { 172 | let fake_response: Vec = vec![2, 3, 4, 5, 6, 7, 8, 9, 10]; 173 | let mut buffer = BufReader::new(&fake_response as &[u8]); 174 | let cr = ChallengeResponse::from_bytes(&mut buffer).unwrap(); 175 | assert_eq!(cr.hash_salt, [6u8, 7u8, 8u8, 9u8]); 176 | } 177 | 178 | { 179 | let fake_response: Vec = vec![3, 3, 4, 5, 6, 7, 8, 9, 10]; 180 | let mut buffer = BufReader::new(&fake_response as &[u8]); 181 | assert!(ChallengeResponse::from_bytes(&mut buffer).is_err()); 182 | } 183 | } 184 | 185 | #[test] 186 | fn test_drcom_wired_login() { 187 | let mut la = LoginAccount::new("usernameusername", "password", [1, 2, 3, 4]); 188 | la.ipaddresses(&[Ipv4Addr::from_str("10.30.22.17").unwrap()]) 189 | .mac_address([0xb8, 0x88, 0xe3, 0x05, 0x16, 0x80]) 190 | .dog_flag(0x1) 191 | .client_version(0xa) 192 | .dog_version(0x0) 193 | .adapter_count(0x1) 194 | .control_check_status(0x20) 195 | .auto_logout(false) 196 | .broadcast_mode(false) 197 | .random(0x13e9) 198 | .auth_extra_option(0x0); 199 | 200 | { 201 | la.ror_version(false); 202 | let lr1 = la.login_request(); 203 | let origin_bytes1 = vec![ 204 | 3, 1, 0, 36, 174, 175, 144, 214, 168, 238, 67, 106, 128, 153, 49, 172, 94, 102, 205 | 177, 222, 117, 115, 101, 114, 110, 97, 109, 101, 117, 115, 101, 114, 110, 97, 109, 206 | 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 1, 22, 39, 207 | 115, 211, 190, 110, 169, 80, 242, 73, 215, 59, 106, 173, 172, 242, 14, 27, 203, 29, 208 | 82, 153, 1, 10, 30, 22, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 144, 84, 80, 240, 209 | 75, 157, 179, 232, 1, 0, 0, 0, 0, 76, 73, 89, 85, 65, 78, 89, 85, 65, 78, 0, 0, 0, 210 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 114, 114, 114, 0, 0, 211 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 148, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0, 40, 212 | 10, 0, 0, 2, 0, 0, 0, 56, 48, 56, 57, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 213 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 214 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 215 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 217 | 2, 12, 224, 42, 126, 213, 0, 0, 184, 136, 227, 5, 22, 128, 0, 0, 233, 19, 218 | ]; 219 | assert_eq!(lr1.unwrap().as_bytes().unwrap(), origin_bytes1); 220 | } 221 | 222 | { 223 | la.ror_version(true); 224 | let lr2 = la.login_request(); 225 | let origin_bytes2 = vec![ 226 | 3, 1, 0, 36, 174, 175, 144, 214, 168, 238, 67, 106, 128, 153, 49, 172, 94, 102, 227 | 177, 222, 117, 115, 101, 114, 110, 97, 109, 101, 117, 115, 101, 114, 110, 97, 109, 228 | 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 1, 22, 39, 229 | 115, 211, 190, 110, 169, 80, 242, 73, 215, 59, 106, 173, 172, 242, 14, 27, 203, 29, 230 | 82, 153, 1, 10, 30, 22, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 144, 84, 80, 240, 231 | 75, 157, 179, 232, 1, 0, 0, 0, 0, 76, 73, 89, 85, 65, 78, 89, 85, 65, 78, 0, 0, 0, 232 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 114, 114, 114, 0, 0, 233 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 148, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0, 40, 234 | 10, 0, 0, 2, 0, 0, 0, 56, 48, 56, 57, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 235 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 236 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 237 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 238 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 239 | 0, 8, 246, 118, 31, 45, 254, 12, 137, 112, 2, 12, 112, 131, 51, 46, 0, 0, 184, 136, 240 | 227, 5, 22, 128, 0, 0, 233, 19, 241 | ]; 242 | assert_eq!(lr2.unwrap().as_bytes().unwrap(), origin_bytes2); 243 | } 244 | 245 | { 246 | let fake_response: Vec = vec![ 247 | 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 248 | 23, 24, 25, 26, 27, 28, 29, 30, 31, 249 | ]; 250 | let mut buffer = BufReader::new(&fake_response as &[u8]); 251 | let cr = LoginResponse::from_bytes(&mut buffer).unwrap(); 252 | assert_eq!(cr.keep_alive_key, [23, 24, 25, 26, 27, 28]); 253 | } 254 | 255 | { 256 | let mut la = LoginAccount::new("usernameusername", "password", [0x7, 0x8, 0x9, 0x10]); 257 | la.ipaddresses(&[Ipv4Addr::from_str("1.2.3.4").unwrap()]) 258 | .mac_address([0xfa, 0xe1, 0x23, 0x45, 0x67, 0x89]) 259 | .dog_flag(0x5) 260 | .client_version(0x1) 261 | .dog_version(0x2) 262 | .adapter_count(0x1) 263 | .control_check_status(0x30) 264 | .auto_logout(false) 265 | .broadcast_mode(false) 266 | .random(0x13e9) 267 | .auth_extra_option(0x0) 268 | .hostname("HAHAHA".to_string()) 269 | .service_pack("WINDOWS".to_string()); 270 | 271 | la.ror_version(true); 272 | let lr = la.login_request(); 273 | let origin_bytes = vec![ 274 | 3, 1, 0, 36, 227, 154, 169, 77, 33, 112, 224, 233, 249, 52, 229, 206, 20, 132, 105, 275 | 72, 117, 115, 101, 114, 110, 97, 109, 101, 117, 115, 101, 114, 110, 97, 109, 101, 276 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 1, 25, 123, 138, 8, 277 | 70, 249, 200, 54, 139, 80, 235, 42, 110, 136, 213, 114, 194, 60, 249, 131, 44, 185, 278 | 1, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78, 76, 93, 208, 174, 102, 158, 279 | 71, 5, 0, 0, 0, 0, 72, 65, 72, 65, 72, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 280 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 114, 114, 114, 0, 0, 0, 0, 0, 0, 0, 0, 281 | 0, 0, 0, 0, 0, 0, 0, 0, 148, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0, 40, 10, 0, 0, 2, 0, 282 | 0, 0, 87, 73, 78, 68, 79, 87, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 283 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 284 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 285 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 286 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 8, 156, 287 | 223, 214, 241, 178, 248, 148, 108, 2, 12, 160, 94, 79, 1, 0, 0, 250, 225, 35, 69, 288 | 103, 137, 0, 0, 233, 19, 289 | ]; 290 | assert_eq!(lr.unwrap().as_bytes().unwrap(), origin_bytes); 291 | 292 | la.ror_version(false); 293 | let lr = la.login_request(); 294 | let origin_bytes = vec![ 295 | 3, 1, 0, 36, 227, 154, 169, 77, 33, 112, 224, 233, 249, 52, 229, 206, 20, 132, 105, 296 | 72, 117, 115, 101, 114, 110, 97, 109, 101, 117, 115, 101, 114, 110, 97, 109, 101, 297 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 1, 25, 123, 138, 8, 298 | 70, 249, 200, 54, 139, 80, 235, 42, 110, 136, 213, 114, 194, 60, 249, 131, 44, 185, 299 | 1, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78, 76, 93, 208, 174, 102, 158, 300 | 71, 5, 0, 0, 0, 0, 72, 65, 72, 65, 72, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 301 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 114, 114, 114, 0, 0, 0, 0, 0, 0, 0, 0, 302 | 0, 0, 0, 0, 0, 0, 0, 0, 148, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0, 40, 10, 0, 0, 2, 0, 303 | 0, 0, 87, 73, 78, 68, 79, 87, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 304 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 305 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 306 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 307 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 12, 32, 0, 308 | 174, 219, 0, 0, 250, 225, 35, 69, 103, 137, 0, 0, 233, 19, 309 | ]; 310 | assert_eq!(lr.unwrap().as_bytes().unwrap(), origin_bytes); 311 | } 312 | } 313 | 314 | #[test] 315 | fn test_drcom_wired_heartbeat() { 316 | let flag_first = HeartbeatFlag::First; 317 | let flag_not_first = HeartbeatFlag::NotFirst; 318 | 319 | let phase1 = PhaseOneRequest::new([1, 2, 3, 4], "password", [5, 6, 7, 8], Some(123456789)); 320 | assert_eq!( 321 | phase1.as_bytes(), 322 | vec![ 323 | 255, 174, 175, 144, 214, 168, 238, 67, 106, 128, 153, 49, 172, 94, 102, 177, 222, 324 | 0, 0, 0, 5, 6, 7, 8, 212, 112, 0, 0, 0, 0, 325 | ] 326 | ); 327 | 328 | { 329 | let phase2 = PhaseTwoRequest::new( 330 | 1, 331 | [5, 6, 7, 8], 332 | &flag_first, 333 | Ipv4Addr::from_str("1.2.3.4").unwrap(), 334 | Some(1), 335 | ); 336 | assert_eq!( 337 | phase2.as_bytes(), 338 | vec![ 339 | 7, 1, 40, 0, 11, 1, 15, 39, 47, 18, 0, 0, 0, 0, 0, 0, 5, 6, 7, 8, 0, 0, 0, 0, 340 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 341 | ] 342 | ); 343 | } 344 | 345 | { 346 | let phase2 = PhaseTwoRequest::new( 347 | 1, 348 | [5, 6, 7, 8], 349 | &flag_first, 350 | Ipv4Addr::from_str("1.2.3.4").unwrap(), 351 | Some(3), 352 | ); 353 | assert_eq!( 354 | phase2.as_bytes(), 355 | vec![ 356 | 7, 1, 40, 0, 11, 3, 15, 39, 47, 18, 0, 0, 0, 0, 0, 0, 5, 6, 7, 8, 0, 0, 0, 0, 357 | 0, 0, 0, 0, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 358 | ] 359 | ); 360 | } 361 | 362 | { 363 | let phase2 = PhaseTwoRequest::new( 364 | 1, 365 | [5, 6, 7, 8], 366 | &flag_not_first, 367 | Ipv4Addr::from_str("1.2.3.4").unwrap(), 368 | Some(3), 369 | ); 370 | assert_eq!( 371 | phase2.as_bytes(), 372 | vec![ 373 | 7, 1, 40, 0, 11, 3, 220, 2, 47, 18, 0, 0, 0, 0, 0, 0, 5, 6, 7, 8, 0, 0, 0, 0, 374 | 0, 0, 0, 0, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 375 | ] 376 | ); 377 | } 378 | 379 | { 380 | let fake_response: Vec = vec![ 381 | 7, 1, 0x28, 0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 382 | 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 383 | 43, 44, 45, 46, 47, 48, 49, 384 | ]; 385 | let mut buffer = BufReader::new(&fake_response as &[u8]); 386 | let response = PhaseTwoResponse::from_bytes(&mut buffer).unwrap(); 387 | assert_eq!(response.sequence, 1); 388 | assert_eq!(response.keep_alive_key, [16, 17, 18, 19]); 389 | } 390 | 391 | { 392 | let fake_response: Vec = vec![7, 3, 4, 5, 6, 7, 8, 9, 10]; 393 | let mut buffer = BufReader::new(&fake_response as &[u8]); 394 | assert!(PhaseOneResponse::from_bytes(&mut buffer).is_ok()); 395 | } 396 | 397 | { 398 | let fake_response: Vec = vec![78, 3, 4, 5, 6, 7, 8, 9, 10]; 399 | let mut buffer = BufReader::new(&fake_response as &[u8]); 400 | assert!(PhaseOneResponse::from_bytes(&mut buffer).is_err()); 401 | } 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /src/drcom/wired/dialer.rs: -------------------------------------------------------------------------------- 1 | use std::net::Ipv4Addr; 2 | use std::num::Wrapping; 3 | use std::str::FromStr; 4 | use std::{io, result}; 5 | 6 | use byteorder::{ByteOrder, NetworkEndian}; 7 | use rand; 8 | use rand::Rng; 9 | 10 | use common::bytes::{BytesAble, BytesAbleNum}; 11 | use common::hex::ToHex; 12 | use common::reader::{ReadBytesError, ReaderHelper}; 13 | use common::utils::current_timestamp; 14 | use crypto::hash::{HasherBuilder, HasherType}; 15 | use drcom::{ 16 | DrCOMCommon, DrCOMResponseCommon, DrCOMValidateError, PACKET_MAGIC_NUMBER, PASSWORD_MAX_LEN, 17 | USERNAME_MAX_LEN, 18 | }; 19 | 20 | #[derive(Debug)] 21 | pub enum LoginError { 22 | ValidateError(DrCOMValidateError), 23 | PacketReadError(ReadBytesError), 24 | FieldValueOverflow(usize, usize), 25 | } 26 | 27 | type LoginResult = result::Result; 28 | 29 | #[derive(Debug)] 30 | pub struct ChallengeRequest { 31 | sequence: u16, 32 | } 33 | 34 | #[derive(Debug)] 35 | pub struct ChallengeResponse { 36 | pub hash_salt: [u8; 4], 37 | } 38 | 39 | #[derive(Debug)] 40 | pub struct TagOSVersionInfo { 41 | major_version: u32, 42 | minor_version: u32, 43 | build_number: u32, 44 | platform_id: u32, 45 | service_pack: String, 46 | } 47 | 48 | #[derive(Debug)] 49 | pub struct TagHostInfo { 50 | hostname: String, 51 | dns_server: Ipv4Addr, 52 | dhcp_server: Ipv4Addr, 53 | backup_dns_server: Ipv4Addr, 54 | wins_ips: [Ipv4Addr; 2], 55 | } 56 | 57 | #[derive(Debug)] 58 | struct TagLDAPAuthInfo { 59 | password_ror_hash: Vec, 60 | } 61 | 62 | #[derive(Debug)] 63 | struct TagAccountInfo { 64 | username: String, 65 | password_md5_hash: [u8; 16], 66 | } 67 | 68 | #[derive(Debug)] 69 | struct TagAdapterInfo { 70 | counts: u8, 71 | password_md5_hash: [u8; 16], 72 | mac_address: [u8; 6], 73 | password_md5_hash_validator: [u8; 16], 74 | ipaddresses: [Ipv4Addr; 4], 75 | } 76 | 77 | #[derive(Debug)] 78 | struct TagAuthExtraInfo<'a> { 79 | origin_data: &'a [u8], 80 | mac_address: [u8; 6], 81 | option: u16, 82 | } 83 | 84 | #[derive(Debug)] 85 | struct TagAuthVersionInfo { 86 | client_version: u8, 87 | dog_version: u8, 88 | } 89 | 90 | #[derive(Debug)] 91 | pub struct LoginRequest { 92 | mac_address: [u8; 6], 93 | account_info: TagAccountInfo, 94 | control_check_status: u8, 95 | adapter_info: TagAdapterInfo, 96 | dog_flag: u8, 97 | host_info: TagHostInfo, 98 | os_version_info: TagOSVersionInfo, 99 | auth_version_info: TagAuthVersionInfo, 100 | auto_logout: bool, 101 | broadcast_mode: bool, 102 | random: u16, 103 | ldap_auth_info: Option, 104 | auth_extra_option: u16, 105 | } 106 | 107 | #[derive(Debug)] 108 | pub struct LoginResponse { 109 | pub keep_alive_key: [u8; 6], 110 | } 111 | 112 | #[derive(Debug)] 113 | pub struct LoginAccount { 114 | username: String, 115 | password: String, 116 | hash_salt: [u8; 4], 117 | adapter_count: u8, 118 | mac_address: [u8; 6], 119 | ipaddresses: [Ipv4Addr; 4], 120 | dog_flag: u8, 121 | client_version: u8, 122 | dog_version: u8, 123 | control_check_status: u8, 124 | ror_version: bool, 125 | hostname: String, 126 | service_pack: String, 127 | dns_server: Ipv4Addr, 128 | dhcp_server: Ipv4Addr, 129 | backup_dns_server: Ipv4Addr, 130 | wins_ips: [Ipv4Addr; 2], 131 | major_version: u32, 132 | minor_version: u32, 133 | build_number: u32, 134 | platform_id: u32, 135 | auto_logout: bool, 136 | broadcast_mode: bool, 137 | random: u16, 138 | auth_extra_option: u16, 139 | } 140 | 141 | const SERVICE_PACK_MAX_LEN: usize = 32; 142 | const HOSTNAME_MAX_LEN: usize = 32; 143 | 144 | macro_rules! validate_field_value_overflow { 145 | ( 146 | $( $field:expr, $max_size:expr );* 147 | ) => { 148 | $( 149 | if $field.len() > $max_size { 150 | return Err(LoginError::FieldValueOverflow($field.len(), $max_size)); 151 | } 152 | )* 153 | } 154 | } 155 | 156 | macro_rules! configurable_field { 157 | ( 158 | $( $field:ident: $ty:ty ),* 159 | ) => { 160 | $( 161 | pub fn $field(&mut self, value: $ty) -> &mut Self { 162 | self.$field = value; 163 | self 164 | } 165 | )* 166 | } 167 | } 168 | 169 | impl DrCOMCommon for ChallengeRequest { 170 | fn code() -> u8 { 171 | 1u8 172 | } 173 | } 174 | 175 | impl ChallengeRequest { 176 | pub fn new(sequence: Option) -> Self { 177 | ChallengeRequest { 178 | sequence: sequence.unwrap_or_else(|| { 179 | current_timestamp() as u16 + rand::thread_rng().gen_range(0xF..0xFF) 180 | }), 181 | } 182 | } 183 | 184 | #[inline] 185 | fn magic_number() -> u32 { 186 | 9u32 187 | } 188 | 189 | #[inline] 190 | fn packet_length() -> usize { 191 | // code + sequence size + () + magic number + padding? 192 | 1 + 1 + Self::sequence_length() + 4 + 12 193 | } 194 | 195 | #[inline] 196 | fn sequence_length() -> usize { 197 | 2 198 | } 199 | 200 | pub fn as_bytes(&self) -> Vec { 201 | let mut result = vec![0u8; Self::packet_length()]; 202 | 203 | result[0] = Self::code(); 204 | result[1] = Self::sequence_length() as u8; 205 | 206 | result[2..4].copy_from_slice(&self.sequence.as_bytes_le()); 207 | result[4..8].copy_from_slice(&Self::magic_number().as_bytes_le()); 208 | result 209 | } 210 | } 211 | 212 | impl DrCOMResponseCommon for ChallengeResponse {} 213 | 214 | impl ChallengeResponse { 215 | pub fn from_bytes(input: &mut io::BufReader) -> LoginResult 216 | where 217 | R: io::Read, 218 | { 219 | // validate packet and consume 1 byte 220 | Self::validate_stream(input, |c| c == 0x02).map_err(LoginError::ValidateError)?; 221 | 222 | // drain unknow bytes 223 | input.read_bytes(3).map_err(LoginError::PacketReadError)?; 224 | 225 | let salt_bytes = input.read_bytes(4).map_err(LoginError::PacketReadError)?; 226 | let mut hash_salt = [0u8; 4]; 227 | hash_salt.clone_from_slice(&salt_bytes); 228 | 229 | Ok(ChallengeResponse { hash_salt }) 230 | } 231 | } 232 | 233 | impl TagOSVersionInfo { 234 | fn validate(&self) -> LoginResult<()> { 235 | validate_field_value_overflow!(self.service_pack, SERVICE_PACK_MAX_LEN); 236 | Ok(()) 237 | } 238 | 239 | #[inline] 240 | fn attribute_length() -> usize { 241 | // attribute_length + () 242 | 4 + Self::content_length() 243 | } 244 | 245 | #[inline] 246 | fn content_length() -> usize { 247 | // major_version + minor_version + build_number + platform_id + service_pack 248 | 4 + 4 + 4 + 4 + 128 249 | } 250 | 251 | pub fn as_bytes(&self) -> LoginResult> { 252 | self.validate()?; 253 | 254 | let mut content_bytes = vec![0u8; Self::content_length()]; 255 | 256 | content_bytes[0..4].copy_from_slice(&self.major_version.as_bytes_le()); 257 | content_bytes[4..8].copy_from_slice(&self.minor_version.as_bytes_le()); 258 | content_bytes[8..12].copy_from_slice(&self.build_number.as_bytes_le()); 259 | content_bytes[12..16].copy_from_slice(&self.platform_id.as_bytes_le()); 260 | content_bytes[16..16 + self.service_pack.len()] 261 | .copy_from_slice(self.service_pack.as_bytes()); 262 | 263 | let mut result = Vec::with_capacity(Self::attribute_length()); 264 | result.extend((Self::attribute_length() as u32).as_bytes_le()); 265 | result.extend(content_bytes); 266 | 267 | Ok(result) 268 | } 269 | } 270 | 271 | impl TagHostInfo { 272 | fn validate(&self) -> LoginResult<()> { 273 | validate_field_value_overflow!(self.hostname, HOSTNAME_MAX_LEN); 274 | Ok(()) 275 | } 276 | 277 | #[inline] 278 | fn attribute_length() -> usize { 279 | // hostname + dns_server + dhcp_server + backup_dns_server + wins_ips 280 | 32 + 4 + 4 + 4 + 8 281 | } 282 | 283 | pub fn as_bytes(&self) -> LoginResult> { 284 | self.validate()?; 285 | 286 | let mut result = Vec::with_capacity(Self::attribute_length()); 287 | 288 | let mut hostname_bytes = [0u8; HOSTNAME_MAX_LEN]; 289 | hostname_bytes[..self.hostname.len()].copy_from_slice(self.hostname.as_bytes()); 290 | result.extend_from_slice(&hostname_bytes); 291 | 292 | result.extend(self.dns_server.as_bytes()); 293 | result.extend(self.dhcp_server.as_bytes()); 294 | result.extend(self.backup_dns_server.as_bytes()); 295 | for ip in &self.wins_ips { 296 | result.extend(ip.as_bytes()); 297 | } 298 | 299 | Ok(result) 300 | } 301 | } 302 | 303 | impl TagLDAPAuthInfo { 304 | fn validate(&self) -> LoginResult<()> { 305 | validate_field_value_overflow!(self.password_ror_hash, PASSWORD_MAX_LEN); 306 | Ok(()) 307 | } 308 | 309 | fn attribute_length(&self) -> usize { 310 | // code + password_ror_hash length + () 311 | 1 + 1 + self.password_ror_hash.len() 312 | } 313 | 314 | fn as_bytes(&self) -> LoginResult> { 315 | self.validate()?; 316 | 317 | let mut result = Vec::with_capacity(self.attribute_length()); 318 | result.push(0u8); 319 | result.push(self.password_ror_hash.len() as u8); 320 | result.extend(&self.password_ror_hash); 321 | 322 | Ok(result) 323 | } 324 | } 325 | 326 | impl LoginAccount { 327 | pub fn new(username: &str, password: &str, hash_salt: [u8; 4]) -> Self { 328 | LoginAccount { 329 | username: username.to_string(), 330 | password: password.to_string(), 331 | hash_salt, 332 | adapter_count: 1, 333 | mac_address: [0, 0, 0, 0, 0, 0], 334 | ipaddresses: [Ipv4Addr::from(0x0); 4], 335 | dog_flag: 0x1, 336 | client_version: 0xa, 337 | dog_version: 0x0, 338 | control_check_status: 0x20, 339 | ror_version: false, 340 | hostname: String::from("LIYUANYUAN"), 341 | service_pack: String::from("8089D"), 342 | dns_server: Ipv4Addr::from_str("114.114.114.114").unwrap(), 343 | dhcp_server: Ipv4Addr::from(0x0), 344 | backup_dns_server: Ipv4Addr::from(0x0), 345 | wins_ips: [Ipv4Addr::from(0x0); 2], 346 | major_version: 0x05, 347 | minor_version: 0x1, 348 | build_number: 0x0a28, 349 | platform_id: 0x2, 350 | auto_logout: false, 351 | broadcast_mode: false, 352 | random: 0x13e9, 353 | auth_extra_option: 0x0u16, 354 | } 355 | } 356 | 357 | fn validate(&self) -> LoginResult<()> { 358 | validate_field_value_overflow!( 359 | self.username, USERNAME_MAX_LEN; 360 | self.password, PASSWORD_MAX_LEN 361 | ); 362 | Ok(()) 363 | } 364 | 365 | fn ror(md5_digest: &[u8; 16], password: &str) -> LoginResult> { 366 | if password.len() > PASSWORD_MAX_LEN { 367 | return Err(LoginError::FieldValueOverflow( 368 | password.len(), 369 | PASSWORD_MAX_LEN, 370 | )); 371 | } 372 | 373 | let mut result = Vec::with_capacity(PASSWORD_MAX_LEN); 374 | for (i, c) in password.as_bytes().into_iter().enumerate() { 375 | let x: u8 = md5_digest[i] ^ c; 376 | result.push((x << 3) + (x >> 5)); 377 | } 378 | Ok(result) 379 | } 380 | 381 | fn password_md5_hash(&self) -> [u8; 16] { 382 | let mut md5 = HasherBuilder::build(HasherType::MD5); 383 | md5.update(&PACKET_MAGIC_NUMBER.as_bytes_le()); 384 | md5.update(&self.hash_salt); 385 | md5.update(self.password.as_bytes()); 386 | 387 | let mut md5_digest = [0u8; 16]; 388 | md5_digest.copy_from_slice(&md5.finish()); 389 | md5_digest 390 | } 391 | 392 | fn password_ror_hash(&self) -> LoginResult> { 393 | Self::ror(&self.password_md5_hash(), &self.password) 394 | } 395 | 396 | fn password_md5_hash_validator(&self) -> [u8; 16] { 397 | let mut md5 = HasherBuilder::build(HasherType::MD5); 398 | md5.update(&[1u8; 1]); 399 | md5.update(self.password.as_bytes()); 400 | md5.update(&self.hash_salt); 401 | md5.update(&[0u8; 4]); 402 | 403 | let mut md5_digest = [0u8; 16]; 404 | md5_digest.copy_from_slice(&md5.finish()); 405 | md5_digest 406 | } 407 | 408 | fn tag_account_info(&self) -> LoginResult { 409 | Ok(TagAccountInfo { 410 | username: self.username.clone(), 411 | password_md5_hash: self.password_md5_hash(), 412 | }) 413 | } 414 | 415 | fn tag_auth_version(&self) -> LoginResult { 416 | Ok(TagAuthVersionInfo { 417 | client_version: self.client_version, 418 | dog_version: self.dog_version, 419 | }) 420 | } 421 | 422 | fn tag_ldap_auth_info(&self) -> LoginResult { 423 | Ok(TagLDAPAuthInfo { 424 | password_ror_hash: self.password_ror_hash()?, 425 | }) 426 | } 427 | 428 | fn tag_adapter_info(&self) -> LoginResult { 429 | Ok(TagAdapterInfo { 430 | counts: self.adapter_count, 431 | password_md5_hash: self.password_md5_hash(), 432 | mac_address: self.mac_address, 433 | password_md5_hash_validator: self.password_md5_hash_validator(), 434 | ipaddresses: self.ipaddresses, 435 | }) 436 | } 437 | 438 | fn tag_os_version(&self) -> LoginResult { 439 | Ok(TagOSVersionInfo { 440 | major_version: self.major_version, 441 | minor_version: self.minor_version, 442 | build_number: self.build_number, 443 | platform_id: self.platform_id, 444 | service_pack: self.service_pack.clone(), 445 | }) 446 | } 447 | 448 | fn tag_host_info(&self) -> LoginResult { 449 | Ok(TagHostInfo { 450 | hostname: self.hostname.clone(), 451 | dns_server: self.dns_server, 452 | dhcp_server: self.dhcp_server, 453 | backup_dns_server: self.backup_dns_server, 454 | wins_ips: self.wins_ips, 455 | }) 456 | } 457 | 458 | pub fn login_request(&self) -> LoginResult { 459 | Ok(LoginRequest { 460 | mac_address: self.mac_address, 461 | account_info: self.tag_account_info()?, 462 | control_check_status: self.control_check_status, 463 | adapter_info: self.tag_adapter_info()?, 464 | dog_flag: self.dog_flag, 465 | host_info: self.tag_host_info()?, 466 | os_version_info: self.tag_os_version()?, 467 | auth_version_info: self.tag_auth_version()?, 468 | auto_logout: self.auto_logout, 469 | broadcast_mode: self.broadcast_mode, 470 | random: self.random, 471 | ldap_auth_info: if self.ror_version { 472 | Some(self.tag_ldap_auth_info()?) 473 | } else { 474 | None 475 | }, 476 | auth_extra_option: self.auth_extra_option, 477 | }) 478 | } 479 | 480 | pub fn ipaddresses(&mut self, value: &[Ipv4Addr]) -> &mut Self { 481 | let mut fixed_ipaddresses = [Ipv4Addr::from(0x0); 4]; 482 | for (i, ip) in value.into_iter().take(4).enumerate() { 483 | fixed_ipaddresses[i] = *ip; 484 | } 485 | self.ipaddresses = fixed_ipaddresses; 486 | self 487 | } 488 | 489 | configurable_field!( 490 | adapter_count: u8, 491 | mac_address: [u8; 6], 492 | dog_flag: u8, 493 | client_version: u8, 494 | dog_version: u8, 495 | control_check_status: u8, 496 | ror_version: bool, 497 | hostname: String, 498 | service_pack: String, 499 | dns_server: Ipv4Addr, 500 | backup_dns_server: Ipv4Addr, 501 | wins_ips: [Ipv4Addr; 2], 502 | major_version: u32, 503 | minor_version: u32, 504 | build_number: u32, 505 | platform_id: u32, 506 | auto_logout: bool, 507 | broadcast_mode: bool, 508 | random: u16, 509 | auth_extra_option: u16 510 | ); 511 | } 512 | 513 | impl TagAccountInfo { 514 | fn validate(&self) -> LoginResult<()> { 515 | validate_field_value_overflow!(self.username, USERNAME_MAX_LEN); 516 | Ok(()) 517 | } 518 | 519 | fn content_length(&self) -> usize { 520 | self.password_md5_hash.len() + self.username.len() + 4 // pading? 521 | } 522 | 523 | fn attribute_length(&self) -> usize { 524 | // attribute length + () 525 | 2 + self.content_length() 526 | } 527 | 528 | fn as_bytes(&self) -> LoginResult> { 529 | self.validate()?; 530 | 531 | let mut result = Vec::with_capacity(self.attribute_length()); 532 | result.extend((self.content_length() as u16).as_bytes_be()); 533 | result.extend_from_slice(&self.password_md5_hash); 534 | result.extend_from_slice(self.username.as_bytes()); 535 | Ok(result) 536 | } 537 | } 538 | 539 | impl TagAdapterInfo { 540 | fn attribute_length() -> usize { 541 | // adapter counts + hashed mac address + password_md5_hash_validator + ipaddress * 4 542 | 1 + 6 + 16 + 4 * 4 543 | } 544 | 545 | fn hash_mac_address(mac_address: [u8; 6], password_md5_hash: &[u8; 16]) -> [u8; 6] { 546 | let prefix = &password_md5_hash[..6]; 547 | let prefix_hex_u64 = u64::from_str_radix(&prefix.to_hex(), 16).unwrap(); 548 | let mac_address_u64 = NetworkEndian::read_uint(&mac_address, 6); 549 | 550 | let mut result = [0u8; 6]; 551 | result.clone_from_slice(&((prefix_hex_u64 ^ mac_address_u64) as u64).as_bytes_be()[2..8]); 552 | result 553 | } 554 | 555 | fn as_bytes(&self) -> LoginResult> { 556 | let mut result = Vec::with_capacity(Self::attribute_length()); 557 | result.push(self.counts); 558 | result.extend_from_slice(&Self::hash_mac_address( 559 | self.mac_address, 560 | &self.password_md5_hash, 561 | )); 562 | result.extend_from_slice(&self.password_md5_hash_validator); 563 | 564 | { 565 | let mut specified_ip_count = 0u8; 566 | let mut ipaddress_bytes = vec![0u8; self.ipaddresses.len() * 4]; 567 | for (i, ip) in self.ipaddresses.iter().enumerate() { 568 | if !ip.is_unspecified() { 569 | specified_ip_count += 1; 570 | ipaddress_bytes[i * 4..i * 4 + 4].copy_from_slice(&ip.as_bytes()); 571 | } 572 | } 573 | result.push(specified_ip_count); 574 | result.extend(ipaddress_bytes); 575 | } 576 | Ok(result) 577 | } 578 | } 579 | 580 | impl TagAuthVersionInfo { 581 | fn attribute_length() -> usize { 582 | // client version + dog version 583 | 1 + 1 584 | } 585 | 586 | fn as_bytes(&self) -> LoginResult> { 587 | let mut result = Vec::with_capacity(Self::attribute_length()); 588 | result.push(self.client_version); 589 | result.push(self.dog_version); 590 | Ok(result) 591 | } 592 | } 593 | 594 | impl<'a> TagAuthExtraInfo<'a> { 595 | fn caculate_check_sum(data: &[u8], initial: Option) -> u32 { 596 | let mut result = Wrapping(initial.unwrap_or(1234u32)); 597 | for chunk in data.chunks(4) { 598 | let mut chunk_vec = chunk.to_vec(); 599 | chunk_vec.extend(vec![0u8; 4 - chunk.len()]); 600 | chunk_vec.reverse(); 601 | result ^= Wrapping(u32::from_str_radix(&chunk_vec.to_hex(), 16).unwrap()); 602 | } 603 | result *= Wrapping(1968); 604 | result.0 605 | } 606 | 607 | #[inline] 608 | fn attribute_length() -> usize { 609 | // code + content length 610 | 1 + 1 + Self::content_length() 611 | } 612 | 613 | #[inline] 614 | fn content_length() -> usize { 615 | // checksum bytes + option bytes + mac_address 616 | 4 + 2 + 6 617 | } 618 | 619 | #[inline] 620 | fn code() -> u8 { 621 | 2u8 622 | } 623 | 624 | fn check_sum(&self) -> u32 { 625 | const CHECK_SUM_PADDING_BYTES: [u8; 6] = [0x1, 0x26, 0x07, 0x11, 0x00, 0x00]; 626 | let mut to_check_data = Vec::from(self.origin_data); 627 | to_check_data.push(Self::code()); 628 | to_check_data.push(Self::content_length() as u8); 629 | to_check_data.extend_from_slice(&CHECK_SUM_PADDING_BYTES); 630 | to_check_data.extend_from_slice(&self.mac_address); 631 | Self::caculate_check_sum(&to_check_data, None) 632 | } 633 | 634 | fn as_bytes(&self) -> LoginResult> { 635 | let mut result = Vec::with_capacity(Self::attribute_length()); 636 | result.push(Self::code()); 637 | result.push(Self::content_length() as u8); 638 | result.extend(self.check_sum().as_bytes_le()); 639 | // padding? 640 | result.extend_from_slice(&[0, 0]); 641 | result.extend_from_slice(&self.mac_address); 642 | 643 | Ok(result) 644 | } 645 | } 646 | 647 | impl LoginRequest { 648 | fn packet_length(&self) -> usize { 649 | // magic number + () + padding? + () + control_check_status 650 | // + () + () + () + auto logout + broadcast mode + random number 651 | 2 + self.account_info.attribute_length() 652 | + 20 653 | + 1 654 | + TagAdapterInfo::attribute_length() 655 | + match self.ldap_auth_info { 656 | Some(ref l) => l.attribute_length(), 657 | None => 0, 658 | } 659 | + TagAuthExtraInfo::attribute_length() 660 | + 1 661 | + 1 662 | + 2 663 | } 664 | 665 | pub fn as_bytes(&self) -> LoginResult> { 666 | let mut result = Vec::with_capacity(self.packet_length()); 667 | 668 | // Phase 1 669 | { 670 | result.extend(PACKET_MAGIC_NUMBER.as_bytes_le()); 671 | result.extend(self.account_info.as_bytes()?); 672 | // padding? 673 | result.extend_from_slice(&[0u8; 20]); 674 | result.push(self.control_check_status); 675 | result.extend(self.adapter_info.as_bytes()?); 676 | } 677 | 678 | // Phase 2 679 | { 680 | const PHASE_TWO_HASH_SALT: [u8; 4] = [0x14, 0x00, 0x07, 0x0b]; 681 | let mut md5 = HasherBuilder::build(HasherType::MD5); 682 | md5.update(&result); 683 | md5.update(&PHASE_TWO_HASH_SALT); 684 | let md5_digest = md5.finish(); 685 | result.extend_from_slice(&md5_digest[..8]); 686 | } 687 | 688 | // Phase 3 689 | { 690 | result.push(self.dog_flag); 691 | // padding? 692 | result.extend_from_slice(&[0u8; 4]); 693 | result.extend(self.host_info.as_bytes()?); 694 | result.extend(self.os_version_info.as_bytes()?); 695 | result.extend(self.auth_version_info.as_bytes()?); 696 | if let Some(ref l) = self.ldap_auth_info { 697 | result.extend(l.as_bytes()?) 698 | }; 699 | } 700 | 701 | // Phase 4 702 | { 703 | let auth_extra_bytes; 704 | { 705 | let auth_extra_info = TagAuthExtraInfo { 706 | origin_data: &result, 707 | mac_address: self.mac_address, 708 | option: self.auth_extra_option, 709 | }; 710 | auth_extra_bytes = auth_extra_info.as_bytes()?; 711 | } 712 | result.extend(auth_extra_bytes); 713 | } 714 | 715 | // Phase 5 716 | { 717 | result.push(self.auto_logout as u8); 718 | result.push(self.broadcast_mode as u8); 719 | result.extend(self.random.as_bytes_le()); 720 | } 721 | 722 | Ok(result) 723 | } 724 | } 725 | 726 | impl DrCOMResponseCommon for LoginResponse {} 727 | 728 | impl DrCOMCommon for LoginResponse { 729 | fn code() -> u8 { 730 | 4u8 731 | } 732 | } 733 | 734 | impl LoginResponse { 735 | pub fn from_bytes(input: &mut io::BufReader) -> LoginResult 736 | where 737 | R: io::Read, 738 | { 739 | // validate packet and consume 1 byte 740 | Self::validate_stream(input, |c| c == Self::code()).map_err(LoginError::ValidateError)?; 741 | 742 | // drain unknow bytes 743 | input.read_bytes(22).map_err(LoginError::PacketReadError)?; 744 | 745 | let key_bytes = input.read_bytes(6).map_err(LoginError::PacketReadError)?; 746 | let mut keep_alive_key = [0u8; 6]; 747 | keep_alive_key.clone_from_slice(&key_bytes); 748 | 749 | Ok(LoginResponse { keep_alive_key }) 750 | } 751 | } 752 | 753 | #[test] 754 | fn test_login_packet_attributes() { 755 | let mut la = LoginAccount::new("usernameusername", "password", [1, 2, 3, 4]); 756 | la.ipaddresses(&[Ipv4Addr::from_str("10.30.22.17").unwrap()]) 757 | .mac_address([0xb8, 0x88, 0xe3, 0x05, 0x16, 0x80]) 758 | .dog_flag(0x1) 759 | .client_version(0xa) 760 | .dog_version(0x0) 761 | .adapter_count(0x1) 762 | .control_check_status(0x20) 763 | .auto_logout(false) 764 | .broadcast_mode(false) 765 | .random(0x13e9) 766 | .ror_version(false) 767 | .auth_extra_option(0x0); 768 | 769 | assert_eq!( 770 | la.tag_os_version().unwrap().as_bytes().unwrap(), 771 | vec![ 772 | 148, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0, 40, 10, 0, 0, 2, 0, 0, 0, 56, 48, 56, 57, 68, 0, 773 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 774 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 775 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 776 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 777 | 0, 0, 0, 0, 0, 0, 778 | ] 779 | ); 780 | 781 | assert_eq!( 782 | la.tag_host_info().unwrap().as_bytes().unwrap(), 783 | vec![ 784 | 76, 73, 89, 85, 65, 78, 89, 85, 65, 78, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 785 | 0, 0, 0, 0, 0, 0, 114, 114, 114, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 786 | ] 787 | ); 788 | 789 | assert_eq!( 790 | la.tag_account_info().unwrap().as_bytes().unwrap(), 791 | vec![ 792 | 0, 36, 174, 175, 144, 214, 168, 238, 67, 106, 128, 153, 49, 172, 94, 102, 177, 222, 793 | 117, 115, 101, 114, 110, 97, 109, 101, 117, 115, 101, 114, 110, 97, 109, 101, 794 | ] 795 | ); 796 | assert_eq!( 797 | la.tag_ldap_auth_info().unwrap().as_bytes().unwrap(), 798 | vec![0, 8, 246, 118, 31, 45, 254, 12, 137, 112] 799 | ); 800 | assert_eq!( 801 | la.tag_adapter_info().unwrap().as_bytes().unwrap(), 802 | vec![ 803 | 1, 22, 39, 115, 211, 190, 110, 169, 80, 242, 73, 215, 59, 106, 173, 172, 242, 14, 27, 804 | 203, 29, 82, 153, 1, 10, 30, 22, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 805 | ] 806 | ); 807 | } 808 | 809 | #[test] 810 | fn test_password_hash() { 811 | assert_eq!( 812 | LoginAccount::ror(&[253u8; 16], "1234567812345678").unwrap(), 813 | vec![102, 126, 118, 78, 70, 94, 86, 46, 102, 126, 118, 78, 70, 94, 86, 46,] 814 | ); 815 | 816 | let mut la = LoginAccount::new("usernameusername", "password", [1, 2, 3, 4]); 817 | la.ipaddresses(&[Ipv4Addr::from_str("10.30.22.17").unwrap()]) 818 | .mac_address([0xb8, 0x88, 0xe3, 0x05, 0x16, 0x80]) 819 | .dog_flag(0x1) 820 | .client_version(0xa) 821 | .dog_version(0x0) 822 | .adapter_count(0x1) 823 | .control_check_status(0x20) 824 | .auto_logout(false) 825 | .broadcast_mode(false) 826 | .random(0x13e9) 827 | .ror_version(false) 828 | .auth_extra_option(0x0); 829 | 830 | assert_eq!( 831 | la.password_md5_hash(), 832 | [174, 175, 144, 214, 168, 238, 67, 106, 128, 153, 49, 172, 94, 102, 177, 222] 833 | ); 834 | assert_eq!( 835 | la.password_md5_hash_validator(), 836 | [169, 80, 242, 73, 215, 59, 106, 173, 172, 242, 14, 27, 203, 29, 82, 153] 837 | ); 838 | 839 | assert_eq!( 840 | TagAdapterInfo::hash_mac_address( 841 | [6, 5, 4, 3, 2, 1], 842 | &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] 843 | ), 844 | [7, 7, 7, 7, 7, 7] 845 | ); 846 | 847 | { 848 | let la = LoginAccount::new("usernameusername", "password", [0x7, 0x8, 0x9, 0x10]); 849 | assert_eq!( 850 | la.password_md5_hash(), 851 | [227, 154, 169, 77, 33, 112, 224, 233, 249, 52, 229, 206, 20, 132, 105, 72] 852 | ); 853 | } 854 | } 855 | 856 | #[test] 857 | fn test_data_check_sum() { 858 | let data: [u8; 326] = [ 859 | 3, 1, 0, 36, 174, 175, 144, 214, 168, 238, 67, 106, 128, 153, 49, 172, 94, 102, 177, 222, 860 | 117, 115, 101, 114, 110, 97, 109, 101, 117, 115, 101, 114, 110, 97, 109, 101, 0, 0, 0, 0, 861 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 1, 22, 39, 115, 211, 190, 110, 169, 80, 862 | 242, 73, 215, 59, 106, 173, 172, 242, 14, 27, 203, 29, 82, 153, 1, 10, 30, 22, 17, 0, 0, 0, 863 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 144, 84, 80, 240, 75, 157, 179, 232, 1, 0, 0, 0, 0, 76, 73, 89, 864 | 85, 65, 78, 89, 85, 65, 78, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 865 | 0, 114, 114, 114, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 148, 0, 0, 0, 5, 0, 866 | 0, 0, 1, 0, 0, 0, 40, 10, 0, 0, 2, 0, 0, 0, 56, 48, 56, 57, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 867 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 868 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 869 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 870 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 2, 12, 1, 871 | 38, 7, 17, 0, 0, 184, 136, 227, 5, 22, 128, 872 | ]; 873 | assert_eq!( 874 | TagAuthExtraInfo::caculate_check_sum(&data, None), 875 | 3_581_815_520 876 | ); 877 | } 878 | -------------------------------------------------------------------------------- /src/drcom/wired/heartbeater.rs: -------------------------------------------------------------------------------- 1 | use std::net::Ipv4Addr; 2 | use std::{io, result}; 3 | 4 | use byteorder::{ByteOrder, NativeEndian}; 5 | 6 | use common::bytes::BytesAbleNum; 7 | use common::reader::{ReadBytesError, ReaderHelper}; 8 | use common::utils::current_timestamp; 9 | use crypto::hash::{HasherBuilder, HasherType}; 10 | use drcom::{DrCOMCommon, DrCOMFlag, DrCOMResponseCommon, DrCOMValidateError, PACKET_MAGIC_NUMBER}; 11 | 12 | #[derive(Debug)] 13 | pub enum HeartbeatError { 14 | ValidateError(DrCOMValidateError), 15 | ResponseLengthMismatch(u16, u16), 16 | PacketReadError(ReadBytesError), 17 | } 18 | 19 | type HeartbeatResult = result::Result; 20 | 21 | #[derive(Debug)] 22 | pub struct PhaseOneRequest { 23 | timestamp: u32, 24 | hash_salt: [u8; 4], 25 | password: String, 26 | keep_alive_key: [u8; 4], 27 | } 28 | 29 | #[derive(Debug)] 30 | pub struct PhaseTwoRequest<'a> { 31 | sequence: u8, 32 | keep_alive_key: [u8; 4], 33 | flag: &'a (dyn DrCOMFlag + 'a), 34 | type_id: u8, 35 | host_ip: Ipv4Addr, 36 | } 37 | 38 | pub struct PhaseOneResponse; 39 | 40 | #[derive(Debug)] 41 | pub struct PhaseTwoResponse { 42 | pub sequence: u8, 43 | pub keep_alive_key: [u8; 4], 44 | } 45 | 46 | #[derive(Debug)] 47 | pub enum HeartbeatFlag { 48 | First, 49 | NotFirst, 50 | } 51 | 52 | impl DrCOMCommon for PhaseOneRequest { 53 | fn code() -> u8 { 54 | 0xffu8 55 | } 56 | } 57 | 58 | impl PhaseOneRequest { 59 | pub fn new( 60 | hash_salt: [u8; 4], 61 | password: &str, 62 | keep_alive_key: [u8; 4], 63 | timestamp: Option, 64 | ) -> Self { 65 | PhaseOneRequest { 66 | timestamp: timestamp.unwrap_or_else(current_timestamp), 67 | hash_salt, 68 | password: password.to_string(), 69 | keep_alive_key, 70 | } 71 | } 72 | 73 | fn packet_length() -> usize { 74 | // code + password hash + padding? + key bytes + timestamp hash + padding? 75 | 1 + 16 + 3 + 4 + 2 + 4 76 | } 77 | 78 | fn password_hash(&self) -> [u8; 16] { 79 | let mut md5 = HasherBuilder::build(HasherType::MD5); 80 | md5.update(&PACKET_MAGIC_NUMBER.as_bytes_le()); 81 | md5.update(&self.hash_salt); 82 | md5.update(self.password.as_bytes()); 83 | 84 | let mut md5_digest = [0u8; 16]; 85 | md5_digest.copy_from_slice(&md5.finish()); 86 | md5_digest 87 | } 88 | 89 | pub fn as_bytes(&self) -> Vec { 90 | let mut result = Vec::with_capacity(Self::packet_length()); 91 | result.push(Self::code()); 92 | result.extend_from_slice(&self.password_hash()); 93 | // padding? 94 | result.extend_from_slice(&[0u8; 3]); 95 | result.extend_from_slice(&self.keep_alive_key); 96 | result.extend(((self.timestamp % 0xFFFF) as u16).as_bytes_be()); 97 | // padding? 98 | result.extend_from_slice(&[0u8; 4]); 99 | result 100 | } 101 | } 102 | 103 | impl DrCOMCommon for PhaseOneResponse { 104 | fn code() -> u8 { 105 | 0x07u8 106 | } 107 | } 108 | 109 | impl DrCOMResponseCommon for PhaseOneResponse {} 110 | 111 | impl PhaseOneResponse { 112 | pub fn from_bytes(input: &mut io::BufReader) -> HeartbeatResult 113 | where 114 | R: io::Read, 115 | { 116 | // validate packet and consume 1 byte 117 | Self::validate_stream(input, |c| c == Self::code()) 118 | .map_err(HeartbeatError::ValidateError)?; 119 | Ok(PhaseOneResponse {}) 120 | } 121 | } 122 | 123 | impl DrCOMFlag for HeartbeatFlag { 124 | fn as_u32(&self) -> u32 { 125 | match *self { 126 | HeartbeatFlag::First => 0x122f_270f, 127 | HeartbeatFlag::NotFirst => 0x122f_02dc, 128 | } 129 | } 130 | } 131 | 132 | impl<'a> DrCOMCommon for PhaseTwoRequest<'a> { 133 | fn code() -> u8 { 134 | 0x7u8 135 | } 136 | } 137 | 138 | impl<'a> PhaseTwoRequest<'a> { 139 | pub fn new( 140 | sequence: u8, 141 | keep_alive_key: [u8; 4], 142 | flag: &'a F, 143 | host_ip: Ipv4Addr, 144 | type_id: Option, 145 | ) -> Self 146 | where 147 | F: DrCOMFlag, 148 | { 149 | PhaseTwoRequest { 150 | sequence, 151 | keep_alive_key, 152 | flag, 153 | type_id: type_id.unwrap_or(1), 154 | host_ip, 155 | } 156 | } 157 | 158 | #[inline] 159 | fn packet_length() -> usize { 160 | // code + sequence + content length + uid length + keep alive key + padding? 161 | 1 + 1 + 2 + 1 + Self::uid_length() + 4 + 4 + Self::footer_length() 162 | } 163 | 164 | #[inline] 165 | fn footer_length() -> usize { 166 | // crc + source ip + padding? 167 | 4 + 4 + 8 168 | } 169 | 170 | #[inline] 171 | fn uid_length() -> usize { 172 | // type id + keep alive flag + padding? 173 | 1 + 4 + 6 174 | } 175 | 176 | pub fn as_bytes(&self) -> Vec { 177 | let mut result = Vec::with_capacity(Self::packet_length()); 178 | result.push(Self::code()); 179 | result.push(self.sequence); 180 | result.extend((Self::packet_length() as u16).as_bytes_le()); 181 | 182 | result.push(Self::uid_length() as u8); 183 | result.push(self.type_id); 184 | result.extend(self.flag.as_u32().as_bytes_le()); 185 | // padding? 186 | result.extend_from_slice(&[0u8; 6]); 187 | result.extend_from_slice(&self.keep_alive_key); 188 | // padding? 189 | result.extend_from_slice(&[0u8; 4]); 190 | 191 | let footer_bytes = match self.type_id { 192 | 3 => { 193 | let mut footer = vec![0u8; Self::footer_length()]; 194 | footer[4..8].copy_from_slice(&self.host_ip.octets()); 195 | footer 196 | } 197 | _ => vec![0u8; Self::footer_length()], 198 | }; 199 | result.extend(footer_bytes); 200 | result 201 | } 202 | } 203 | 204 | impl DrCOMCommon for PhaseTwoResponse {} 205 | 206 | impl DrCOMResponseCommon for PhaseTwoResponse {} 207 | 208 | impl PhaseTwoResponse { 209 | pub fn from_bytes(input: &mut io::BufReader) -> HeartbeatResult 210 | where 211 | R: io::Read, 212 | { 213 | const PHASE_TWO_RESPONSE_LENGTH: u16 = 0x28; 214 | 215 | // validate packet and consume 1 byte 216 | Self::validate_stream(input, |c| c == Self::code()) 217 | .map_err(HeartbeatError::ValidateError)?; 218 | 219 | let sequence = input 220 | .read_bytes(1) 221 | .map_err(HeartbeatError::PacketReadError)?[0]; 222 | 223 | // validate length bytes 224 | { 225 | let length_bytes = input 226 | .read_bytes(2) 227 | .map_err(HeartbeatError::PacketReadError)?; 228 | let length = NativeEndian::read_u16(&length_bytes); 229 | if length != PHASE_TWO_RESPONSE_LENGTH { 230 | return Err(HeartbeatError::ResponseLengthMismatch( 231 | length, 232 | PHASE_TWO_RESPONSE_LENGTH, 233 | )); 234 | } 235 | } 236 | 237 | // drain unknow bytes 238 | input 239 | .read_bytes(12) 240 | .map_err(HeartbeatError::PacketReadError)?; 241 | 242 | let mut keep_alive_key = [0u8; 4]; 243 | keep_alive_key.copy_from_slice( 244 | &input 245 | .read_bytes(4) 246 | .map_err(HeartbeatError::PacketReadError)?, 247 | ); 248 | Ok(PhaseTwoResponse { 249 | sequence, 250 | keep_alive_key, 251 | }) 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /src/drcom/wired/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dialer; 2 | pub mod heartbeater; 3 | -------------------------------------------------------------------------------- /src/ghca/dialer.rs: -------------------------------------------------------------------------------- 1 | use common::bytes::BytesAbleNum; 2 | use common::dialer::Dialer; 3 | use common::hex::ToHex; 4 | use common::utils::current_timestamp; 5 | use crypto::hash::{HasherBuilder, HasherType}; 6 | 7 | #[derive(Debug)] 8 | pub enum GhcaDialerError { 9 | InvalidUsername(String), 10 | InvalidPassword(String), 11 | } 12 | 13 | #[derive(Debug, Clone, Copy)] 14 | pub enum Configuration { 15 | SichuanMac, 16 | } 17 | 18 | #[derive(Debug)] 19 | pub struct GhcaDialer { 20 | pub share_key: String, 21 | pub prefix: String, 22 | pub version: String, 23 | } 24 | 25 | impl GhcaDialer { 26 | fn new(share_key: &str, prefix: &str, version: &str) -> Self { 27 | GhcaDialer { 28 | share_key: share_key.to_string(), 29 | prefix: prefix.to_string(), 30 | version: version.to_string(), 31 | } 32 | } 33 | 34 | fn validate(username: &str, password: &str) -> Result<(), GhcaDialerError> { 35 | if username.len() > 60 { 36 | return Err(GhcaDialerError::InvalidUsername(username.to_string())); 37 | } 38 | if password.len() > 60 { 39 | return Err(GhcaDialerError::InvalidUsername(password.to_string())); 40 | } 41 | Ok(()) 42 | } 43 | 44 | pub fn encrypt_account( 45 | &self, 46 | username: &str, 47 | password: &str, 48 | fst_timestamp: Option, 49 | sec_timestamp: Option, 50 | ) -> Result { 51 | Self::validate(username, password)?; 52 | let name_len = username.len() as u32; 53 | let pwd_len = password.len() as u32; 54 | 55 | let fst_timestamp = fst_timestamp.unwrap_or_else(current_timestamp); 56 | let sec_timestamp = sec_timestamp.unwrap_or_else(current_timestamp); 57 | 58 | let mut cursor = fst_timestamp % pwd_len; 59 | if cursor < 1 { 60 | cursor += 1; 61 | } 62 | let match_flag = if cursor == pwd_len { 1 } else { 0 }; 63 | 64 | let delta = cursor - match_flag; 65 | let md5_hash_prefix; 66 | { 67 | let mut md5 = HasherBuilder::build(HasherType::MD5); 68 | 69 | let prefix_len = delta + 1; 70 | let suffix_len = pwd_len - prefix_len; 71 | let pwd_prefix = &password[..prefix_len as usize]; 72 | let pwd_suffix = &password[prefix_len as usize..pwd_len as usize]; 73 | 74 | md5.update(&sec_timestamp.as_bytes_be()); 75 | md5.update(self.share_key[..(60 - prefix_len) as usize].as_bytes()); 76 | md5.update(pwd_prefix.as_bytes()); 77 | md5.update(username.as_bytes()); 78 | md5.update(self.share_key[..(64 - name_len - suffix_len) as usize].as_bytes()); 79 | md5.update(pwd_suffix.as_bytes()); 80 | 81 | let first_hashed_bytes = md5.finish(); 82 | let mut md5 = HasherBuilder::build(HasherType::MD5); 83 | md5.update(&first_hashed_bytes); 84 | md5_hash_prefix = md5.finish()[..8].to_hex().to_uppercase(); 85 | } 86 | 87 | let pwd_char_sum = password 88 | .as_bytes() 89 | .iter() 90 | .fold(0, |sum, x| sum + u32::from(*x)); 91 | let pin = format!("{:04X}", delta ^ pwd_char_sum); 92 | Ok(format!( 93 | "{}{:08X}{}{}{}{}", 94 | self.prefix, sec_timestamp, self.version, md5_hash_prefix, pin, username 95 | )) 96 | } 97 | } 98 | 99 | impl Configuration { 100 | pub fn share_key(self) -> &'static str { 101 | match self { 102 | Configuration::SichuanMac => { 103 | "aI0fC8RslXg6HXaKAUa6kpvcAXszvTcxYP8jmS9sBnVfIqTRdJS1eZNHmBjKN28j" 104 | } 105 | } 106 | } 107 | 108 | pub fn prefix(self) -> &'static str { 109 | match self { 110 | _ => "~ghca", 111 | } 112 | } 113 | 114 | pub fn version(self) -> &'static str { 115 | match self { 116 | Configuration::SichuanMac => "2023", 117 | } 118 | } 119 | } 120 | 121 | impl Dialer for GhcaDialer { 122 | type C = Configuration; 123 | 124 | fn load_from_config(config: Self::C) -> Self { 125 | GhcaDialer::new(config.share_key(), config.prefix(), config.version()) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/ghca/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dialer; 2 | 3 | #[cfg(test)] 4 | mod tests; 5 | -------------------------------------------------------------------------------- /src/ghca/tests.rs: -------------------------------------------------------------------------------- 1 | use common::dialer::Dialer; 2 | use ghca::dialer::{Configuration, GhcaDialer}; 3 | 4 | #[test] 5 | fn test_ghca_username_encrypt() { 6 | let dialer = GhcaDialer::load_from_config(Configuration::SichuanMac); 7 | let encrypted = dialer 8 | .encrypt_account( 9 | "05802278989@HYXY.XY", 10 | "123456", 11 | Some(0x57F486F7), 12 | Some(0x57F48719), 13 | ) 14 | .unwrap(); 15 | let encrypted2 = dialer 16 | .encrypt_account( 17 | "05802278989@HYXY.XY", 18 | "1", 19 | Some(0x57F4B79E), 20 | Some(0x57F4B7B0), 21 | ) 22 | .unwrap(); 23 | let err_result = dialer.encrypt_account( 24 | "05802278989@HYXY.XY", 25 | "123456123456123456123456123456123456123456123456123456123456123456123456", 26 | Some(0x57F4B79E), 27 | Some(0x57F4B7B0), 28 | ); 29 | assert_eq!( 30 | encrypted, 31 | "~ghca57F487192023484F1BD1D9AB5DC5013405802278989@HYXY.XY" 32 | ); 33 | assert_eq!( 34 | encrypted2, 35 | "~ghca57F4B7B020234370C48B10C2AF5E003105802278989@HYXY.XY" 36 | ); 37 | assert!(err_result.is_err()); 38 | } 39 | -------------------------------------------------------------------------------- /src/ipclient/dialer.rs: -------------------------------------------------------------------------------- 1 | use std::net::Ipv4Addr; 2 | use std::num::Wrapping; 3 | 4 | use common::bytes::BytesAbleNum; 5 | 6 | const USERNAME_MAX_LEN: usize = 30; 7 | const MAC_ADDRESS_LEN: usize = 18; 8 | 9 | #[derive(Debug)] 10 | pub enum MACOpenErr { 11 | UsernameTooLong(String), 12 | MACAddressError(String), 13 | } 14 | 15 | #[derive(Debug)] 16 | pub struct MACOpenPacket { 17 | username: String, 18 | ipaddress: Ipv4Addr, 19 | mac_address: String, 20 | isp: ISPCode, 21 | } 22 | 23 | #[derive(Debug, Clone, Copy)] 24 | pub enum Configuration { 25 | GUET, 26 | GXNU, 27 | } 28 | 29 | #[derive(Debug, Clone, Copy)] 30 | pub enum ISPCode { 31 | CChinaUnicom = 1 << 8, 32 | CChinaTelecom = 2 << 8, 33 | CChinaMobile = 3 << 8, 34 | } 35 | 36 | impl Configuration { 37 | pub fn hash_key(self) -> u32 { 38 | match self { 39 | _ => 0x4E67_C6A7, 40 | } 41 | } 42 | } 43 | 44 | impl MACOpenPacket { 45 | pub fn new(username: &str, ipaddress: Ipv4Addr, mac_address: &str, isp: ISPCode) -> Self { 46 | MACOpenPacket { 47 | username: username.to_string(), 48 | ipaddress, 49 | mac_address: mac_address.to_string(), 50 | isp, 51 | } 52 | } 53 | 54 | pub fn as_bytes(&self, hash_key: u32) -> Result, MACOpenErr> { 55 | let mut macopen_packet = Vec::with_capacity(60); 56 | { 57 | self.validate()?; 58 | 59 | let mut username_bytes = [0; USERNAME_MAX_LEN]; 60 | let mut mac_address_bytes = [0; MAC_ADDRESS_LEN]; 61 | username_bytes[..self.username.len()].clone_from_slice(self.username.as_bytes()); 62 | mac_address_bytes[..self.mac_address.len()] 63 | .clone_from_slice(self.mac_address.as_bytes()); 64 | 65 | macopen_packet.extend_from_slice(&username_bytes); 66 | macopen_packet.extend_from_slice(&self.ipaddress.octets()); 67 | macopen_packet.extend_from_slice(&mac_address_bytes); 68 | macopen_packet.extend((self.isp as u32).as_bytes_be()); 69 | 70 | let hash_bytes = Self::hash_bytes(&macopen_packet, hash_key); 71 | macopen_packet.extend_from_slice(&hash_bytes); 72 | } 73 | 74 | Ok(macopen_packet) 75 | } 76 | 77 | fn validate(&self) -> Result<(), MACOpenErr> { 78 | if self.username.len() > USERNAME_MAX_LEN - 1 { 79 | return Err(MACOpenErr::UsernameTooLong(self.username.clone())); 80 | } 81 | if self.mac_address.len() != MAC_ADDRESS_LEN - 1 { 82 | return Err(MACOpenErr::MACAddressError(self.mac_address.clone())); 83 | } 84 | Ok(()) 85 | } 86 | 87 | fn hash_bytes(bytes: &[u8], hash_key: u32) -> [u8; 4] { 88 | let mut hash = Wrapping(hash_key as i32); 89 | for c in bytes.iter() { 90 | hash ^= (hash << 5) + (hash >> 2) + Wrapping(i32::from(*c)); 91 | } 92 | hash &= Wrapping(0x7fff_ffff); 93 | 94 | let mut hash_bytes = [0; 4]; 95 | (hash.0 as u32).write_bytes_le(&mut hash_bytes); 96 | hash_bytes 97 | } 98 | } 99 | 100 | #[test] 101 | fn test_mac_opener_hash_bytes() { 102 | let bytes1 = [1, 2, 3, 4, 5, 6, 7, 0]; 103 | let hash_bytes1 = MACOpenPacket::hash_bytes(&bytes1, Configuration::GUET.hash_key()); 104 | 105 | let bytes2 = [ 106 | 97, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 107 | 172, 16, 1, 1, 52, 48, 58, 54, 49, 58, 56, 54, 58, 56, 55, 58, 57, 70, 58, 70, 49, 0, 0, 0, 108 | 1, 0, 109 | ]; 110 | let hash_bytes2 = MACOpenPacket::hash_bytes(&bytes2, Configuration::GUET.hash_key()); 111 | 112 | assert_eq!(hash_bytes1, [0x9c, 0x89, 0xf8, 0x3d]); 113 | assert_eq!(hash_bytes2, [255, 189, 40, 90]); 114 | } 115 | -------------------------------------------------------------------------------- /src/ipclient/mod.rs: -------------------------------------------------------------------------------- 1 | // copy from https://github.com/xuzhipengnt/ipclient_gxnu 2 | pub mod dialer; 3 | 4 | #[cfg(test)] 5 | mod tests; 6 | -------------------------------------------------------------------------------- /src/ipclient/tests.rs: -------------------------------------------------------------------------------- 1 | use ipclient::dialer::{Configuration, ISPCode, MACOpenPacket}; 2 | use std::net::Ipv4Addr; 3 | use std::str::FromStr; 4 | 5 | #[test] 6 | fn test_ipclient_macopener_packet() { 7 | let packet = MACOpenPacket::new( 8 | "a", 9 | Ipv4Addr::from_str("172.16.1.1").unwrap(), 10 | "40:61:86:87:9F:F1", 11 | ISPCode::CChinaUnicom, 12 | ); 13 | let packet_bytes = packet.as_bytes(Configuration::GUET.hash_key()).unwrap(); 14 | 15 | let real_bytes: Vec = vec![ 16 | 97, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17 | 172, 16, 1, 1, 52, 48, 58, 54, 49, 58, 56, 54, 58, 56, 55, 58, 57, 70, 58, 70, 49, 0, 0, 0, 18 | 1, 0, 255, 189, 40, 90, 19 | ]; 20 | assert_eq!(packet_bytes, real_bytes); 21 | let packet_err = MACOpenPacket::new( 22 | "05802278989@HYXY.XY05802278989@HYXY.XY05802278989@HYXY.\ 23 | XY05802278989@HYXY.XY05802278989@HYXY.XY", 24 | Ipv4Addr::from_str("172.16.1.1").unwrap(), 25 | "40:61:86:87:9F:F1", 26 | ISPCode::CChinaUnicom, 27 | ); 28 | assert!(packet_err.as_bytes(Configuration::GUET.hash_key()).is_err()); 29 | } 30 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | extern crate aes_frast; 4 | extern crate byteorder; 5 | extern crate chrono; 6 | extern crate linked_hash_map; 7 | extern crate md4; 8 | extern crate md5; 9 | extern crate rand; 10 | extern crate sha1; 11 | 12 | #[cfg(feature = "drcom")] 13 | pub mod drcom; 14 | #[cfg(feature = "ghca")] 15 | pub mod ghca; 16 | #[cfg(feature = "ipclient")] 17 | pub mod ipclient; 18 | #[cfg(feature = "netkeeper")] 19 | pub mod netkeeper; 20 | #[cfg(feature = "netkeeper4")] 21 | pub mod netkeeper4; 22 | #[cfg(feature = "singlenet")] 23 | pub mod singlenet; 24 | #[cfg(feature = "srun3k")] 25 | pub mod srun3k; 26 | 27 | pub mod common; 28 | mod crypto; 29 | 30 | #[cfg(test)] 31 | mod tests {} 32 | -------------------------------------------------------------------------------- /src/netkeeper/dialer.rs: -------------------------------------------------------------------------------- 1 | use std::str; 2 | 3 | use common::bytes::BytesAbleNum; 4 | use common::dialer::Dialer; 5 | use common::hex::ToHex; 6 | use common::utils::current_timestamp; 7 | use crypto::hash::{HasherBuilder, HasherType}; 8 | 9 | // copy from https://github.com/miao1007/Openwrt-NetKeeper 10 | #[derive(Debug, Clone, Copy)] 11 | pub enum Configuration { 12 | Zhejiang, 13 | SingleNet, 14 | Enterprise, 15 | Chongqing, 16 | Chongqing2, 17 | Wuhan, 18 | Qinghai, 19 | Xinjiang, 20 | Hebei, 21 | Shandong, 22 | Shanxi, 23 | Gansu, 24 | } 25 | 26 | #[derive(Debug)] 27 | pub struct NetkeeperDialer { 28 | pub share_key: String, 29 | pub prefix: String, 30 | } 31 | 32 | impl NetkeeperDialer { 33 | pub fn new(share_key: &str, prefix: &str) -> Self { 34 | NetkeeperDialer { 35 | share_key: share_key.to_string(), 36 | prefix: prefix.to_string(), 37 | } 38 | } 39 | 40 | pub fn pin27_bytes(source: u32) -> [u8; 6] { 41 | let mut time_hash: [u8; 4] = [0; 4]; 42 | let mut result: [u8; 6] = [0; 6]; 43 | for (i, code) in time_hash.iter_mut().enumerate() { 44 | for j in 0..8 { 45 | *code += ((((source >> (i + 4 * j)) & 1) << (7 - j)) & 0xFF) as u8; 46 | } 47 | } 48 | 49 | result[0] = (time_hash[0] >> 2) & 0x3F; 50 | result[1] = ((time_hash[0] & 0x03) << 4) | ((time_hash[1] >> 4) & 0x0F); 51 | result[2] = ((time_hash[1] & 0x0F) << 2) | ((time_hash[2] >> 6) & 0x03); 52 | result[3] = time_hash[2] & 0x3F; 53 | result[4] = (time_hash[3] >> 2) & 0x3F; 54 | result[5] = (time_hash[3] & 0x03) << 4; 55 | 56 | for byte in result.iter_mut().take(6) { 57 | *byte += 0x20; 58 | if *byte > 0x40 { 59 | *byte += 1; 60 | } 61 | } 62 | result 63 | } 64 | 65 | pub fn encrypt_account(&self, username: &str, timestamp: Option) -> String { 66 | let username = username.to_uppercase(); 67 | let timenow = timestamp.unwrap_or_else(current_timestamp); 68 | let time_div_by_five: u32 = timenow / 5; 69 | 70 | let pin27_bytes: [u8; 6] = Self::pin27_bytes(time_div_by_five); 71 | let pin27_str = unsafe { str::from_utf8_unchecked(&pin27_bytes) }; 72 | 73 | let pin89_str = { 74 | let mut md5 = HasherBuilder::build(HasherType::MD5); 75 | md5.update(&time_div_by_five.as_bytes_be()); 76 | md5.update(username.split('@').nth(0).unwrap().as_bytes()); 77 | md5.update(self.share_key.as_bytes()); 78 | 79 | let hashed_bytes = md5.finish(); 80 | hashed_bytes[0..1].to_hex() 81 | }; 82 | 83 | format!("{}{}{}{}", self.prefix, pin27_str, pin89_str, username) 84 | } 85 | } 86 | 87 | impl Configuration { 88 | pub fn share_key(self) -> &'static str { 89 | match self { 90 | Configuration::Zhejiang => "zjxinlisx01", 91 | Configuration::SingleNet => "singlenet01", 92 | Configuration::Enterprise => "zjxinlisx02", 93 | Configuration::Chongqing => "cqxinliradius002", 94 | Configuration::Chongqing2 => "xianxinli1radius", 95 | Configuration::Wuhan => "hubtxinli01", 96 | Configuration::Qinghai => "shd@xiaoyuan0002", 97 | Configuration::Xinjiang => "xinjiang@0724", 98 | Configuration::Hebei => "hebeicncxinli002", 99 | Configuration::Shandong => "shandongmobile13", 100 | Configuration::Shanxi => "sh_xi@xiaoyuan01", 101 | Configuration::Gansu => "xiaoyuanyixun001", 102 | } 103 | } 104 | 105 | pub fn prefix(self) -> &'static str { 106 | match self { 107 | _ => "\r\n", 108 | } 109 | } 110 | } 111 | 112 | impl Dialer for NetkeeperDialer { 113 | type C = Configuration; 114 | 115 | fn load_from_config(config: Self::C) -> Self { 116 | NetkeeperDialer::new(config.share_key(), config.prefix()) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/netkeeper/heartbeater.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | use std::{io, result, str}; 3 | 4 | use byteorder::{ByteOrder, NetworkEndian}; 5 | use crypto::cipher::{CipherError, SimpleCipher}; 6 | use crypto::hash::{HasherBuilder, HasherType}; 7 | use linked_hash_map::LinkedHashMap; 8 | 9 | use common::bytes::BytesAbleNum; 10 | use common::reader::{ReadBytesError, ReaderHelper}; 11 | use common::utils::current_timestamp; 12 | 13 | #[derive(Debug)] 14 | pub enum NetkeeperHeartbeatError { 15 | PacketCipherError(CipherError), 16 | PacketReadError(ReadBytesError), 17 | UnexpectedBytes(Vec), 18 | } 19 | 20 | type PacketResult = result::Result; 21 | 22 | #[derive(Debug)] 23 | pub struct Frame { 24 | type_name: String, 25 | content: LinkedHashMap, 26 | } 27 | 28 | #[derive(Debug)] 29 | pub struct Packet { 30 | magic_number: u16, 31 | version: u8, 32 | code: u16, 33 | frame: Frame, 34 | } 35 | 36 | pub struct PacketUtils; 37 | 38 | impl Frame { 39 | pub fn new(type_name: &str, content: Option>) -> Self { 40 | let content = content.unwrap_or_else(LinkedHashMap::new); 41 | Frame { 42 | type_name: type_name.to_string(), 43 | content, 44 | } 45 | } 46 | 47 | pub fn add(&mut self, name: &str, value: &str) { 48 | self.content.insert(name.to_string(), value.to_string()); 49 | } 50 | 51 | fn as_bytes(&self, join_with: Option<&str>) -> Vec { 52 | let mut linked_content: Vec = Vec::new(); 53 | let join_with = join_with.unwrap_or("&"); 54 | linked_content.push(format!("TYPE={}", self.type_name)); 55 | for (key, value) in self.content.iter() { 56 | linked_content.push(format!("{}={}", key, value)); 57 | } 58 | linked_content.join(join_with).as_bytes().to_vec() 59 | } 60 | 61 | fn from_bytes(bytes: &[u8], split_with: Option<&str>) -> Self { 62 | let split_with = split_with.unwrap_or("&"); 63 | 64 | let byte_content; 65 | unsafe { 66 | byte_content = str::from_utf8_unchecked(bytes); 67 | } 68 | 69 | let mut type_name = String::from(""); 70 | let mut frame_content: LinkedHashMap = LinkedHashMap::new(); 71 | for param in byte_content.split(split_with) { 72 | if !param.contains('=') { 73 | continue; 74 | } 75 | let parts: Vec = param.splitn(2, '=').map(|s| s.to_string()).collect(); 76 | if parts[0].to_lowercase() == "type" { 77 | type_name = String::from_str(&parts[1]).unwrap(); 78 | } else { 79 | frame_content.insert( 80 | String::from_str(&parts[0]).unwrap(), 81 | String::from_str(&parts[1]).unwrap(), 82 | ); 83 | } 84 | } 85 | 86 | Frame { 87 | type_name, 88 | content: frame_content, 89 | } 90 | } 91 | 92 | fn len(&self) -> u32 { 93 | self.as_bytes(None).len() as u32 94 | } 95 | } 96 | 97 | impl Packet { 98 | fn magic_number() -> u16 { 99 | // as little endian 100 | 0x4852u16 101 | } 102 | 103 | pub fn new(version: u8, code: u16, frame: Frame) -> Self { 104 | Packet { 105 | magic_number: Self::magic_number(), 106 | version, 107 | code, 108 | frame, 109 | } 110 | } 111 | 112 | pub fn as_bytes(&self, encrypter: &E) -> PacketResult> 113 | where 114 | E: SimpleCipher, 115 | { 116 | let mut packet_bytes = Vec::new(); 117 | { 118 | let version_str = self.version.to_string(); 119 | let enc_content = encrypter 120 | .encrypt(&self.frame.as_bytes(None)) 121 | .map_err(NetkeeperHeartbeatError::PacketCipherError)?; 122 | 123 | packet_bytes.extend(self.magic_number.as_bytes_be()); 124 | packet_bytes.extend(version_str.as_bytes()); 125 | packet_bytes.extend(self.code.as_bytes_be()); 126 | packet_bytes.extend((enc_content.len() as u32).as_bytes_be()); 127 | packet_bytes.extend(enc_content); 128 | } 129 | Ok(packet_bytes) 130 | } 131 | 132 | pub fn from_bytes( 133 | input: &mut io::BufReader, 134 | encrypter: &E, 135 | split_with: Option<&str>, 136 | ) -> PacketResult 137 | where 138 | E: SimpleCipher, 139 | R: io::Read, 140 | { 141 | { 142 | let magic_number_bytes = input 143 | .read_bytes(2) 144 | .map_err(NetkeeperHeartbeatError::PacketReadError)?; 145 | let magic_number = NetworkEndian::read_u16(&magic_number_bytes); 146 | if magic_number != Self::magic_number() { 147 | return Err(NetkeeperHeartbeatError::UnexpectedBytes(magic_number_bytes)); 148 | } 149 | } 150 | 151 | let version; 152 | { 153 | let version_bytes = input 154 | .read_bytes(2) 155 | .map_err(NetkeeperHeartbeatError::PacketReadError)?; 156 | let version_str = String::from_utf8(version_bytes).unwrap(); 157 | version = version_str.parse::().unwrap(); 158 | } 159 | 160 | let code; 161 | { 162 | let code_bytes = input 163 | .read_bytes(2) 164 | .map_err(NetkeeperHeartbeatError::PacketReadError)?; 165 | code = NetworkEndian::read_u16(&code_bytes); 166 | } 167 | 168 | let content_length; 169 | { 170 | let content_length_bytes = input 171 | .read_bytes(4) 172 | .map_err(NetkeeperHeartbeatError::PacketReadError)?; 173 | content_length = NetworkEndian::read_u32(&content_length_bytes); 174 | } 175 | 176 | let encrypted_content = input 177 | .read_bytes(content_length as usize) 178 | .map_err(NetkeeperHeartbeatError::PacketReadError)?; 179 | let plain_content = encrypter 180 | .decrypt(&encrypted_content) 181 | .map_err(NetkeeperHeartbeatError::PacketCipherError)?; 182 | let frame = Frame::from_bytes(&plain_content, split_with); 183 | 184 | Ok(Self::new(version, code, frame)) 185 | } 186 | } 187 | 188 | impl PacketUtils { 189 | pub fn claculate_pin(timestamp: Option) -> String { 190 | let timestamp = timestamp.unwrap_or_else(current_timestamp); 191 | let salts = ["wanglei", "zhangni", "wangtianyou"]; 192 | let mut hashed_bytes = [0; 16]; 193 | let timestamp_hex = format!("{:08x}", timestamp); 194 | let timestamp_hex_chars: Vec = timestamp_hex.chars().collect(); 195 | { 196 | let mut md5 = HasherBuilder::build(HasherType::MD5); 197 | let salt = salts[(timestamp % 3) as usize]; 198 | 199 | md5.update(timestamp_hex.as_bytes()); 200 | md5.update(salt.as_bytes()); 201 | hashed_bytes.clone_from_slice(&md5.finish()); 202 | } 203 | 204 | format!( 205 | "{}{}{:02x}{:02x}{}{}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{}{}{:02x}{:\ 206 | 02x}{:02x}{}{}{:02x}{:02x}{:02x}", 207 | timestamp_hex_chars[0], 208 | timestamp_hex_chars[1], 209 | hashed_bytes[0], 210 | hashed_bytes[1], 211 | timestamp_hex_chars[2], 212 | timestamp_hex_chars[3], 213 | hashed_bytes[2], 214 | hashed_bytes[3], 215 | hashed_bytes[4], 216 | hashed_bytes[5], 217 | hashed_bytes[6], 218 | hashed_bytes[7], 219 | hashed_bytes[8], 220 | hashed_bytes[9], 221 | timestamp_hex_chars[4], 222 | timestamp_hex_chars[5], 223 | hashed_bytes[10], 224 | hashed_bytes[11], 225 | hashed_bytes[12], 226 | timestamp_hex_chars[6], 227 | timestamp_hex_chars[7], 228 | hashed_bytes[13], 229 | hashed_bytes[14], 230 | hashed_bytes[15] 231 | ) 232 | } 233 | } 234 | 235 | #[test] 236 | fn test_frame_concat() { 237 | let mut content = LinkedHashMap::new(); 238 | content.insert("USER_NAME".to_string(), "05802278989@HYXY.XY".to_string()); 239 | content.insert("PASSWORD".to_string(), "000000".to_string()); 240 | let frame = Frame::new("HEARTBEAT", Some(content)); 241 | let frame_bytes = frame.as_bytes(None); 242 | let frame_str = ::std::str::from_utf8(&frame_bytes).unwrap(); 243 | 244 | assert_eq!( 245 | frame_str, 246 | "TYPE=HEARTBEAT&USER_NAME=05802278989@HYXY.XY&PASSWORD=000000" 247 | ); 248 | assert_eq!(frame.len(), 60); 249 | } 250 | 251 | #[test] 252 | fn test_frame_parse_from_bytes() { 253 | let origin = "TYPE=HEARTBEAT&USER_NAME=05802278989@HYXY.XY&PASSWORD=000000".to_string(); 254 | let bytes = origin.as_bytes(); 255 | let frame = Frame::from_bytes(bytes, None); 256 | 257 | let frame_bytes = frame.as_bytes(None); 258 | let frame_str = ::std::str::from_utf8(&frame_bytes).unwrap(); 259 | 260 | assert_eq!(frame_str, origin); 261 | assert_eq!(frame.len(), 60); 262 | } 263 | 264 | #[test] 265 | fn test_calc_heartbeat_pin() { 266 | let pin = PacketUtils::claculate_pin(Some(1472483020)); 267 | assert_eq!(pin, "57c41bc45b493cfb5f5016074e987ef9cca96334"); 268 | } 269 | 270 | #[test] 271 | fn test_aes_128_ecb_encrypt() { 272 | use crypto::cipher::AES_128_ECB; 273 | 274 | let aes = AES_128_ECB::from_key(b"xlzjhrprotocol3x").unwrap(); 275 | let plain_text = "TYPE=HEARTBEAT&USER_NAME=05802278989@HYXY.XY&PASSWORD=000000"; 276 | let encrypted = aes.encrypt(plain_text.as_bytes()).unwrap(); 277 | let real_data = vec![ 278 | 66, 100, 164, 73, 167, 41, 222, 211, 188, 8, 14, 110, 252, 246, 121, 119, 79, 18, 254, 193, 279 | 72, 163, 54, 136, 248, 60, 221, 177, 221, 0, 13, 10, 146, 141, 142, 244, 89, 10, 176, 106, 280 | 162, 242, 204, 38, 73, 34, 55, 137, 180, 223, 253, 142, 43, 158, 209, 80, 100, 141, 11, 15, 281 | 146, 20, 207, 10, 282 | ]; 283 | assert_eq!(encrypted, real_data); 284 | } 285 | -------------------------------------------------------------------------------- /src/netkeeper/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dialer; 2 | pub mod heartbeater; 3 | 4 | #[cfg(test)] 5 | mod tests; 6 | -------------------------------------------------------------------------------- /src/netkeeper/tests.rs: -------------------------------------------------------------------------------- 1 | use common::dialer::Dialer; 2 | use crypto::cipher::AES_128_ECB; 3 | use netkeeper::dialer::{Configuration, NetkeeperDialer}; 4 | use netkeeper::heartbeater::{Frame, Packet}; 5 | use std::io::BufReader; 6 | 7 | #[test] 8 | fn test_netkeeper_username_encrypt() { 9 | let dialer = NetkeeperDialer::load_from_config(Configuration::Zhejiang); 10 | let encrypted = dialer.encrypt_account("05802278989@HYXY.XY", Some(1472483020)); 11 | assert_eq!(encrypted, "\r\n:R#(P 5005802278989@HYXY.XY"); 12 | } 13 | 14 | #[test] 15 | fn test_netkeeper_heartbeat() { 16 | let mut frame = Frame::new("HEARTBEAT", None); 17 | frame.add("USER_NAME", "05802278989@HYXY.XY"); 18 | frame.add("PASSWORD", "123456"); 19 | frame.add("IP", "124.77.234.214"); 20 | frame.add("MAC", "08:00:27:00:24:FD"); 21 | frame.add("VERSION_NUMBER", "1.0.1"); 22 | frame.add("PIN", "MAC_TEST"); 23 | frame.add("DRIVER", "1"); 24 | frame.add("KEY", "123456"); 25 | 26 | let packet = Packet::new(30 as u8, 0x0205, frame); 27 | let encrypter = AES_128_ECB::from_key(b"xlzjhrprotocol3x").unwrap(); 28 | 29 | let packet_bytes = packet.as_bytes(&encrypter).unwrap(); 30 | let real_bytes = vec![ 31 | 72, 82, 51, 48, 2, 5, 0, 0, 0, 160, 66, 100, 164, 73, 167, 41, 222, 211, 188, 8, 14, 110, 32 | 252, 246, 121, 119, 79, 18, 254, 193, 72, 163, 54, 136, 248, 60, 221, 177, 221, 0, 13, 10, 33 | 146, 141, 142, 244, 89, 10, 176, 106, 162, 242, 204, 38, 73, 34, 55, 137, 97, 126, 9, 165, 34 | 70, 31, 157, 168, 71, 197, 187, 163, 41, 229, 167, 53, 122, 190, 181, 154, 87, 111, 227, 35 | 123, 69, 129, 67, 51, 81, 241, 122, 165, 40, 52, 89, 244, 80, 95, 124, 126, 112, 49, 174, 36 | 27, 56, 156, 90, 142, 92, 15, 46, 198, 142, 57, 101, 139, 41, 47, 207, 36, 92, 216, 48, 37 | 176, 133, 151, 154, 242, 123, 13, 94, 251, 108, 64, 46, 78, 158, 38, 66, 163, 102, 61, 241, 38 | 207, 125, 163, 239, 153, 239, 75, 85, 0, 97, 237, 41, 117, 94, 251, 126, 197, 12, 140, 230, 39 | 63, 40, 52, 240, 253, 15, 197, 60, 48, 40 | ]; 41 | assert_eq!(packet_bytes, real_bytes); 42 | } 43 | 44 | #[test] 45 | fn test_netkeeper_heartbeat_parse() { 46 | let encrypter = AES_128_ECB::from_key(b"xlzjhrprotocol3x").unwrap(); 47 | let origin_bytes: Vec = vec![ 48 | 72, 82, 51, 48, 2, 5, 0, 0, 0, 160, 66, 100, 164, 73, 167, 41, 222, 211, 188, 8, 14, 110, 49 | 252, 246, 121, 119, 79, 18, 254, 193, 72, 163, 54, 136, 248, 60, 221, 177, 221, 0, 13, 10, 50 | 146, 141, 142, 244, 89, 10, 176, 106, 162, 242, 204, 38, 73, 34, 55, 137, 97, 126, 9, 165, 51 | 70, 31, 157, 168, 71, 197, 187, 163, 41, 229, 167, 53, 122, 190, 181, 154, 87, 111, 227, 52 | 123, 69, 129, 67, 51, 81, 241, 122, 165, 40, 52, 89, 244, 80, 95, 124, 126, 112, 49, 174, 53 | 27, 56, 156, 90, 142, 92, 15, 46, 198, 142, 57, 101, 139, 41, 47, 207, 36, 92, 216, 48, 54 | 176, 133, 151, 154, 242, 123, 13, 94, 251, 108, 64, 46, 78, 158, 38, 66, 163, 102, 61, 241, 55 | 207, 125, 163, 239, 153, 239, 75, 85, 0, 97, 237, 41, 117, 94, 251, 126, 197, 12, 140, 230, 56 | 63, 40, 52, 240, 253, 15, 197, 60, 48, 57 | ]; 58 | 59 | let mut buffer = BufReader::new(&origin_bytes as &[u8]); 60 | let packet = Packet::from_bytes(&mut buffer, &encrypter, None).unwrap(); 61 | let packet_bytes = packet.as_bytes(&encrypter).unwrap(); 62 | assert_eq!(packet_bytes, origin_bytes); 63 | } 64 | -------------------------------------------------------------------------------- /src/netkeeper4/dialer.rs: -------------------------------------------------------------------------------- 1 | use std::str; 2 | 3 | use common::dialer::Dialer; 4 | use common::utils::current_timestamp; 5 | use crypto::hash::{HasherBuilder, HasherType}; 6 | use netkeeper::dialer::NetkeeperDialer; 7 | 8 | #[derive(Debug, Copy, Clone)] 9 | pub enum Configuration { 10 | Zhejiang, 11 | } 12 | 13 | #[derive(Debug)] 14 | pub struct Netkeeper4Dialer { 15 | pub share_key: String, 16 | pub prefix: String, 17 | pub padding: String, 18 | } 19 | 20 | impl Configuration { 21 | pub fn share_key(self) -> &'static str { 22 | match self { 23 | Configuration::Zhejiang => "zjdxxyfsj2018", 24 | } 25 | } 26 | 27 | pub fn prefix(self) -> &'static str { 28 | match self { 29 | Configuration::Zhejiang => "\r1", 30 | } 31 | } 32 | 33 | pub fn padding(self) -> &'static str { 34 | match self { 35 | Configuration::Zhejiang => "GJxDpkZLtSEFarOMuHv", 36 | } 37 | } 38 | } 39 | 40 | impl Netkeeper4Dialer { 41 | pub fn new(share_key: &str, prefix: &str, padding: &str) -> Self { 42 | Netkeeper4Dialer { 43 | share_key: share_key.to_string(), 44 | prefix: prefix.to_string(), 45 | padding: padding.to_string(), 46 | } 47 | } 48 | 49 | fn prepare_md5_bytes(pin27_bytes: [u8; 6], username: &str, padded_key: &str) -> Vec { 50 | let padded_bytes = padded_key.as_bytes(); 51 | let name_bytes = username.split('@').nth(0).unwrap().as_bytes(); 52 | let mut md5_bytes = vec![0u8; 64]; 53 | let (mut j, mut k, mut l) = (0usize, 0usize, 0usize); 54 | for (i, item) in md5_bytes.iter_mut().enumerate().take(64) { 55 | match i % 3 { 56 | 0 => { 57 | if j < name_bytes.len() { 58 | *item = name_bytes[j]; 59 | j += 1; 60 | continue; 61 | } 62 | } 63 | 1 => { 64 | if k < 6 { 65 | *item = pin27_bytes[k]; 66 | k += 1; 67 | continue; 68 | } 69 | } 70 | 2 => { 71 | if l < 32 { 72 | *item = padded_bytes[l]; 73 | l += 1; 74 | continue; 75 | } 76 | } 77 | _ => unreachable!(), 78 | } 79 | if k < 6 { 80 | *item = pin27_bytes[k]; 81 | k += 1; 82 | continue; 83 | } 84 | if l < 32 { 85 | *item = padded_bytes[l]; 86 | l += 1; 87 | continue; 88 | } 89 | *item = i as u8; 90 | } 91 | md5_bytes 92 | } 93 | 94 | pub fn encrypt_account(&self, username: &str, timestamp: Option) -> String { 95 | let username = username.to_uppercase(); 96 | let timenow = timestamp.unwrap_or_else(current_timestamp); 97 | let time_div_by_five: u32 = timenow / 5; 98 | 99 | let pin27_bytes: [u8; 6] = NetkeeperDialer::pin27_bytes(time_div_by_five); 100 | let pin27_str = unsafe { str::from_utf8_unchecked(&pin27_bytes) }; 101 | 102 | let pin89_str = { 103 | let padded = format!("{}{}", self.share_key, self.padding); 104 | let mut md5 = HasherBuilder::build(HasherType::MD5); 105 | md5.update(&Self::prepare_md5_bytes(pin27_bytes, &username, &padded)); 106 | let hashed_bytes = md5.finish(); 107 | let x = hashed_bytes[((hashed_bytes[11] & 0xF0) >> 4) as usize]; 108 | let y = hashed_bytes[((hashed_bytes[13] & 0x3C) >> 2) as usize]; 109 | let z = hashed_bytes[(hashed_bytes[6] & 0x0F) as usize]; 110 | let result = ((x & 0xF0) >> 6) + (y & 0x3C) + ((z & 0x0F) << 6); 111 | format!("{:02x}", result) 112 | }; 113 | 114 | format!("{}{}{}{}", self.prefix, pin27_str, pin89_str, username) 115 | } 116 | } 117 | 118 | impl Dialer for Netkeeper4Dialer { 119 | type C = Configuration; 120 | 121 | fn load_from_config(config: Self::C) -> Self { 122 | Netkeeper4Dialer::new(config.share_key(), config.prefix(), config.padding()) 123 | } 124 | } 125 | 126 | #[test] 127 | fn test_prepare_md5_bytes() { 128 | let md5_bytes = Netkeeper4Dialer::prepare_md5_bytes( 129 | [0, 1, 2, 3, 4, 5], 130 | "05802278989@HYXY.XY", 131 | "112233445566778899aabbccddeeffgg", 132 | ); 133 | assert_eq!( 134 | md5_bytes, 135 | vec![ 136 | 48, 0, 49, 53, 1, 49, 56, 2, 50, 48, 3, 50, 50, 4, 51, 50, 5, 51, 55, 52, 52, 56, 53, 137 | 53, 57, 54, 54, 56, 55, 55, 57, 56, 56, 57, 57, 97, 97, 98, 98, 99, 99, 100, 100, 101, 138 | 101, 102, 102, 103, 103, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 139 | ] 140 | ); 141 | } 142 | -------------------------------------------------------------------------------- /src/netkeeper4/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dialer; 2 | 3 | #[cfg(test)] 4 | mod tests; 5 | -------------------------------------------------------------------------------- /src/netkeeper4/tests.rs: -------------------------------------------------------------------------------- 1 | use common::dialer::Dialer; 2 | use netkeeper4::dialer::{Configuration, Netkeeper4Dialer}; 3 | 4 | #[test] 5 | fn test_netkeeper4_username_encrypt() { 6 | let dialer = Netkeeper4Dialer::load_from_config(Configuration::Zhejiang); 7 | let encrypted = dialer.encrypt_account("05802278989@HYXY.XY", Some(1535814909)); 8 | assert_eq!(encrypted, "\r1I7L]. 1905802278989@HYXY.XY"); 9 | } 10 | -------------------------------------------------------------------------------- /src/singlenet/attributes.rs: -------------------------------------------------------------------------------- 1 | use std::net::Ipv4Addr; 2 | use std::{result, str}; 3 | 4 | use byteorder::{ByteOrder, NetworkEndian}; 5 | 6 | use common::bytes::{BytesAble, BytesAbleNum}; 7 | use common::hex::ToHex; 8 | use common::utils::current_timestamp; 9 | use crypto::hash::{HasherBuilder, HasherType}; 10 | 11 | #[derive(Debug)] 12 | pub enum ParseAttributesError { 13 | // Expect length {}, got {} 14 | UnexpectDataLength(usize, usize), 15 | } 16 | 17 | type AttributeResult = result::Result; 18 | 19 | #[derive(Debug, Copy, Clone)] 20 | pub enum AttributeValueType { 21 | TInteger = 0x0, 22 | TIPAddress = 0x1, 23 | // or Integer array? 24 | TString = 0x2, 25 | TGroup = 0x3, 26 | // Attributes group 27 | } 28 | 29 | #[derive(Debug, Copy, Clone)] 30 | pub enum AttributeType { 31 | TAttribute, 32 | TUserName, 33 | TClientIPAddress, 34 | TClientVersion, 35 | TClientType, 36 | TOSVersion, 37 | TOSLang, 38 | TAdapterInfo, 39 | TCPUInfo, 40 | TMACAddress, 41 | TMemorySize, 42 | TDefaultExplorer, 43 | TBubble, 44 | TBubbleId, 45 | TBubbleTitle, 46 | TBubbleContext, 47 | TBubbleContextURL, 48 | TBubbleKeepTime, 49 | TBubbleDelayTime, 50 | TBubbleType, 51 | TChannel, 52 | TChannelNote, 53 | TChannelContextURL, 54 | TChannelContextURL2, 55 | TChannelOrder, 56 | TPlugin, 57 | TPluginName, 58 | TPluginConfigureData, 59 | TUpdateVersion, 60 | TUpdateDownloadURL, 61 | TUpdateDescription, 62 | TKeepAliveTime, 63 | TKeepAliveInterval, 64 | TKeepAliveData, 65 | TProcessCheckInterval, 66 | TProcessCheckList, 67 | TRealTimeBubbleServer, 68 | TRealTimeBubbleInterval, 69 | TWifiTransmitIPList, 70 | TWifiShareNumber, 71 | TWifiShareCode, 72 | TWifiShareErrorString, 73 | TWifiShareBindRequired, 74 | TPlugin2, 75 | TDeviceSN, 76 | TDeviceType, 77 | TWifiRedirectURL, 78 | } 79 | 80 | #[derive(Debug)] 81 | pub struct Attribute { 82 | name: String, 83 | parent_id: u8, 84 | attribute_id: u8, 85 | value_type_id: u8, 86 | data: Vec, 87 | } 88 | 89 | pub struct KeepaliveDataCalculator; 90 | 91 | pub trait AttributeValue: BytesAble {} 92 | 93 | pub trait AttributeVec { 94 | fn as_bytes(&self) -> Vec; 95 | fn length(&self) -> u16; 96 | 97 | fn from_bytes(bytes: &[u8]) -> AttributeResult>; 98 | } 99 | 100 | impl Attribute { 101 | pub fn new( 102 | name: &str, 103 | parent_id: u8, 104 | attribute_id: u8, 105 | value_type_id: u8, 106 | data: Vec, 107 | ) -> Self { 108 | Attribute { 109 | name: name.to_string(), 110 | parent_id, 111 | attribute_id, 112 | value_type_id, 113 | data, 114 | } 115 | } 116 | 117 | pub fn from_type(attribute_type: AttributeType, value: &V) -> Self 118 | where 119 | V: AttributeValue, 120 | { 121 | Self::new( 122 | attribute_type.name(), 123 | attribute_type.parent().id(), 124 | attribute_type.id(), 125 | attribute_type.value_type() as u8, 126 | value.as_bytes().to_vec(), 127 | ) 128 | } 129 | 130 | fn header_length() -> u16 { 131 | 3u16 132 | } 133 | 134 | pub fn length(&self) -> u16 { 135 | self.data_length() + Self::header_length() 136 | } 137 | 138 | pub fn as_bytes(&self) -> Vec { 139 | let mut attribute_bytes = Vec::new(); 140 | { 141 | let raw_attribute_id = self.attribute_id as u8; 142 | attribute_bytes.push(raw_attribute_id); 143 | attribute_bytes.extend(self.length().as_bytes_be()); 144 | attribute_bytes.extend_from_slice(&self.data); 145 | } 146 | attribute_bytes 147 | } 148 | 149 | fn data_length(&self) -> u16 { 150 | self.data.len() as u16 151 | } 152 | } 153 | 154 | impl KeepaliveDataCalculator { 155 | pub fn calculate(timestamp: Option, last_data: Option<&str>) -> String { 156 | let timenow = timestamp.unwrap_or_else(current_timestamp); 157 | let salt = last_data.unwrap_or("llwl"); 158 | 159 | let keepalive_data; 160 | { 161 | let mut md5 = HasherBuilder::build(HasherType::MD5); 162 | md5.update(&timenow.as_bytes_be()); 163 | md5.update(salt.as_bytes()); 164 | 165 | let hashed_bytes = md5.finish(); 166 | keepalive_data = hashed_bytes[..].to_hex(); 167 | } 168 | keepalive_data 169 | } 170 | } 171 | 172 | impl AttributeType { 173 | pub fn name(self) -> &'static str { 174 | match self { 175 | AttributeType::TUserName => "User-Name", 176 | AttributeType::TClientIPAddress => "Client-IP-Address", 177 | AttributeType::TClientVersion => "Client-Version", 178 | AttributeType::TClientType => "Client-Type", 179 | AttributeType::TOSVersion => "OS-Version", 180 | AttributeType::TOSLang => "OS-Lang", 181 | AttributeType::TAdapterInfo => "Adapter-Info", 182 | AttributeType::TCPUInfo => "CPU-Info", 183 | AttributeType::TMACAddress => "MAC-Address", 184 | AttributeType::TMemorySize => "Memory-Size", 185 | AttributeType::TDefaultExplorer => "Default-Explorer", 186 | AttributeType::TBubble => "Bubble", 187 | AttributeType::TBubbleId => "Bubble-Id", 188 | AttributeType::TBubbleTitle => "Bubble-Title", 189 | AttributeType::TBubbleContext => "Bubble-Context", 190 | AttributeType::TBubbleContextURL => "Bubble-Context-URL", 191 | AttributeType::TBubbleKeepTime => "Bubble-Keep-Time", 192 | AttributeType::TBubbleDelayTime => "Bubble-Delay-Time", 193 | AttributeType::TBubbleType => "Bubble-Type", 194 | AttributeType::TChannel => "Channel", 195 | AttributeType::TChannelNote => "Channel-Note", 196 | AttributeType::TChannelContextURL => "Channel-Context-URL", 197 | AttributeType::TChannelContextURL2 => "Channel-Context-URL", 198 | AttributeType::TChannelOrder => "Channel-Order", 199 | AttributeType::TPlugin => "Plugin", 200 | AttributeType::TPluginName => "Plugin-Name", 201 | AttributeType::TPluginConfigureData => "Plugin-Configure-Data", 202 | AttributeType::TUpdateVersion => "Update-Version", 203 | AttributeType::TUpdateDownloadURL => "Update-Download-URL", 204 | AttributeType::TUpdateDescription => "Update-Description", 205 | AttributeType::TKeepAliveTime => "KeepAlive-Time", 206 | AttributeType::TKeepAliveInterval => "KeepAlive-Interval", 207 | AttributeType::TKeepAliveData => "KeepAlive-Data", 208 | AttributeType::TProcessCheckInterval => "Process-Check-Interval", 209 | AttributeType::TProcessCheckList => "Process-Check-List", 210 | AttributeType::TRealTimeBubbleServer => "RealTime-Bubble-Server", 211 | AttributeType::TRealTimeBubbleInterval => "RealTime-Bubble-Interval", 212 | AttributeType::TWifiTransmitIPList => "Wifi-Transmit-IP-List", 213 | AttributeType::TWifiShareNumber => "Wifi-Share-Number", 214 | AttributeType::TWifiShareCode => "Wifi-Share-Code", 215 | AttributeType::TWifiShareErrorString => "Wifi-Share-Error-String", 216 | AttributeType::TWifiShareBindRequired => "Wifi-Share-Bind-Required", 217 | AttributeType::TPlugin2 => "Plugin", 218 | AttributeType::TDeviceSN => "Device-SN", 219 | AttributeType::TDeviceType => "Device-Type", 220 | AttributeType::TWifiRedirectURL => "Wifi-Redirect-URL", 221 | 222 | _ => "", 223 | } 224 | } 225 | 226 | pub fn id(self) -> u8 { 227 | match self { 228 | AttributeType::TAttribute => 0x0, 229 | AttributeType::TUserName => 0x1, 230 | AttributeType::TClientIPAddress => 0x2, 231 | AttributeType::TClientVersion => 0x3, 232 | AttributeType::TClientType => 0x4, 233 | AttributeType::TOSVersion => 0x5, 234 | AttributeType::TOSLang => 0x6, 235 | AttributeType::TAdapterInfo => 0x7, 236 | AttributeType::TCPUInfo => 0x8, 237 | AttributeType::TMACAddress => 0x9, 238 | AttributeType::TMemorySize => 0xa, 239 | AttributeType::TDefaultExplorer => 0xb, 240 | AttributeType::TBubble => 0xc, 241 | AttributeType::TBubbleId => 0x1, 242 | AttributeType::TBubbleTitle => 0x2, 243 | AttributeType::TBubbleContext => 0x3, 244 | AttributeType::TBubbleContextURL => 0x4, 245 | AttributeType::TBubbleKeepTime => 0x5, 246 | AttributeType::TBubbleDelayTime => 0x6, 247 | AttributeType::TBubbleType => 0x7, 248 | AttributeType::TChannel => 0xd, 249 | AttributeType::TChannelNote => 0x1, 250 | AttributeType::TChannelContextURL => 0x2, 251 | AttributeType::TChannelContextURL2 => 0x3, 252 | AttributeType::TChannelOrder => 0x4, 253 | AttributeType::TPlugin => 0xe, 254 | AttributeType::TPluginName => 0x1, 255 | AttributeType::TPluginConfigureData => 0x2, 256 | AttributeType::TUpdateVersion => 0xf, 257 | AttributeType::TUpdateDownloadURL => 0x10, 258 | AttributeType::TUpdateDescription => 0x11, 259 | AttributeType::TKeepAliveTime => 0x12, 260 | AttributeType::TKeepAliveInterval => 0x13, 261 | AttributeType::TKeepAliveData => 0x14, 262 | AttributeType::TProcessCheckInterval => 0x15, 263 | AttributeType::TProcessCheckList => 0x16, 264 | AttributeType::TRealTimeBubbleServer => 0x17, 265 | AttributeType::TRealTimeBubbleInterval => 0x18, 266 | AttributeType::TWifiTransmitIPList => 0x19, 267 | AttributeType::TWifiShareNumber => 0x1a, 268 | AttributeType::TWifiShareCode => 0x1b, 269 | AttributeType::TWifiShareErrorString => 0x1c, 270 | AttributeType::TWifiShareBindRequired => 0x1d, 271 | AttributeType::TPlugin2 => 0x1e, 272 | AttributeType::TDeviceSN => 0x1, 273 | AttributeType::TDeviceType => 0x2, 274 | AttributeType::TWifiRedirectURL => 0x1f, 275 | } 276 | } 277 | 278 | pub fn parent(self) -> Self { 279 | match self { 280 | AttributeType::TBubbleId 281 | | AttributeType::TBubbleTitle 282 | | AttributeType::TBubbleContext 283 | | AttributeType::TBubbleContextURL 284 | | AttributeType::TBubbleKeepTime 285 | | AttributeType::TBubbleDelayTime 286 | | AttributeType::TBubbleType => AttributeType::TBubble, 287 | 288 | AttributeType::TChannelNote 289 | | AttributeType::TChannelContextURL 290 | | AttributeType::TChannelContextURL2 291 | | AttributeType::TChannelOrder => AttributeType::TChannel, 292 | 293 | AttributeType::TPluginName | AttributeType::TPluginConfigureData => { 294 | AttributeType::TPlugin 295 | } 296 | 297 | AttributeType::TDeviceSN | AttributeType::TDeviceType => AttributeType::TPlugin2, 298 | 299 | _ => AttributeType::TAttribute, 300 | } 301 | } 302 | 303 | pub fn value_type(self) -> AttributeValueType { 304 | match self { 305 | AttributeType::TClientIPAddress => AttributeValueType::TIPAddress, 306 | 307 | AttributeType::TBubble 308 | | AttributeType::TChannel 309 | | AttributeType::TPlugin 310 | | AttributeType::TPlugin2 => AttributeValueType::TGroup, 311 | 312 | AttributeType::TMemorySize 313 | | AttributeType::TBubbleId 314 | | AttributeType::TBubbleKeepTime 315 | | AttributeType::TBubbleDelayTime 316 | | AttributeType::TBubbleType 317 | | AttributeType::TChannelOrder 318 | | AttributeType::TKeepAliveTime 319 | | AttributeType::TKeepAliveInterval 320 | | AttributeType::TProcessCheckInterval 321 | | AttributeType::TRealTimeBubbleInterval 322 | | AttributeType::TWifiShareNumber 323 | | AttributeType::TWifiShareCode 324 | | AttributeType::TWifiShareBindRequired => AttributeValueType::TInteger, 325 | 326 | _ => AttributeValueType::TString, 327 | } 328 | } 329 | } 330 | 331 | impl AttributeVec for Vec { 332 | fn as_bytes(&self) -> Vec { 333 | let mut attributes_bytes: Vec = Vec::new(); 334 | for attr in self { 335 | attributes_bytes.extend(attr.as_bytes()); 336 | } 337 | attributes_bytes 338 | } 339 | 340 | fn length(&self) -> u16 { 341 | self.iter().fold(0, |sum, attr| sum + attr.length()) as u16 342 | } 343 | 344 | /// Now only support parse from `AttributeType::TAttribute`'s attributes, 345 | /// `parent_id` and `value_type_id` will be missed. 346 | fn from_bytes(bytes: &[u8]) -> AttributeResult> { 347 | let mut index = 0; 348 | let mut attributes: Vec = Vec::new(); 349 | let header_length = Attribute::header_length() as usize; 350 | loop { 351 | let cursor = &bytes[index..]; 352 | let bytes_length = cursor.len() as usize; 353 | if bytes_length == 0 { 354 | return Ok(attributes); 355 | } 356 | if bytes_length < header_length { 357 | return Err(ParseAttributesError::UnexpectDataLength( 358 | header_length, 359 | bytes_length, 360 | )); 361 | } 362 | let attribute_id = cursor[0]; 363 | 364 | let data_length = NetworkEndian::read_u16(&cursor[1..header_length]) as usize; 365 | index += data_length; 366 | 367 | if data_length > bytes_length { 368 | return Err(ParseAttributesError::UnexpectDataLength( 369 | data_length, 370 | bytes_length, 371 | )); 372 | } 373 | 374 | let mut data: Vec = Vec::new(); 375 | data.extend_from_slice(&cursor[header_length..data_length]); 376 | attributes.push(Attribute::new("", 0u8, attribute_id, 0u8, data)) 377 | } 378 | } 379 | } 380 | 381 | impl AttributeValue for String {} 382 | 383 | impl AttributeValue for Ipv4Addr {} 384 | 385 | impl AttributeValue for u32 {} 386 | 387 | #[test] 388 | fn test_attribute_gen_bytes() { 389 | let un = Attribute::from_type(AttributeType::TUserName, &"05802278989@HYXY.XY".to_string()); 390 | let assert_data: &[u8] = &[ 391 | 1, 0, 22, 48, 53, 56, 48, 50, 50, 55, 56, 57, 56, 57, 64, 72, 89, 88, 89, 46, 88, 89, 392 | ]; 393 | assert_eq!(&un.as_bytes()[..], assert_data); 394 | } 395 | 396 | #[test] 397 | fn test_attributes_parse_bytes() { 398 | let assert_data: &[u8] = &[ 399 | 2, 0, 7, 10, 0, 0, 1, 3, 0, 12, 49, 46, 50, 46, 50, 50, 46, 51, 54, 20, 0, 35, 102, 102, 400 | 98, 48, 98, 50, 97, 102, 57, 52, 54, 57, 51, 102, 100, 49, 98, 97, 52, 99, 57, 51, 101, 54, 401 | 98, 57, 97, 101, 98, 100, 51, 102, 18, 0, 7, 87, 196, 78, 204, 1, 0, 22, 48, 53, 56, 48, 402 | 50, 50, 55, 56, 57, 56, 57, 64, 72, 89, 88, 89, 46, 88, 89, 403 | ]; 404 | let attributes: Vec = Vec::::from_bytes(assert_data).unwrap(); 405 | assert_eq!(attributes.as_bytes(), assert_data); 406 | } 407 | 408 | #[test] 409 | fn test_keepalive_data() { 410 | let kp_data1 = KeepaliveDataCalculator::calculate(Some(1472483020), None); 411 | let kp_data2 = KeepaliveDataCalculator::calculate( 412 | Some(1472483020), 413 | Some("ffb0b2af94693fd1ba4c93e6b9aebd3f"), 414 | ); 415 | assert_eq!(kp_data1, "ffb0b2af94693fd1ba4c93e6b9aebd3f"); 416 | assert_eq!(kp_data2, "d0dce2b013c8adfac646a2917fdab802"); 417 | } 418 | 419 | // [[SNAttributeType alloc] initWithHuman:@"User-Name" :0x0 :0x1 :0x2]; 420 | // [[SNAttributeType alloc] initWithHuman:@"Client-IP-Address" :0x0 :0x2 :0x1]; 421 | // [[SNAttributeType alloc] initWithHuman:@"Client-Version" :0x0 :0x3 :0x2]; 422 | // [[SNAttributeType alloc] initWithHuman:@"Client-Type" :0x0 :0x4 :0x2]; 423 | // [[SNAttributeType alloc] initWithHuman:@"OS-Version" :0x0 :0x5 :0x2]; 424 | // [[SNAttributeType alloc] initWithHuman:@"OS-Lang" :0x0 :0x6 :0x2]; 425 | // [[SNAttributeType alloc] initWithHuman:@"Adapter-Info" :0x0 :0x7 :0x2]; 426 | // [[SNAttributeType alloc] initWithHuman:@"CPU-Info" :0x0 :0x8 :0x2]; 427 | // [[SNAttributeType alloc] initWithHuman:@"MAC-Address" :0x0 :0x9 :0x2]; 428 | // [[SNAttributeType alloc] initWithHuman:@"Memory-Size" :0x0 :0xa :0x0]; 429 | // [[SNAttributeType alloc] initWithHuman:@"Default-Explorer" :0x0 :0xb :0x2]; 430 | // [[SNAttributeType alloc] initWithHuman:@"Bubble" :0x0 :0xc :0x3]; 431 | // [[SNAttributeType alloc] initWithHuman:@"Bubble-Id" :0xc :0x1 :0x0]; 432 | // [[SNAttributeType alloc] initWithHuman:@"Bubble-Title" :0xc :0x2 :0x2]; 433 | // [[SNAttributeType alloc] initWithHuman:@"Bubble-Context" :0xc :0x3 :0x2]; 434 | // [[SNAttributeType alloc] initWithHuman:@"Bubble-Context-URL" :0xc :0x4 :0x2]; 435 | // [[SNAttributeType alloc] initWithHuman:@"Bubble-Keep-Time" :0xc :0x5 :0x0]; 436 | // [[SNAttributeType alloc] initWithHuman:@"Bubble-Delay-Time" :0xc :0x6 :0x0]; 437 | // [[SNAttributeType alloc] initWithHuman:@"Bubble-Type" :0xc :0x7 :0x0]; 438 | // [[SNAttributeType alloc] initWithHuman:@"Channel" :0x0 :0xd :0x3]; 439 | // [[SNAttributeType alloc] initWithHuman:@"Channel-Note" :0xd :0x1 :0x2]; 440 | // [[SNAttributeType alloc] initWithHuman:@"Channel-Context-URL" :0xd :0x2 :0x2]; 441 | // [[SNAttributeType alloc] initWithHuman:@"Channel-Context-URL" :0xd :0x3 :0x2]; 442 | // [[SNAttributeType alloc] initWithHuman:@"Channel-Order" :0xd :0x4 :0x0]; 443 | // [[SNAttributeType alloc] initWithHuman:@"Plugin" :0x0 :0xe :0x3]; 444 | // [[SNAttributeType alloc] initWithHuman:@"Plugin-Name" :0xe :0x1 :0x2]; 445 | // [[SNAttributeType alloc] initWithHuman:@"Plugin-Configure-Data" :0xe :0x2 :0x2]; 446 | // [[SNAttributeType alloc] initWithHuman:@"Update-Version" :0x0 :0xf :0x2]; 447 | // [[SNAttributeType alloc] initWithHuman:@"Update-Download-URL" :0x0 :0x10 :0x2]; 448 | // [[SNAttributeType alloc] initWithHuman:@"Update-Description" :0x0 :0x11 :0x2]; 449 | // [[SNAttributeType alloc] initWithHuman:@"KeepAlive-Time" :0x0 :0x12 :0x0]; 450 | // [[SNAttributeType alloc] initWithHuman:@"KeepAlive-Interval" :0x0 :0x13 :0x0]; 451 | // [[SNAttributeType alloc] initWithHuman:@"KeepAlive-Data" :0x0 :0x14 :0x2]; 452 | // [[SNAttributeType alloc] initWithHuman:@"Process-Check-Interval" :0x0 :0x15 :0x0]; 453 | // [[SNAttributeType alloc] initWithHuman:@"Process-Check-List" :0x0 :0x16 :0x2]; 454 | // [[SNAttributeType alloc] initWithHuman:@"RealTime-Bubble-Server" :0x0 :0x17 :0x2]; 455 | // [[SNAttributeType alloc] initWithHuman:@"RealTime-Bubble-Interval" :0x0 :0x18 :0x0]; 456 | // [[SNAttributeType alloc] initWithHuman:@"Wifi-Transmit-IP-List" :0x0 :0x19 :0x2]; 457 | // [[SNAttributeType alloc] initWithHuman:@"Wifi-Share-Number" :0x0 :0x1a :0x0]; 458 | // [[SNAttributeType alloc] initWithHuman:@"Wifi-Share-Code" :0x0 :0x1b :0x0]; 459 | // [[SNAttributeType alloc] initWithHuman:@"Wifi-Share-Error-String" :0x0 :0x1c :0x2]; 460 | // [[SNAttributeType alloc] initWithHuman:@"Wifi-Share-Bind-Required" :0x0 :0x1d :0x0]; 461 | // [[SNAttributeType alloc] initWithHuman:@"Plugin" :0x0 :0x1e :0x3]; 462 | // [[SNAttributeType alloc] initWithHuman:@"Device-SN" :0x1e :0x1 :0x2]; 463 | // [[SNAttributeType alloc] initWithHuman:@"Device-Type" :0x1e :0x2 :0x2]; 464 | // [[SNAttributeType alloc] initWithHuman:@"Wifi-Redirect-URL" :0x0 :0x1f :0x2]; 465 | -------------------------------------------------------------------------------- /src/singlenet/dialer.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | use std::str; 3 | 4 | use byteorder::{ByteOrder, NativeEndian, NetworkEndian, ReadBytesExt}; 5 | 6 | use common::dialer::Dialer; 7 | use common::utils::current_timestamp; 8 | 9 | #[derive(Debug)] 10 | pub enum Configuration { 11 | Hainan, 12 | } 13 | 14 | #[derive(Debug)] 15 | pub struct SingleNetDialer { 16 | share_key: String, 17 | secret_key: String, 18 | key_table: String, 19 | } 20 | 21 | impl SingleNetDialer { 22 | pub fn new(share_key: &str, secret_key: &str, key_table: &str) -> Self { 23 | SingleNetDialer { 24 | share_key: share_key.to_string(), 25 | secret_key: secret_key.to_string(), 26 | key_table: key_table.to_string(), 27 | } 28 | } 29 | 30 | pub fn encrypt_account(&self, username: &str, timestamp: Option) -> String { 31 | let username = username.to_uppercase(); 32 | let time_now = timestamp.unwrap_or_else(current_timestamp); 33 | 34 | let first_hash: u16; 35 | { 36 | let mut time_now_bytes = [0u8; 4]; 37 | NetworkEndian::write_u32(&mut time_now_bytes, time_now); 38 | let mut hash_data: Vec = Vec::new(); 39 | hash_data.extend_from_slice(&time_now_bytes); 40 | hash_data.extend(self.share_key.as_bytes()); 41 | hash_data.extend(username.split('@').nth(0).unwrap().as_bytes()); 42 | first_hash = Self::calc_hash(&hash_data) 43 | } 44 | 45 | let second_hash: u16; 46 | { 47 | let mut first_hash_bytes = [0u8; 2]; 48 | NetworkEndian::write_u16(&mut first_hash_bytes, first_hash); 49 | let mut hash_data: Vec = Vec::new(); 50 | hash_data.extend_from_slice(&first_hash_bytes); 51 | hash_data.extend(self.secret_key.as_bytes()); 52 | second_hash = Self::calc_hash(&hash_data); 53 | } 54 | 55 | let mut scheduled_table: Vec = Vec::with_capacity(8); 56 | { 57 | let time_now_high = (time_now >> 16) as u16; 58 | let time_now_low = (time_now & 0xFFFF) as u16; 59 | 60 | let mut time_now_high_bytes = [0u8; 2]; 61 | let mut time_now_low_bytes = [0u8; 2]; 62 | let mut first_hash_bytes = [0u8; 2]; 63 | let mut second_hash_bytes = [0u8; 2]; 64 | 65 | NetworkEndian::write_u16(&mut time_now_high_bytes, time_now_high); 66 | NetworkEndian::write_u16(&mut time_now_low_bytes, time_now_low); 67 | NativeEndian::write_u16(&mut first_hash_bytes, first_hash); 68 | NativeEndian::write_u16(&mut second_hash_bytes, second_hash); 69 | 70 | scheduled_table.extend_from_slice(&time_now_high_bytes); 71 | scheduled_table.extend_from_slice(&first_hash_bytes); 72 | scheduled_table.extend_from_slice(&time_now_low_bytes); 73 | scheduled_table.extend_from_slice(&second_hash_bytes); 74 | } 75 | 76 | let mut vectors: [u8; 12] = [0; 12]; 77 | for i in 0..4 { 78 | let j = 2 * i + 1; 79 | let k = 3 * i + 1; 80 | vectors[k - 1] = scheduled_table[j - 1] >> 0x3 & 0x1F; 81 | vectors[k] = 82 | ((scheduled_table[j - 1] & 0x7) << 0x2) | (scheduled_table[j] >> 0x6 & 0x3); 83 | vectors[k + 1] = scheduled_table[j] & 0x3F; 84 | } 85 | 86 | let key_table_bytes = self.key_table_bytes(); 87 | let pin: Vec = vectors 88 | .iter() 89 | .map(|c| key_table_bytes[*c as usize]) 90 | .collect(); 91 | 92 | let pin_str = str::from_utf8(&pin).unwrap(); 93 | format!("~LL_{}_{}", pin_str, username) 94 | } 95 | 96 | fn calc_hash(data: &[u8]) -> u16 { 97 | let length = data.len(); 98 | let mut summary: u32 = 0; 99 | let mut data = data; 100 | 101 | if length % 2 != 0 { 102 | summary = u32::from(data[length - 1]); 103 | data = &data[0..length - 1]; 104 | } 105 | 106 | let data_shorts = { 107 | let mut shorts = vec![0u16; 0]; 108 | let mut rdr = Cursor::new(data); 109 | while let Ok(s) = rdr.read_u16::() { 110 | shorts.push(s); 111 | } 112 | shorts 113 | }; 114 | 115 | summary = data_shorts 116 | .iter() 117 | .fold(summary, |sum, x| sum + u32::from(*x)); 118 | if summary & 0xFFFF_0000 != 0 { 119 | summary = ((summary >> 0x10) + summary) & 0xFFFF; 120 | } 121 | 122 | !summary as u16 123 | } 124 | 125 | fn key_table_bytes(&self) -> &[u8] { 126 | self.key_table.as_bytes() 127 | } 128 | } 129 | 130 | impl Configuration { 131 | pub fn share_key(&self) -> &'static str { 132 | match *self { 133 | Configuration::Hainan => "hngx01", 134 | } 135 | } 136 | 137 | pub fn secret_key(&self) -> &'static str { 138 | match *self { 139 | Configuration::Hainan => "000c29270712", 140 | } 141 | } 142 | 143 | pub fn key_table(&self) -> &'static str { 144 | match *self { 145 | Configuration::Hainan => { 146 | "abcdefghijklmnopqrstuvwxyz1234567890ZYXWVUTSRQPONMLKJIHGFEDCBA:_" 147 | } 148 | } 149 | } 150 | } 151 | 152 | impl Dialer for SingleNetDialer { 153 | type C = Configuration; 154 | 155 | fn load_from_config(config: Self::C) -> Self { 156 | SingleNetDialer::new(config.share_key(), config.secret_key(), config.key_table()) 157 | } 158 | } 159 | 160 | #[test] 161 | fn test_hash_key() { 162 | let str1 = "123456".to_string(); 163 | let str2 = "1234567".to_string(); 164 | let hash1 = SingleNetDialer::calc_hash(str1.as_bytes()); 165 | let hash2 = SingleNetDialer::calc_hash(str2.as_bytes()); 166 | assert_eq!(hash1, 25446u16); 167 | assert_eq!(hash2, 25391u16); 168 | } 169 | -------------------------------------------------------------------------------- /src/singlenet/heartbeater.rs: -------------------------------------------------------------------------------- 1 | use std::net::Ipv4Addr; 2 | use std::{io, result}; 3 | 4 | use byteorder::{ByteOrder, NetworkEndian}; 5 | use crypto::hash::{HasherBuilder, HasherType}; 6 | 7 | use common::bytes::BytesAbleNum; 8 | use common::reader::{ReadBytesError, ReaderHelper}; 9 | use common::utils::current_timestamp; 10 | use singlenet::attributes::{ 11 | Attribute, AttributeType, AttributeVec, KeepaliveDataCalculator, ParseAttributesError, 12 | }; 13 | 14 | #[derive(Debug)] 15 | pub enum SinglenetHeartbeatError { 16 | PacketReadError(ReadBytesError), 17 | ParseAttributesError(ParseAttributesError), 18 | UnexpectedBytes(Vec), 19 | } 20 | 21 | type PacketResult = result::Result; 22 | 23 | #[derive(Debug, Copy, Clone)] 24 | pub enum PacketCode { 25 | CRegisterRequest = 0x1, 26 | CRegisterResponse = 0x2, 27 | CKeepAliveRequest = 0x3, 28 | CKeepAliveResponse = 0x4, 29 | CBubbleRequest = 0x5, 30 | CBubbleResponse = 0x6, 31 | CChannelRequest = 0x7, 32 | CChannelResponse = 0x8, 33 | CPluginRequest = 0x9, 34 | CPluginResponse = 0xa, 35 | CRealTimeBubbleRequest = 0xb, 36 | CRealTimeBubbleResponse = 0xc, 37 | } 38 | 39 | #[derive(Debug)] 40 | pub struct Packet { 41 | magic_number: u16, 42 | length: u16, 43 | code: PacketCode, 44 | seq: u8, 45 | authorization: [u8; 16], 46 | attributes: Vec, 47 | } 48 | 49 | pub struct PacketAuthenticator { 50 | salt: String, 51 | } 52 | 53 | pub struct PacketFactoryMac; 54 | 55 | pub struct PacketFactoryWin; 56 | 57 | impl PacketAuthenticator { 58 | pub fn new(salt: &str) -> Self { 59 | PacketAuthenticator { 60 | salt: salt.to_string(), 61 | } 62 | } 63 | 64 | pub fn authenticate(&self, bytes: &[u8]) -> [u8; 16] { 65 | let mut md5 = HasherBuilder::build(HasherType::MD5); 66 | 67 | md5.update(bytes); 68 | md5.update(self.salt.as_bytes()); 69 | 70 | let mut authorization = [0; 16]; 71 | authorization.clone_from_slice(&md5.finish()); 72 | authorization 73 | } 74 | } 75 | 76 | impl Packet { 77 | fn magic_number() -> u16 { 78 | 0x534eu16 79 | } 80 | 81 | fn header_length() -> u16 { 82 | // len(magic_number) + len(length) + len(code) + len(seq) + \ 83 | // len(authenticator) 84 | // 2 + 2 + 1 + 1 + 16 85 | 22u16 86 | } 87 | 88 | pub fn new( 89 | code: PacketCode, 90 | seq: u8, 91 | authorization: Option<[u8; 16]>, 92 | attributes: Vec, 93 | ) -> Self { 94 | let authorization = authorization.unwrap_or_default(); 95 | let mut packet = Packet { 96 | magic_number: Self::magic_number(), 97 | length: 0, 98 | code, 99 | seq, 100 | authorization, 101 | attributes, 102 | }; 103 | packet.length = Self::calc_length(&packet); 104 | packet 105 | } 106 | 107 | pub fn calc_length(packet: &Self) -> u16 { 108 | Self::header_length() + packet.attributes.length() 109 | } 110 | 111 | pub fn as_bytes(&self, authenticator: Option<&PacketAuthenticator>) -> Vec { 112 | let mut bytes = Vec::new(); 113 | let authorization = match authenticator { 114 | Some(authenticator) => authenticator.authenticate(&self.as_bytes(None)), 115 | None => self.authorization, 116 | }; 117 | 118 | { 119 | let attributes_bytes = self.attributes.as_bytes(); 120 | let raw_packet_code = self.code as u8; 121 | 122 | bytes.extend(self.magic_number.as_bytes_be()); 123 | bytes.extend(self.length.as_bytes_be()); 124 | bytes.push(raw_packet_code); 125 | bytes.push(self.seq); 126 | bytes.extend_from_slice(&authorization); 127 | bytes.extend(attributes_bytes); 128 | } 129 | bytes 130 | } 131 | 132 | pub fn from_bytes(input: &mut io::BufReader) -> PacketResult 133 | where 134 | R: io::Read, 135 | { 136 | { 137 | let magic_number_bytes = input 138 | .read_bytes(2) 139 | .map_err(SinglenetHeartbeatError::PacketReadError)?; 140 | let magic_number = NetworkEndian::read_u16(&magic_number_bytes); 141 | if magic_number != Self::magic_number() { 142 | return Err(SinglenetHeartbeatError::UnexpectedBytes(magic_number_bytes)); 143 | } 144 | } 145 | 146 | let length; 147 | { 148 | let length_bytes = input 149 | .read_bytes(2) 150 | .map_err(SinglenetHeartbeatError::PacketReadError)?; 151 | length = NetworkEndian::read_u16(&length_bytes); 152 | } 153 | 154 | let code; 155 | { 156 | let code_bytes = input 157 | .read_bytes(1) 158 | .map_err(SinglenetHeartbeatError::PacketReadError)?; 159 | let code_u8 = code_bytes[0]; 160 | match PacketCode::from_u8(code_u8) { 161 | Some(packet_code) => code = packet_code, 162 | None => return Err(SinglenetHeartbeatError::UnexpectedBytes(code_bytes)), 163 | } 164 | } 165 | 166 | let seq; 167 | { 168 | seq = input 169 | .read_bytes(1) 170 | .map_err(SinglenetHeartbeatError::PacketReadError)?[0]; 171 | } 172 | 173 | let mut authorization = [0u8; 16]; 174 | { 175 | let authorization_bytes = input 176 | .read_bytes(16) 177 | .map_err(SinglenetHeartbeatError::PacketReadError)?; 178 | authorization.copy_from_slice(&authorization_bytes); 179 | } 180 | 181 | let attributes; 182 | { 183 | let attributes_bytes = input 184 | .read_bytes((length - Self::header_length()) as usize) 185 | .map_err(SinglenetHeartbeatError::PacketReadError)?; 186 | attributes = Vec::::from_bytes(&attributes_bytes) 187 | .map_err(SinglenetHeartbeatError::ParseAttributesError)?; 188 | } 189 | 190 | Ok(Packet::new(code, seq, Some(authorization), attributes)) 191 | } 192 | } 193 | 194 | impl PacketFactoryWin { 195 | fn calc_seq(timestamp: Option) -> u8 { 196 | // only be used in windows version, 197 | let timestamp = timestamp.unwrap_or_else(current_timestamp); 198 | 199 | let tmp_num = ((u64::from(timestamp) * 0x3_43fd) + 0x26_9ec3) as u32; 200 | ((tmp_num >> 0x10) & 0xff) as u8 201 | } 202 | 203 | pub fn keepalive_request( 204 | username: &str, 205 | ipaddress: Ipv4Addr, 206 | timestamp: Option, 207 | last_keepalive_data: Option<&str>, 208 | version: Option<&str>, 209 | ) -> Packet { 210 | // FIXME: this protocol needs update 211 | let version = version.unwrap_or("1.2.22.36"); 212 | let timestamp = timestamp.unwrap_or_else(current_timestamp); 213 | let keepalive_data = 214 | KeepaliveDataCalculator::calculate(Some(timestamp), last_keepalive_data); 215 | 216 | let attributes = vec![ 217 | Attribute::from_type(AttributeType::TClientIPAddress, &ipaddress), 218 | Attribute::from_type(AttributeType::TClientVersion, &version.to_string()), 219 | Attribute::from_type(AttributeType::TKeepAliveData, &keepalive_data.to_string()), 220 | Attribute::from_type(AttributeType::TKeepAliveTime, ×tamp), 221 | Attribute::from_type(AttributeType::TUserName, &username.to_string()), 222 | ]; 223 | 224 | Packet::new( 225 | PacketCode::CKeepAliveRequest, 226 | Self::calc_seq(Some(timestamp)), 227 | None, 228 | attributes, 229 | ) 230 | } 231 | } 232 | 233 | impl PacketFactoryMac { 234 | fn calc_seq() -> u8 { 235 | 0x1u8 236 | } 237 | 238 | fn client_type() -> String { 239 | "Mac-SingletNet".to_string() 240 | } 241 | 242 | pub fn register_request( 243 | username: &str, 244 | ipaddress: Ipv4Addr, 245 | version: Option<&str>, 246 | mac_address: Option<&str>, 247 | explorer: Option<&str>, 248 | ) -> Packet { 249 | let version = version.unwrap_or("1.1.0"); 250 | let mac_address = mac_address.unwrap_or("10:dd:b1:d5:95:ca"); 251 | let explorer = explorer.unwrap_or_default(); 252 | let client_type = &Self::client_type(); 253 | let cpu_info = "Intel(R) Core(TM) i5-5287U CPU @ 2.90GHz"; 254 | let memory_size = 0x2000; 255 | let os_version = "Mac OS X Version 10.12 (Build 16A323)"; 256 | let os_language = "zh_CN"; 257 | 258 | let attributes = vec![ 259 | Attribute::from_type(AttributeType::TUserName, &username.to_string()), 260 | Attribute::from_type(AttributeType::TClientVersion, &version.to_string()), 261 | Attribute::from_type(AttributeType::TClientType, &client_type.to_string()), 262 | Attribute::from_type(AttributeType::TClientIPAddress, &ipaddress), 263 | Attribute::from_type(AttributeType::TMACAddress, &mac_address.to_string()), 264 | Attribute::from_type(AttributeType::TDefaultExplorer, &explorer.to_string()), 265 | Attribute::from_type(AttributeType::TCPUInfo, &cpu_info.to_string()), 266 | Attribute::from_type(AttributeType::TMemorySize, &memory_size), 267 | Attribute::from_type(AttributeType::TOSVersion, &os_version.to_string()), 268 | Attribute::from_type(AttributeType::TOSLang, &os_language.to_string()), 269 | ]; 270 | 271 | Packet::new( 272 | PacketCode::CRegisterRequest, 273 | Self::calc_seq(), 274 | None, 275 | attributes, 276 | ) 277 | } 278 | 279 | pub fn bubble_request( 280 | username: &str, 281 | ipaddress: Ipv4Addr, 282 | version: Option<&str>, 283 | mac_address: Option<&str>, 284 | ) -> Packet { 285 | let version = version.unwrap_or("1.1.0"); 286 | let mac_address = mac_address.unwrap_or("10:dd:b1:d5:95:ca"); 287 | let client_type = &Self::client_type(); 288 | 289 | let attributes = vec![ 290 | Attribute::from_type(AttributeType::TUserName, &username.to_string()), 291 | Attribute::from_type(AttributeType::TClientVersion, &version.to_string()), 292 | Attribute::from_type(AttributeType::TClientType, &client_type.to_string()), 293 | Attribute::from_type(AttributeType::TClientIPAddress, &ipaddress), 294 | Attribute::from_type(AttributeType::TMACAddress, &mac_address.to_string()), 295 | ]; 296 | 297 | Packet::new( 298 | PacketCode::CBubbleRequest, 299 | Self::calc_seq(), 300 | None, 301 | attributes, 302 | ) 303 | } 304 | 305 | pub fn real_time_bubble_request( 306 | username: &str, 307 | ipaddress: Ipv4Addr, 308 | version: Option<&str>, 309 | mac_address: Option<&str>, 310 | ) -> Packet { 311 | let version = version.unwrap_or("1.1.0"); 312 | let mac_address = mac_address.unwrap_or("10:dd:b1:d5:95:ca"); 313 | let client_type = &Self::client_type(); 314 | 315 | let attributes = vec![ 316 | Attribute::from_type(AttributeType::TUserName, &username.to_string()), 317 | Attribute::from_type(AttributeType::TClientVersion, &version.to_string()), 318 | Attribute::from_type(AttributeType::TClientType, &client_type.to_string()), 319 | Attribute::from_type(AttributeType::TClientIPAddress, &ipaddress), 320 | Attribute::from_type(AttributeType::TMACAddress, &mac_address.to_string()), 321 | ]; 322 | 323 | Packet::new( 324 | PacketCode::CRealTimeBubbleRequest, 325 | Self::calc_seq(), 326 | None, 327 | attributes, 328 | ) 329 | } 330 | } 331 | 332 | impl PacketCode { 333 | fn from_u8(code: u8) -> Option { 334 | match code { 335 | 0x1 => Some(PacketCode::CRegisterRequest), 336 | 0x2 => Some(PacketCode::CRegisterResponse), 337 | 0x3 => Some(PacketCode::CKeepAliveRequest), 338 | 0x4 => Some(PacketCode::CKeepAliveResponse), 339 | 0x5 => Some(PacketCode::CBubbleRequest), 340 | 0x6 => Some(PacketCode::CBubbleResponse), 341 | 0x7 => Some(PacketCode::CChannelRequest), 342 | 0x8 => Some(PacketCode::CChannelResponse), 343 | 0x9 => Some(PacketCode::CPluginRequest), 344 | 0xa => Some(PacketCode::CPluginResponse), 345 | 0xb => Some(PacketCode::CRealTimeBubbleRequest), 346 | 0xc => Some(PacketCode::CRealTimeBubbleResponse), 347 | 348 | _ => None, 349 | } 350 | } 351 | } 352 | 353 | #[test] 354 | fn test_calc_seq() { 355 | let seq = PacketFactoryWin::calc_seq(Some(1472483020)); 356 | assert_eq!(seq, 43u8); 357 | } 358 | 359 | #[test] 360 | fn test_authenticator() { 361 | let data: &[u8] = &[ 362 | 83, 78, 0, 105, 3, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 7, 10, 0, 0, 363 | 1, 3, 0, 12, 49, 46, 50, 46, 50, 50, 46, 51, 54, 20, 0, 35, 100, 48, 100, 99, 101, 50, 98, 364 | 48, 49, 51, 99, 56, 97, 100, 102, 97, 99, 54, 52, 54, 97, 50, 57, 49, 55, 102, 100, 97, 98, 365 | 56, 48, 50, 18, 0, 7, 87, 196, 78, 204, 1, 0, 22, 48, 53, 56, 48, 50, 50, 55, 56, 57, 56, 366 | 57, 64, 72, 89, 88, 89, 46, 88, 89, 367 | ]; 368 | let authenticator = PacketAuthenticator::new("LLWLXA_TPSHARESECRET"); 369 | let authorization = authenticator.authenticate(data); 370 | let real_authorization: [u8; 16] = [ 371 | 240, 67, 87, 201, 164, 134, 179, 142, 110, 163, 208, 119, 121, 90, 173, 75, 372 | ]; 373 | assert_eq!(authorization, real_authorization); 374 | } 375 | -------------------------------------------------------------------------------- /src/singlenet/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod attributes; 2 | pub mod dialer; 3 | pub mod heartbeater; 4 | 5 | #[cfg(test)] 6 | mod tests; 7 | -------------------------------------------------------------------------------- /src/singlenet/tests.rs: -------------------------------------------------------------------------------- 1 | use common::dialer::Dialer; 2 | use singlenet::dialer::{Configuration, SingleNetDialer}; 3 | use singlenet::heartbeater::{Packet, PacketAuthenticator, PacketFactoryMac, PacketFactoryWin}; 4 | use std::io::BufReader; 5 | use std::net::Ipv4Addr; 6 | use std::str::FromStr; 7 | 8 | #[test] 9 | fn test_singlenet_username_encrypt() { 10 | let dialer = SingleNetDialer::load_from_config(Configuration::Hainan); 11 | let encrypted = dialer.encrypt_account("05802278989@HYXY.XY", Some(1472483020)); 12 | assert_eq!(encrypted, "~LL_k6ecvpj2mrjA_05802278989@HYXY.XY"); 13 | } 14 | 15 | #[test] 16 | fn test_keepalive_request_generate_and_parse() { 17 | let ka1 = PacketFactoryWin::keepalive_request( 18 | "05802278989@HYXY.XY", 19 | Ipv4Addr::from_str("10.0.0.1").unwrap(), 20 | Some(1472483020), 21 | None, 22 | None, 23 | ); 24 | let ka2 = PacketFactoryWin::keepalive_request( 25 | "05802278989@HYXY.XY", 26 | Ipv4Addr::from_str("10.0.0.1").unwrap(), 27 | Some(1472483020), 28 | Some("ffb0b2af94693fd1ba4c93e6b9aebd3f"), 29 | None, 30 | ); 31 | 32 | let authenticator = PacketAuthenticator::new("LLWLXA_TPSHARESECRET"); 33 | let ka1_bytes = ka1.as_bytes(Some(&authenticator)); 34 | let ka2_bytes = ka2.as_bytes(Some(&authenticator)); 35 | let real1_bytes: Vec = vec![ 36 | 83, 78, 0, 105, 3, 43, 220, 250, 219, 227, 84, 6, 40, 77, 138, 217, 220, 230, 189, 142, 37 | 123, 179, 2, 0, 7, 10, 0, 0, 1, 3, 0, 12, 49, 46, 50, 46, 50, 50, 46, 51, 54, 20, 0, 35, 38 | 102, 102, 98, 48, 98, 50, 97, 102, 57, 52, 54, 57, 51, 102, 100, 49, 98, 97, 52, 99, 57, 39 | 51, 101, 54, 98, 57, 97, 101, 98, 100, 51, 102, 18, 0, 7, 87, 196, 78, 204, 1, 0, 22, 48, 40 | 53, 56, 48, 50, 50, 55, 56, 57, 56, 57, 64, 72, 89, 88, 89, 46, 88, 89, 41 | ]; 42 | let real2_bytes: Vec = vec![ 43 | 83, 78, 0, 105, 3, 43, 240, 67, 87, 201, 164, 134, 179, 142, 110, 163, 208, 119, 121, 90, 44 | 173, 75, 2, 0, 7, 10, 0, 0, 1, 3, 0, 12, 49, 46, 50, 46, 50, 50, 46, 51, 54, 20, 0, 35, 45 | 100, 48, 100, 99, 101, 50, 98, 48, 49, 51, 99, 56, 97, 100, 102, 97, 99, 54, 52, 54, 97, 46 | 50, 57, 49, 55, 102, 100, 97, 98, 56, 48, 50, 18, 0, 7, 87, 196, 78, 204, 1, 0, 22, 48, 53, 47 | 56, 48, 50, 50, 55, 56, 57, 56, 57, 64, 72, 89, 88, 89, 46, 88, 89, 48 | ]; 49 | assert_eq!(ka1_bytes, real1_bytes); 50 | assert_eq!(ka2_bytes, real2_bytes); 51 | 52 | let mut buffer = BufReader::new(&real1_bytes as &[u8]); 53 | let ka1_p1 = Packet::from_bytes(&mut buffer).unwrap(); 54 | let ka1_p1_bytes = ka1_p1.as_bytes(None); 55 | assert_eq!(ka1_p1_bytes, real1_bytes); 56 | } 57 | 58 | #[test] 59 | fn test_register_request() { 60 | let authenticator = PacketAuthenticator::new("LLWLXA"); 61 | let reg = PacketFactoryMac::register_request( 62 | "05802278989@HYXY.XY", 63 | Ipv4Addr::from_str("10.8.0.4").unwrap(), 64 | None, 65 | None, 66 | None, 67 | ); 68 | let reg_bytes = reg.as_bytes(Some(&authenticator)); 69 | let real_bytes: Vec = vec![ 70 | 83, 78, 0, 197, 1, 1, 111, 131, 14, 200, 48, 216, 23, 80, 223, 56, 164, 152, 147, 120, 164, 71 | 191, 1, 0, 22, 48, 53, 56, 48, 50, 50, 55, 56, 57, 56, 57, 64, 72, 89, 88, 89, 46, 88, 89, 72 | 3, 0, 8, 49, 46, 49, 46, 48, 4, 0, 17, 77, 97, 99, 45, 83, 105, 110, 103, 108, 101, 116, 73 | 78, 101, 116, 2, 0, 7, 10, 8, 0, 4, 9, 0, 20, 49, 48, 58, 100, 100, 58, 98, 49, 58, 100, 74 | 53, 58, 57, 53, 58, 99, 97, 11, 0, 3, 8, 0, 43, 73, 110, 116, 101, 108, 40, 82, 41, 32, 67, 75 | 111, 114, 101, 40, 84, 77, 41, 32, 105, 53, 45, 53, 50, 56, 55, 85, 32, 67, 80, 85, 32, 64, 76 | 32, 50, 46, 57, 48, 71, 72, 122, 10, 0, 7, 0, 0, 32, 0, 5, 0, 40, 77, 97, 99, 32, 79, 83, 77 | 32, 88, 32, 86, 101, 114, 115, 105, 111, 110, 32, 49, 48, 46, 49, 50, 32, 40, 66, 117, 105, 78 | 108, 100, 32, 49, 54, 65, 51, 50, 51, 41, 6, 0, 8, 122, 104, 95, 67, 78, 79 | ]; 80 | assert_eq!(reg_bytes, real_bytes); 81 | } 82 | 83 | #[test] 84 | fn test_real_time_bubble_request() { 85 | let authenticator = PacketAuthenticator::new("LLWLXA"); 86 | let reg = PacketFactoryMac::real_time_bubble_request( 87 | "05802278989@HYXY.XY", 88 | Ipv4Addr::from_str("10.8.0.4").unwrap(), 89 | None, 90 | None, 91 | ); 92 | let reg_bytes = reg.as_bytes(Some(&authenticator)); 93 | let real_bytes: Vec = vec![ 94 | 83, 78, 0, 96, 11, 1, 166, 14, 39, 63, 156, 69, 236, 221, 210, 50, 156, 211, 85, 237, 232, 95 | 220, 1, 0, 22, 48, 53, 56, 48, 50, 50, 55, 56, 57, 56, 57, 64, 72, 89, 88, 89, 46, 88, 89, 96 | 3, 0, 8, 49, 46, 49, 46, 48, 4, 0, 17, 77, 97, 99, 45, 83, 105, 110, 103, 108, 101, 116, 97 | 78, 101, 116, 2, 0, 7, 10, 8, 0, 4, 9, 0, 20, 49, 48, 58, 100, 100, 58, 98, 49, 58, 100, 98 | 53, 58, 57, 53, 58, 99, 97, 99 | ]; 100 | assert_eq!(reg_bytes, real_bytes); 101 | } 102 | 103 | #[test] 104 | fn test_bubble_request() { 105 | let authenticator = PacketAuthenticator::new("LLWLXA"); 106 | let reg = PacketFactoryMac::bubble_request( 107 | "05802278989@HYXY.XY", 108 | Ipv4Addr::from_str("10.8.0.4").unwrap(), 109 | None, 110 | None, 111 | ); 112 | let reg_bytes = reg.as_bytes(Some(&authenticator)); 113 | let real_bytes: Vec = vec![ 114 | 83, 78, 0, 96, 5, 1, 55, 73, 135, 12, 152, 235, 170, 225, 149, 154, 105, 61, 230, 140, 53, 115 | 242, 1, 0, 22, 48, 53, 56, 48, 50, 50, 55, 56, 57, 56, 57, 64, 72, 89, 88, 89, 46, 88, 89, 116 | 3, 0, 8, 49, 46, 49, 46, 48, 4, 0, 17, 77, 97, 99, 45, 83, 105, 110, 103, 108, 101, 116, 117 | 78, 101, 116, 2, 0, 7, 10, 8, 0, 4, 9, 0, 20, 49, 48, 58, 100, 100, 58, 98, 49, 58, 100, 118 | 53, 58, 57, 53, 58, 99, 97, 119 | ]; 120 | assert_eq!(reg_bytes, real_bytes); 121 | } 122 | -------------------------------------------------------------------------------- /src/srun3k/dialer.rs: -------------------------------------------------------------------------------- 1 | use common::dialer::Dialer; 2 | 3 | #[derive(Debug)] 4 | pub enum Configuration { 5 | TaLiMu, 6 | } 7 | 8 | pub struct Srun3kDialer { 9 | pub configuration: Configuration, 10 | } 11 | 12 | impl Srun3kDialer { 13 | pub fn new(config: Option) -> Self { 14 | let config = config.unwrap_or(Configuration::TaLiMu); 15 | Srun3kDialer { 16 | configuration: config, 17 | } 18 | } 19 | 20 | pub fn encrypt_account_v20(&self, username: &str) -> String { 21 | let encrypted_bytes: Vec = username.bytes().map(|c| c + 4).collect(); 22 | let encrypted_username; 23 | unsafe { 24 | encrypted_username = String::from_utf8_unchecked(encrypted_bytes); 25 | }; 26 | format!("{{SRUN3}}\r\n{}", encrypted_username) 27 | } 28 | } 29 | 30 | impl Dialer for Srun3kDialer { 31 | type C = Configuration; 32 | 33 | fn load_from_config(config: Self::C) -> Self { 34 | Srun3kDialer::new(Some(config)) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/srun3k/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dialer; 2 | 3 | #[cfg(test)] 4 | mod tests; 5 | -------------------------------------------------------------------------------- /src/srun3k/tests.rs: -------------------------------------------------------------------------------- 1 | use common::dialer::Dialer; 2 | use srun3k::dialer::{Configuration, Srun3kDialer}; 3 | 4 | #[test] 5 | fn test_srun3k_v20_username_encrypt() { 6 | let username = "admin"; 7 | let dialer = Srun3kDialer::load_from_config(Configuration::TaLiMu); 8 | let encrypted_result = dialer.encrypt_account_v20(username); 9 | assert_eq!(encrypted_result, "{SRUN3}\r\nehqmr"); 10 | } 11 | --------------------------------------------------------------------------------