[
  {
    "path": ".gitignore",
    "content": "build/\n.gradle/\n.idea/\n*.iml\n/local.properties\n"
  },
  {
    "path": "DEVELOP.md",
    "content": "# Gnirehtet for developers\n\n\n## Getting started\n\n### Requirements\n\nYou need the [Android SDK] (_Android Studio_) and the JDK 8 (`openjdk-8-jdk`).\n\nYou also need the [Rust] environment to build the Rust version:\n\n```bash\nwget https://sh.rustup.rs -O rustup-init\nsh rustup-init\n```\n\n[Android SDK]: https://developer.android.com/studio/index.html\n[Rust]: https://www.rust-lang.org/\n\n\n### Build\n\n#### Everything\n\nIf `gradle` is installed on your computer:\n\n    gradle build\n\nOtherwise, you can call the [gradle wrapper]:\n\n    ./gradlew build\n\nThis will build the Android application, the Java and Rust relay servers, both\nin debug and release versions.\n\n[gradle wrapper]: https://docs.gradle.org/current/userguide/gradle_wrapper.html\n\n\n#### Specific parts\n\nSeveral _gradle_ tasks are exposed in the root project. For instance:\n\n - `debugJava` and `releaseJava` build the Android application and the Java\n   relay server;\n - `debugRust` and `releaseRust` build the Android application and the Rust\n   relay server.\n\nEven if the Rust build tasks are exposed through `gradle` (which wraps calls to\n`cargo`), it is often more convenient to use `cargo` directly.\n\nFor instance, to build a release version of the Rust relay server:\n\n    cd relay-rust\n    cargo build --release\n\nIt will generate the binary in `target/release/gnirehtet`.\n\n\n#### Cross-compile the Rust relay server from Linux to Windows\n\nTo build `gnirehtet.exe` from Linux, install the cross-compile toolchain (on\nDebian):\n\n    sudo apt install gcc-mingw-w64-x86-64\n    rustup target add x86_64-pc-windows-gnu\n\nAdd the following lines to `~/.cargo/config`:\n\n    [target.x86_64-pc-windows-gnu]\n    linker = \"x86_64-w64-mingw32-gcc\"\n    ar = \"x86_64-w64-mingw32-gcc-ar\"\n\nThen build:\n\n    cargo build --release --target=x86_64-pc-windows-gnu\n\nIt will generate `target/x86_64-pc-windows-gnu/release/gnirehtet.exe`.\n\n\n### Android Studio\n\nTo import the project in _Android Studio_: File → Import…\n\nFrom there, you can develop on the Android application and the Java relay\nserver. You can also execute any _gradle_ tasks, and run the tests with visual\nresults.\n\n\n## Overview\n\nThe client registers itself as a [VPN], in order to intercept the whole device\nnetwork traffic.\n\nIt exchanges raw [IPv4 packets] as `byte[]` with the device:\n - it receives packets from the Android applications or system;\n - it must forge response packets.\n\nThe client (executed on the Android device) just maintains a TCP connection to\nthe relay server, and sends the raw packets to it.\n\nThis TCP connection is established over _adb_, after we started a reverse\nport redirection:\n\n    adb reverse localabstract:gnirehtet tcp:31416\n\nThis means that every connection initiated to `localhost:31416` from the device\nwill be redirected to the port `31416` on the computer, on which the relay\nserver is listening.\n\nThe relay server does all the hard work. It receives the IP packets from every\nconnected client and opens [standard sockets][berkeley] (which, of course, don't\nrequire _root_) accordingly, then relays data in both directions. This requires\nto translate packets between level 3 (on the device side) and level 5 (on the\nnetwork side) in the [OSI model].\n\nIn a sense, the relay server behaves like a [NAT] (more precisely a\n[port-restricted cone NAT][portNAT]), in that it opens connections on behalf of\nprivate peers. However, it differs from a standard NAT in the way it\ncommunicates with the clients (the private peers), by using a very specific\n(though simple) protocol, over a TCP connection.\n\n[VPN]: https://developer.android.com/reference/android/net/VpnService.html\n[IPv4 packets]: https://en.wikipedia.org/wiki/IPv4#Packet_structure\n[OSI model]: https://en.wikipedia.org/wiki/OSI_model\n[berkeley]: https://en.wikipedia.org/wiki/Berkeley_sockets\n[NAT]: https://en.wikipedia.org/wiki/Network_address_translation\n[portNAT]: https://en.wikipedia.org/wiki/Network_address_translation#Methods_of_translation\n\n\n## Client\n\nThe client is an _Android_ project located in [`app/`](app/).\n\nThe [`VpnService`] is implemented by [`GnirehtetService`].\n\nWe control the application through intents to [`GnirehtetActivity`].\n\nSome configuration options may be passed as extra parameters, converted to a\n[`VpnConfiguration`] instance. Currently, the user can configure the DNS servers\nto use.\n\nThe very first time, Android requests to the user the permission to enable the\nVPN. In that case, the API requires to call\n[`startActivityForResult`], so we need an [`Activity`]: this is the purpose\nof [`AuthorizationActivity`].\n\n[`RelayTunnel`] manages one connection to the relay server.\n[`PersistentRelayTunnel`] manages [`RelayTunnel`] instances to handle\nreconnections, so that we can stop and start the relay while the client keeps\nrunning.\n\nTo send response packets to the system, we must write one packet at a time to\nthe VPN interface. Since we receive packets from the relay server over a TCP\nconnection, we have to split writes at packet boundaries: this is the purpose\nof [`IPPacketOutputStream`].\n\n[`VpnService`]: https://developer.android.com/reference/android/net/VpnService.html\n[`GnirehtetService`]: app/src/main/java/com/genymobile/gnirehtet/GnirehtetService.java\n[`GnirehtetActivity`]: app/src/main/java/com/genymobile/gnirehtet/GnirehtetActivity.java\n[`VpnConfiguration`]: app/src/main/java/com/genymobile/gnirehtet/VpnConfiguration.java\n[`startActivityForResult`]: https://developer.android.com/reference/android/app/Activity.html#startActivityForResult%28android.content.Intent,%20int%29\n[`Activity`]: https://developer.android.com/reference/android/app/Activity.html\n[`AuthorizationActivity`]: app/src/main/java/com/genymobile/gnirehtet/AuthorizationActivity.java\n[`RelayTunnel`]: app/src/main/java/com/genymobile/gnirehtet/RelayTunnel.java\n[`PersistentRelayTunnel`]: app/src/main/java/com/genymobile/gnirehtet/PersistentRelayTunnel.java\n[`IPPacketOutputStream`]: app/src/main/java/com/genymobile/gnirehtet/IPPacketOutputStream.java\n\n\n## Relay server\n\nThe relay server comes in two flavors:\n - the **Java** version is a _Java 8_ project located in\n   [`relay-java/`](relay-java/);\n - the **Rust** version is a _Rust_ project located in\n   [`relay-rust/`](relay-rust/).\n\nIt is implemented using [asynchronous I/O] (through [Java NIO] and [Rust mio]).\nAs a consequence, it is essentially monothreaded, so there is no need for\nsynchronization to handle packets.\n\n[asynchronous I/O]: https://en.wikipedia.org/wiki/Asynchronous_I/O\n[Java NIO]: https://en.wikipedia.org/wiki/New_I/O_%28Java%29\n[Rust mio]: https://docs.rs/mio/0.6.10/mio/\n\n\n### Selector\n\nThere are different _socket channels_ registered to a unique _selector_:\n - one for the server socket, listening on port 31416;\n - one for each _client_, accepted by the server socket;\n - one for each _TCP connection_ to the network;\n - one for each _UDP connection_ to the network.\n\nInitially, only the server socket _channel_ is registered.\n\nIn **Java**, the _channels_ ([`SelectableChannel`][nio/SelectableChannel]) are\nregistered to the _selector_ ([`Selector`][nio/Selector]) defined in\n[`Relay`][java/Relay], with their [`SelectionHandler`][java/SelectionHandler] as\n[attachment][nio/attachment] (for better decoupling). A [`Client`][java/Client]\nis created for every accepted _client_.\n\n[nio/Selector]: https://docs.oracle.com/javase/8/docs/api/java/nio/channels/Selector.html\n[nio/SelectableChannel]: https://docs.oracle.com/javase/8/docs/api/java/nio/channels/SelectableChannel.html\n[java/Relay]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Relay.java\n[java/SelectionHandler]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/SelectionHandler.java\n[nio/attachment]: https://docs.oracle.com/javase/8/docs/api/java/nio/channels/SelectionKey.html#attachment--\n[java/Client]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Client.java\n\nIn **Rust**, our own [`Selector`][rust/selector] class wraps the\n[`Poll`][mio/Poll] from _mio_ to expose an API accepting event handlers instead\nof low-level [tokens][mio/Token]. The _selector_ instance is created in\n[`Relay`][rust/relay]. The _channels_ are called _\"handles\"_ in _mio_; they are\nsimply the socket instances themselves ([`TcpListener`][mio/TcpListener],\n[`TcpStream`][mio/TcpStream] and [`UdpSocket`][mio/UdpSocket]). A\n[`Client`][rust/client] is created for every accepted _client_.\n\n[mio/Poll]: https://docs.rs/mio/0.6.10/mio/struct.Poll.html\n[mio/Token]: https://docs.rs/mio/0.6.10/mio/struct.Token.html\n[mio/TcpListener]: https://docs.rs/mio/0.6.10/mio/net/struct.TcpListener.html\n[mio/TcpStream]: https://docs.rs/mio/0.6.10/mio/net/struct.TcpStream.html\n[mio/UdpSocket]: https://docs.rs/mio/0.6.10/mio/net/struct.UdpSocket.html\n[rust/selector]: relay-rust/src/relay/selector.rs\n[rust/relay]: relay-rust/src/relay/relay.rs\n[rust/client]: relay-rust/src/relay/client.rs\n\n![archi](assets/archi.png)\n\n### Client\n\nEach _client_ manages a TCP socket, used to transmit raw IP packets from and to\nthe _Gnirehtet_ Android client. Thus, these IP packets are encapsulated into TCP\n(they are transmitted as the TCP payload).\n\nWhen a client connects, the relay server assigns an integer id to it, which it\nwrites to the TCP socket. The client considers itself connected to the relay\nserver only once it has received this number. This allows to detect any\nend-to-end connection issue immediately. For instance, a TCP _connect_ initiated\nby a client succeeds whenever a port redirection is enabled (typically through\n`adb reverse`), even if the relay server is not listening. In that case, the\nfirst _read_ will fail.\n\n\n### Packets\n\nA class representing an _IPv4 packet_\n([`IPv4Packet`][java/IPv4Packet] | [`Ipv4Packet`][rust/ipv4-packet]) provides a\nstructured view to read and write packet data, which is physically stored in the\nbuffers (the little squares on the schema). Since we handle one packet at a time\nwith asynchronous I/O, there is no need to copy or synchronize access to the\npackets data: the packets just point to the buffer where they are stored.\n\n[java/IPv4Packet]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/IPv4Packet.java\n[rust/ipv4-packet]: relay-rust/src/relay/ipv4\\_packet.rs\n\nEach packet contains an instance of _IPv4 headers_ and _transport headers_\n(which might be _TCP_ or _UDP_ headers).\n\nIn **Java**, this is straightforward: [`IPv4Header`][java/IPv4Header],\n[`TCPHeader`][java/TCPHeader] and [`UDPHeader`][java/UDPHeader] just share a\nslice of the raw packet buffer.\n\n[java/IPv4Header]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/IPv4Header.java\n[java/TCPHeader]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/TCPHeader.java\n[java/UDPHeader]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/UDPHeader.java\n\nIn **Rust**, the borrowing rules prevent to share a mutable reference.\nTherefore, _header data_ classes (`*HeaderData`) are used to store the fields,\nand lifetime-bound views (`*Header<'a>` and `*HeaderMut<'a>`) reference both\nthe raw array and the _header data_:\n\n - [`ipv4_header`][rust/ipv4-header]:\n   - data: `Ipv4HeaderData`\n   - view: `Ipv4Header<'a>`\n   - mutable view: `Ipv4HeaderMut<'a>`\n - [`tcp_header`][rust/tcp-header]:\n   - data: `TcpHeaderData`\n   - view: `TcpHeader<'a>`\n   - mutable view: `TcpHeaderMut<'a>`\n - [`udp_header`][rust/udp-header]:\n   - data: `UdpHeaderData`\n   - view: `UdpHeader<'a>`\n   - mutable view: `UdpHeaderMut<'a>`\n\nIn addition, we use [enums][rust-enums] for _transport headers_ to statically\ndispatch calls to _UDP_ and _TCP_ header classes:\n\n - [`transport_header`][rust/transport-header]:\n   - data: `TransportHeaderData`\n   - view: `TransportHeader<'a>`\n   - mutable view: `TransportHeaderMut<'a>`\n\n[rust/ipv4-header]: relay-rust/src/relay/ipv4\\_header.rs\n[rust/tcp-header]: relay-rust/src/relay/tcp\\_header.rs\n[rust/udp-header]: relay-rust/src/relay/udp\\_header.rs\n[rust/transport-header]: relay-rust/src/relay/transport\\_header.rs\n[rust-enums]: https://doc.rust-lang.org/book/first-edition/enums.html\n\n\n### Router\n\nEach _client_ holds a _router_\n([`Router`][java/Router] | [`Router`][rust/router]), responsible for sending the\npackets to the right _connection_, identified by these 5 properties available in\nthe IP and transport headers:\n\n - protocol\n - source address\n - source port\n - destination address\n - destination port\n\nThese identifiers are stored in a _connection id_\n([`ConnectionId`][java/ConnectionId] | [`ConnectionId`][rust/connection]),\nused as a key to find or create the associated _connection_.\n\n[java/Router]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Router.java\n[java/ConnectionId]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/ConnectionId.java\n[rust/Router]: relay-rust/src/relay/router.rs\n[rust/connection]: relay-rust/src/relay/connection.rs\n\n\n### Connections\n\nA _connection_ ([`Connection`][java/Connection] |\n[`Connection`][rust/connection]) is either a _TCP connection_\n([`TCPConnection`][java/TCPConnection] | [`TcpConnection`][rust/tcp-connection])\nor a _UDP connection_ ([`UDPConnection`][java/UDPConnection] |\n[`UdpConnection`][rust/udp-connection]) to the requested destination. It\nregisters its own _channel_ to the _selector_.\n\n[java/Connection]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Connection.java\n[java/TCPConnection]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/TCPConnection.java\n[java/UDPConnection]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/UDPConnection.java\n[rust/connection]: relay-rust/src/relay/connection.rs\n[rust/tcp-connection]: relay-rust/src/relay/tcp\\_connection.rs\n[rust/udp-connection]: relay-rust/src/relay/udp\\_connection.rs\n\nThe connection is responsible for converting data from level 3 to level 5 for\ndevice-to-network packets, and from level 5 to level 3 for network-to-device\npackets. For _UDP connections_, it consists essentially in removing or\nadding IP and transport headers. For _TCP connections_, however, it\nrequires to respond to the client according to the TCP protocol ([RFC 793]),\nin such a way as to ensure a correct end-to-end communication.\n\n[RFC 793]: https://tools.ietf.org/html/rfc793\n\nA _packetizer_ ([`Packetizer`][java/Packetizer] |\n[`Packetizer`][rust/packetizer]) converts from level 5 to level 3 by appending\ncorrect IP and transport headers.\n\n[java/Packetizer]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/Packetizer.java\n[rust/packetizer]: relay-rust/src/relay/packetizer.rs\n\n\n#### UDP connection\n\nWhen the first packet for a specific UDP connection is received from the device,\na new `UdpConnection` is created. It keeps a copy of the IP and UDP headers\nof this first packet, swapping the source and the destination, in order to use\nthem as headers for all response packets.\n\nThe relaying is simple for UDP: each packet received from one side must be sent\nto the other side, without any splitting or merging (datagram boundaries must be\npreserved for UDP).\n\nSince UDP is not a connected protocol, a UDP connection is never \"closed\".\nTherefore, the _selector_ wakes up once per minute (using a timeout) to clean\nexpired (in practice, unused for more than 2 minutes) UDP connections.\n\n\n#### TCP connection\n\n`TcpConnection` also keeps, as a reference, a copy of the IP and TCP headers\nof the first packet received.\n\nHowever, contrary to UDP, TCP must provide reliable delivery. In particular,\nlost packets have to be retransmitted. Nonetheless, we can take advantage of the\ntwo TCP we are proxifying, so that we can provide reliability by delegating the\nretransmission mechanism to them. In fact, it is sufficient to guarantee that\n**we cannot lose packets from network to device**.\n\nIndeed, any packet written to a TCP channel is safe, since it will be managed by\nthe TCP implementation from the system. Losing a raw IP packet received from the\ndevice is also safe: the device TCP implementation will follow the TCP protocol\nto retransmit it. Therefore, **dropping packets from device to network does not\nbreak the connection**.\n\nOn the other hand, once we retrieved a packet from a TCP channel from the\nnetwork, we are responsible for it. Would it be dropped, there would be no way\nto recover the connection.\n\nAs far as I know, there are only two possible causes of packet loss for which we\nare responsible:\n\n 1. When **our buffers are full**, we won't resize them indefinitely, so we have to\ndrop packets. Typically, this may happen if the data from the network is\nreceived at a higher rate than that they can be sent to the device.\n\n 2. When **a raw packet is considered invalid** by the device, it is rejected.\nThis may happen for example if the checksum is invalid or if the TCP sequence\nnumber is [out-of-the-window][flow control].\n\n[flow control]: https://en.wikipedia.org/wiki/Transmission_Control_Protocol#Flow_control\n\nTherefore, by [contraposition], if we guarantee that we never retrieve a packet\nthat we won't be able to store, and that we provide a valid checksum and respect\nthe client TCP window, then **we won't lose any packet without implementing any\nretransmission mechanism**.\n\n[contraposition]: https://en.wikipedia.org/wiki/Contraposition\n\nTo prevent retrieving a packet while our buffers are full, we indicate that we\nare not interested in reading ([`interestOps`][nio/interestOps] |\n[`interest`][mio/reregister]) the TCP channel when some pending data remain to\nbe written to the client buffer. Once some space becomes available, the client\nthen _pulls_ the available packets from the `TcpConnection`s, which are _packet\nsources_ ([`PacketSource`][java/PacketSource] |\n[`PacketSource`][rust/packet-source]).\n\n[nio/interestOps]: https://developer.android.com/reference/java/nio/channels/SelectionKey.html#interestOps%28int%29\n[mio/reregister]: https://docs.rs/mio/0.6.10/mio/struct.Poll.html#method.reregister\n[java/PacketSource]: relay-java/src/main/java/com/genymobile/gnirehtet/relay/PacketSource.java\n[rust/packet-source]: relay-rust/src/relay/packet\\_source.rs\n\n\n## Hack\n\nFor more details, go read the code!\n\nIf you find a bug, or have an awesome idea to implement, please discuss and\ncontribute ;-)\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright (C) 2017 Genymobile\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n"
  },
  {
    "path": "README.md",
    "content": "# Gnirehtet (v2.5.1)\n\nThis project provides **reverse tethering** over `adb` for Android: it\nallows devices to use the internet connection of the computer they are plugged\non. It does not require any _root_ access (neither on the device nor on the\ncomputer). It works on _GNU/Linux_, _Windows_ and _Mac OS_.\n\nCurrently, it relays [TCP] and [UDP] over [IPv4] traffic, but it does not\nsupport [IPv6] (yet?).\n\n[TCP]: https://en.wikipedia.org/wiki/Transmission_Control_Protocol\n[UDP]: https://fr.wikipedia.org/wiki/User_Datagram_Protocol\n[IPv4]: https://en.wikipedia.org/wiki/IPv4\n[IPv6]: https://en.wikipedia.org/wiki/IPv6\n\n_**This project is not actively maintained anymore, only major blockers (like\nbuild issues) are fixed. It should still work, though.**_\n\n\n## Flavors\n\nTwo implementations of _Gnirehtet_ are available:\n - one in **Java**;\n - one in **Rust**.\n\n\n### Which one to choose?\n\nUse the **Rust** implementation. The native binary consumes less CPU and memory,\nand does not require a _Java_ runtime environment.\n\nThe relay server of _Gnirehtet_ was initially only implemented in Java. As a\nbenefit, the same \"binary\" runs on every platform having _Java 8_ runtime\ninstalled. It is still maintained to provide a working alternative in case of\nproblems with the Rust version.\n\n\n## Requirements\n\nThe Android application requires at least API 21 (Android 5.0).\n\nFor the _Java_ version only, _Java 8_ (JRE) is required on your computer. On\nDebian-based distros, install the package `openjdk-8-jre`.\n\n### adb\n\nYou need a recent version of [adb] (where `adb reverse` is implemented, it\nworks with 1.0.36).\n\nIt is available in the [Android SDK platform tools][platform-tools].\n\nOn Debian-based distros, you can alternatively install the package\n`android-tools-adb`.\n\nOn Windows, if you need `adb` only for this application, just download the\n[platform-tools][platform-tools-windows] and extract the following files to the\n_gnirehtet_ directory:\n - `adb.exe`\n - `AdbWinApi.dll`\n - `AdbWinUsbApi.dll`\n\nMake sure you [enabled adb debugging][enable-adb] on your device(s).\n\n[adb]: https://developer.android.com/studio/command-line/adb.html\n[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling\n[platform-tools]: https://developer.android.com/studio/releases/platform-tools.html\n[platform-tools-windows]: https://dl.google.com/android/repository/platform-tools-latest-windows.zip\n\n\n## Get the app\n\n### Homebrew\n\nIf you use [Homebrew](https://brew.sh/), getting up and running is very quick.\nTo install the Rust version:\n\n```\nbrew install gnirehtet\n```\n\n### Download\n\nDownload the [latest release][latest] in the flavor you want.\n\n[latest]: https://github.com/Genymobile/gnirehtet/releases/latest\n\n\n#### Rust\n\n - **Linux:** [`gnirehtet-rust-linux64-v2.5.1.zip`][direct-rust-linux64]  \n   (SHA-256: _dee55499ca4fef00ce2559c767d2d8130163736d43fdbce753e923e75309c275_)\n - **Windows:** [`gnirehtet-rust-win64-v2.5.1.zip`][direct-rust-win64]  \n   (SHA-256: _7f5b1063e7895182aa60def1437e50363c3758144088dcd079037bb7c3c46a1c_)\n - **MacOS:** [`gnirehtet-rust-macos64-v2.2.1.zip`][direct-rust-macos64]\n   _(old release)_  \n   (SHA-256: _902103e6497f995e1e9b92421be212559950cca4a8b557e1f0403769aee06fc8_)\n\n[direct-rust-linux64]: https://github.com/Genymobile/gnirehtet/releases/download/v2.5.1/gnirehtet-rust-linux64-v2.5.1.zip\n[direct-rust-win64]: https://github.com/Genymobile/gnirehtet/releases/download/v2.5.1/gnirehtet-rust-win64-v2.5.1.zip\n[direct-rust-macos64]: https://github.com/Genymobile/gnirehtet/releases/download/v2.2.1/gnirehtet-rust-macos64-v2.2.1.zip\n\nThen extract it.\n\nThe Linux and MacOS archives contain:\n - `gnirehtet.apk`\n - `gnirehtet`\n\nThe Windows archive contains:\n - `gnirehtet.apk`\n - `gnirehtet.exe`\n - `gnirehtet-run.cmd`\n\n\n#### Java\n\n - **All platforms:** [`gnirehtet-java-v2.5.1.zip`][direct-java]  \n   (SHA-256: _816748078fa6a304600a294a13338a06ac778bcc0e57b62d88328c7968ad2d3a_)\n\n[direct-java]: https://github.com/Genymobile/gnirehtet/releases/download/v2.5.1/gnirehtet-java-v2.5.1.zip\n\nThen extract it. The archive contains:\n - `gnirehtet.apk`\n - `gnirehtet.jar`\n - `gnirehtet`\n - `gnirehtet.cmd`\n - `gnirehtet-run.cmd`\n\n\n## Run (simple)\n\n_Note: On Windows, replace `./gnirehtet` by `gnirehtet` in the following\ncommands._\n\nThe application has no UI, and is intended to be controlled from the computer\nonly.\n\nIf you want to activate reverse tethering for exactly one device, just execute:\n\n    ./gnirehtet run\n\nReverse tethering remains active until you press _Ctrl+C_.\n\nOn Windows, for convenience, you can double-click on `gnirehtet-run.cmd`\ninstead (it just executes `gnirehtet run`, without requiring to open a\nterminal).\n\nThe very first start should open a popup to request permission:\n\n![request](assets/request.jpg)\n\nA \"key\" logo appears in the status bar whenever _Gnirehtet_ is active:\n\n![key](assets/key.png)\n\nAlternatively, you can enable reverse tethering for all connected devices\n(present and future) by calling:\n\n    ./gnirehtet autorun\n\n\n## Run\n\nYou can execute the actions separately (it may be useful if you want to reverse\ntether several devices simultaneously).\n\nStart the relay server and keep it open:\n\n    ./gnirehtet relay\n\nInstall the `apk` on your Android device:\n\n    ./gnirehtet install [serial]\n\nIn another terminal, for each client, execute:\n\n    ./gnirehtet start [serial]\n\nTo stop a client:\n\n    ./gnirehtet stop [serial]\n\nTo reset the tunnel (useful to get the connection back when a device is\nunplugged and plugged back while gnirehtet is active):\n\n    ./gnirehtet tunnel [serial]\n\nThe _serial_ parameter is required only if `adb devices` outputs more than one\ndevice.\n\nFor advanced options, call `./gnirehtet` without arguments to get more details.\n\n\n## Run manually\n\nThe `gnirehtet` program exposes a simple command-line interface that executes\nlower-level commands. You can call them manually instead.\n\nTo start the relay server:\n\n    ./gnirehtet relay\n\nTo install the apk:\n\n    adb install -r gnirehtet.apk\n\nTo start a client:\n\n    adb reverse localabstract:gnirehtet tcp:31416\n    adb shell am start -a com.genymobile.gnirehtet.START \\\n        -n com.genymobile.gnirehtet/.GnirehtetActivity\n\nTo stop a client:\n\n    adb shell am start -a com.genymobile.gnirehtet.STOP \\\n        -n com.genymobile.gnirehtet/.GnirehtetActivity\n\n\n## Environment variables\n\n`ADB` defines a custom path to the `adb` executable:\n\n```bash\nADB=/path/to/my/adb ./gnirehtet run\n```\n\n`GNIREHTET_APK` defines a custom path to `gnirehtet.apk`:\n\n```bash\nGNIREHTET_APK=/usr/share/gnirehtet/gnirehtet.apk ./gnirehtet run\n```\n\n\n## Why _gnirehtet_?\n\n    rev <<< tethering\n\n(in _Bash_)\n\n\n## Developers\n\nRead the [developers page].\n\n[developers page]: DEVELOP.md\n\n\n## Licence\n\n    Copyright (C) 2017 Genymobile\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n        http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n\n\n## Articles\n\n- [Introducing “gnirehtet”, a reverse tethering tool for Android][medium-1] ([French version][blog-1])\n- [Gnirehtet 2: our reverse tethering tool for Android now available in Rust][medium-2]\n- [Gnirehtet rewritten in Rust][blog-2-en] ([French version][blog-2-fr])\n\n[medium-1]: https://medium.com/@rom1v/gnirehtet-reverse-tethering-android-2afacdbdaec7\n[blog-1]: https://blog.rom1v.com/2017/03/gnirehtet/\n[medium-2]: https://medium.com/genymobile/gnirehtet-2-our-reverse-tethering-tool-for-android-now-available-in-rust-999960483d5a\n[blog-2-en]: https://blog.rom1v.com/2017/09/gnirehtet-rewritten-in-rust/\n[blog-2-fr]: https://blog.rom1v.com/2017/09/gnirehtet-reecrit-en-rust/\n"
  },
  {
    "path": "app/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion rootProject.ext.compileSdkVersion\n    buildToolsVersion rootProject.ext.buildToolsVersion\n\n    defaultConfig {\n        archivesBaseName = \"gnirehtet\" // change apk name\n        applicationId \"com.genymobile.gnirehtet\"\n        minSdkVersion 21\n        targetSdkVersion 29\n        versionCode 9\n        versionName \"2.5.1\"\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {\n        exclude group: 'com.android.support', module: 'support-annotations'\n    })\n    testImplementation 'junit:junit:4.12'\n}\n\napply from: \"$project.rootDir/config/android-checkstyle.gradle\"\napply from: \"$project.rootDir/config/android-signing.gradle\"\n"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /home/rom/android/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    package=\"com.genymobile.gnirehtet\">\n\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\" />\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n\n    <application\n        android:allowBackup=\"false\"\n        android:icon=\"@null\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\"\n        tools:ignore=\"GoogleAppIndexingWarning\">\n        <activity\n            android:name=\"com.genymobile.gnirehtet.GnirehtetActivity\"\n            android:exported=\"true\"\n            android:permission=\"android.permission.WRITE_SECURE_SETTINGS\"\n            android:theme=\"@style/Theme.Transparent\">\n            <intent-filter>\n                <action android:name=\"com.genymobile.gnirehtet.START\" />\n                <action android:name=\"com.genymobile.gnirehtet.STOP\" />\n            </intent-filter>\n        </activity>\n\n        <service\n            android:name=\"com.genymobile.gnirehtet.GnirehtetService\"\n            android:enabled=\"true\"\n            android:exported=\"true\"\n            android:permission=\"android.permission.BIND_VPN_SERVICE\">\n            <intent-filter>\n                <action android:name=\"android.net.VpnService\" />\n            </intent-filter>\n        </service>\n    </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/java/com/genymobile/gnirehtet/Binary.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet;\n\n@SuppressWarnings(\"checkstyle:MagicNumber\")\npublic final class Binary {\n\n    private static final int MAX_STRING_PACKET_SIZE = 20;\n\n    private Binary() {\n        // not instantiable\n    }\n\n    public static int unsigned(byte value) {\n        return value & 0xff;\n    }\n\n    public static int unsigned(short value) {\n        return value & 0xffff;\n    }\n\n    public static long unsigned(int value) {\n        return value & 0xffffffffL;\n    }\n\n    public static String buildPacketString(byte[] data, int len) {\n        int limit = Math.min(MAX_STRING_PACKET_SIZE, len);\n        StringBuilder builder = new StringBuilder();\n        builder.append('[').append(len).append(\" bytes] \");\n        for (int i = 0; i < limit; ++i) {\n            if (i != 0) {\n                String sep = i % 4 == 0 ? \"  \" : \" \";\n                builder.append(sep);\n            }\n            builder.append(String.format(\"%02X\", data[i] & 0xff));\n        }\n        if (limit < len) {\n            builder.append(\" ... +\").append(len - limit).append(\" bytes\");\n        }\n        return builder.toString();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/genymobile/gnirehtet/CIDR.java",
    "content": "/*\n * Copyright (C) 2018 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\nimport android.util.Log;\n\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\n\npublic class CIDR implements Parcelable {\n\n    private InetAddress address;\n    private int prefixLength;\n\n    public CIDR(InetAddress address, int prefixLength) {\n        this.address = address;\n        this.prefixLength = prefixLength;\n    }\n\n    private CIDR(Parcel source) {\n        try {\n            address = InetAddress.getByAddress(source.createByteArray());\n        } catch (UnknownHostException e) {\n            throw new AssertionError(\"Invalid address\", e);\n        }\n        prefixLength = source.readInt();\n    }\n\n\n    @SuppressWarnings(\"checkstyle:MagicNumber\")\n    public static CIDR parse(String cidr) throws InvalidCIDRException {\n        int slashIndex = cidr.indexOf('/');\n        InetAddress address;\n        int prefix;\n        try {\n            if (slashIndex != -1) {\n                address = Net.toInetAddress(cidr.substring(0, slashIndex));\n                prefix = Integer.parseInt(cidr.substring(slashIndex + 1));\n            } else {\n                address = Net.toInetAddress(cidr);\n                prefix = 32;\n            }\n            return new CIDR(address, prefix);\n        } catch (IllegalArgumentException e) {\n            Log.e(\"Error\", e.getMessage(), e);\n            throw new InvalidCIDRException(cidr, e);\n        } catch (Throwable e) {\n            Log.e(\"Error\", e.getMessage(), e);\n            throw e;\n        }\n    }\n\n    public InetAddress getAddress() {\n        return address;\n    }\n\n    public int getPrefixLength() {\n        return prefixLength;\n    }\n\n    @Override\n    public String toString() {\n        return address.getHostAddress() + \"/\" + prefixLength;\n    }\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeByteArray(address.getAddress());\n        dest.writeInt(prefixLength);\n    }\n\n    public static final Creator<CIDR> CREATOR = new Creator<CIDR>() {\n        @Override\n        public CIDR createFromParcel(Parcel source) {\n            return new CIDR(source);\n        }\n\n        @Override\n        public CIDR[] newArray(int size) {\n            return new CIDR[size];\n        }\n    };\n}\n"
  },
  {
    "path": "app/src/main/java/com/genymobile/gnirehtet/Forwarder.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet;\n\nimport android.net.VpnService;\nimport android.util.Log;\n\nimport java.io.FileDescriptor;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InterruptedIOException;\nimport java.net.DatagramPacket;\nimport java.net.DatagramSocket;\nimport java.net.InetAddress;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\n\npublic class Forwarder {\n\n    private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(3);\n\n    private static final String TAG = Forwarder.class.getSimpleName();\n\n    private static final int BUFSIZE = 0x10000;\n\n    private static final byte[] DUMMY_ADDRESS = {42, 42, 42, 42};\n    private static final int DUMMY_PORT = 4242;\n\n    private final FileDescriptor vpnFileDescriptor;\n    private final PersistentRelayTunnel tunnel;\n\n    private Future<?> deviceToTunnelFuture;\n    private Future<?> tunnelToDeviceFuture;\n\n    public Forwarder(VpnService vpnService, FileDescriptor vpnFileDescriptor, RelayTunnelListener listener) {\n        this.vpnFileDescriptor = vpnFileDescriptor;\n        tunnel = new PersistentRelayTunnel(vpnService, listener);\n    }\n\n    public void forward() {\n        deviceToTunnelFuture = EXECUTOR_SERVICE.submit(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    forwardDeviceToTunnel(tunnel);\n                } catch (InterruptedIOException e) {\n                    Log.d(TAG, \"Device to tunnel interrupted\");\n                } catch (IOException e) {\n                    Log.e(TAG, \"Device to tunnel exception\", e);\n                }\n            }\n        });\n        tunnelToDeviceFuture = EXECUTOR_SERVICE.submit(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    forwardTunnelToDevice(tunnel);\n                } catch (InterruptedIOException e) {\n                    Log.d(TAG, \"Device to tunnel interrupted\");\n                } catch (IOException e) {\n                    Log.e(TAG, \"Tunnel to device exception\", e);\n                }\n            }\n        });\n    }\n\n    public void stop() {\n        tunnel.close();\n        tunnelToDeviceFuture.cancel(true);\n        deviceToTunnelFuture.cancel(true);\n        wakeUpReadWorkaround();\n    }\n\n    @SuppressWarnings(\"checkstyle:MagicNumber\")\n    private void forwardDeviceToTunnel(Tunnel tunnel) throws IOException {\n        Log.d(TAG, \"Device to tunnel forwarding started\");\n        FileInputStream vpnInput = new FileInputStream(vpnFileDescriptor);\n        byte[] buffer = new byte[BUFSIZE];\n        while (true) {\n            // blocking read\n            int r = vpnInput.read(buffer);\n            if (r == -1) {\n                Log.d(TAG, \"VPN closed\");\n                break;\n            }\n            if (r > 0) {\n                int version = buffer[0] >> 4;\n                if (version == 4) {\n                    // blocking send\n                    tunnel.send(buffer, r);\n                } else {\n                    // see <https://github.com/Genymobile/gnirehtet/issues/69>\n                    Log.w(TAG, \"Unexpected packet IP version: \" + version);\n                }\n            } else {\n                Log.d(TAG, \"Empty read\");\n            }\n        }\n        Log.d(TAG, \"Device to tunnel forwarding stopped\");\n    }\n\n    private void forwardTunnelToDevice(Tunnel tunnel) throws IOException {\n        Log.d(TAG, \"Tunnel to device forwarding started\");\n        FileOutputStream vpnOutput = new FileOutputStream(vpnFileDescriptor);\n        IPPacketOutputStream packetOutputStream = new IPPacketOutputStream(vpnOutput);\n\n        byte[] buffer = new byte[BUFSIZE];\n        while (true) {\n            // blocking receive\n            int w = tunnel.receive(buffer);\n            if (w == -1) {\n                Log.d(TAG, \"Tunnel closed\");\n                break;\n            }\n            if (w > 0) {\n                // blocking write\n                packetOutputStream.write(buffer, 0, w);\n            } else {\n                Log.d(TAG, \"Empty write\");\n            }\n        }\n        Log.d(TAG, \"Tunnel to device forwarding stopped\");\n    }\n\n    /**\n     * Neither vpnInterface.close() nor vpnInputStream.close() wake up a blocking\n     * vpnInputStream.read().\n     * <p>\n     * Therefore, we need to make Android send a packet to the VPN interface (here by sending a UDP\n     * packet), so that any blocking read will be woken up.\n     * <p>\n     * Since the tunnel is closed at this point, it will never reach the network.\n     */\n    private void wakeUpReadWorkaround() {\n        // network actions may not be called from the main thread\n        EXECUTOR_SERVICE.execute(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    DatagramSocket socket = new DatagramSocket();\n                    InetAddress dummyAddr = InetAddress.getByAddress(DUMMY_ADDRESS);\n                    DatagramPacket packet = new DatagramPacket(new byte[0], 0, dummyAddr, DUMMY_PORT);\n                    socket.send(packet);\n                } catch (IOException e) {\n                    // ignore\n                }\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/genymobile/gnirehtet/GnirehtetActivity.java",
    "content": "package com.genymobile.gnirehtet;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.net.VpnService;\nimport android.os.Bundle;\nimport android.util.Log;\n\n/**\n * This (invisible) activity receives the {@link #ACTION_GNIREHTET_START START} and\n * {@link #ACTION_GNIREHTET_STOP} actions from the command line.\n * <p>\n * Recent versions of Android refuse to directly start a {@link android.app.Service Service} or a\n * {@link android.content.BroadcastReceiver BroadcastReceiver}, so actions are always managed by\n * this activity.\n */\npublic class GnirehtetActivity extends Activity {\n\n    private static final String TAG = GnirehtetActivity.class.getSimpleName();\n\n    public static final String ACTION_GNIREHTET_START = \"com.genymobile.gnirehtet.START\";\n    public static final String ACTION_GNIREHTET_STOP = \"com.genymobile.gnirehtet.STOP\";\n\n    public static final String EXTRA_DNS_SERVERS = \"dnsServers\";\n    public static final String EXTRA_ROUTES = \"routes\";\n\n    private static final int VPN_REQUEST_CODE = 0;\n\n    private VpnConfiguration requestedConfig;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        handleIntent(getIntent());\n    }\n\n    private void handleIntent(Intent intent) {\n        String action = intent.getAction();\n        Log.d(TAG, \"Received request \" + action);\n        boolean finish = true;\n        if (ACTION_GNIREHTET_START.equals(action)) {\n            VpnConfiguration config = createConfig(intent);\n            finish = startGnirehtet(config);\n        } else if (ACTION_GNIREHTET_STOP.equals(action)) {\n            stopGnirehtet();\n        }\n\n        if (finish) {\n            finish();\n        }\n    }\n\n    private static VpnConfiguration createConfig(Intent intent) {\n        String[] dnsServers = intent.getStringArrayExtra(EXTRA_DNS_SERVERS);\n        if (dnsServers == null) {\n            dnsServers = new String[0];\n        }\n        String[] routes = intent.getStringArrayExtra(EXTRA_ROUTES);\n        if (routes == null) {\n            routes = new String[0];\n        }\n        return new VpnConfiguration(Net.toInetAddresses(dnsServers), Net.toCIDRs(routes));\n    }\n\n    private boolean startGnirehtet(VpnConfiguration config) {\n        Intent vpnIntent = VpnService.prepare(this);\n        if (vpnIntent == null) {\n            Log.d(TAG, \"VPN was already authorized\");\n            // we got the permission, start the service now\n            GnirehtetService.start(this, config);\n            return true;\n        }\n\n        Log.w(TAG, \"VPN requires the authorization from the user, requesting...\");\n        requestAuthorization(vpnIntent, config);\n        return false; // do not finish now\n    }\n\n    private void stopGnirehtet() {\n        GnirehtetService.stop(this);\n    }\n\n    private void requestAuthorization(Intent vpnIntent, VpnConfiguration config) {\n        this.requestedConfig = config;\n        startActivityForResult(vpnIntent, VPN_REQUEST_CODE);\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n        if (requestCode == VPN_REQUEST_CODE && resultCode == RESULT_OK) {\n            GnirehtetService.start(this, requestedConfig);\n        }\n        requestedConfig = null;\n        finish();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/genymobile/gnirehtet/GnirehtetService.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.net.ConnectivityManager;\nimport android.net.LinkAddress;\nimport android.net.LinkProperties;\nimport android.net.Network;\nimport android.net.VpnService;\nimport android.os.Build;\nimport android.os.Handler;\nimport android.os.Message;\nimport android.os.ParcelFileDescriptor;\nimport android.util.Log;\n\nimport java.io.IOException;\nimport java.net.InetAddress;\nimport java.util.List;\n\npublic class GnirehtetService extends VpnService {\n\n    public static final boolean VERBOSE = false;\n\n    private static final String ACTION_START_VPN = \"com.genymobile.gnirehtet.START_VPN\";\n    private static final String ACTION_CLOSE_VPN = \"com.genymobile.gnirehtet.CLOSE_VPN\";\n    private static final String EXTRA_VPN_CONFIGURATION = \"vpnConfiguration\";\n\n    private static final String TAG = GnirehtetService.class.getSimpleName();\n\n    private static final InetAddress VPN_ADDRESS = Net.toInetAddress(new byte[] {10, 0, 0, 2});\n    // magic value: higher (like 0x8000 or 0xffff) or lower (like 1500) values show poorer performances\n    private static final int MTU = 0x4000;\n\n    private final Notifier notifier = new Notifier(this);\n    private final Handler handler = new RelayTunnelConnectionStateHandler(this);\n\n    private ParcelFileDescriptor vpnInterface = null;\n    private Forwarder forwarder;\n\n    public static void start(Context context, VpnConfiguration config) {\n        Intent intent = new Intent(context, GnirehtetService.class);\n        intent.setAction(ACTION_START_VPN);\n        intent.putExtra(GnirehtetService.EXTRA_VPN_CONFIGURATION, config);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            context.startForegroundService(intent);\n        } else {\n            context.startService(intent);\n        }\n    }\n\n    public static void stop(Context context) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            context.startForegroundService(createStopIntent(context));\n        } else {\n            context.startService(createStopIntent(context));\n        }\n    }\n\n    static Intent createStopIntent(Context context) {\n        Intent intent = new Intent(context, GnirehtetService.class);\n        intent.setAction(ACTION_CLOSE_VPN);\n        return intent;\n    }\n\n    @Override\n    public int onStartCommand(Intent intent, int flags, int startId) {\n        String action = intent.getAction();\n        Log.d(TAG, \"Received request \" + action);\n        if (ACTION_START_VPN.equals(action)) {\n            if (isRunning()) {\n                Log.d(TAG, \"VPN already running, ignore START request\");\n            } else {\n                VpnConfiguration config = intent.getParcelableExtra(EXTRA_VPN_CONFIGURATION);\n                if (config == null) {\n                    config = new VpnConfiguration();\n                }\n                startVpn(config);\n            }\n        } else if (ACTION_CLOSE_VPN.equals(action)) {\n            close();\n        }\n        return START_NOT_STICKY;\n    }\n\n    private boolean isRunning() {\n        return vpnInterface != null;\n    }\n\n    private void startVpn(VpnConfiguration config) {\n        notifier.start();\n        if (setupVpn(config)) {\n            startForwarding();\n        }\n    }\n\n    @SuppressWarnings(\"checkstyle:MagicNumber\")\n    private boolean setupVpn(VpnConfiguration config) {\n        Builder builder = new Builder();\n        builder.addAddress(VPN_ADDRESS, 32);\n        builder.setSession(getString(R.string.app_name));\n\n        CIDR[] routes = config.getRoutes();\n        if (routes.length == 0) {\n            // no routes defined, redirect the whole network traffic\n            builder.addRoute(\"0.0.0.0\", 0);\n        } else {\n            for (CIDR route : routes) {\n                builder.addRoute(route.getAddress(), route.getPrefixLength());\n            }\n        }\n\n        InetAddress[] dnsServers = config.getDnsServers();\n        if (dnsServers.length == 0) {\n            // no DNS server defined, use Google DNS\n            builder.addDnsServer(\"8.8.8.8\");\n        } else {\n            for (InetAddress dnsServer : dnsServers) {\n                builder.addDnsServer(dnsServer);\n            }\n        }\n\n        // non-blocking by default, but FileChannel is not selectable, that's stupid!\n        // so switch to synchronous I/O to avoid polling\n        builder.setBlocking(true);\n        builder.setMtu(MTU);\n\n        vpnInterface = builder.establish();\n        if (vpnInterface == null) {\n            Log.w(TAG, \"VPN starting failed, please retry\");\n            // establish() may return null if the application is not prepared or is revoked\n            return false;\n        }\n\n        setAsUndernlyingNetwork();\n        return true;\n    }\n\n    @SuppressWarnings(\"checkstyle:MagicNumber\")\n    private void setAsUndernlyingNetwork() {\n        if (Build.VERSION.SDK_INT >= 22) {\n            Network vpnNetwork = findVpnNetwork();\n            if (vpnNetwork != null) {\n                // so that applications knows that network is available\n                setUnderlyingNetworks(new Network[] {vpnNetwork});\n            }\n        } else {\n            Log.w(TAG, \"Cannot set underlying network, API version \" + Build.VERSION.SDK_INT + \" < 22\");\n        }\n    }\n\n    private Network findVpnNetwork() {\n        ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);\n        Network[] networks = cm.getAllNetworks();\n        for (Network network : networks) {\n            LinkProperties linkProperties = cm.getLinkProperties(network);\n            List<LinkAddress> addresses = linkProperties.getLinkAddresses();\n            for (LinkAddress addr : addresses) {\n                if (addr.getAddress().equals(VPN_ADDRESS)) {\n                    return network;\n                }\n            }\n        }\n        return null;\n    }\n\n    private void startForwarding() {\n        forwarder = new Forwarder(this, vpnInterface.getFileDescriptor(), new RelayTunnelListener(handler));\n        forwarder.forward();\n    }\n\n    private void close() {\n        if (!isRunning()) {\n            // already closed\n            return;\n        }\n\n        notifier.stop();\n\n        try {\n            forwarder.stop();\n            forwarder = null;\n            vpnInterface.close();\n            vpnInterface = null;\n        } catch (IOException e) {\n            Log.w(TAG, \"Cannot close VPN file descriptor\", e);\n        }\n    }\n\n\n    private static final class RelayTunnelConnectionStateHandler extends Handler {\n\n        private final GnirehtetService vpnService;\n\n        private RelayTunnelConnectionStateHandler(GnirehtetService vpnService) {\n            this.vpnService = vpnService;\n        }\n\n        @Override\n        public void handleMessage(Message message) {\n            if (!vpnService.isRunning()) {\n                // if the VPN is not running anymore, ignore obsolete events\n                return;\n            }\n            switch (message.what) {\n                case RelayTunnelListener.MSG_RELAY_TUNNEL_CONNECTED:\n                    Log.d(TAG, \"Relay tunnel connected\");\n                    vpnService.notifier.setFailure(false);\n                    break;\n                case RelayTunnelListener.MSG_RELAY_TUNNEL_DISCONNECTED:\n                    Log.d(TAG, \"Relay tunnel disconnected\");\n                    vpnService.notifier.setFailure(true);\n                    break;\n                default:\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/genymobile/gnirehtet/IPPacketOutputStream.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet;\n\nimport android.util.Log;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.nio.ByteBuffer;\n\n/**\n * Wrapper for writing one IP packet at a time to an {@link OutputStream}.\n */\n@SuppressWarnings(\"checkstyle:MagicNumber\")\npublic class IPPacketOutputStream extends OutputStream {\n\n    private static final String TAG = IPPacketOutputStream.class.getSimpleName();\n\n    private static final int MAX_IP_PACKET_LENGTH = 1 << 16; // packet length is stored on 16 bits\n\n    private final OutputStream target;\n    // must always accept 1 full packet + any partial packet\n    private final ByteBuffer buffer = ByteBuffer.allocate(2 * MAX_IP_PACKET_LENGTH);\n\n    public IPPacketOutputStream(OutputStream target) {\n        this.target = target;\n    }\n\n    @Override\n    public void close() throws IOException {\n        target.close();\n    }\n\n    @Override\n    public void flush() throws IOException {\n        target.flush();\n    }\n\n    @Override\n    public void write(byte[] b, int off, int len) throws IOException {\n        if (len > MAX_IP_PACKET_LENGTH) {\n            throw new IOException(\"IPPacketOutputStream does not support writing more than one packet at a time\");\n        }\n        // by design, the buffer must always have enough space for one packet\n        if (BuildConfig.DEBUG && len > buffer.remaining()) {\n            Log.e(TAG, len  + \" must be <= than \" + buffer.remaining());\n            Log.e(TAG, buffer.toString());\n            throw new AssertionError(\"Buffer is unexpectedly full\");\n        }\n        buffer.put(b, off, len);\n        buffer.flip();\n        sink();\n        buffer.compact();\n    }\n\n    @Override\n    public void write(int b) throws IOException {\n        if (!buffer.hasRemaining()) {\n            throw new IOException(\"IPPacketOutputStream buffer is full\");\n        }\n        buffer.put((byte) b);\n        buffer.flip();\n        sink();\n        buffer.compact();\n    }\n\n    private void sink() throws IOException {\n        // sink all packets\n        while (sinkPacket()) {\n            // continue\n        }\n    }\n\n    private boolean sinkPacket() throws IOException {\n        int version = readPacketVersion(buffer);\n        if (version == -1) {\n            // no packet at all\n            return false;\n        }\n        if (version != 4) {\n            Log.e(TAG, \"Unsupported packet received, IP version is:\" + version);\n            Log.d(TAG, \"Clearing buffer\");\n            buffer.clear();\n            return false;\n        }\n        int packetLength = readPacketLength(buffer);\n        if (packetLength == -1 || packetLength > buffer.remaining()) {\n            // no packet\n            return false;\n        }\n\n        target.write(buffer.array(), buffer.arrayOffset() + buffer.position(), packetLength);\n        buffer.position(buffer.position() + packetLength);\n        return true;\n    }\n\n    /**\n     * Read the packet IP version, assuming that an IP packets is stored at absolute position 0.\n     *\n     * @param buffer the buffer\n     * @return the IP version, or {@code -1} if not available\n     */\n    public static int readPacketVersion(ByteBuffer buffer) {\n        if (!buffer.hasRemaining()) {\n            // buffer is empty\n            return -1;\n        }\n        // version is stored in the 4 first bits\n        byte versionAndIHL = buffer.get(buffer.position());\n        return (versionAndIHL & 0xf0) >> 4;\n    }\n\n    /**\n     * Read the packet length, assuming thatan IP packet is stored at absolute position 0.\n     *\n     * @param buffer the buffer\n     * @return the packet length, or {@code -1} if not available\n     */\n    public static int readPacketLength(ByteBuffer buffer) {\n        if (buffer.limit() < buffer.position() + 4) {\n            // buffer does not even contains the length field\n            return -1;\n        }\n        // packet length is 16 bits starting at offset 2\n        return Binary.unsigned(buffer.getShort(buffer.position() + 2));\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/genymobile/gnirehtet/InvalidCIDRException.java",
    "content": "/*\n * Copyright (C) 2018 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet;\n\npublic class InvalidCIDRException extends Exception {\n\n    private String cidr;\n\n    private static String createMessage(String cidr) {\n        return \"Invalid CIDR:\" + cidr;\n    }\n\n    public InvalidCIDRException(String cidr, Throwable cause) {\n        super(createMessage(cidr), cause);\n        this.cidr = cidr;\n    }\n\n    public InvalidCIDRException(String cidr) {\n        super(createMessage(cidr));\n        this.cidr = cidr;\n    }\n\n    public String getCIDR() {\n        return cidr;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/genymobile/gnirehtet/Net.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet;\n\nimport java.net.Inet4Address;\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\n\npublic final class Net {\n    private Net() {\n        // not instantiable\n    }\n\n    public static InetAddress[] toInetAddresses(String... addresses) {\n        InetAddress[] result = new InetAddress[addresses.length];\n        for (int i = 0; i < result.length; ++i) {\n            result[i] = toInetAddress(addresses[i]);\n        }\n        return result;\n    }\n\n    public static InetAddress toInetAddress(String address) {\n        try {\n            return InetAddress.getByName(address);\n        } catch (UnknownHostException e) {\n            throw new IllegalArgumentException(e);\n        }\n    }\n\n    public static InetAddress toInetAddress(byte[] raw) {\n        try {\n            return InetAddress.getByAddress(raw);\n        } catch (UnknownHostException e) {\n            throw new IllegalArgumentException(e);\n        }\n    }\n\n    public static CIDR toCIDR(String cidr) {\n        try {\n            return CIDR.parse(cidr);\n        } catch (InvalidCIDRException e) {\n            throw new IllegalArgumentException(e);\n        }\n    }\n\n    public static CIDR[] toCIDRs(String... cidrs) {\n        CIDR[] result = new CIDR[cidrs.length];\n        for (int i = 0; i < result.length; ++i) {\n            result[i] = toCIDR(cidrs[i]);\n        }\n        return result;\n    }\n\n    @SuppressWarnings(\"checkstyle:MagicNumber\")\n    public static Inet4Address getLocalhostIPv4() {\n        byte[] localhost = {127, 0, 0, 1};\n        return (Inet4Address) toInetAddress(localhost);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/genymobile/gnirehtet/Notifier.java",
    "content": "package com.genymobile.gnirehtet;\n\nimport android.annotation.TargetApi;\nimport android.app.Notification;\nimport android.app.NotificationChannel;\nimport android.app.NotificationManager;\nimport android.app.PendingIntent;\nimport android.app.Service;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Build;\n\n/**\n * Manage the notification necessary for the foreground service (mandatory since Android O).\n */\npublic class Notifier {\n\n    private static final int NOTIFICATION_ID = 42;\n    private static final String CHANNEL_ID = \"Gnirehtet\";\n\n    private final Service context;\n    private boolean failure;\n\n    public Notifier(Service context) {\n        this.context = context;\n    }\n\n    private Notification createNotification(boolean failure) {\n        Notification.Builder notificationBuilder = createNotificationBuilder();\n        notificationBuilder.setContentTitle(context.getString(R.string.app_name));\n        if (failure) {\n            notificationBuilder.setContentText(context.getString(R.string.relay_disconnected));\n            notificationBuilder.setSmallIcon(R.drawable.ic_report_problem_24dp);\n        } else {\n            notificationBuilder.setContentText(context.getString(R.string.relay_connected));\n            notificationBuilder.setSmallIcon(R.drawable.ic_usb_24dp);\n        }\n        notificationBuilder.addAction(createStopAction());\n        return notificationBuilder.build();\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    private Notification.Builder createNotificationBuilder() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            return new Notification.Builder(context, CHANNEL_ID);\n        }\n        return new Notification.Builder(context);\n    }\n\n    @TargetApi(26)\n    private void createNotificationChannel() {\n        NotificationChannel channel = new NotificationChannel(CHANNEL_ID, context.getString(R.string.app_name), NotificationManager\n                .IMPORTANCE_DEFAULT);\n        getNotificationManager().createNotificationChannel(channel);\n    }\n\n    @TargetApi(26)\n    private void deleteNotificationChannel() {\n        getNotificationManager().deleteNotificationChannel(CHANNEL_ID);\n    }\n\n    public void start() {\n        failure = false; // reset failure flag\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            createNotificationChannel();\n        }\n        context.startForeground(NOTIFICATION_ID, createNotification(false));\n    }\n\n    public void stop() {\n        context.stopForeground(true);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            deleteNotificationChannel();\n        }\n    }\n\n    public void setFailure(boolean failure) {\n        if (this.failure != failure) {\n            this.failure = failure;\n            Notification notification = createNotification(failure);\n            getNotificationManager().notify(NOTIFICATION_ID, notification);\n        }\n    }\n\n    private Notification.Action createStopAction() {\n        Intent stopIntent = GnirehtetService.createStopIntent(context);\n        PendingIntent stopPendingIntent = PendingIntent.getService(context, 0, stopIntent, PendingIntent.FLAG_ONE_SHOT);\n        // the non-deprecated constructor is not available in API 21\n        @SuppressWarnings(\"deprecation\")\n        Notification.Action.Builder actionBuilder = new Notification.Action.Builder(R.drawable.ic_close_24dp, context.getString(R.string.stop_vpn),\n                stopPendingIntent);\n        return actionBuilder.build();\n    }\n\n    private NotificationManager getNotificationManager() {\n        return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/genymobile/gnirehtet/PersistentRelayTunnel.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet;\n\nimport android.net.VpnService;\nimport android.util.Log;\n\nimport java.io.IOException;\nimport java.io.InterruptedIOException;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * Expose a {@link Tunnel} that automatically handles {@link RelayTunnel} reconnections.\n */\npublic class PersistentRelayTunnel implements Tunnel {\n\n    private static final String TAG = PersistentRelayTunnel.class.getSimpleName();\n\n    private final RelayTunnelProvider provider;\n    private final AtomicBoolean stopped = new AtomicBoolean();\n\n    public PersistentRelayTunnel(VpnService vpnService, RelayTunnelListener listener) {\n        provider = new RelayTunnelProvider(vpnService, listener);\n    }\n\n    @Override\n    public void send(byte[] packet, int len) throws IOException {\n        while (!stopped.get()) {\n            Tunnel tunnel = null;\n            try {\n                tunnel = provider.getCurrentTunnel();\n                tunnel.send(packet, len);\n                return;\n            } catch (IOException | InterruptedException e) {\n                Log.e(TAG, \"Cannot send to tunnel\", e);\n                if (tunnel != null) {\n                    provider.invalidateTunnel(tunnel);\n                }\n            }\n        }\n        throw new InterruptedIOException(\"Persistent tunnel stopped\");\n    }\n\n    @Override\n    public int receive(byte[] packet) throws IOException {\n        while (!stopped.get()) {\n            Tunnel tunnel = null;\n            try {\n                tunnel = provider.getCurrentTunnel();\n                int r = tunnel.receive(packet);\n                if (r == -1) {\n                    Log.d(TAG, \"Tunnel read EOF\");\n                    provider.invalidateTunnel(tunnel);\n                    continue;\n                }\n                return r;\n            } catch (IOException | InterruptedException e) {\n                Log.e(TAG, \"Cannot receive from tunnel\", e);\n                if (tunnel != null) {\n                    provider.invalidateTunnel(tunnel);\n                }\n            }\n        }\n        throw new InterruptedIOException(\"Persistent tunnel stopped\");\n    }\n\n    @Override\n    public void close() {\n        stopped.set(true);\n        provider.invalidateTunnel();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/genymobile/gnirehtet/RelayTunnel.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet;\n\nimport android.net.LocalSocket;\nimport android.net.LocalSocketAddress;\nimport android.net.VpnService;\nimport android.util.Log;\n\nimport java.io.DataInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\npublic final class RelayTunnel implements Tunnel {\n\n    private static final String TAG = RelayTunnel.class.getSimpleName();\n\n    private static final String LOCAL_ABSTRACT_NAME = \"gnirehtet\";\n\n    private final LocalSocket localSocket = new LocalSocket();\n\n    private RelayTunnel() {\n        // exposed through open() static method\n    }\n\n    @SuppressWarnings(\"unused\")\n    public static RelayTunnel open(VpnService vpnService) throws IOException {\n        Log.d(TAG, \"Opening a new relay tunnel...\");\n        // since we use a local socket, we don't need to protect the socket from the vpnService anymore\n        // but this is an implementation detail, so keep the method signature\n        return new RelayTunnel();\n    }\n\n    public void connect() throws IOException {\n        localSocket.connect(new LocalSocketAddress(LOCAL_ABSTRACT_NAME));\n        readClientId(localSocket.getInputStream());\n    }\n\n    /**\n     * The relay server is accessible through an \"adb reverse\" port redirection.\n     * <p>\n     * If the port redirection is enabled but the relay server is not started, then the call to\n     * channel.connect() will succeed, but the first read() will return -1.\n     * <p>\n     * As a consequence, the connection state of the relay server would be invalid temporarily (we\n     * would switch to CONNECTED state then switch back to DISCONNECTED).\n     * <p>\n     * To avoid this problem, we must actually read from the server, so that an error occurs\n     * immediately if the relay server is not accessible.\n     * <p>\n     * Therefore, the relay server immediately sends the client id: consume it and log it.\n     *\n     * @param inputStream the input stream to receive data from the relay server\n     * @throws IOException if an I/O error occurs\n     */\n    private static void readClientId(InputStream inputStream) throws IOException {\n        Log.d(TAG, \"Requesting client id\");\n        int clientId = new DataInputStream(inputStream).readInt();\n        Log.d(TAG, \"Connected to the relay server as #\" + Binary.unsigned(clientId));\n    }\n\n    @Override\n    public void send(byte[] packet, int len) throws IOException {\n        if (GnirehtetService.VERBOSE) {\n            Log.v(TAG, \"Sending packet: \" + Binary.buildPacketString(packet, len));\n        }\n        localSocket.getOutputStream().write(packet, 0, len);\n    }\n\n    @Override\n    public int receive(byte[] packet) throws IOException {\n        int r = localSocket.getInputStream().read(packet);\n        if (GnirehtetService.VERBOSE) {\n            Log.v(TAG, \"Receiving packet: \" + Binary.buildPacketString(packet, r));\n        }\n        return r;\n    }\n\n    @Override\n    public void close() {\n        try {\n            if (localSocket.getFileDescriptor() != null) {\n                // close the streams to interrupt pending read and writes\n                localSocket.shutdownInput();\n                localSocket.shutdownOutput();\n            }\n            localSocket.close();\n        } catch (IOException e) {\n            // what could we do?\n            throw new RuntimeException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/genymobile/gnirehtet/RelayTunnelListener.java",
    "content": "package com.genymobile.gnirehtet;\n\nimport android.os.Handler;\n\n/**\n * Convenient wrapper to dispatch events to the given {@link Handler}.\n */\npublic class RelayTunnelListener {\n\n    static final int MSG_RELAY_TUNNEL_CONNECTED = 0;\n    static final int MSG_RELAY_TUNNEL_DISCONNECTED = 1;\n\n    private final Handler handler;\n\n    public RelayTunnelListener(Handler handler) {\n        this.handler = handler;\n    }\n\n    public void notifyRelayTunnelConnected() {\n        handler.sendEmptyMessage(MSG_RELAY_TUNNEL_CONNECTED);\n    }\n\n    public void notifyRelayTunnelDisconnected() {\n        handler.sendEmptyMessage(MSG_RELAY_TUNNEL_DISCONNECTED);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/genymobile/gnirehtet/RelayTunnelProvider.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet;\n\nimport android.net.VpnService;\n\nimport java.io.IOException;\n\n/**\n * Provide a valid {@link RelayTunnel}, creating a new one if necessary.\n */\npublic class RelayTunnelProvider {\n\n    private static final int DELAY_BETWEEN_ATTEMPTS_MS = 5000;\n\n    private final Object getCurrentTunnelLock = new Object(); // protects getCurrentTunnel()\n\n    private final VpnService vpnService;\n    private final RelayTunnelListener listener;\n    private RelayTunnel tunnel; // protected both by \"this\" and \"getCurrentTunnelLock\"\n    private boolean first = true; // protected by \"getCurrentTunnelLock\"\n    private long lastFailureTimestamp; // protected by \"this\"\n\n    public RelayTunnelProvider(VpnService vpnService, RelayTunnelListener listener) {\n        this.vpnService = vpnService;\n        this.listener = listener;\n    }\n\n    public RelayTunnel getCurrentTunnel() throws IOException, InterruptedException {\n        /*\n         * To make sure that both the sending and receiving threads use the same tunnel, we must\n         * guarantee that this method may not be called several times concurrently.\n         *\n         * However, since it executes potentially long-running blocking calls, we still want to be\n         * able to call invalidateTunnel() concurrently, which requires to protect some fields.\n         *\n         * Therefore, use one mutex (\"getCurrentTunnelLock\") to avoid concurrent calls to\n         * getCurrentTunnel(), and another one (\"this\") to protect fields shared with\n         * invalidateTunnel().\n         */\n        synchronized (getCurrentTunnelLock) {\n            synchronized (this) {\n                if (tunnel != null) {\n                    return tunnel;\n                }\n\n                waitUntilNextAttemptSlot();\n\n                // \"tunnel\" has not changed during waiting (only getCurrentTunnel() may write it)\n                tunnel = RelayTunnel.open(vpnService);\n            }\n\n            // the first connection must either notify \"connected\" or \"disconnected\"\n            boolean notifyDisconnectedOnError = first;\n            first = false;\n            connectTunnel(notifyDisconnectedOnError);\n        }\n        return tunnel;\n    }\n\n    private void connectTunnel(boolean notifyDisconnectedOnError) throws IOException {\n        try {\n            tunnel.connect();\n            notifyConnected();\n        } catch (IOException e) {\n            touchFailure();\n            if (notifyDisconnectedOnError) {\n                notifyDisconnected();\n            }\n            throw e;\n        }\n    }\n\n    public synchronized void invalidateTunnel() {\n        if (tunnel != null) {\n            touchFailure();\n            tunnel.close();\n            tunnel = null;\n            notifyDisconnected();\n        }\n    }\n\n    /**\n     * Call {@link #invalidateTunnel()} only if {@code tunnelToInvalidate} is the current tunnel (or\n     * is {@code null}).\n     *\n     * @param tunnelToInvalidate the tunnel to invalidate\n     */\n    public synchronized void invalidateTunnel(Tunnel tunnelToInvalidate) {\n        if (tunnel == tunnelToInvalidate || tunnelToInvalidate == null) {\n            invalidateTunnel();\n        }\n    }\n\n    private synchronized void touchFailure() {\n        lastFailureTimestamp = System.currentTimeMillis();\n    }\n\n    private void waitUntilNextAttemptSlot() throws InterruptedException {\n        if (first) {\n            // do not wait on first attempt\n            return;\n        }\n        long delay = lastFailureTimestamp + DELAY_BETWEEN_ATTEMPTS_MS - System.currentTimeMillis();\n        while (delay > 0) {\n            wait(delay);\n            delay = lastFailureTimestamp + DELAY_BETWEEN_ATTEMPTS_MS - System.currentTimeMillis();\n        }\n    }\n\n    private void notifyConnected() {\n        if (listener != null) {\n            listener.notifyRelayTunnelConnected();\n        }\n    }\n\n    private void notifyDisconnected() {\n        if (listener != null) {\n            listener.notifyRelayTunnelDisconnected();\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/genymobile/gnirehtet/Tunnel.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet;\n\nimport java.io.IOException;\n\npublic interface Tunnel {\n\n    // blocking\n    void send(byte[] packet, int len) throws IOException;\n\n    // blocking\n    int receive(byte[] packet) throws IOException;\n\n    // blocking\n    void close();\n}\n"
  },
  {
    "path": "app/src/main/java/com/genymobile/gnirehtet/VpnConfiguration.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\n\npublic class VpnConfiguration implements Parcelable {\n\n    private final InetAddress[] dnsServers;\n    private final CIDR[] routes;\n\n    public VpnConfiguration() {\n        this.dnsServers = new InetAddress[0];\n        this.routes = new CIDR[0];\n    }\n\n    public VpnConfiguration(InetAddress[] dnsServers, CIDR[] routes) {\n        this.dnsServers = dnsServers;\n        this.routes = routes;\n    }\n\n    private VpnConfiguration(Parcel source) {\n        int dnsCount = source.readInt();\n        dnsServers = new InetAddress[dnsCount];\n        try {\n            for (int i = 0; i < dnsCount; ++i) {\n                dnsServers[i] = InetAddress.getByAddress(source.createByteArray());\n            }\n        } catch (UnknownHostException e) {\n            throw new AssertionError(\"Invalid address\", e);\n        }\n        routes = source.createTypedArray(CIDR.CREATOR);\n    }\n\n    public InetAddress[] getDnsServers() {\n        return dnsServers;\n    }\n\n    public CIDR[] getRoutes() {\n        return routes;\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeInt(dnsServers.length);\n        for (InetAddress addr : dnsServers) {\n            dest.writeByteArray(addr.getAddress());\n        }\n        dest.writeTypedArray(routes, 0);\n    }\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    public static final Creator<VpnConfiguration> CREATOR = new Creator<VpnConfiguration>() {\n        @Override\n        public VpnConfiguration createFromParcel(Parcel source) {\n            return new VpnConfiguration(source);\n        }\n\n        @Override\n        public VpnConfiguration[] newArray(int size) {\n            return new VpnConfiguration[size];\n        }\n    };\n}\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_close_24dp.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FFFFFFFF\"\n        android:pathData=\"M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_report_problem_24dp.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FFFFFFFF\"\n        android:pathData=\"M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_usb_24dp.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FFFFFFFF\"\n        android:pathData=\"M15,7v4h1v2h-3V5h2l-3,-4 -3,4h2v8H8v-2.07c0.7,-0.37 1.2,-1.08 1.2,-1.93 0,-1.21 -0.99,-2.2 -2.2,-2.2 -1.21,0 -2.2,0.99 -2.2,2.2 0,0.85 0.5,1.56 1.2,1.93V13c0,1.11 0.89,2 2,2h3v3.05c-0.71,0.37 -1.2,1.1 -1.2,1.95 0,1.22 0.99,2.2 2.2,2.2 1.21,0 2.2,-0.98 2.2,-2.2 0,-0.85 -0.49,-1.58 -1.2,-1.95V15h3c1.11,0 2,-0.89 2,-2v-2h1V7h-4z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\" translatable=\"false\">Gnirehtet</string>\n    <string name=\"relay_connected\">Reverse tethering enabled</string>\n    <string name=\"relay_disconnected\">Disconnected from the relay server</string>\n    <string name=\"stop_vpn\">Stop Gnirehtet</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\">\n        <!-- Customize your theme here. -->\n    </style>\n\n    <style name=\"Theme.Transparent\" parent=\"android:Theme\">\n      <item name=\"android:windowIsTranslucent\">true</item>\n      <item name=\"android:windowBackground\">@android:color/transparent</item>\n      <item name=\"android:windowContentOverlay\">@null</item>\n      <item name=\"android:windowNoTitle\">true</item>\n      <item name=\"android:windowIsFloating\">true</item>\n      <item name=\"android:backgroundDimEnabled\">false</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"relay_connected\">Reverse tethering activé</string>\n    <string name=\"relay_disconnected\">Déconnecté du serveur relais</string>\n    <string name=\"stop_vpn\">Arrêter Gnirehtet</string>\n</resources>"
  },
  {
    "path": "app/src/test/java/com/genymobile/gnirehtet/TestIPPacketOutputSteam.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.util.Arrays;\n\n@SuppressWarnings(\"checkstyle:MagicNumber\")\npublic class TestIPPacketOutputSteam {\n\n    private ByteBuffer createMockPacket() {\n        ByteBuffer buffer = ByteBuffer.allocate(32);\n        writeMockPacketTo(buffer);\n        buffer.flip();\n        return buffer;\n    }\n\n    private void writeMockPacketTo(ByteBuffer buffer) {\n        buffer.put((byte) ((4 << 4) | 5)); // versionAndIHL\n        buffer.put((byte) 0); // ToS\n        buffer.putShort((short) 32); // total length 20 + 8 + 4\n        buffer.putInt(0); // IdFlagsFragmentOffset\n        buffer.put((byte) 0); // TTL\n        buffer.put((byte) 17); // protocol (UDP)\n        buffer.putShort((short) 0); // checksum\n        buffer.putInt(0x12345678); // source address\n        buffer.putInt(0x42424242); // destination address\n\n        buffer.putShort((short) 1234); // source port\n        buffer.putShort((short) 5678); // destination port\n        buffer.putShort((short) 12); // length\n        buffer.putShort((short) 0); // checksum\n\n        buffer.putInt(0x11223344); // payload\n    }\n\n    @Test\n    public void testSimplePacket() throws IOException {\n        ByteArrayOutputStream bos = new ByteArrayOutputStream();\n        IPPacketOutputStream pos = new IPPacketOutputStream(bos);\n\n        byte[] rawPacket = createMockPacket().array();\n\n        pos.write(rawPacket, 0, 14);\n        Assert.assertEquals(\"Partial packet should not be written\", 0, bos.size());\n\n        pos.write(rawPacket, 14, 14);\n        Assert.assertEquals(\"Partial packet should not be written\", 0, bos.size());\n\n        pos.write(rawPacket, 28, 4);\n        Assert.assertEquals(\"Complete packet should be written\", 32, bos.size());\n\n        byte[] result = bos.toByteArray();\n        Assert.assertTrue(\"Resulting array must be identical\", Arrays.equals(rawPacket, result));\n    }\n\n    @Test\n    public void testSeveralPacketsAtOnce() throws IOException {\n        class CapturingOutputStream extends ByteArrayOutputStream {\n            private int packetCount;\n\n            @Override\n            public void write(byte[] b, int off, int len) {\n                super.write(b, off, len);\n                ++packetCount;\n            }\n        }\n        CapturingOutputStream cos = new CapturingOutputStream();\n        IPPacketOutputStream pos = new IPPacketOutputStream(cos);\n\n        ByteBuffer buffer = ByteBuffer.allocate(3 * 32);\n        for (int i = 0; i < 3; ++i) {\n            writeMockPacketTo(buffer);\n        }\n        byte[] rawPackets = buffer.array();\n\n        pos.write(rawPackets, 0, 70); // 2 packets + 6 bytes\n        Assert.assertEquals(\"Exactly 2 packets should have been written\", 64, cos.size());\n        Assert.assertEquals(\"Packets should be written individually to the target\", 2, cos.packetCount);\n\n        pos.write(rawPackets, 70, 26);\n        Assert.assertEquals(\"Exactly 3 packets should have been written\", 96, cos.size());\n        Assert.assertEquals(\"Packets should be written individually to the target\", 3, cos.packetCount);\n    }\n}\n"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\next {\n    compileSdkVersion = 28\n    buildToolsVersion = \"28.0.3\"\n}\n\nbuildscript {\n    repositories {\n        jcenter()\n        google()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:3.5.0'\n\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    repositories {\n        jcenter()\n        google()\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n\ntask debugJava(dependsOn: [':app:assembleDebug', ':relay-java:assembleDebug'])\ntask releaseJava(dependsOn: [':app:assembleRelease', ':relay-java:assembleRelease'])\n\ntask debugRust(dependsOn: [':app:assembleDebug', ':relay-rust:debug'])\ntask releaseRust(dependsOn: [':app:assembleRelease', ':relay-rust:release'])\n\ntask releaseRustWindows(dependsOn: [':app:assembleRelease', 'relay-rust:releaseCrossToWindows'])\n\ntask debugAll(dependsOn: ['debugJava', 'debugRust'])\ntask releaseAll(dependsOn: ['releaseJava', 'releaseRust'])\n\ntask checkJava(dependsOn: [':app:check', ':relay-java:check'])\ntask checkRust(dependsOn: ['app:check', ':relay-rust:check'])\ntask checkAll(dependsOn: ['checkJava', 'checkRust'])\n"
  },
  {
    "path": "config/android-checkstyle.gradle",
    "content": "apply plugin: 'checkstyle'\ncheck.dependsOn 'checkstyle'\n\ncheckstyle {\n    toolVersion = '6.19'\n}\n\ntask checkstyle(type: Checkstyle) {\n    description = \"Check Java style with Checkstyle\"\n    configFile = rootProject.file(\"config/checkstyle/checkstyle.xml\")\n    source = javaSources()\n    classpath = files()\n    ignoreFailures = true\n}\n\ndef javaSources() {\n    def files = []\n    android.sourceSets.each { sourceSet ->\n        sourceSet.java.each { javaSource ->\n            javaSource.getSrcDirs().each {\n                if (it.exists()) {\n                    files.add(it)\n                }\n            }\n        }\n    }\n    return files\n}\n"
  },
  {
    "path": "config/android-signing.gradle",
    "content": "if (project.hasProperty(\"RELEASE_STORE_FILE\")) {\n    android.signingConfigs {\n        release {\n            // to be defined in gradle.properties\n            storeFile file(RELEASE_STORE_FILE)\n            storePassword RELEASE_STORE_PASSWORD\n            keyAlias RELEASE_KEY_ALIAS\n            keyPassword RELEASE_KEY_PASSWORD\n        }\n    }\n    android.buildTypes.release.signingConfig = android.signingConfigs.release\n}\n\n"
  },
  {
    "path": "config/checkstyle/checkstyle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE module PUBLIC\n  \"-//Puppy Crawl//DTD Check Configuration 1.3//EN\"\n  \"http://www.puppycrawl.com/dtds/configuration_1_3.dtd\">\n\n<!-- This is a checkstyle configuration file. For descriptions of\nwhat the following rules do, please see the checkstyle configuration\npage at http://checkstyle.sourceforge.net/config.html -->\n\n<module name=\"Checker\">\n\n  <!-- Checks whether files end with a new line.                        -->\n  <!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile -->\n  <module name=\"NewlineAtEndOfFile\" />\n\n  <!-- Checks that property files contain the same keys.         -->\n  <!-- See http://checkstyle.sf.net/config_misc.html#Translation -->\n  <module name=\"Translation\" />\n\n  <!-- Checks for Size Violations.                    -->\n  <!-- See http://checkstyle.sf.net/config_sizes.html -->\n  <module name=\"FileLength\" />\n\n  <!-- Checks for whitespace                               -->\n  <!-- See http://checkstyle.sf.net/config_whitespace.html -->\n  <module name=\"FileTabCharacter\" />\n\n  <!-- Miscellaneous other checks.                   -->\n  <!-- See http://checkstyle.sf.net/config_misc.html -->\n  <module name=\"RegexpSingleline\">\n    <property name=\"format\" value=\"\\s+$\" />\n    <property name=\"minimum\" value=\"0\" />\n    <property name=\"maximum\" value=\"0\" />\n    <property name=\"message\" value=\"Line has trailing spaces.\" />\n    <property name=\"severity\" value=\"info\" />\n  </module>\n\n  <module name=\"SuppressWarningsFilter\"/>\n\n  <module name=\"TreeWalker\">\n\n    <!-- Checks for Naming Conventions.                  -->\n    <!-- See http://checkstyle.sf.net/config_naming.html -->\n    <module name=\"ConstantName\" />\n    <module name=\"LocalFinalVariableName\" />\n    <module name=\"LocalVariableName\" />\n    <module name=\"MemberName\" />\n    <module name=\"MethodName\" />\n    <module name=\"PackageName\" />\n    <module name=\"ParameterName\" />\n    <module name=\"StaticVariableName\" />\n    <module name=\"TypeName\" />\n\n    <module name=\"SuppressWarningsHolder\"/>\n\n    <!-- Checks for imports                              -->\n    <!-- See http://checkstyle.sf.net/config_import.html -->\n    <module name=\"AvoidStarImport\">\n      <property name=\"allowStaticMemberImports\" value=\"true\" />\n    </module>\n    <module name=\"IllegalImport\" />\n    <!-- defaults to sun.* packages -->\n    <module name=\"RedundantImport\" />\n    <module name=\"UnusedImports\" />\n    <module name=\"CustomImportOrder\">\n        <property name=\"thirdPartyPackageRegExp\" value=\".*\"/>\n        <property name=\"specialImportsRegExp\" value=\"com.genymobile\"/>\n        <property name=\"separateLineBetweenGroups\" value=\"true\"/>\n        <property name=\"customImportOrderRules\" value=\"SPECIAL_IMPORTS###THIRD_PARTY_PACKAGE###STANDARD_JAVA_PACKAGE###STATIC\"/>\n    </module>\n\n\n    <!-- Checks for Size Violations.                    -->\n    <!-- See http://checkstyle.sf.net/config_sizes.html -->\n    <module name=\"LineLength\">\n      <!-- what is a good max value? -->\n      <property name=\"max\" value=\"150\" />\n      <!-- ignore lines like \"$File: //depot/... $\" -->\n      <property name=\"ignorePattern\" value=\"\\$File.*\\$\" />\n      <property name=\"severity\" value=\"info\" />\n    </module>\n    <module name=\"MethodLength\" />\n    <module name=\"ParameterNumber\">\n      <property name=\"ignoreOverriddenMethods\" value=\"true\"/>\n    </module>\n\n\n    <!-- Checks for whitespace                               -->\n    <!-- See http://checkstyle.sf.net/config_whitespace.html -->\n    <module name=\"EmptyForIteratorPad\" />\n    <module name=\"GenericWhitespace\" />\n    <module name=\"MethodParamPad\" />\n    <module name=\"NoWhitespaceAfter\" />\n    <module name=\"NoWhitespaceBefore\" />\n    <module name=\"OperatorWrap\" />\n    <module name=\"ParenPad\" />\n    <module name=\"TypecastParenPad\" />\n    <module name=\"WhitespaceAfter\" />\n    <module name=\"WhitespaceAround\" />\n\n    <!-- Modifier Checks                                    -->\n    <!-- See http://checkstyle.sf.net/config_modifiers.html -->\n    <module name=\"ModifierOrder\" />\n    <module name=\"RedundantModifier\" />\n\n\n    <!-- Checks for blocks. You know, those {}'s         -->\n    <!-- See http://checkstyle.sf.net/config_blocks.html -->\n    <module name=\"AvoidNestedBlocks\" />\n    <module name=\"EmptyBlock\">\n      <property name=\"option\" value=\"text\" />\n    </module>\n    <module name=\"LeftCurly\" />\n    <module name=\"NeedBraces\" />\n    <module name=\"RightCurly\" />\n\n\n    <!-- Checks for common coding problems               -->\n    <!-- See http://checkstyle.sf.net/config_coding.html -->\n    <!-- <module name=\"AvoidInlineConditionals\"/> -->\n    <module name=\"EmptyStatement\" />\n    <module name=\"EqualsHashCode\" />\n    <module name=\"HiddenField\">\n      <property name=\"tokens\" value=\"VARIABLE_DEF\" />\n      <!-- only check variables not parameters -->\n      <property name=\"ignoreConstructorParameter\" value=\"true\" />\n      <property name=\"ignoreSetter\" value=\"true\" />\n      <property name=\"severity\" value=\"warning\" />\n    </module>\n    <module name=\"IllegalInstantiation\" />\n    <module name=\"InnerAssignment\" />\n    <module name=\"MagicNumber\">\n      <property name=\"severity\" value=\"info\" />\n      <property name=\"ignoreHashCodeMethod\" value=\"true\" />\n      <property name=\"ignoreAnnotation\" value=\"true\" />\n    </module>\n    <module name=\"MissingSwitchDefault\" />\n    <module name=\"SimplifyBooleanExpression\" />\n    <module name=\"SimplifyBooleanReturn\" />\n\n    <!-- Checks for class design                         -->\n    <!-- See http://checkstyle.sf.net/config_design.html -->\n    <!-- <module name=\"DesignForExtension\"/> -->\n    <module name=\"FinalClass\" />\n    <module name=\"HideUtilityClassConstructor\" />\n    <module name=\"InterfaceIsType\" />\n    <module name=\"VisibilityModifier\" />\n\n\n    <!-- Miscellaneous other checks.                   -->\n    <!-- See http://checkstyle.sf.net/config_misc.html -->\n    <module name=\"ArrayTypeStyle\" />\n    <!-- <module name=\"FinalParameters\"/> -->\n    <module name=\"TodoComment\">\n      <property name=\"format\" value=\"TODO\" />\n      <property name=\"severity\" value=\"info\" />\n    </module>\n    <module name=\"UpperEll\" />\n\n    <module name=\"FileContentsHolder\" />\n    <!-- Required by comment suppression filters -->\n\n  </module>\n\n  <module name=\"SuppressionFilter\">\n    <!--<property name=\"file\" value=\"team-props/checkstyle/checkstyle-suppressions.xml\" />-->\n  </module>\n\n  <!-- Enable suppression comments -->\n  <module name=\"SuppressionCommentFilter\">\n    <property name=\"offCommentFormat\" value=\"CHECKSTYLE IGNORE\\s+(\\S+)\" />\n    <property name=\"onCommentFormat\" value=\"CHECKSTYLE END IGNORE\\s+(\\S+)\" />\n    <property name=\"checkFormat\" value=\"$1\" />\n  </module>\n  <module name=\"SuppressWithNearbyCommentFilter\">\n    <!-- Syntax is \"SUPPRESS CHECKSTYLE name\" -->\n    <property name=\"commentFormat\" value=\"SUPPRESS CHECKSTYLE (\\w+)\" />\n    <property name=\"checkFormat\" value=\"$1\" />\n    <property name=\"influenceFormat\" value=\"1\" />\n  </module>\n\n</module>\n"
  },
  {
    "path": "config/java-checkstyle.gradle",
    "content": "apply plugin: 'checkstyle'\n\ncheckstyle {\n    toolVersion = '6.19'\n    configFile = rootProject.file(\"config/checkstyle/checkstyle.xml\")\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Sat Sep 07 21:43:49 CEST 2019\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-5.4.1-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windowz variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "relay-java/build.gradle",
    "content": "apply plugin: 'application'\n\nmainClassName = 'com.genymobile.gnirehtet.Main'\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n    testCompile 'junit:junit:4.12'\n}\n\njar {\n    manifest {\n        attributes(\n                'Main-Class': mainClassName\n        )\n    }\n    baseName 'gnirehtet'\n}\n\ntask assembleDebug(dependsOn: 'jar')\ntask assembleRelease(dependsOn: ['build', 'jar'])\n\napply from: \"$project.rootDir/config/java-checkstyle.gradle\"\n\ntest {\n    // to log using System.out.println(…) in tests\n    testLogging.showStandardStreams = true\n}\n"
  },
  {
    "path": "relay-java/scripts/gnirehtet",
    "content": "#!/bin/bash\njava -jar gnirehtet.jar \"$@\"\n"
  },
  {
    "path": "relay-java/scripts/gnirehtet-run.cmd",
    "content": "@java -jar gnirehtet.jar run\r\n@pause\r\n"
  },
  {
    "path": "relay-java/scripts/gnirehtet.cmd",
    "content": "@java -jar gnirehtet.jar %*\r\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/AdbMonitor.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet;\n\nimport com.genymobile.gnirehtet.relay.Log;\n\nimport java.io.EOFException;\nimport java.io.IOException;\nimport java.net.Inet4Address;\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.ByteChannel;\nimport java.nio.channels.ReadableByteChannel;\nimport java.nio.channels.SocketChannel;\nimport java.nio.channels.WritableByteChannel;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class AdbMonitor {\n\n    public interface AdbDevicesCallback {\n        void onNewDeviceConnected(String serial);\n    }\n\n    private static final String TAG = AdbMonitor.class.getSimpleName();\n    private static final int ADBD_PORT = 5037;\n\n    private static final String TRACK_DEVICES_REQUEST = \"0012host:track-devices\";\n    private static final int BUFFER_SIZE = 1024;\n    private static final int LENGTH_FIELD_SIZE = 4;\n    private static final int OKAY_SIZE = 4;\n    private static final long RETRY_DELAY_ADB_DAEMON_OK = 1000;\n    private static final long RETRY_DELAY_ADB_DAEMON_KO = 5000;\n\n    private List<String> connectedDevices = new ArrayList<>();\n\n    private AdbDevicesCallback callback;\n\n    private static final byte[] BUFFER = new byte[BUFFER_SIZE]; // used only locally to avoid allocations, so static is ok\n    private final ByteBuffer socketBuffer = ByteBuffer.allocate(BUFFER_SIZE);\n\n    public AdbMonitor(AdbDevicesCallback callback) {\n        this.callback = callback;\n    }\n\n    public void monitor() {\n        while (true) {\n            try {\n                trackDevices();\n            } catch (Exception e) {\n                Log.e(TAG, \"Failed to monitor adb devices\", e);\n                repairAdbDaemon();\n            }\n        }\n    }\n\n    private void trackDevices() throws IOException {\n        SocketChannel socketChannel = SocketChannel.open();\n        try {\n            socketChannel.connect(new InetSocketAddress(Inet4Address.getLoopbackAddress(), ADBD_PORT));\n            trackDevicesOnChannel(socketChannel);\n        } finally {\n            socketChannel.close();\n        }\n    }\n\n    private void trackDevicesOnChannel(ByteChannel channel) throws IOException {\n        socketBuffer.clear();\n        writeRequest(channel, TRACK_DEVICES_REQUEST);\n        // the daemon initially sends \"OKAY\" if it understands the request\n        if (!consumeOkay(channel)) {\n            return;\n        }\n        while (true) {\n            String packet = nextPacket(channel);\n            handlePacket(packet);\n        }\n    }\n\n    private static void writeRequest(WritableByteChannel channel, String request) throws IOException {\n        ByteBuffer requestBuffer = ByteBuffer.wrap(request.getBytes(StandardCharsets.US_ASCII));\n        channel.write(requestBuffer);\n    }\n\n    private boolean consumeOkay(ReadableByteChannel channel) throws IOException {\n        while (channel.read(socketBuffer) != -1) {\n            if (socketBuffer.position() < OKAY_SIZE) {\n                // not enough data\n                continue;\n            }\n            socketBuffer.flip();\n            socketBuffer.get(BUFFER, 0, OKAY_SIZE);\n            socketBuffer.compact();\n            socketBuffer.flip();\n            String text = new String(BUFFER, 0, OKAY_SIZE, StandardCharsets.US_ASCII);\n            return \"OKAY\".equals(text);\n        }\n        return false;\n    }\n\n    private String nextPacket(ReadableByteChannel channel) throws IOException {\n        String packet;\n        while ((packet = readPacket(socketBuffer)) == null) {\n            // need more data\n            fillBufferFrom(channel);\n        }\n        return packet;\n    }\n\n    private void fillBufferFrom(ReadableByteChannel channel) throws IOException {\n        socketBuffer.compact();\n        int r;\n        if (channel.read(socketBuffer) == -1) {\n            throw new EOFException(\"ADB daemon closed the track-devices connexion\");\n        }\n        socketBuffer.flip();\n    }\n\n    static String readPacket(ByteBuffer input) {\n        if (input.remaining() < LENGTH_FIELD_SIZE) {\n            return null;\n        }\n        // each packet contains 4 bytes representing the String length in hexa, followed by a list of device states, one per line;\n        // each line contains: the device serial, `\\t', the state, '\\n'\n        // for example: \"00360123456789abcdef\\tdevice\\nfedcba9876543210\\tunauthorized\\n\":\n        //  - 0036 indicates that the data is 0x36 (54) bytes length\n        //  - the device with serial 0123456789abcdef is connected\n        //  - the device with serial fedcba9876543210 is unauthorized\n        input.get(BUFFER, 0, LENGTH_FIELD_SIZE);\n        int length = parseLength(BUFFER);\n        if (length > BUFFER.length) {\n            throw new IllegalArgumentException(\"Packet size should not be that big: \" + length);\n        }\n        if (input.remaining() < length) {\n            // not enough data\n            input.rewind();\n            return null;\n        }\n        input.get(BUFFER, 0, length);\n        return new String(BUFFER, 0, length, StandardCharsets.UTF_8);\n    }\n\n    void handlePacket(String packet) {\n        List<String> currentConnectedDevices = parseConnectedDevices(packet);\n        for (String serial : currentConnectedDevices) {\n            if (!connectedDevices.contains(serial)) {\n                callback.onNewDeviceConnected(serial);\n            }\n        }\n        connectedDevices = currentConnectedDevices;\n    }\n\n    private static List<String> parseConnectedDevices(String packet) {\n        List<String> list = new ArrayList<>();\n        for (String line : packet.split(\"\\\\n\")) {\n            String[] tokens = line.split(\"\\\\s+\");\n            if (tokens.length == 2) {\n                String state = tokens[1];\n                if (\"device\".equals(state)) {\n                    String serial = tokens[0];\n                    list.add(serial);\n                }\n            }\n        }\n        return list;\n    }\n\n    @SuppressWarnings(\"checkstyle:MagicNumber\")\n    private static int parseLength(byte[] data) {\n        if (data.length < LENGTH_FIELD_SIZE) {\n            throw new IllegalArgumentException(\"Length field must be at least 4 bytes length\");\n        }\n        int result = 0;\n        for (int i = 0; i < LENGTH_FIELD_SIZE; ++i) {\n            char c = (char) data[i];\n            result = (result << 4) + Character.digit(c, 0x10);\n        }\n        return result;\n    }\n\n    private static void repairAdbDaemon() {\n        if (startAdbDaemon()) {\n            sleep(RETRY_DELAY_ADB_DAEMON_OK);\n        } else {\n            sleep(RETRY_DELAY_ADB_DAEMON_KO);\n        }\n    }\n\n    private static boolean startAdbDaemon() {\n        Log.i(TAG, \"Restarting adb deamon\");\n        try {\n            Process process = new ProcessBuilder(\"adb\", \"start-server\")\n                    .redirectOutput(ProcessBuilder.Redirect.INHERIT)\n                    .redirectError(ProcessBuilder.Redirect.INHERIT).start();\n            int exitCode = process.waitFor();\n            if (exitCode != 0) {\n                Log.e(TAG, \"Could not restart adb daemon (exited on error)\");\n                return false;\n            }\n            return true;\n        } catch (InterruptedException | IOException e) {\n            Log.e(TAG, \"Could not restart adb daemon\", e);\n            return false;\n        }\n    }\n\n    private static void sleep(long delay) {\n        try {\n            Thread.sleep(delay);\n        } catch (InterruptedException e) {\n            // should never happen\n        }\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/CommandLineArguments.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet;\n\n/**\n * Simple specific command-line arguments parser.\n */\n@SuppressWarnings(\"checkstyle:MagicNumber\")\npublic class CommandLineArguments {\n\n    public static final int PARAM_NONE = 0;\n    public static final int PARAM_SERIAL = 1;\n    public static final int PARAM_DNS_SERVER = 1 << 1;\n    public static final int PARAM_ROUTES = 1 << 2;\n    public static final int PARAM_PORT = 1 << 3;\n\n    public static final int DEFAULT_PORT = 31416;\n\n    private int port;\n    private String serial;\n    private String dnsServers;\n    private String routes;\n\n    public static CommandLineArguments parse(int acceptedParameters, String... args) {\n        CommandLineArguments arguments = new CommandLineArguments();\n        for (int i = 0; i < args.length; ++i) {\n            String arg = args[i];\n            if ((acceptedParameters & PARAM_DNS_SERVER) != 0 && \"-d\".equals(arg)) {\n                if (arguments.dnsServers != null) {\n                    throw new IllegalArgumentException(\"DNS servers already set\");\n                }\n                if (i == args.length - 1) {\n                    throw new IllegalArgumentException(\"Missing -d parameter\");\n                }\n                arguments.dnsServers = args[i + 1];\n                ++i; // consume the -d parameter\n            } else if ((acceptedParameters & PARAM_ROUTES) != 0 && \"-r\".equals(arg)) {\n                if (arguments.routes != null) {\n                    throw new IllegalArgumentException(\"Routes already set\");\n                }\n                if (i == args.length - 1) {\n                    throw new IllegalArgumentException(\"Missing -r parameter\");\n                }\n                arguments.routes = args[i + 1];\n                ++i; // consume the -r parameter\n            } else if ((acceptedParameters & PARAM_PORT) != 0 && \"-p\".equals(arg)) {\n                if (arguments.port != 0) {\n                    throw new IllegalArgumentException(\"Port already set\");\n                }\n                if (i == args.length - 1) {\n                    throw new IllegalArgumentException(\"Missing -p parameter\");\n                }\n                arguments.port = Integer.parseInt(args[i + 1]);\n                if (arguments.port <= 0 || arguments.port >= 65536) {\n                    throw new IllegalArgumentException(\"Invalid port: \" + arguments.port);\n                }\n                ++i;\n            } else if ((acceptedParameters & PARAM_SERIAL) != 0 && arguments.serial == null) {\n                arguments.serial = arg;\n            } else {\n                throw new IllegalArgumentException(\"Unexpected argument: \\\"\" + arg + \"\\\"\");\n            }\n        }\n        if (arguments.port == 0) {\n            arguments.port = DEFAULT_PORT;\n        }\n        return arguments;\n    }\n\n    public String getSerial() {\n        return serial;\n    }\n\n    public String getDnsServers() {\n        return dnsServers;\n    }\n\n    public String getRoutes() {\n        return routes;\n    }\n\n    public int getPort() {\n        return port;\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/Main.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet;\n\nimport com.genymobile.gnirehtet.relay.CommandExecutionException;\nimport com.genymobile.gnirehtet.relay.Log;\nimport com.genymobile.gnirehtet.relay.Relay;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Scanner;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic final class Main {\n    private static final String TAG = \"Gnirehtet\";\n    private static final String NL = System.lineSeparator();\n    private static final String REQUIRED_APK_VERSION_CODE = \"9\";\n\n    private Main() {\n        // not instantiable\n    }\n\n    private static String getAdbPath() {\n        String adb = System.getenv(\"ADB\");\n        return adb != null ? adb : \"adb\";\n    }\n\n    private static String getApkPath() {\n        String apk = System.getenv(\"GNIREHTET_APK\");\n        return apk != null ? apk : \"gnirehtet.apk\";\n    }\n\n    enum Command {\n        INSTALL(\"install\", CommandLineArguments.PARAM_SERIAL) {\n            @Override\n            String getDescription() {\n                return \"Install the client on the Android device and exit.\\n\"\n                        + \"If several devices are connected via adb, then serial must be\\n\"\n                        + \"specified.\";\n            }\n\n            @Override\n            void execute(CommandLineArguments args) throws Exception {\n                cmdInstall(args.getSerial());\n            }\n        },\n        UNINSTALL(\"uninstall\", CommandLineArguments.PARAM_SERIAL) {\n            @Override\n            String getDescription() {\n                return \"Uninstall the client from the Android device and exit.\\n\"\n                        + \"If several devices are connected via adb, then serial must be\\n\"\n                        + \"specified.\";\n            }\n\n            @Override\n            void execute(CommandLineArguments args) throws Exception {\n                cmdUninstall(args.getSerial());\n            }\n        },\n        REINSTALL(\"reinstall\", CommandLineArguments.PARAM_SERIAL) {\n            @Override\n            String getDescription() {\n                return \"Uninstall then install.\";\n            }\n\n            @Override\n            void execute(CommandLineArguments args) throws Exception {\n                cmdReinstall(args.getSerial());\n            }\n        },\n        RUN(\"run\", CommandLineArguments.PARAM_SERIAL | CommandLineArguments.PARAM_DNS_SERVER | CommandLineArguments.PARAM_ROUTES\n                | CommandLineArguments.PARAM_PORT) {\n            @Override\n            String getDescription() {\n                return \"Enable reverse tethering for exactly one device:\\n\"\n                        + \"  - install the client if necessary;\\n\"\n                        + \"  - start the client;\\n\"\n                        + \"  - start the relay server;\\n\"\n                        + \"  - on Ctrl+C, stop both the relay server and the client.\";\n            }\n\n            @Override\n            void execute(CommandLineArguments args) throws Exception {\n                cmdRun(args.getSerial(), args.getDnsServers(), args.getRoutes(), args.getPort());\n            }\n        },\n        AUTORUN(\"autorun\", CommandLineArguments.PARAM_DNS_SERVER | CommandLineArguments.PARAM_ROUTES | CommandLineArguments.PARAM_PORT) {\n            @Override\n            String getDescription() {\n                return \"Enable reverse tethering for all devices:\\n\"\n                        + \"  - monitor devices and start clients (autostart);\\n\"\n                        + \"  - start the relay server.\";\n            }\n\n            @Override\n            void execute(CommandLineArguments args) throws Exception {\n                cmdAutorun(args.getDnsServers(), args.getRoutes(), args.getPort());\n            }\n        },\n        START(\"start\", CommandLineArguments.PARAM_SERIAL | CommandLineArguments.PARAM_DNS_SERVER | CommandLineArguments.PARAM_ROUTES\n                | CommandLineArguments.PARAM_PORT) {\n            @Override\n            String getDescription() {\n                return \"Start a client on the Android device and exit.\\n\"\n                        + \"If several devices are connected via adb, then serial must be\\n\"\n                        + \"specified.\\n\"\n                        + \"If -d is given, then make the Android device use the specified\\n\"\n                        + \"DNS server(s). Otherwise, use 8.8.8.8 (Google public DNS).\\n\"\n                        + \"If -r is given, then only reverse tether the specified routes.\\n\"\n                        + \"If -p is given, then make the relay server listen on the specified\\n\"\n                        + \"port. Otherwise, use port 31416.\\n\"\n                        + \"Otherwise, use 0.0.0.0/0 (redirect the whole traffic).\\n\"\n                        + \"If the client is already started, then do nothing, and ignore\\n\"\n                        + \"the other parameters.\\n\"\n                        + \"10.0.2.2 is mapped to the host 'localhost'.\";\n            }\n\n            @Override\n            void execute(CommandLineArguments args) throws Exception {\n                cmdStart(args.getSerial(), args.getDnsServers(), args.getRoutes(), args.getPort());\n            }\n        },\n        AUTOSTART(\"autostart\", CommandLineArguments.PARAM_DNS_SERVER | CommandLineArguments.PARAM_ROUTES | CommandLineArguments.PARAM_PORT) {\n            @Override\n            String getDescription() {\n                return \"Listen for device connexions and start a client on every detected\\n\"\n                        + \"device.\\n\"\n                        + \"Accept the same parameters as the start command (excluding the\\n\"\n                        + \"serial, which will be taken from the detected device).\";\n            }\n\n            @Override\n            void execute(CommandLineArguments args) throws Exception {\n                cmdAutostart(args.getDnsServers(), args.getRoutes(), args.getPort());\n            }\n        },\n        STOP(\"stop\", CommandLineArguments.PARAM_SERIAL) {\n            @Override\n            String getDescription() {\n                return \"Stop the client on the Android device and exit.\\n\"\n                        + \"If several devices are connected via adb, then serial must be\\n\"\n                        + \"specified.\";\n            }\n\n            @Override\n            void execute(CommandLineArguments args) throws Exception {\n                cmdStop(args.getSerial());\n            }\n        },\n        RESTART(\"restart\", CommandLineArguments.PARAM_SERIAL | CommandLineArguments.PARAM_DNS_SERVER | CommandLineArguments.PARAM_ROUTES\n                | CommandLineArguments.PARAM_PORT) {\n            @Override\n            String getDescription() {\n                return \"Stop then start.\";\n            }\n\n            @Override\n            void execute(CommandLineArguments args) throws Exception {\n                cmdRestart(args.getSerial(), args.getDnsServers(), args.getRoutes(), args.getPort());\n            }\n        },\n        TUNNEL(\"tunnel\", CommandLineArguments.PARAM_SERIAL | CommandLineArguments.PARAM_PORT) {\n            @Override\n            String getDescription() {\n                return \"Set up the 'adb reverse' tunnel.\\n\"\n                        + \"If a device is unplugged then plugged back while gnirehtet is\\n\"\n                        + \"active, resetting the tunnel is sufficient to get the\\n\"\n                        + \"connection back.\";\n            }\n\n            @Override\n            void execute(CommandLineArguments args) throws Exception {\n                cmdTunnel(args.getSerial(), args.getPort());\n            }\n        },\n        RELAY(\"relay\", CommandLineArguments.PARAM_PORT) {\n            @Override\n            String getDescription() {\n                return \"Start the relay server in the current terminal.\";\n            }\n\n            @Override\n            void execute(CommandLineArguments args) throws Exception {\n                cmdRelay(args.getPort());\n            }\n        };\n\n        private String command;\n        private int acceptedParameters;\n\n        Command(String command, int acceptedParameters) {\n            this.command = command;\n            this.acceptedParameters = acceptedParameters;\n        }\n\n        abstract String getDescription();\n\n        abstract void execute(CommandLineArguments args) throws Exception;\n    }\n\n    private static void cmdInstall(String serial) throws InterruptedException, IOException, CommandExecutionException {\n        Log.i(TAG, \"Installing gnirehtet client...\");\n        execAdb(serial, \"install\", \"-r\", getApkPath());\n    }\n\n    private static void cmdUninstall(String serial) throws InterruptedException, IOException, CommandExecutionException {\n        Log.i(TAG, \"Uninstalling gnirehtet client...\");\n        execAdb(serial, \"uninstall\", \"com.genymobile.gnirehtet\");\n    }\n\n    private static void cmdReinstall(String serial) throws InterruptedException, IOException, CommandExecutionException {\n        cmdUninstall(serial);\n        cmdInstall(serial);\n    }\n\n    private static void cmdRun(String serial, String dnsServers, String routes, int port) throws IOException {\n        // start in parallel so that the relay server is ready when the client connects\n        asyncStart(serial, dnsServers, routes, port);\n\n        Runtime.getRuntime().addShutdownHook(new Thread(() -> {\n            // executed on Ctrl+C\n            try {\n                cmdStop(serial);\n            } catch (Exception e) {\n                Log.e(TAG, \"Cannot stop client\", e);\n            }\n        }));\n\n        cmdRelay(port);\n    }\n\n    private static void cmdAutorun(final String dnsServers, final String routes, int port) throws IOException {\n        new Thread(() -> {\n            try {\n                cmdAutostart(dnsServers, routes, port);\n            } catch (Exception e) {\n                Log.e(TAG, \"Cannot auto start clients\", e);\n            }\n        }).start();\n\n        cmdRelay(port);\n    }\n\n    @SuppressWarnings(\"checkstyle:MagicNumber\")\n    private static void cmdStart(String serial, String dnsServers, String routes, int port) throws InterruptedException, IOException,\n            CommandExecutionException {\n        if (mustInstallClient(serial)) {\n            cmdInstall(serial);\n            // wait a bit after the app is installed so that intent actions are correctly registered\n            Thread.sleep(500); // ms\n        }\n\n        Log.i(TAG, \"Starting client...\");\n        cmdTunnel(serial, port);\n\n        List<String> cmd = new ArrayList<>();\n        Collections.addAll(cmd, \"shell\", \"am\", \"start\", \"-a\", \"com.genymobile.gnirehtet.START\", \"-n\",\n                \"com.genymobile.gnirehtet/.GnirehtetActivity\");\n        if (dnsServers != null) {\n            Collections.addAll(cmd, \"--esa\", \"dnsServers\", dnsServers);\n        }\n        if (routes != null) {\n            Collections.addAll(cmd, \"--esa\", \"routes\", routes);\n        }\n        execAdb(serial, cmd);\n    }\n\n    private static void cmdAutostart(final String dnsServers, final String routes, int port) {\n        AdbMonitor adbMonitor = new AdbMonitor((serial) -> {\n            asyncStart(serial, dnsServers, routes, port);\n        });\n        adbMonitor.monitor();\n    }\n\n    private static void cmdStop(String serial) throws InterruptedException, IOException, CommandExecutionException {\n        Log.i(TAG, \"Stopping client...\");\n        execAdb(serial, \"shell\", \"am\", \"start\", \"-a\", \"com.genymobile.gnirehtet.STOP\", \"-n\",\n                \"com.genymobile.gnirehtet/.GnirehtetActivity\");\n    }\n\n    private static void cmdRestart(String serial, String dnsServers, String routes, int port) throws InterruptedException, IOException,\n            CommandExecutionException {\n        cmdStop(serial);\n        cmdStart(serial, dnsServers, routes, port);\n    }\n\n    private static void cmdTunnel(String serial, int port) throws InterruptedException, IOException, CommandExecutionException {\n        execAdb(serial, \"reverse\", \"localabstract:gnirehtet\", \"tcp:\" + port);\n    }\n\n    private static void cmdRelay(int port) throws IOException {\n        Log.i(TAG, \"Starting relay server on port \" + port + \"...\");\n        new Relay(port).run();\n    }\n\n    private static void asyncStart(String serial, String dnsServers, String routes, int port) {\n        new Thread(() -> {\n            try {\n                cmdStart(serial, dnsServers, routes, port);\n            } catch (Exception e) {\n                Log.e(TAG, \"Cannot start client\", e);\n            }\n        }).start();\n    }\n\n    private static void execAdb(String serial, String... adbArgs) throws InterruptedException, IOException, CommandExecutionException {\n        execSync(createAdbCommand(serial, adbArgs));\n    }\n\n    private static List<String> createAdbCommand(String serial, String... adbArgs) {\n        List<String> command = new ArrayList<>();\n        command.add(getAdbPath());\n        if (serial != null) {\n            command.add(\"-s\");\n            command.add(serial);\n        }\n        Collections.addAll(command, adbArgs);\n        return command;\n    }\n\n    private static void execAdb(String serial, List<String> adbArgList) throws InterruptedException, IOException, CommandExecutionException {\n        String[] adbArgs = adbArgList.toArray(new String[adbArgList.size()]);\n        execAdb(serial, adbArgs);\n    }\n\n    private static void execSync(List<String> command) throws InterruptedException, IOException, CommandExecutionException {\n        Log.d(TAG, \"Execute: \" + command);\n        ProcessBuilder processBuilder = new ProcessBuilder(command);\n        processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT).redirectError(ProcessBuilder.Redirect.INHERIT);\n        Process process = processBuilder.start();\n        int exitCode = process.waitFor();\n        if (exitCode != 0) {\n            throw new CommandExecutionException(command, exitCode);\n        }\n    }\n\n    private static boolean mustInstallClient(String serial) throws InterruptedException, IOException, CommandExecutionException {\n        Log.i(TAG, \"Checking gnirehtet client...\");\n        List<String> command = createAdbCommand(serial, \"shell\", \"dumpsys\", \"package\", \"com.genymobile.gnirehtet\");\n        Log.d(TAG, \"Execute: \" + command);\n        Process process = new ProcessBuilder(command).start();\n        try {\n            Scanner scanner = new Scanner(process.getInputStream());\n            // read the versionCode of the installed package\n            Pattern pattern = Pattern.compile(\"^    versionCode=(\\\\p{Digit}+).*\");\n            while (scanner.hasNextLine()) {\n                Matcher matcher = pattern.matcher(scanner.nextLine());\n                if (matcher.matches()) {\n                    String installedVersionCode = matcher.group(1);\n                    return !REQUIRED_APK_VERSION_CODE.equals(installedVersionCode);\n                }\n            }\n        } finally {\n            int exitCode = process.waitFor();\n            if (exitCode != 0) {\n                // Overwrite any pending exception, the command just failed\n                throw new CommandExecutionException(command, exitCode);\n            }\n        }\n        return true;\n    }\n\n\n    private static void printUsage() {\n        StringBuilder builder = new StringBuilder(\"Syntax: gnirehtet (\");\n        Command[] commands = Command.values();\n        for (int i = 0; i < commands.length; ++i) {\n            if (i != 0) {\n                builder.append('|');\n            }\n            builder.append(commands[i].command);\n        }\n        builder.append(\") ...\").append(NL);\n\n        for (Command command : commands) {\n            builder.append(NL);\n            appendCommandUsage(builder, command);\n        }\n\n        System.err.print(builder.toString());\n    }\n\n    private static void appendCommandUsage(StringBuilder builder, Command command) {\n        builder.append(\"  gnirehtet \").append(command.command);\n        if ((command.acceptedParameters & CommandLineArguments.PARAM_SERIAL) != 0) {\n            builder.append(\" [serial]\");\n        }\n        if ((command.acceptedParameters & CommandLineArguments.PARAM_DNS_SERVER) != 0) {\n            builder.append(\" [-d DNS[,DNS2,...]]\");\n        }\n        if ((command.acceptedParameters & CommandLineArguments.PARAM_PORT) != 0) {\n            builder.append(\" [-p PORT]\");\n        }\n        if ((command.acceptedParameters & CommandLineArguments.PARAM_ROUTES) != 0) {\n            builder.append(\" [-r ROUTE[,ROUTE2,...]]\");\n        }\n        builder.append(NL);\n        String[] descLines = command.getDescription().split(\"\\n\");\n        for (String descLine : descLines) {\n            builder.append(\"      \").append(descLine).append(NL);\n        }\n    }\n\n    private static void printCommandUsage(Command command) {\n        StringBuilder builder = new StringBuilder();\n        appendCommandUsage(builder, command);\n        System.err.print(builder.toString());\n    }\n\n    public static void main(String... args) throws Exception {\n        if (args.length == 0) {\n            printUsage();\n            return;\n        }\n\n        String cmd = args[0];\n        for (Command command : Command.values()) {\n            if (cmd.equals(command.command)) {\n                // forget args[0] containing the command name\n                String[] commandArgs = Arrays.copyOfRange(args, 1, args.length);\n\n                CommandLineArguments arguments;\n                try {\n                    arguments = CommandLineArguments.parse(command.acceptedParameters, commandArgs);\n                } catch (IllegalArgumentException e) {\n                    Log.e(TAG, e.getMessage());\n                    printCommandUsage(command);\n                    return;\n                }\n\n                command.execute(arguments);\n                return;\n            }\n        }\n\n        if (\"rt\".equals(cmd)) {\n            Log.e(TAG, \"The 'rt' command has been renamed to 'run'. Try 'gnirehtet run' instead.\");\n            printCommandUsage(Command.RUN);\n        } else {\n            Log.e(TAG, \"Unknown command: \" + cmd);\n            printUsage();\n        }\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/AbstractConnection.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\n\npublic abstract class AbstractConnection implements Connection {\n\n    private static final int LOCALHOST_FORWARD = 0x0a000202; // 10.0.2.2 must be forwarded to localhost\n\n    private final ConnectionId id;\n    private final Client client;\n\n    protected AbstractConnection(ConnectionId id, Client client) {\n        this.id = id;\n        this.client = client;\n    }\n\n    @Override\n    public ConnectionId getId() {\n        return id;\n    }\n\n    protected void close() {\n        disconnect();\n        client.getRouter().remove(this);\n    }\n\n    protected void consume(PacketSource source) {\n        client.consume(source);\n    }\n\n    protected boolean sendToClient(IPv4Packet packet) {\n        return client.sendToClient(packet);\n    }\n\n    private static InetAddress getRewrittenAddress(int ip) {\n        return ip == LOCALHOST_FORWARD ? InetAddress.getLoopbackAddress() : Net.toInetAddress(ip);\n    }\n\n    /**\n     * Get destination, rewritten to {@code localhost} if it was {@code 10.0.2.2}.\n     *\n     * @return Destination to connect to.\n     */\n    protected InetSocketAddress getRewrittenDestination() {\n        int destIp = id.getDestinationIp();\n        int port = id.getDestinationPort();\n        return new InetSocketAddress(getRewrittenAddress(destIp), port);\n    }\n\n    public void logv(String tag, String message, Throwable e) {\n        Log.v(tag, id + \" \" + message);\n    }\n\n    public void logv(String tag, String message) {\n        logv(tag, message, null);\n    }\n\n    public void logd(String tag, String message, Throwable e) {\n        Log.d(tag, id + \" \" + message);\n    }\n\n    public void logd(String tag, String message) {\n        logd(tag, message, null);\n    }\n\n    public void logi(String tag, String message, Throwable e) {\n        Log.i(tag, id + \" \" + message);\n    }\n\n    public void logi(String tag, String message) {\n        logi(tag, message, null);\n    }\n\n    public void logw(String tag, String message, Throwable e) {\n        Log.w(tag, id + \" \" + message);\n    }\n\n    public void logw(String tag, String message) {\n        logw(tag, message, null);\n    }\n\n    public void loge(String tag, String message, Throwable e) {\n        Log.e(tag, id + \" \" + message);\n    }\n\n    public void loge(String tag, String message) {\n        loge(tag, message, null);\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/Binary.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport java.nio.ByteBuffer;\n\n@SuppressWarnings(\"checkstyle:MagicNumber\")\npublic final class Binary {\n\n    private static final int MAX_STRING_PACKET_SIZE = 20;\n\n    private Binary() {\n        // not instantiable\n    }\n\n    public static String buildPacketString(byte[] data, int offset, int len) {\n        int limit = Math.min(MAX_STRING_PACKET_SIZE, len);\n        StringBuilder builder = new StringBuilder();\n        builder.append('[').append(len).append(\" bytes] \");\n        for (int i = 0; i < limit; ++i) {\n            if (i != 0) {\n                String sep = i % 4 == 0 ? \"  \" : \" \";\n                builder.append(sep);\n            }\n            builder.append(String.format(\"%02X\", data[offset + i] & 0xff));\n        }\n        if (limit < len) {\n            builder.append(\"  ... +\").append(len - limit).append(\" bytes\");\n        }\n        return builder.toString();\n    }\n\n    public static String buildPacketString(ByteBuffer buffer) {\n        return buildPacketString(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());\n    }\n\n    public static ByteBuffer copy(ByteBuffer buffer) {\n        buffer.rewind();\n        ByteBuffer result = ByteBuffer.allocate(buffer.remaining());\n        result.put(buffer);\n        buffer.rewind();\n        result.flip();\n        return result;\n    }\n\n    public static ByteBuffer slice(ByteBuffer buffer, int offset, int length) {\n        // save\n        int position = buffer.position();\n        int limit = buffer.limit();\n\n        // slice\n        buffer.limit(offset + length).position(offset);\n        ByteBuffer result = buffer.slice();\n\n        // restore\n        buffer.limit(limit).position(position);\n\n        return result;\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/Client.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.ClosedChannelException;\nimport java.nio.channels.SelectionKey;\nimport java.nio.channels.Selector;\nimport java.nio.channels.SocketChannel;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\n\npublic class Client {\n\n    private static final String TAG = Client.class.getSimpleName();\n\n    private static int nextId = 0;\n\n    private final int id;\n    private final SocketChannel clientChannel;\n    private final SelectionKey selectionKey;\n    private final CloseListener<Client> closeListener;\n    private int interests;\n\n    private final IPv4PacketBuffer clientToNetwork = new IPv4PacketBuffer();\n    private final StreamBuffer networkToClient = new StreamBuffer(16 * IPv4Packet.MAX_PACKET_LENGTH);\n    private final Router router;\n\n    private final List<PacketSource> pendingPacketSources = new ArrayList<>();\n\n    // store the remaining bytes of \"id\" to send to the client before relaying any data\n    private ByteBuffer pendingIdBuffer;\n\n    public Client(Selector selector, SocketChannel clientChannel, CloseListener<Client> closeListener) throws ClosedChannelException {\n        id = nextId++;\n        this.clientChannel = clientChannel;\n        router = new Router(this, selector);\n        pendingIdBuffer = createIntBuffer(id);\n\n        SelectionHandler selectionHandler = (selectionKey) -> {\n            if (selectionKey.isValid() && selectionKey.isWritable()) {\n                processSend();\n            }\n            if (selectionKey.isValid() && selectionKey.isReadable()) {\n                processReceive();\n            }\n            if (selectionKey.isValid()) {\n                updateInterests();\n            }\n        };\n        // on start, we are interested only in writing (we must first send the client id)\n        interests = SelectionKey.OP_WRITE;\n        selectionKey = clientChannel.register(selector, interests, selectionHandler);\n\n        this.closeListener = closeListener;\n    }\n\n    private static ByteBuffer createIntBuffer(int value) {\n        final int intSize = 4;\n        ByteBuffer buffer = ByteBuffer.allocate(intSize);\n        buffer.putInt(value);\n        buffer.flip();\n        return buffer;\n    }\n\n    public int getId() {\n        return id;\n    }\n\n    public Router getRouter() {\n        return router;\n    }\n\n    private void processReceive() {\n        if (!read()) {\n            close();\n            return;\n        }\n        pushToNetwork();\n    }\n\n    private void processSend() {\n        if (mustSendId()) {\n            if (!sendId()) {\n                close();\n            }\n            return;\n        }\n        if (!write()) {\n            close();\n            return;\n        }\n        processPending();\n    }\n\n    private boolean read() {\n        try {\n            return clientToNetwork.readFrom(clientChannel) != -1;\n        } catch (IOException e) {\n            Log.e(TAG, \"Cannot read\", e);\n            return false;\n        }\n    }\n\n    private boolean write() {\n        try {\n            return networkToClient.writeTo(clientChannel) != -1;\n        } catch (IOException e) {\n            Log.e(TAG, \"Cannot write\", e);\n            return false;\n        }\n    }\n\n    private boolean mustSendId() {\n        return pendingIdBuffer != null && pendingIdBuffer.hasRemaining();\n    }\n\n    private boolean sendId() {\n        assert mustSendId();\n        try {\n            if (clientChannel.write(pendingIdBuffer) == -1) {\n                Log.w(TAG, \"Cannot write client id #\" + id + \" (EOF)\");\n                return false;\n            }\n            if (!pendingIdBuffer.hasRemaining()) {\n                // we don't need this buffer anymore, release it\n                Log.d(TAG, \"Client id #\" + id + \" sent to client\");\n                pendingIdBuffer = null;\n            }\n            return true;\n        } catch (IOException e) {\n            Log.e(TAG, \"Cannot write client id #\" + id, e);\n            return false;\n        }\n    }\n\n    private void pushToNetwork() {\n        IPv4Packet packet;\n        while ((packet = clientToNetwork.asIPv4Packet()) != null) {\n            router.sendToNetwork(packet);\n            clientToNetwork.next();\n        }\n    }\n\n    private void close() {\n        selectionKey.cancel();\n        try {\n            clientChannel.close();\n        } catch (IOException e) {\n            Log.e(TAG, \"Cannot close client connection\", e);\n        }\n        router.clear();\n        closeListener.onClosed(this);\n    }\n\n    private void updateInterests() {\n        int interestOps = SelectionKey.OP_READ; // we always want to read\n        if (!networkToClient.isEmpty()) {\n            interestOps |= SelectionKey.OP_WRITE;\n        }\n        if (interests != interestOps) {\n            // interests must be changed\n            interests = interestOps;\n            selectionKey.interestOps(interestOps);\n        }\n    }\n\n    public boolean sendToClient(IPv4Packet packet) {\n        if (networkToClient.remaining() < packet.getRawLength()) {\n            Log.w(TAG, \"Client buffer full\");\n            return false;\n        }\n        networkToClient.readFrom(packet.getRaw());\n        updateInterests();\n        return true;\n    }\n\n    public void consume(PacketSource source) {\n        IPv4Packet packet = source.get();\n        if (sendToClient(packet)) {\n            source.next();\n            return;\n        }\n        assert !pendingPacketSources.contains(source);\n        pendingPacketSources.add(source);\n    }\n\n    private void processPending() {\n        Iterator<PacketSource> iterator = pendingPacketSources.iterator();\n        while (iterator.hasNext()) {\n            PacketSource packetSource = iterator.next();\n            IPv4Packet packet = packetSource.get();\n            if (sendToClient(packet)) {\n                packetSource.next();\n                Log.d(TAG, \"Pending packet sent to client (\" + packet.getRawLength() + \")\");\n                iterator.remove();\n            } else {\n                Log.w(TAG, \"Pending packet not sent to client (\" + packet.getRawLength() + \"), client buffer full again\");\n                return;\n            }\n        }\n    }\n\n    public void cleanExpiredConnections() {\n        router.cleanExpiredConnections();\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/CloseListener.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\npublic interface CloseListener<T> {\n    void onClosed(T object);\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/CommandExecutionException.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport java.util.List;\n\npublic class CommandExecutionException extends Exception {\n\n    private List<String> command;\n    private int exitCode;\n\n    public CommandExecutionException(List<String> command, int exitCode) {\n        super(createMessage(command, exitCode));\n        this.command = command;\n        this.exitCode = exitCode;\n    }\n\n    private static String createMessage(List<String> command, int exitCode) {\n        return \"Command \" + command + \" returned with value \" + exitCode;\n    }\n\n    public int getExitCode() {\n        return exitCode;\n    }\n\n    public List<String> getCommand() {\n        return command;\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/Connection.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\npublic interface Connection {\n\n    ConnectionId getId();\n    void sendToNetwork(IPv4Packet packet);\n    void disconnect();\n    boolean isExpired();\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/ConnectionId.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\npublic class ConnectionId {\n\n    private final IPv4Header.Protocol protocol;\n    private final int sourceIp;\n    private final short sourcePort;\n    private final int destIp;\n    private final short destPort;\n    private final String idString;\n\n    public ConnectionId(IPv4Header.Protocol protocol, int sourceIp, short sourcePort, int destIp, short destPort) {\n        this.protocol = protocol;\n        this.sourceIp = sourceIp;\n        this.sourcePort = sourcePort;\n        this.destIp = destIp;\n        this.destPort = destPort;\n\n        // compute the String representation only once\n        idString = protocol + \" \" + Net.toString(sourceIp, sourcePort) + \" -> \" + Net.toString(destIp, destPort);\n    }\n\n    public IPv4Header.Protocol getProtocol() {\n        return protocol;\n    }\n\n    public int getSourceIp() {\n        return sourceIp;\n    }\n\n    public int getSourcePort() {\n        return Short.toUnsignedInt(sourcePort);\n    }\n\n    public int getDestinationIp() {\n        return destIp;\n    }\n\n    public int getDestinationPort() {\n        return Short.toUnsignedInt(destPort);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n\n        ConnectionId that = (ConnectionId) o;\n        return sourceIp == that.sourceIp\n                && sourcePort == that.sourcePort\n                && destIp == that.destIp\n                && destPort == that.destPort\n                && protocol == that.protocol;\n    }\n\n    @Override\n    public int hashCode() {\n        int result = protocol.hashCode();\n        result = 31 * result + sourceIp;\n        result = 31 * result + (int) sourcePort;\n        result = 31 * result + destIp;\n        result = 31 * result + (int) destPort;\n        return result;\n    }\n\n    @Override\n    public String toString() {\n        return idString;\n    }\n\n    public static ConnectionId from(IPv4Header ipv4Header, TransportHeader transportHeader) {\n        IPv4Header.Protocol protocol = ipv4Header.getProtocol();\n        int sourceAddress = ipv4Header.getSource();\n        short sourcePort = (short) transportHeader.getSourcePort();\n        int destinationAddress = ipv4Header.getDestination();\n        short destinationPort = (short) transportHeader.getDestinationPort();\n        return new ConnectionId(protocol, sourceAddress, sourcePort, destinationAddress, destinationPort);\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/DatagramBuffer.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.WritableByteChannel;\n\n/**\n * Circular buffer to store datagrams (preserving their boundaries).\n * <p>\n * <pre>\n *     circularBufferLength\n * |<------------------------->| extra space for storing the last datagram in one block\n * +---------------------------+------+\n * |                           |      |\n * |[D4]     [  D1  ][ D2 ][  D3  ]   |\n * +---------------------------+------+\n *     ^     ^\n *  head     tail\n * </pre>\n */\n@SuppressWarnings(\"checkstyle:MagicNumber\")\npublic class DatagramBuffer {\n\n    private static final String TAG = DatagramBuffer.class.getSimpleName();\n\n    // every datagram is stored along with a header storing its length, on 16 bits\n    private static final int HEADER_LENGTH = 2;\n    private static final int MAX_DATAGRAM_LENGTH = 1 << 16;\n    private static final int MAX_BLOCK_LENGTH = HEADER_LENGTH + MAX_DATAGRAM_LENGTH;\n\n    private final byte[] data;\n    private final ByteBuffer wrapper;\n    private int head;\n    private int tail;\n    private final int circularBufferLength;\n\n    public DatagramBuffer(int capacity) {\n        data = new byte[capacity + MAX_BLOCK_LENGTH];\n        wrapper = ByteBuffer.wrap(data);\n        circularBufferLength = capacity + 1;\n    }\n\n    public boolean isEmpty() {\n        return head == tail;\n    }\n\n    public boolean hasEnoughSpaceFor(int datagramLength) {\n        if (head >= tail) {\n            // there is at least the extra space for storing 1 packet\n            return true;\n        }\n        int remaining = tail - head - 1; // 1 extra byte to distinguish empty vs full\n        return HEADER_LENGTH + datagramLength <= remaining;\n    }\n\n    public int capacity() {\n        return circularBufferLength - 1;\n    }\n\n    public boolean writeTo(WritableByteChannel channel) throws IOException {\n        int length = readLength();\n        wrapper.limit(tail + length).position(tail);\n        tail += length;\n        if (tail >= circularBufferLength) {\n            tail = 0;\n        }\n        int w = channel.write(wrapper);\n        if (w != length) {\n            Log.e(TAG, \"Cannot write the whole datagram to the channel (only \" + w + \"/\" + length + \")\");\n            return false;\n        }\n        return true;\n    }\n\n    public boolean readFrom(ByteBuffer buffer) {\n        int length = buffer.remaining();\n        if (length > MAX_DATAGRAM_LENGTH) {\n            throw new IllegalArgumentException(\"Datagram length (\" + buffer.remaining() + \") may not be greater than \"\n                    + MAX_DATAGRAM_LENGTH + \" bytes\");\n        }\n        if (!hasEnoughSpaceFor(length)) {\n            return false;\n        }\n        writeLength(length);\n        buffer.get(data, head, length);\n        head += length;\n        if (head >= circularBufferLength) {\n            head = 0;\n        }\n        return true;\n    }\n\n    private void writeLength(int length) {\n        assert (length & ~0xffff) == 0 : \"Length must be stored on 16 bits\";\n        data[head++] = (byte) ((length >> 8) & 0xff);\n        data[head++] = (byte) (length & 0xff);\n    }\n\n    private int readLength() {\n        int length = ((data[tail] & 0xff) << 8) | (data[tail + 1] & 0xff);\n        tail += 2;\n        return length;\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/IPv4Header.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport java.nio.ByteBuffer;\n\n@SuppressWarnings(\"checkstyle:MagicNumber\")\npublic class IPv4Header {\n\n    public enum Protocol {\n        TCP(6), UDP(17), OTHER(-1);\n\n        private final int number;\n\n        Protocol(int number) {\n            this.number = number;\n        }\n\n        int getNumber() {\n            return number;\n        }\n\n        static Protocol fromNumber(int number) {\n            if (number == TCP.number) {\n                return TCP;\n            }\n            if (number == UDP.number) {\n                return UDP;\n            }\n            return OTHER;\n        }\n    }\n\n    private static final int MIN_IPV4_HEADER_LENGTH = 20;\n\n    private ByteBuffer raw;\n    private byte version;\n    private int headerLength;\n    private int totalLength;\n    private Protocol protocol;\n    private int source;\n    private int destination;\n\n    public IPv4Header(ByteBuffer raw) {\n        assert raw.limit() >= MIN_IPV4_HEADER_LENGTH : \"IPv4 headers length must be at least 20 bytes\";\n        this.raw = raw;\n\n        byte versionAndIHL = raw.get(0);\n        version = (byte) (versionAndIHL >> 4);\n\n        byte ihl = (byte) (versionAndIHL & 0xf);\n        headerLength = ihl << 2;\n\n        raw.limit(headerLength);\n\n        totalLength = Short.toUnsignedInt(raw.getShort(2));\n        //raw.limit(); // by design\n        //assert totalLength == Binary.unsigned(raw.getShort(2)) : \"Inconsistent packet length\";\n\n        int protocolNumber = Short.toUnsignedInt(raw.get(9));\n        protocol = Protocol.fromNumber(protocolNumber);\n\n        source = raw.getInt(12);\n        destination = raw.getInt(16);\n    }\n\n    public boolean isSupported() {\n        return version == 4 && protocol != Protocol.OTHER;\n    }\n\n    public Protocol getProtocol() {\n        return protocol;\n    }\n\n    public int getHeaderLength() {\n        return headerLength;\n    }\n\n    public int getTotalLength() {\n        return totalLength;\n    }\n\n    public void setTotalLength(int totalLength) {\n        this.totalLength = totalLength;\n        // apply changes to raw\n        raw.putShort(2, (short) totalLength);\n    }\n\n    public int getSource() {\n        return source;\n    }\n\n    public int getDestination() {\n        return destination;\n    }\n\n    public void setSource(int source) {\n        this.source = source;\n        raw.putInt(12, source);\n    }\n\n    public void setDestination(int destination) {\n        this.destination = destination;\n        raw.putInt(16, destination);\n    }\n\n    public void swapSourceAndDestination() {\n        int tmp = source;\n        setSource(destination);\n        setDestination(tmp);\n    }\n\n    public ByteBuffer getRaw() {\n        raw.rewind();\n        return raw.slice();\n    }\n\n    public IPv4Header copyTo(ByteBuffer target) {\n        raw.rewind();\n        ByteBuffer slice = Binary.slice(target, target.position(), getHeaderLength());\n        target.put(raw);\n        return new IPv4Header(slice);\n    }\n\n    public IPv4Header copy() {\n        return new IPv4Header(Binary.copy(raw));\n    }\n\n    public void computeChecksum() {\n        // reset checksum field\n        setChecksum((short) 0);\n\n        // checksum computation is the most CPU-intensive task in gnirehtet\n        // prefer optimization over readability\n        byte[] rawArray = raw.array();\n        int rawArrayOffset = raw.arrayOffset();\n\n        int sum = 0;\n        for (int i = 0; i < headerLength / 2; ++i) {\n            // compute a 16-bit value from two 8-bit values manually\n            sum += (rawArray[rawArrayOffset + 2 * i] & 0xff) << 8 | (rawArray[rawArrayOffset + 2 * i + 1] & 0xff);\n        }\n        while ((sum & ~0xffff) != 0) {\n            sum = (sum & 0xffff) + (sum >> 16);\n        }\n        setChecksum((short) ~sum);\n    }\n\n    private void setChecksum(short checksum) {\n        raw.putShort(10, checksum);\n    }\n\n    public short getChecksum() {\n        return raw.getShort(10);\n    }\n\n    /**\n     * Read the packet IP version, assuming that an IP packets is stored at absolute position 0.\n     *\n     * @param buffer the buffer\n     * @return the IP version, or {@code -1} if not available\n     */\n    public static int readVersion(ByteBuffer buffer) {\n        if (buffer.limit() == 0) {\n            // buffer is empty\n            return -1;\n        }\n        // version is stored in the 4 first bits\n        byte versionAndIHL = buffer.get(0);\n        return (versionAndIHL & 0xf0) >> 4;\n    }\n\n    /**\n     * Read the packet length, assuming thatan IP packet is stored at absolute position 0.\n     *\n     * @param buffer the buffer\n     * @return the packet length, or {@code -1} if not available\n     */\n    public static int readLength(ByteBuffer buffer) {\n        if (buffer.limit() < 4) {\n            // buffer does not even contains the length field\n            return -1;\n        }\n        // packet length is 16 bits starting at offset 2\n        return Short.toUnsignedInt(buffer.getShort(2));\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/IPv4Packet.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport java.nio.ByteBuffer;\n\npublic class IPv4Packet {\n\n    private static final String TAG = IPv4Packet.class.getSimpleName();\n\n    @SuppressWarnings(\"checkstyle:MagicNumber\")\n    public static final int MAX_PACKET_LENGTH = 1 << 16; // packet length is stored on 16 bits\n\n    private final ByteBuffer raw;\n    private final IPv4Header ipv4Header;\n    private final TransportHeader transportHeader;\n\n    public IPv4Packet(ByteBuffer raw) {\n        this.raw = raw;\n        raw.rewind();\n\n        if (Log.isVerboseEnabled()) {\n            Log.v(TAG, \"IPv4Packet: \" + Binary.buildPacketString(raw));\n        }\n\n        ipv4Header = new IPv4Header(raw.duplicate());\n        if (!ipv4Header.isSupported()) {\n            Log.d(TAG, \"Unsupported IPv4 headers\");\n            transportHeader = null;\n            return;\n        }\n        transportHeader = createTransportHeader();\n        raw.limit(ipv4Header.getTotalLength());\n    }\n\n    public boolean isValid() {\n        return transportHeader != null;\n    }\n\n    private TransportHeader createTransportHeader() {\n        IPv4Header.Protocol protocol = ipv4Header.getProtocol();\n        switch (protocol) {\n            case UDP:\n                return new UDPHeader(getRawTransport());\n            case TCP:\n                return new TCPHeader(getRawTransport());\n            default:\n                throw new AssertionError(\"Should be unreachable if ipv4Header.isSupported()\");\n        }\n    }\n\n    private ByteBuffer getRawTransport() {\n        raw.position(ipv4Header.getHeaderLength());\n        return raw.slice();\n    }\n\n    public IPv4Header getIpv4Header() {\n        return ipv4Header;\n    }\n\n    public TransportHeader getTransportHeader() {\n        return transportHeader;\n    }\n\n    public void swapSourceAndDestination() {\n        ipv4Header.swapSourceAndDestination();\n        transportHeader.swapSourceAndDestination();\n    }\n\n    public ByteBuffer getRaw() {\n        raw.rewind();\n        return raw.duplicate();\n    }\n\n    public int getRawLength() {\n        return raw.limit();\n    }\n\n    public ByteBuffer getPayload() {\n        int headersLength = ipv4Header.getHeaderLength() + transportHeader.getHeaderLength();\n        raw.position(headersLength);\n        return raw.slice();\n    }\n\n    public int getPayloadLength() {\n        return raw.limit() - ipv4Header.getHeaderLength() - transportHeader.getHeaderLength();\n    }\n\n    public void computeChecksums() {\n        ipv4Header.computeChecksum();\n        transportHeader.computeChecksum(ipv4Header, getPayload());\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/IPv4PacketBuffer.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.ReadableByteChannel;\n\npublic class IPv4PacketBuffer {\n\n    private final ByteBuffer buffer = ByteBuffer.allocate(IPv4Packet.MAX_PACKET_LENGTH);\n\n    public int readFrom(ReadableByteChannel channel) throws IOException {\n        return channel.read(buffer);\n    }\n\n    @SuppressWarnings(\"checkstyle:MagicNumber\")\n    private int getAvailablePacketLength() {\n        int length = IPv4Header.readLength(buffer);\n        assert length == -1 || IPv4Header.readVersion(buffer) == 4 : \"This function must not be called when the packet is not IPv4\";\n        if (length == -1) {\n            // no packet\n            return 0;\n        }\n        if (length > buffer.remaining()) {\n            // no full packet available\n            return 0;\n        }\n        return length;\n    }\n\n    public IPv4Packet asIPv4Packet() {\n        buffer.flip();\n        int length = getAvailablePacketLength();\n        if (length == 0) {\n            buffer.compact();\n            return null;\n        }\n        int limit = buffer.limit();\n        buffer.limit(length).position(0);\n        ByteBuffer packetBuffer = buffer.slice();\n        buffer.limit(limit).position(length);\n        // In order to avoid copies, packetBuffer is shared with this IPv4Packet instance that is returned.\n        // Don't use it after another call to next()!\n        return new IPv4Packet(packetBuffer);\n    }\n\n    public void next() {\n        buffer.compact();\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/Log.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport java.io.PrintStream;\nimport java.text.DateFormat;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\n\npublic final class Log {\n\n    enum Level {\n        VERBOSE(\"V\"),\n        DEBUG(\"D\"),\n        INFO(\"I\"),\n        WARNING(\"W\"),\n        ERROR(\"E\");\n\n        private final String id;\n\n        Level(String id) {\n            this.id = id;\n        }\n    }\n\n    private static Level threshold = Level.INFO;\n\n    private static final DateFormat FORMAT = new SimpleDateFormat(\"YYYY-MM-dd HH:mm:ss.SSS\");\n    private static final Date DATE = new Date();\n\n    private Log() {\n        // not instantiable\n    }\n\n    public static Level getThreshold() {\n        return threshold;\n    }\n\n    public static void setThreshold(Level threshold) {\n        Log.threshold = threshold;\n    }\n\n    public static boolean isEnabled(Level level) {\n        return level.ordinal() >= threshold.ordinal();\n    }\n\n    public static boolean isVerboseEnabled() {\n        return isEnabled(Level.VERBOSE);\n    }\n\n    public static boolean isDebugEnabled() {\n        return isEnabled(Level.DEBUG);\n    }\n\n    public static boolean isInfoEnabled() {\n        return isEnabled(Level.INFO);\n    }\n\n    public static boolean isWarningEnabled() {\n        return isEnabled(Level.WARNING);\n    }\n\n    public static boolean isErrorEnabled() {\n        return isEnabled(Level.ERROR);\n    }\n\n    private static String getDate() {\n        DATE.setTime(System.currentTimeMillis());\n        return FORMAT.format(DATE);\n    }\n\n    private static String format(Level level, String tag, String message) {\n        return getDate() + \" \" + level.id + \" \" + tag + \": \" + message;\n    }\n\n    private static void l(Level level, PrintStream stream, String tag, String message, Throwable e) {\n        if (isEnabled(level)) {\n            stream.println(format(level, tag, message));\n            if (e != null) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    public static void v(String tag, String message, Throwable e) {\n        l(Level.VERBOSE, System.out, tag, message, e);\n    }\n\n    public static void v(String tag, String message) {\n        v(tag, message, null);\n    }\n\n    public static void d(String tag, String message, Throwable e) {\n        l(Level.DEBUG, System.out, tag, message, e);\n    }\n\n    public static void d(String tag, String message) {\n        d(tag, message, null);\n    }\n\n    public static void i(String tag, String message, Throwable e) {\n        l(Level.INFO, System.out, tag, message, e);\n    }\n\n    public static void i(String tag, String message) {\n        i(tag, message, null);\n    }\n\n    public static void w(String tag, String message, Throwable e) {\n        l(Level.WARNING, System.out, tag, message, e);\n    }\n\n    public static void w(String tag, String message) {\n        w(tag, message, null);\n    }\n\n    public static void e(String tag, String message, Throwable e) {\n        l(Level.ERROR, System.err, tag, message, e);\n    }\n\n    public static void e(String tag, String message) {\n        e(tag, message, null);\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/Net.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.net.UnknownHostException;\n\npublic final class Net {\n    private Net() {\n        // not instantiable\n    }\n\n    public static InetAddress[] toInetAddresses(String... addresses) {\n        InetAddress[] result = new InetAddress[addresses.length];\n        for (int i = 0; i < result.length; ++i) {\n            result[i] = toInetAddress(addresses[i]);\n        }\n        return result;\n    }\n\n    public static InetAddress toInetAddress(String address) {\n        try {\n            return InetAddress.getByName(address);\n        } catch (UnknownHostException e) {\n            throw new IllegalArgumentException(e);\n        }\n    }\n\n    public static InetAddress toInetAddress(byte[] raw) {\n        try {\n            return InetAddress.getByAddress(raw);\n        } catch (UnknownHostException e) {\n            throw new IllegalArgumentException(e);\n        }\n    }\n\n    @SuppressWarnings(\"checkstyle:MagicNumber\")\n    public static InetAddress toInetAddress(int ipAddr) {\n        byte[] ip = {\n                (byte) (ipAddr >>> 24),\n                (byte) ((ipAddr >> 16) & 0xff),\n                (byte) ((ipAddr >> 8) & 0xff),\n                (byte) (ipAddr & 0xff)\n        };\n        return toInetAddress(ip);\n    }\n\n    public static String toString(InetSocketAddress address) {\n        return address.getAddress().getHostAddress() + \":\" + address.getPort();\n    }\n\n    public static String toString(int ip, short port) {\n        return toString(new InetSocketAddress(Net.toInetAddress(ip), Short.toUnsignedInt(port)));\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/PacketSource.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\n/**\n * Source that may produce packets.\n * <p>\n * When a {@link TCPConnection} sends a packet to the {@link Client} while its buffers are full,\n * then it fails. To recover, once some space becomes available, the {@link Client} must pull the\n * available packets.\n * <p>\n * This interface provides the abstraction of a packet source from which it can pull packets.\n * <p>\n * It is implemented by {@link TCPConnection}.\n */\npublic interface PacketSource {\n\n    IPv4Packet get();\n\n    void next();\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/Packetizer.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.ReadableByteChannel;\n\n/**\n * Convert from level 5 to level 3 by appending correct IP and transport headers.\n */\npublic class Packetizer {\n\n    private final ByteBuffer buffer = ByteBuffer.allocate(IPv4Packet.MAX_PACKET_LENGTH);\n    private final ByteBuffer payloadBuffer;\n\n    private final IPv4Header responseIPv4Header;\n    private final TransportHeader responseTransportHeader;\n\n    public Packetizer(IPv4Header ipv4Header, TransportHeader transportHeader) {\n        responseIPv4Header = ipv4Header.copyTo(buffer);\n        responseTransportHeader = transportHeader.copyTo(buffer);\n        payloadBuffer = buffer.slice();\n    }\n\n    public IPv4Header getResponseIPv4Header() {\n        return responseIPv4Header;\n    }\n\n    public TransportHeader getResponseTransportHeader() {\n        return responseTransportHeader;\n    }\n\n    public IPv4Packet packetizeEmptyPayload() {\n        payloadBuffer.limit(0).position(0);\n        return inflate();\n    }\n\n    public IPv4Packet packetize(ReadableByteChannel channel, int maxChunkSize) throws IOException {\n        payloadBuffer.limit(maxChunkSize).position(0);\n        int payloadLength = channel.read(payloadBuffer);\n        if (payloadLength == -1) {\n            return null;\n        }\n        payloadBuffer.flip();\n        return inflate();\n    }\n\n    public IPv4Packet packetize(ReadableByteChannel channel) throws IOException {\n        return packetize(channel, payloadBuffer.capacity());\n    }\n\n    private IPv4Packet inflate() {\n        int payloadLength = payloadBuffer.remaining();\n        buffer.limit(payloadBuffer.arrayOffset() + payloadBuffer.limit()).position(0);\n\n        int ipv4HeaderLength = responseIPv4Header.getHeaderLength();\n        int transportHeaderLength = responseTransportHeader.getHeaderLength();\n        int totalLength = ipv4HeaderLength + transportHeaderLength + payloadLength;\n\n        responseIPv4Header.setTotalLength(totalLength);\n        responseTransportHeader.setPayloadLength(payloadLength);\n\n        // In order to avoid copies, buffer is shared with this IPv4Packet instance that is returned.\n        // Don't use it after another call to packetize()!\n        IPv4Packet packet = new IPv4Packet(buffer);\n        packet.computeChecksums();\n        return packet;\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/Relay.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport java.io.IOException;\nimport java.nio.channels.SelectionKey;\nimport java.nio.channels.Selector;\nimport java.util.Set;\n\npublic class Relay {\n\n    private static final String TAG = Relay.class.getSimpleName();\n\n    private static final int CLEANING_INTERVAL = 60 * 1000;\n\n    private final int port;\n\n    public Relay(int port) {\n        this.port = port;\n    }\n\n    public void run() throws IOException {\n        Selector selector = Selector.open();\n\n        // will register the socket on the selector\n        TunnelServer tunnelServer = new TunnelServer(port, selector);\n\n        Log.i(TAG, \"Relay server started\");\n\n        long nextCleaningDeadline = System.currentTimeMillis() + UDPConnection.IDLE_TIMEOUT;\n        while (true) {\n            long timeout = Math.max(0, nextCleaningDeadline - System.currentTimeMillis());\n            selector.select(timeout);\n            Set<SelectionKey> selectedKeys = selector.selectedKeys();\n\n            long now = System.currentTimeMillis();\n            if (now >= nextCleaningDeadline || selectedKeys.isEmpty()) {\n                tunnelServer.cleanUp();\n                nextCleaningDeadline = now + CLEANING_INTERVAL;\n            }\n\n            for (SelectionKey selectedKey : selectedKeys) {\n                SelectionHandler selectionHandler = (SelectionHandler) selectedKey.attachment();\n                selectionHandler.onReady(selectedKey);\n            }\n            // by design, we handled everything\n            selectedKeys.clear();\n        }\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/Router.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport java.io.IOException;\nimport java.nio.channels.Selector;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Router {\n\n    private static final String TAG = Router.class.getSimpleName();\n\n    private final Client client;\n    private final Selector selector;\n\n    // there are typically only few connections per client, HashMap would be less efficient\n    private final List<Connection> connections = new ArrayList<>();\n\n    public Router(Client client, Selector selector) {\n        this.client = client;\n        this.selector = selector;\n    }\n\n    public void sendToNetwork(IPv4Packet packet) {\n        if (!packet.isValid()) {\n            Log.w(TAG, \"Dropping invalid packet\");\n            if (Log.isVerboseEnabled()) {\n                Log.v(TAG, Binary.buildPacketString(packet.getRaw()));\n            }\n            return;\n        }\n        try {\n            Connection connection = getConnection(packet.getIpv4Header(), packet.getTransportHeader());\n            connection.sendToNetwork(packet);\n        } catch (IOException e) {\n            Log.e(TAG, \"Cannot create connection, dropping packet\", e);\n        }\n    }\n\n    private Connection getConnection(IPv4Header ipv4Header, TransportHeader transportHeader) throws IOException {\n        ConnectionId id = ConnectionId.from(ipv4Header, transportHeader);\n        Connection connection = find(id);\n        if (connection == null) {\n            connection = createConnection(id, ipv4Header, transportHeader);\n            connections.add(connection);\n        }\n        return connection;\n    }\n\n    private Connection createConnection(ConnectionId id, IPv4Header ipv4Header, TransportHeader transportHeader) throws IOException {\n        IPv4Header.Protocol protocol = id.getProtocol();\n        if (protocol == IPv4Header.Protocol.UDP) {\n            return new UDPConnection(id, client, selector, ipv4Header, (UDPHeader) transportHeader);\n        }\n        if (protocol == IPv4Header.Protocol.TCP) {\n            return new TCPConnection(id, client, selector, ipv4Header, (TCPHeader) transportHeader);\n        }\n        throw new UnsupportedOperationException(\"Unsupported protocol: \" + protocol);\n    }\n\n    private Connection find(ConnectionId id) {\n        for (Connection connection : connections) {\n            if (id.equals(connection.getId())) {\n                return connection;\n            }\n        }\n        return null;\n    }\n\n    public void clear() {\n        for (Connection connection : connections) {\n            connection.disconnect();\n        }\n        connections.clear();\n    }\n\n    public void remove(Connection connection) {\n        if (!connections.remove(connection)) {\n            throw new AssertionError(\"Removed a connection unknown from the router\");\n        }\n    }\n\n    public void cleanExpiredConnections() {\n        for (int i = connections.size() - 1; i >= 0; --i) {\n            Connection connection = connections.get(i);\n            if (connection.isExpired()) {\n                Log.d(TAG, \"Remove expired connection: \" + connection.getId());\n                connection.disconnect();\n                connections.remove(i);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/SelectionHandler.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport java.nio.channels.SelectionKey;\n\npublic interface SelectionHandler {\n\n    void onReady(SelectionKey selectionKey);\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/StreamBuffer.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.WritableByteChannel;\n\n/**\n * Circular buffer to store a stream. Read/write boundaries are not preserved.\n */\npublic class StreamBuffer {\n\n    private final byte[] data;\n    private final ByteBuffer wrapper;\n    private int head;\n    private int tail;\n\n    public StreamBuffer(int capacity) {\n        data = new byte[capacity + 1];\n        wrapper = ByteBuffer.wrap(data);\n    }\n\n    public boolean isEmpty() {\n        return head == tail;\n    }\n\n    public boolean isFull() {\n        return (head + 1) % data.length == tail;\n    }\n\n    public int size() {\n        if (head < tail) {\n            return head + data.length - tail;\n        }\n        return head - tail;\n    }\n\n    public int capacity() {\n        return data.length - 1;\n    }\n\n    public int remaining() {\n        return capacity() - size();\n    }\n\n    public int writeTo(WritableByteChannel channel) throws IOException {\n        if (head > tail) {\n            wrapper.limit(head).position(tail);\n            int w = channel.write(wrapper);\n            tail = wrapper.position();\n            optimize();\n            return w;\n        }\n\n        if (head < tail) {\n            wrapper.limit(data.length).position(tail);\n            int w = channel.write(wrapper);\n            tail = wrapper.position() % data.length;\n            optimize();\n            return w;\n        }\n\n        // else head == tail, which means empty buffer, nothing to do\n        return 0;\n    }\n\n    public void readFrom(ByteBuffer buffer) {\n        int requested = Math.min(buffer.remaining(), remaining());\n        if (requested <= data.length - head) {\n            buffer.get(data, head, requested);\n        } else {\n            buffer.get(data, head, data.length - head);\n            buffer.get(data, 0, head + requested - data.length);\n        }\n        head = (head + requested) % data.length;\n    }\n\n    /**\n     * To avoid unnecessary copies, StreamBuffer writes at most until the \"end\" of the circular\n     * buffer, which is suboptimal (it could have written more data if they have been contiguous).\n     * <p>\n     * In order to minimize the occurrence of this event, reset the head and tail to 0 when the\n     * buffer is empty (no copy is involved).\n     * <p>\n     * This is especially useful when the StreamBuffer is used to read/write one packet at a time,\n     * so the \"end\" of the buffer is guaranteed to never be reached.\n     */\n    private void optimize() {\n        if (isEmpty()) {\n            head = 0;\n            tail = 0;\n        }\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/TCPConnection.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport java.io.IOException;\nimport java.nio.channels.SelectionKey;\nimport java.nio.channels.Selector;\nimport java.nio.channels.SocketChannel;\nimport java.util.Random;\n\npublic class TCPConnection extends AbstractConnection implements PacketSource {\n\n    private static final String TAG = TCPConnection.class.getSimpleName();\n\n    // same value as GnirehtetService.MTU in the client\n    private static final int MTU = 0x4000;\n    // 20 bytes for IP headers, 20 bytes for TCP headers\n    private static final int MAX_PAYLOAD_SIZE = MTU - 20 - 20;\n\n    private static final Random RANDOM = new Random();\n\n    /**\n     * See <a href=\"https://tools.ietf.org/html/rfc793#page-23\">RFC793</a>.\n     */\n    public enum State {\n        SYN_SENT,\n        SYN_RECEIVED,\n        ESTABLISHED,\n        CLOSE_WAIT,\n        LAST_ACK,\n        CLOSING,\n        FIN_WAIT_1,\n        FIN_WAIT_2;\n\n        public boolean isConnected() {\n            return this != SYN_SENT && this != SYN_RECEIVED;\n        }\n\n        public boolean isClosed() {\n            return this == FIN_WAIT_1 || this == FIN_WAIT_2 || this == CLOSING || this == LAST_ACK;\n        }\n    }\n\n    private final StreamBuffer clientToNetwork = new StreamBuffer(4 * IPv4Packet.MAX_PACKET_LENGTH);\n    private final Packetizer networkToClient;\n    private IPv4Packet packetForClient;\n\n    private final SocketChannel channel;\n    private final SelectionKey selectionKey;\n    private int interests;\n\n    private State state;\n    private int synSequenceNumber;\n    private int sequenceNumber;\n    private int acknowledgementNumber;\n    private int theirAcknowledgementNumber;\n    private Integer finSequenceNumber; // null means \"no FIN sent yet\"\n    private boolean finReceived;\n    private int clientWindow;\n\n    public TCPConnection(ConnectionId id, Client client, Selector selector, IPv4Header ipv4Header, TCPHeader tcpHeader) throws IOException {\n        super(id, client);\n\n        TCPHeader shrinkedTcpHeader = tcpHeader.copy();\n        shrinkedTcpHeader.shrinkOptions(); // no TCP options\n\n        networkToClient = new Packetizer(ipv4Header, shrinkedTcpHeader);\n        networkToClient.getResponseIPv4Header().swapSourceAndDestination();\n        networkToClient.getResponseTransportHeader().swapSourceAndDestination();\n\n        SelectionHandler selectionHandler = (selectionKey) -> {\n            if (selectionKey.isValid() && selectionKey.isConnectable()) {\n                processConnect();\n            }\n            if (selectionKey.isValid() && selectionKey.isReadable()) {\n                processReceive();\n            }\n            if (selectionKey.isValid() && selectionKey.isWritable()) {\n                processSend();\n            }\n            updateInterests();\n        };\n        channel = createChannel();\n        // interests will be set on the first packet received\n        // set the initial value now so that they won't need to be updated\n        interests = SelectionKey.OP_CONNECT;\n        selectionKey = channel.register(selector, interests, selectionHandler);\n    }\n\n    @Override\n    public void disconnect() {\n        logi(TAG, \"Close\");\n        selectionKey.cancel();\n        try {\n            channel.close();\n        } catch (IOException e) {\n            loge(TAG, \"Cannot close connection channel\", e);\n        }\n    }\n\n    private void processReceive() {\n        try {\n            assert packetForClient == null : \"The IPv4Packet shares the networkToClient buffer, it must not be corrupted\";\n            int remainingClientWindow = getRemainingClientWindow();\n            assert remainingClientWindow > 0 : \"If remainingClientWindow is 0, then processReceive() should not have been called\";\n            int maxPayloadSize = Math.min(remainingClientWindow, MAX_PAYLOAD_SIZE);\n            updateHeaders(TCPHeader.FLAG_ACK | TCPHeader.FLAG_PSH);\n            packetForClient = networkToClient.packetize(channel, maxPayloadSize);\n            if (packetForClient == null) {\n                eof();\n                return;\n            }\n            consume(this);\n        } catch (IOException e) {\n            loge(TAG, \"Cannot read\", e);\n            resetConnection();\n        }\n    }\n\n    private void processSend() {\n        try {\n            int w = clientToNetwork.writeTo(channel);\n            if (w > 0) {\n                acknowledgementNumber += w;\n\n                logd(TAG, w + \" bytes written to the network socket\");\n\n                if (finReceived && clientToNetwork.isEmpty()) {\n                    logd(TAG, \"No more pending data, process the pending FIN\");\n                    doHandleFin();\n                } else {\n                    logd(TAG, \"Sending ACK \" + numbers() + \" to client\");\n                    sendEmptyPacketToClient(TCPHeader.FLAG_ACK);\n                }\n            } else {\n                close();\n            }\n        } catch (IOException e) {\n            loge(TAG, \"Cannot write\", e);\n            resetConnection();\n        }\n    }\n\n    private void eof() {\n        sendEmptyPacketToClient(TCPHeader.FLAG_FIN | TCPHeader.FLAG_ACK);\n\n        finSequenceNumber = sequenceNumber;\n        ++sequenceNumber; // FIN counts for 1 byte\n        if (state == State.CLOSE_WAIT) {\n            state = State.LAST_ACK;\n        } else {\n            state = State.FIN_WAIT_1;\n        }\n        logd(TAG, \"State = \" + state);\n    }\n\n    private int getRemainingClientWindow() {\n        // in Java, (signed) integer overflow is well-defined: it wraps around\n        int remaining = theirAcknowledgementNumber + clientWindow - sequenceNumber;\n        if (remaining < 0 || remaining > clientWindow) {\n            // our sequence number is outside their window\n            return 0;\n        }\n        return remaining;\n    }\n\n    @Override\n    public boolean isExpired() {\n        // no external timeout expiration\n        return false;\n    }\n\n    private void updateHeaders(int flags) {\n        TCPHeader tcpHeader = (TCPHeader) networkToClient.getResponseTransportHeader();\n        tcpHeader.setFlags(flags);\n        tcpHeader.setSequenceNumber(sequenceNumber);\n        tcpHeader.setAcknowledgementNumber(acknowledgementNumber);\n    }\n\n    private SocketChannel createChannel() throws IOException {\n        logi(TAG, \"Open\");\n        SocketChannel socketChannel = SocketChannel.open();\n        socketChannel.configureBlocking(false);\n        socketChannel.connect(getRewrittenDestination());\n        return socketChannel;\n    }\n\n    @Override\n    public void sendToNetwork(IPv4Packet packet) {\n        handlePacket(packet);\n        logd(TAG, \"current ack=\" + acknowledgementNumber);\n        updateInterests();\n    }\n\n    private void handlePacket(IPv4Packet packet) {\n        TCPHeader tcpHeader = (TCPHeader) packet.getTransportHeader();\n        if (state == null) {\n            handleFirstPacket(packet);\n            return;\n        }\n\n        if (tcpHeader.isSyn()) {\n            // the client always initiates the connection\n            // at this point, any SYN packet received is duplicate\n            handleDuplicateSyn(packet);\n            return;\n        }\n\n        int packetSequenceNumber = tcpHeader.getSequenceNumber();\n        int expectedPacket = acknowledgementNumber + clientToNetwork.size();\n        if (packetSequenceNumber != expectedPacket) {\n            // ignore packet already received or out-of-order, retransmission is already managed by both sides\n            logw(TAG, \"Ignoring packet \" + packetSequenceNumber + \" (acking \" + tcpHeader.getAcknowledgementNumber() + \"); expecting \"\n                    + expectedPacket + \"; flags=\" + tcpHeader.getFlags());\n            return;\n        }\n\n        clientWindow = tcpHeader.getWindow();\n        theirAcknowledgementNumber = tcpHeader.getAcknowledgementNumber();\n\n        logd(TAG, \"Receiving expected paquet \" + packetSequenceNumber + \" (flags = \" + tcpHeader.getFlags() + \")\");\n\n        if (tcpHeader.isRst()) {\n            logd(TAG, \"Reset requested, closing\");\n            close();\n            return;\n        }\n\n        if (tcpHeader.isAck()) {\n            logd(TAG, \"Client acked \" + tcpHeader.getAcknowledgementNumber());\n            handleAck(packet);\n        }\n\n        if (tcpHeader.isFin()) {\n            handleFin();\n        }\n\n        if (finSequenceNumber != null && tcpHeader.getAcknowledgementNumber() == finSequenceNumber + 1) {\n            logd(TAG, \"Received ACK of FIN\");\n            handleFinAck();\n        }\n    }\n\n    private void handleFirstPacket(IPv4Packet packet) {\n        logd(TAG, \"handleFirstPacket()\");\n        TCPHeader tcpHeader = (TCPHeader) packet.getTransportHeader();\n        if (!tcpHeader.isSyn()) {\n            logw(TAG, \"Unexpected first packet \" + tcpHeader.getSequenceNumber() + \"; acking \" + tcpHeader.getAcknowledgementNumber()\n                    + \"; flags=\" + tcpHeader.getFlags());\n            sequenceNumber = tcpHeader.getAcknowledgementNumber(); // make a RST in the window client\n            resetConnection();\n            return;\n        }\n\n        int theirSequenceNumber = tcpHeader.getSequenceNumber();\n        acknowledgementNumber = theirSequenceNumber + 1;\n        synSequenceNumber = theirSequenceNumber;\n\n        sequenceNumber = RANDOM.nextInt();\n        logd(TAG, \"initialized seqNum=\" + sequenceNumber + \"; ackNum=\" + acknowledgementNumber);\n        clientWindow = tcpHeader.getWindow();\n        state = State.SYN_SENT;\n        logd(TAG, \"State = \" + state);\n    }\n\n    private void handleDuplicateSyn(IPv4Packet packet) {\n        TCPHeader tcpHeader = (TCPHeader) packet.getTransportHeader();\n        int theirSequenceNumber = tcpHeader.getSequenceNumber();\n        if (state == State.SYN_SENT) {\n            // we didn't call finishConnect() yet, we can accept this packet as if it were the first SYN\n            synSequenceNumber = theirSequenceNumber;\n            acknowledgementNumber = theirSequenceNumber + 1;\n        } else if (theirSequenceNumber != synSequenceNumber) {\n            // duplicate SYN with different sequence number\n            resetConnection();\n        }\n    }\n\n    private void handleFin() {\n        logd(TAG, \"Received a FIN from the client \" + numbers());\n        finReceived = true;\n        if (clientToNetwork.isEmpty()) {\n            logd(TAG, \"No pending data, process the FIN immediately\");\n            doHandleFin();\n        }\n        // otherwise, the FIN will be processed once clientToNetwork is empty\n    }\n\n    private void doHandleFin() {\n        ++acknowledgementNumber; // received FIN counts for 1 byte\n\n        switch (state) {\n            case ESTABLISHED:\n                sendEmptyPacketToClient(TCPHeader.FLAG_FIN | TCPHeader.FLAG_ACK);\n                finSequenceNumber = sequenceNumber;\n                ++sequenceNumber; // FIN counts for 1 byte\n                // do not wait for the real network connection, switch immediately to LAST_ACK (bypass CLOSE_WAIT)\n                state = State.LAST_ACK;\n                logd(TAG, \"State = \" + state);\n                break;\n            case FIN_WAIT_1:\n                sendEmptyPacketToClient(TCPHeader.FLAG_ACK);\n                state = State.CLOSING;\n                logd(TAG, \"State = \" + state);\n                break;\n            case FIN_WAIT_2:\n                sendEmptyPacketToClient(TCPHeader.FLAG_ACK);\n                close();\n                break;\n            default:\n                logw(TAG, \"Received FIN while state was \" + state);\n        }\n    }\n\n    private void handleFinAck() {\n        switch (state) {\n            case LAST_ACK:\n            case CLOSING:\n                close();\n                break;\n            case FIN_WAIT_1:\n                state = State.FIN_WAIT_2;\n                logd(TAG, \"State = \" + state);\n                break;\n            case FIN_WAIT_2:\n                // do nothing\n                break;\n            default:\n                logw(TAG, \"Received FIN ACK while state was \" + state);\n        }\n    }\n\n    private void handleAck(IPv4Packet packet) {\n        logd(TAG, \"handleAck()\");\n        if (state == State.SYN_RECEIVED) {\n            state = State.ESTABLISHED;\n            logd(TAG, \"State = \" + state);\n            return;\n        }\n\n        if (Log.isVerboseEnabled()) {\n            logv(TAG, Binary.buildPacketString(packet.getRaw()));\n        }\n\n        int payloadLength = packet.getPayloadLength();\n        if (payloadLength == 0) {\n            // no data to transmit\n            return;\n        }\n\n        if (clientToNetwork.remaining() < payloadLength) {\n            logw(TAG, \"Not enough space, dropping packet\");\n            return;\n        }\n\n        clientToNetwork.readFrom(packet.getPayload());\n        // data will be ACKed once written to the network socket\n    }\n\n    private void processConnect() {\n        logd(TAG, \"processConnect()\");\n        if (!finishConnect()) {\n            close();\n            return;\n        }\n        logd(TAG, \"SYN_RECEIVED, acking \" + numbers());\n        state = State.SYN_RECEIVED;\n        logd(TAG, \"State = \" + state);\n        sendEmptyPacketToClient(TCPHeader.FLAG_SYN | TCPHeader.FLAG_ACK);\n        ++sequenceNumber; // SYN counts for 1 byte\n    }\n\n    private boolean finishConnect() {\n        try {\n            return channel.finishConnect();\n        } catch (IOException e) {\n            loge(TAG, \"Cannot finish connect\", e);\n            return false;\n        }\n    }\n\n    private void resetConnection() {\n        logd(TAG, \"Resetting connection\");\n        state = null;\n        sendEmptyPacketToClient(TCPHeader.FLAG_RST);\n        close();\n    }\n\n    private IPv4Packet createEmptyResponsePacket(int flags) {\n        updateHeaders(flags);\n        IPv4Packet packet = networkToClient.packetizeEmptyPayload();\n        logd(TAG, \"Forging empty response (flags=\" + flags + \") \" + numbers());\n        if (Log.isVerboseEnabled()) {\n            logd(TAG, Binary.buildPacketString(packet.getRaw()));\n        }\n        if ((flags & TCPHeader.FLAG_ACK) != 0) {\n            logd(TAG, \"Acking \" + numbers());\n        }\n        return packet;\n    }\n\n    private void sendEmptyPacketToClient(int flags) {\n        sendToClient(createEmptyResponsePacket(flags));\n    }\n\n    protected void updateInterests() {\n        if (!selectionKey.isValid()) {\n            return;\n        }\n        int interestOps = 0;\n        if (mayRead()) {\n            interestOps |= SelectionKey.OP_READ;\n        }\n        if (mayWrite()) {\n            interestOps |= SelectionKey.OP_WRITE;\n        }\n        if (mayConnect()) {\n            interestOps |= SelectionKey.OP_CONNECT;\n        }\n        if (interests != interestOps) {\n            // interests must be changed\n            interests = interestOps;\n            selectionKey.interestOps(interestOps);\n        }\n    }\n\n    private boolean mayRead() {\n        if (!state.isConnected() || state.isClosed()) {\n            return false;\n        }\n        if (packetForClient != null) {\n            // a packet is already pending\n            return false;\n        }\n        return getRemainingClientWindow() > 0;\n    }\n\n    private boolean mayWrite() {\n        return !clientToNetwork.isEmpty();\n    }\n\n    private boolean mayConnect() {\n        return state == State.SYN_SENT;\n    }\n\n    private String numbers() {\n        return \"(seq=\" + sequenceNumber + \", ack=\" + acknowledgementNumber + \")\";\n    }\n\n    @Override\n    public IPv4Packet get() {\n        // TODO update only when necessary\n        updateAcknowledgementNumber(packetForClient);\n        return packetForClient;\n    }\n\n    private void updateAcknowledgementNumber(IPv4Packet packet) {\n        TCPHeader tcpHeader = (TCPHeader) packet.getTransportHeader();\n        tcpHeader.setAcknowledgementNumber(acknowledgementNumber);\n        packet.computeChecksums();\n    }\n\n    @Override\n    public void next() {\n        logd(TAG, \"Packet (\" + packetForClient.getPayloadLength() + \" bytes) sent to client \" + numbers());\n        if (Log.isVerboseEnabled()) {\n            logv(TAG, Binary.buildPacketString(packetForClient.getRaw()));\n        }\n        sequenceNumber += packetForClient.getPayloadLength();\n        packetForClient = null;\n        updateInterests();\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/TCPHeader.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport java.nio.ByteBuffer;\n\n@SuppressWarnings(\"checkstyle:MagicNumber\")\npublic class TCPHeader implements TransportHeader {\n\n    public static final int FLAG_FIN = 1 << 0;\n    public static final int FLAG_SYN = 1 << 1;\n    public static final int FLAG_RST = 1 << 2;\n    public static final int FLAG_PSH = 1 << 3;\n    public static final int FLAG_ACK = 1 << 4;\n    public static final int FLAG_URG = 1 << 5;\n\n    private final ByteBuffer raw;\n    private int sourcePort;\n    private int destinationPort;\n    private int headerLength;\n    private int sequenceNumber;\n    private int acknowledgementNumber;\n    private int flags;\n    private int window;\n\n    public TCPHeader(ByteBuffer raw) {\n        this.raw = raw;\n        sourcePort = Short.toUnsignedInt(raw.getShort(0));\n        destinationPort = Short.toUnsignedInt(raw.getShort(2));\n\n        sequenceNumber = raw.getInt(4);\n        acknowledgementNumber = raw.getInt(8);\n\n        short dataOffsetAndFlags = raw.getShort(12);\n        headerLength = (dataOffsetAndFlags & 0xf000) >> 10;\n        flags = dataOffsetAndFlags & 0x1ff;\n\n        window = Short.toUnsignedInt(raw.getShort(14));\n\n        raw.limit(headerLength);\n    }\n\n    public int getWindow() {\n        return window;\n    }\n\n    @Override\n    public int getSourcePort() {\n        return sourcePort;\n    }\n\n    @Override\n    public int getDestinationPort() {\n        return destinationPort;\n    }\n\n    @Override\n    public void setSourcePort(int sourcePort) {\n        this.sourcePort = sourcePort;\n        raw.putShort(0, (short) sourcePort);\n    }\n\n    @Override\n    public void setDestinationPort(int destinationPort) {\n        this.destinationPort = destinationPort;\n        raw.putShort(2, (short) destinationPort);\n    }\n\n    public int getSequenceNumber() {\n        return sequenceNumber;\n    }\n\n    public void setSequenceNumber(int sequenceNumber) {\n        this.sequenceNumber = sequenceNumber;\n        raw.putInt(4, sequenceNumber);\n    }\n\n    public int getAcknowledgementNumber() {\n        return acknowledgementNumber;\n    }\n\n    public void setAcknowledgementNumber(int acknowledgementNumber) {\n        this.acknowledgementNumber = acknowledgementNumber;\n        raw.putInt(8, acknowledgementNumber);\n    }\n\n    @Override\n    public int getHeaderLength() {\n        return headerLength;\n    }\n\n    @Override\n    public void setPayloadLength(int payloadLength) {\n        // do nothing\n    }\n\n    public int getFlags() {\n        return flags;\n    }\n\n    public void setFlags(int flags) {\n        this.flags = flags;\n        short dataOffsetAndFlags = raw.getShort(12);\n        dataOffsetAndFlags = (short) (dataOffsetAndFlags & 0xfe00 | flags & 0x1ff);\n        raw.putShort(12, dataOffsetAndFlags);\n    }\n\n    public void shrinkOptions() {\n        setDataOffset(5);\n        raw.limit(20);\n    }\n\n    private void setDataOffset(int dataOffset) {\n        short dataOffsetAndFlags = raw.getShort(12);\n        dataOffsetAndFlags = (short) (dataOffsetAndFlags & 0x0fff | (dataOffset << 12));\n        raw.putShort(12, dataOffsetAndFlags);\n        headerLength = dataOffset << 2;\n    }\n\n    public boolean isFin() {\n        return (flags & FLAG_FIN) != 0;\n    }\n\n    public boolean isSyn() {\n        return (flags & FLAG_SYN) != 0;\n    }\n\n    public boolean isRst() {\n        return (flags & FLAG_RST) != 0;\n    }\n\n    public boolean isPsh() {\n        return (flags & FLAG_PSH) != 0;\n    }\n\n    public boolean isAck() {\n        return (flags & FLAG_ACK) != 0;\n    }\n\n    public boolean isUrg() {\n        return (flags & FLAG_URG) != 0;\n    }\n\n    @Override\n    public ByteBuffer getRaw() {\n        raw.rewind();\n        return raw.slice();\n    }\n\n    @Override\n    public TCPHeader copyTo(ByteBuffer target) {\n        raw.rewind();\n        ByteBuffer slice = Binary.slice(target, target.position(), getHeaderLength());\n        target.put(raw);\n        return new TCPHeader(slice);\n    }\n\n    public TCPHeader copy() {\n        return new TCPHeader(Binary.copy(raw));\n    }\n\n    @Override\n    public void computeChecksum(IPv4Header ipv4Header, ByteBuffer payload) {\n        // checksum computation is the most CPU-intensive task in gnirehtet\n        // prefer optimization over readability\n        byte[] rawArray = raw.array();\n        int rawOffset = raw.arrayOffset();\n\n        byte[] payloadArray = payload.array();\n        int payloadOffset = payload.arrayOffset();\n\n        // pseudo-header checksum (cf rfc793 section 3.1)\n\n        int source = ipv4Header.getSource();\n        int destination = ipv4Header.getDestination();\n        int length = ipv4Header.getTotalLength() - ipv4Header.getHeaderLength();\n        assert (length & ~0xffff) == 0 : \"Length cannot take more than 16 bits\"; // by design\n\n        int sum = source >>> 16;\n        sum += source & 0xffff;\n        sum += destination >>> 16;\n        sum += destination & 0xffff;\n        sum += IPv4Header.Protocol.TCP.getNumber();\n        sum += length;\n\n        // reset checksum field\n        setChecksum((short) 0);\n\n        for (int i = 0; i < headerLength / 2; ++i) {\n            // compute a 16-bit value from two 8-bit values manually\n            sum += ((rawArray[rawOffset + 2 * i] & 0xff) << 8) | (rawArray[rawOffset + 2 * i + 1] & 0xff);\n        }\n\n        int payloadLength = length - headerLength;\n        assert payloadLength == payload.limit() : \"Payload length does not match\";\n        for (int i = 0; i < payloadLength / 2; ++i) {\n            // compute a 16-bit value from two 8-bit values manually\n            sum += ((payloadArray[payloadOffset + 2 * i] & 0xff) << 8) | (payloadArray[payloadOffset + 2 * i + 1] & 0xff);\n        }\n        if (payloadLength % 2 != 0) {\n            sum += (payloadArray[payloadOffset + payloadLength - 1] & 0xff) << 8;\n        }\n\n        while ((sum & ~0xffff) != 0) {\n            sum = (sum & 0xffff) + (sum >> 16);\n        }\n        setChecksum((short) ~sum);\n    }\n\n    private void setChecksum(short checksum) {\n        raw.putShort(16, checksum);\n    }\n\n    public short getChecksum() {\n        return raw.getShort(16);\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/TransportHeader.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport java.nio.ByteBuffer;\n\npublic interface TransportHeader {\n\n    int getSourcePort();\n\n    int getDestinationPort();\n\n    void setSourcePort(int port);\n\n    void setDestinationPort(int port);\n\n    int getHeaderLength();\n\n    void setPayloadLength(int payloadLength);\n\n    ByteBuffer getRaw();\n\n    TransportHeader copyTo(ByteBuffer buffer);\n\n    void computeChecksum(IPv4Header ipv4Header, ByteBuffer payload);\n\n    default void swapSourceAndDestination() {\n        int tmp = getSourcePort();\n        setSourcePort(getDestinationPort());\n        setDestinationPort(tmp);\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/TunnelServer.java",
    "content": "package com.genymobile.gnirehtet.relay;\n\nimport java.io.IOException;\nimport java.net.Inet4Address;\nimport java.net.InetSocketAddress;\nimport java.nio.channels.SelectionKey;\nimport java.nio.channels.Selector;\nimport java.nio.channels.ServerSocketChannel;\nimport java.nio.channels.SocketChannel;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Handle the connections from the clients.\n */\npublic class TunnelServer {\n\n    private static final String TAG = TunnelServer.class.getSimpleName();\n\n    private final List<Client> clients = new ArrayList<>();\n\n    public TunnelServer(int port, Selector selector) throws IOException {\n        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();\n        serverSocketChannel.configureBlocking(false);\n        // ServerSocketChannel.bind() requires API 24\n        serverSocketChannel.socket().bind(new InetSocketAddress(Inet4Address.getLoopbackAddress(), port));\n\n        SelectionHandler socketChannelHandler = (selectionKey) -> {\n            try {\n                ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();\n                acceptClient(selector, channel);\n            } catch (IOException e) {\n                Log.e(TAG, \"Cannot accept client\", e);\n            }\n        };\n        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT, socketChannelHandler);\n    }\n\n    private void acceptClient(Selector selector, ServerSocketChannel serverSocketChannel) throws IOException {\n        SocketChannel socketChannel = serverSocketChannel.accept();\n        socketChannel.configureBlocking(false);\n        // will register the socket on the selector\n        Client client = new Client(selector, socketChannel, this::removeClient);\n        clients.add(client);\n        Log.i(TAG, \"Client #\" + client.getId() + \" connected\");\n    }\n\n    private void removeClient(Client client) {\n        clients.remove(client);\n        Log.i(TAG, \"Client #\" + client.getId() + \" disconnected\");\n    }\n\n    public void cleanUp() {\n        for (Client client : clients) {\n            client.cleanExpiredConnections();\n        }\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/UDPConnection.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport java.io.IOException;\nimport java.nio.channels.DatagramChannel;\nimport java.nio.channels.SelectionKey;\nimport java.nio.channels.Selector;\n\npublic class UDPConnection extends AbstractConnection {\n\n    public static final long IDLE_TIMEOUT = 2 * 60 * 1000;\n\n    private static final String TAG = UDPConnection.class.getSimpleName();\n\n    private final DatagramBuffer clientToNetwork = new DatagramBuffer(4 * IPv4Packet.MAX_PACKET_LENGTH);\n    private final Packetizer networkToClient;\n\n    private final DatagramChannel channel;\n    private final SelectionKey selectionKey;\n    private int interests;\n\n    private long idleSince;\n\n    public UDPConnection(ConnectionId id, Client client, Selector selector, IPv4Header ipv4Header, UDPHeader udpHeader) throws IOException {\n        super(id, client);\n\n        networkToClient = new Packetizer(ipv4Header, udpHeader);\n        networkToClient.getResponseIPv4Header().swapSourceAndDestination();\n        networkToClient.getResponseTransportHeader().swapSourceAndDestination();\n\n        touch();\n\n        SelectionHandler selectionHandler = (selectionKey) -> {\n            touch();\n            if (selectionKey.isValid() && selectionKey.isReadable()) {\n                processReceive();\n            }\n            if (selectionKey.isValid() && selectionKey.isWritable()) {\n                processSend();\n            }\n            updateInterests();\n        };\n        channel = createChannel();\n        interests = SelectionKey.OP_READ;\n        selectionKey = channel.register(selector, interests, selectionHandler);\n    }\n\n    @Override\n    public void sendToNetwork(IPv4Packet packet) {\n        if (!clientToNetwork.readFrom(packet.getPayload())) {\n            logw(TAG, \"Cannot send to network, dropping packet\");\n            return;\n        }\n        updateInterests();\n    }\n\n    @Override\n    public void disconnect() {\n        logi(TAG, \"Close\");\n        selectionKey.cancel();\n        try {\n            channel.close();\n        } catch (IOException e) {\n            loge(TAG, \"Cannot close connection channel\", e);\n        }\n    }\n\n    @Override\n    public boolean isExpired() {\n        return System.currentTimeMillis() >= idleSince + IDLE_TIMEOUT;\n    }\n\n    private DatagramChannel createChannel() throws IOException {\n        logi(TAG, \"Open\");\n        DatagramChannel datagramChannel = DatagramChannel.open();\n        datagramChannel.configureBlocking(false);\n        datagramChannel.connect(getRewrittenDestination());\n        return datagramChannel;\n    }\n\n    private void touch() {\n        idleSince = System.currentTimeMillis();\n    }\n\n    private void processReceive() {\n        IPv4Packet packet = read();\n        if (packet == null) {\n            close();\n            return;\n        }\n        pushToClient(packet);\n    }\n\n    private void processSend() {\n        if (!write()) {\n            close();\n        }\n    }\n\n    private IPv4Packet read() {\n        try {\n            return networkToClient.packetize(channel);\n        } catch (IOException e) {\n            loge(TAG, \"Cannot read\", e);\n            return null;\n        }\n    }\n\n    private boolean write() {\n        try {\n            return clientToNetwork.writeTo(channel);\n        } catch (IOException e) {\n            loge(TAG, \"Cannot write\", e);\n            return false;\n        }\n    }\n\n    private void pushToClient(IPv4Packet packet) {\n        if (!sendToClient(packet)) {\n            logw(TAG, \"Cannot send to client, dropping packet\");\n            return;\n        }\n        logd(TAG, \"Packet (\" + packet.getPayloadLength() + \" bytes) sent to client\");\n        if (Log.isVerboseEnabled()) {\n            logv(TAG, Binary.buildPacketString(packet.getRaw()));\n        }\n    }\n\n    protected void updateInterests() {\n        if (!selectionKey.isValid()) {\n            return;\n        }\n        int interestOps = SelectionKey.OP_READ;\n        if (mayWrite()) {\n            interestOps |= SelectionKey.OP_WRITE;\n        }\n        if (interests != interestOps) {\n            // interests must be changed\n            interests = interestOps;\n            selectionKey.interestOps(interestOps);\n        }\n    }\n\n    private boolean mayWrite() {\n        return !clientToNetwork.isEmpty();\n    }\n}\n"
  },
  {
    "path": "relay-java/src/main/java/com/genymobile/gnirehtet/relay/UDPHeader.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport java.nio.ByteBuffer;\n\n@SuppressWarnings(\"checkstyle:MagicNumber\")\npublic class UDPHeader implements TransportHeader {\n\n    private static final int UDP_HEADER_LENGTH = 8;\n\n    private final ByteBuffer raw;\n    private int sourcePort;\n    private int destinationPort;\n\n    public UDPHeader(ByteBuffer raw) {\n        this.raw = raw;\n        raw.limit(UDP_HEADER_LENGTH);\n        sourcePort = Short.toUnsignedInt(raw.getShort(0));\n        destinationPort = Short.toUnsignedInt(raw.getShort(2));\n    }\n\n    @Override\n    public int getSourcePort() {\n        return sourcePort;\n    }\n\n    @Override\n    public int getDestinationPort() {\n        return destinationPort;\n    }\n\n    @Override\n    public void setSourcePort(int sourcePort) {\n        this.sourcePort = sourcePort;\n        raw.putShort(0, (short) sourcePort);\n    }\n\n    @Override\n    public void setDestinationPort(int destinationPort) {\n        this.destinationPort = destinationPort;\n        raw.putShort(2, (short) destinationPort);\n    }\n\n    @Override\n    public int getHeaderLength() {\n        return UDP_HEADER_LENGTH;\n    }\n\n    @Override\n    public void setPayloadLength(int payloadLength) {\n        int length = getHeaderLength() + payloadLength;\n        raw.putShort(4, (short) length);\n    }\n\n    @Override\n    public ByteBuffer getRaw() {\n        raw.rewind();\n        return raw.slice();\n    }\n\n    @Override\n    public UDPHeader copyTo(ByteBuffer target) {\n        raw.rewind();\n        ByteBuffer slice = Binary.slice(target, target.position(), getHeaderLength());\n        target.put(raw);\n        return new UDPHeader(slice);\n    }\n\n    @Override\n    public void computeChecksum(IPv4Header ipv4Header, ByteBuffer payload) {\n        // disable checksum validation\n        raw.putShort(6, (short) 0);\n    }\n}\n"
  },
  {
    "path": "relay-java/src/test/java/com/genymobile/gnirehtet/AdbMonitorTest.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\n\npublic class AdbMonitorTest {\n\n    private static ByteBuffer toByteBuffer(String s) {\n        return ByteBuffer.wrap(s.getBytes(StandardCharsets.US_ASCII));\n    }\n\n    @Test\n    public void testReadValidPacket() {\n        String data = \"00180123456789ABCDEF\\tdevice\\n\";\n        String result = AdbMonitor.readPacket(toByteBuffer(data));\n        Assert.assertEquals(\"0123456789ABCDEF\\tdevice\\n\", result);\n    }\n\n    @Test\n    public void testReadValidPackets() {\n        String data = \"00300123456789ABCDEF\\tdevice\\nFEDCBA9876543210\\tdevice\\n\";\n        String result = AdbMonitor.readPacket(toByteBuffer(data));\n        Assert.assertEquals(\"0123456789ABCDEF\\tdevice\\nFEDCBA9876543210\\tdevice\\n\", result);\n    }\n\n    @Test\n    public void testReadValidPacketWithGarbage() {\n        String data = \"00180123456789ABCDEF\\tdevice\\ngarbage\";\n        String result = AdbMonitor.readPacket(toByteBuffer(data));\n        Assert.assertEquals(\"0123456789ABCDEF\\tdevice\\n\", result);\n    }\n\n    @Test\n    public void testReadShortPacket() {\n        String data = \"00180123456789ABCDEF\\tdevi\";\n        String result = AdbMonitor.readPacket(toByteBuffer(data));\n        Assert.assertNull(result);\n    }\n\n    @Test\n    public void testHandlePacketDevice() {\n        final String[] pSerial = new String[1];\n        AdbMonitor monitor = new AdbMonitor((serial) -> pSerial[0] = serial);\n        String packet = \"0123456789ABCDEF\\tdevice\\n\";\n        monitor.handlePacket(packet);\n        Assert.assertEquals(\"0123456789ABCDEF\", pSerial[0]);\n    }\n\n    @Test\n    public void testHandlePacketOffline() {\n        final String[] pSerial = new String[1];\n        AdbMonitor monitor = new AdbMonitor((serial) -> pSerial[0] = serial);\n        String packet = \"0123456789ABCDEF\\toffline\\n\";\n        monitor.handlePacket(packet);\n        Assert.assertNull(pSerial[0]);\n    }\n\n    @Test\n    public void testMultipleConnectedDevices() {\n        final String[] pSerials = new String[2];\n        AdbMonitor monitor = new AdbMonitor(new AdbMonitor.AdbDevicesCallback() {\n            private int i;\n            @Override\n            public void onNewDeviceConnected(String serial) {\n                pSerials[i++] = serial;\n            }\n        });\n        String packet = \"0123456789ABCDEF\\tdevice\\nFEDCBA9876543210\\tdevice\\n\";\n        monitor.handlePacket(packet);\n        Assert.assertEquals(\"0123456789ABCDEF\", pSerials[0]);\n        Assert.assertEquals(\"FEDCBA9876543210\", pSerials[1]);\n    }\n\n    @Test\n    @SuppressWarnings(\"checkstyle:MagicNumber\")\n    public void testMultipleConnectedDevicesWithDisconnection() {\n        final String[] pSerials = new String[3];\n        AdbMonitor monitor = new AdbMonitor(new AdbMonitor.AdbDevicesCallback() {\n            private int i;\n            @Override\n            public void onNewDeviceConnected(String serial) {\n                pSerials[i++] = serial;\n            }\n        });\n        String packet = \"0123456789ABCDEF\\tdevice\\nFEDCBA9876543210\\tdevice\\n\";\n        monitor.handlePacket(packet);\n        packet = \"0123456789ABCDEF\\tdevice\\n\";\n        monitor.handlePacket(packet);\n        packet = \"0123456789ABCDEF\\tdevice\\nFEDCBA9876543210\\tdevice\\n\";\n        monitor.handlePacket(packet);\n        Assert.assertEquals(\"0123456789ABCDEF\", pSerials[0]);\n        Assert.assertEquals(\"FEDCBA9876543210\", pSerials[1]);\n        Assert.assertEquals(\"FEDCBA9876543210\", pSerials[2]);\n    }\n}\n"
  },
  {
    "path": "relay-java/src/test/java/com/genymobile/gnirehtet/CommandLineArgumentsTest.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\npublic class CommandLineArgumentsTest {\n\n    private static final int ACCEPT_ALL = CommandLineArguments.PARAM_SERIAL | CommandLineArguments.PARAM_DNS_SERVER\n            | CommandLineArguments.PARAM_ROUTES;\n\n    @Test\n    public void testNoArgs() {\n        CommandLineArguments args = CommandLineArguments.parse(ACCEPT_ALL);\n        Assert.assertNull(args.getSerial());\n        Assert.assertNull(args.getDnsServers());\n    }\n\n    @Test\n    public void testSerialOnly() {\n        CommandLineArguments args = CommandLineArguments.parse(ACCEPT_ALL, \"myserial\");\n        Assert.assertEquals(\"myserial\", args.getSerial());\n        Assert.assertNull(args.getDnsServers());\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testInvalidParameter() {\n        CommandLineArguments.parse(ACCEPT_ALL, \"myserial\", \"other\");\n    }\n\n    @Test\n    public void testDnsServersOnly() {\n        CommandLineArguments args = CommandLineArguments.parse(ACCEPT_ALL, \"-d\", \"8.8.8.8\");\n        Assert.assertNull(args.getSerial());\n        Assert.assertEquals(\"8.8.8.8\", args.getDnsServers());\n    }\n\n    @Test\n    public void testSerialAndDnsServers() {\n        CommandLineArguments args = CommandLineArguments.parse(ACCEPT_ALL, \"myserial\", \"-d\", \"8.8.8.8\");\n        Assert.assertEquals(\"myserial\", args.getSerial());\n        Assert.assertEquals(\"8.8.8.8\", args.getDnsServers());\n    }\n\n    @Test\n    public void testDnsServersAndSerial() {\n        CommandLineArguments args = CommandLineArguments.parse(ACCEPT_ALL, \"-d\", \"8.8.8.8\", \"myserial\");\n        Assert.assertEquals(\"myserial\", args.getSerial());\n        Assert.assertEquals(\"8.8.8.8\", args.getDnsServers());\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testSerialWithNoDnsServersParameter() {\n        CommandLineArguments.parse(ACCEPT_ALL, \"myserial\", \"-d\");\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testNoDnsServersParameter() {\n        CommandLineArguments.parse(ACCEPT_ALL, \"-d\");\n    }\n\n    @Test\n    public void testRoutesParameter() {\n        CommandLineArguments args = CommandLineArguments.parse(ACCEPT_ALL, \"-r\", \"1.2.3.0/24\");\n        Assert.assertEquals(\"1.2.3.0/24\", args.getRoutes());\n    }\n\n    @Test(expected = IllegalArgumentException.class)\n    public void testNoRoutesParameter() {\n        CommandLineArguments.parse(ACCEPT_ALL, \"-r\");\n    }\n}\n"
  },
  {
    "path": "relay-java/src/test/java/com/genymobile/gnirehtet/relay/DatagramBufferTest.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.Channels;\nimport java.nio.channels.WritableByteChannel;\n\n@SuppressWarnings(\"checkstyle:MagicNumber\")\npublic class DatagramBufferTest {\n\n    private static ByteBuffer createDatagram(int size) {\n        byte[] data = new byte[size];\n        for (int i = 0; i < size; ++i) {\n            data[i] = (byte) i;\n        }\n        return ByteBuffer.wrap(data);\n    }\n\n    @Test\n    public void testSimple() throws IOException {\n        ByteBuffer datagram = createDatagram(5);\n\n        DatagramBuffer datagramBuffer = new DatagramBuffer(9);\n        ByteArrayOutputStream bos = new ByteArrayOutputStream();\n        WritableByteChannel channel = Channels.newChannel(bos);\n\n        datagramBuffer.readFrom(datagram);\n        datagramBuffer.writeTo(channel);\n\n        byte[] result = bos.toByteArray();\n        Assert.assertArrayEquals(datagram.array(), result);\n    }\n\n    @Test\n    public void testDatagramBoundaries() throws IOException {\n        DatagramBuffer datagramBuffer = new DatagramBuffer(32);\n        ByteArrayOutputStream bos = new ByteArrayOutputStream();\n        WritableByteChannel channel = Channels.newChannel(bos);\n\n        ByteBuffer datagram5 = createDatagram(5);\n        ByteBuffer datagram0 = createDatagram(0);\n        ByteBuffer datagram3 = createDatagram(3);\n        ByteBuffer datagram4 = createDatagram(4);\n\n        datagramBuffer.readFrom(datagram5);\n        datagramBuffer.readFrom(datagram0);\n        datagramBuffer.readFrom(datagram3);\n        datagramBuffer.readFrom(datagram4);\n\n        datagramBuffer.writeTo(channel);\n        byte[] result = bos.toByteArray();\n        Assert.assertArrayEquals(datagram5.array(), result);\n\n        bos.reset();\n\n        datagramBuffer.writeTo(channel);\n        result = bos.toByteArray();\n        Assert.assertArrayEquals(datagram0.array(), result);\n\n        bos.reset();\n\n        datagramBuffer.writeTo(channel);\n        result = bos.toByteArray();\n        Assert.assertArrayEquals(datagram3.array(), result);\n\n        bos.reset();\n\n        datagramBuffer.writeTo(channel);\n        result = bos.toByteArray();\n        Assert.assertArrayEquals(datagram4.array(), result);\n    }\n\n    @Test\n    public void testCircular() throws IOException {\n        ByteBuffer datagram5 = createDatagram(5);\n        ByteBuffer datagram3 = createDatagram(3);\n\n        DatagramBuffer datagramBuffer = new DatagramBuffer(14);\n        ByteArrayOutputStream bos = new ByteArrayOutputStream();\n        WritableByteChannel channel = Channels.newChannel(bos);\n\n        // write and consume 10 bytes\n        datagramBuffer.readFrom(createDatagram(10));\n        datagramBuffer.writeTo(Channels.newChannel(new ByteArrayOutputStream())); // forget\n\n        // DatagramBuffer is expected to store the whole datagram (even if it exceeds its \"capacity\")\n        datagramBuffer.readFrom(datagram5);\n        datagramBuffer.readFrom(datagram3);\n\n        datagramBuffer.writeTo(channel);\n        byte[] result = bos.toByteArray();\n        Assert.assertArrayEquals(datagram5.array(), result);\n\n        bos.reset();\n\n        datagramBuffer.writeTo(channel);\n        result = bos.toByteArray();\n        Assert.assertArrayEquals(datagram3.array(), result);\n    }\n}\n"
  },
  {
    "path": "relay-java/src/test/java/com/genymobile/gnirehtet/relay/IPv4HeaderTest.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport org.junit.Assert;\nimport org.junit.Ignore;\nimport org.junit.Test;\n\nimport java.nio.ByteBuffer;\n\n@SuppressWarnings(\"checkstyle:MagicNumber\")\npublic class IPv4HeaderTest {\n\n    @Test\n    public void testReadIPVersionUnavailable() {\n        ByteBuffer buffer = ByteBuffer.allocate(20);\n        buffer.flip();\n        int firstPacketVersion = IPv4Header.readVersion(buffer);\n        Assert.assertEquals(\"IPv4 packet version must be unknown\", -1, firstPacketVersion);\n    }\n\n    @Test\n    public void testReadIPVersionAvailable() {\n        ByteBuffer buffer = ByteBuffer.allocate(20);\n        byte versionAndIHL = (4 << 4) | 5;\n        buffer.put(versionAndIHL);\n        buffer.flip();\n        int firstPacketVersion = IPv4Header.readVersion(buffer);\n        Assert.assertEquals(\"Wrong IP version field value\", 4, firstPacketVersion);\n    }\n\n    @Test\n    public void testReadLengthUnavailable() {\n        ByteBuffer buffer = ByteBuffer.allocate(20);\n        buffer.flip();\n        int firstPacketLength = IPv4Header.readLength(buffer);\n        Assert.assertEquals(\"IPv4 packet length must be unknown\", -1, firstPacketLength);\n    }\n\n    @Test\n    public void testReadLengthAvailable() {\n        ByteBuffer buffer = ByteBuffer.allocate(20);\n        buffer.put(2, (byte) 0x01);\n        buffer.put(3, (byte) 0x23);\n        buffer.position(20); // consider we wrote the whole header\n        buffer.flip();\n        int firstPacketLength = IPv4Header.readLength(buffer);\n        Assert.assertEquals(\"Wrong IP length field value\", 0x123, firstPacketLength);\n    }\n\n    private static ByteBuffer createMockHeaders() {\n        ByteBuffer buffer = ByteBuffer.allocate(28);\n\n        buffer.put((byte) ((4 << 4) | 5)); // versionAndIHL\n        buffer.put((byte) 0); // ToS\n        buffer.putShort((short) 28); // total length\n        buffer.putInt(0); // IdFlagsFragmentOffset\n        buffer.put((byte) 0); // TTL\n        buffer.put((byte) 17); // protocol (UDP)\n        buffer.putShort((short) 0); // checksum\n        buffer.putInt(0x12345678); // source address\n        buffer.putInt(0x42424242); // destination address\n\n        buffer.limit(28);\n        buffer.flip();\n\n        return buffer;\n    }\n\n    @Test\n    public void testParsePacketHeaders() {\n        IPv4Header header = new IPv4Header(createMockHeaders());\n        Assert.assertNotNull(\"Valid IPv4 header not parsed\", header);\n        Assert.assertTrue(header.isSupported());\n        Assert.assertEquals(IPv4Header.Protocol.UDP, header.getProtocol());\n        Assert.assertEquals(20, header.getHeaderLength());\n        Assert.assertEquals(28, header.getTotalLength());\n    }\n\n    @Test\n    public void testEditHeaders() {\n        ByteBuffer buffer = createMockHeaders();\n        IPv4Header header = new IPv4Header(buffer);\n\n        header.setSource(0x87654321);\n        header.setDestination(0x24242424);\n        header.setTotalLength(42);\n\n        Assert.assertEquals(0x87654321, header.getSource());\n        Assert.assertEquals(0x24242424, header.getDestination());\n        Assert.assertEquals(42, header.getTotalLength());\n\n        // assert the buffer has been modified\n        int source = buffer.getInt(12);\n        int destination = buffer.getInt(16);\n        int totalLength = Short.toUnsignedInt(buffer.getShort(2));\n\n        Assert.assertEquals(0x87654321, source);\n        Assert.assertEquals(0x24242424, destination);\n        Assert.assertEquals(42, totalLength);\n\n        header.swapSourceAndDestination();\n\n        Assert.assertEquals(0x24242424, header.getSource());\n        Assert.assertEquals(0x87654321, header.getDestination());\n\n        source = buffer.getInt(12);\n        destination = buffer.getInt(16);\n\n        Assert.assertEquals(0x24242424, source);\n        Assert.assertEquals(0x87654321, destination);\n    }\n\n    @Test\n    public void testComputeChecksum() {\n        ByteBuffer buffer = createMockHeaders();\n        IPv4Header header = new IPv4Header(buffer);\n\n        // set a fake checksum value to assert that it is correctly computed\n        buffer.putShort(10, (short) 0x79);\n\n        header.computeChecksum();\n\n        int sum = 0x4500 + 0x001c + 0x0000 + 0x0000 + 0x0011 + 0x0000 + 0x1234 + 0x5678 + 0x4242 + 0x4242;\n        while ((sum & ~0xffff) != 0) {\n            sum = (sum & 0xffff) + (sum >> 16);\n        }\n        short checksum = (short) ~sum;\n\n        Assert.assertEquals(checksum, header.getChecksum());\n    }\n\n    @Ignore // manual benchmark\n    @Test\n    public void benchComputeChecksum() {\n        ByteBuffer buffer = createMockHeaders();\n        IPv4Header header = new IPv4Header(buffer);\n\n        long start = System.currentTimeMillis();\n        for (int i = 0; i < 5000000; ++i) {\n            header.computeChecksum();\n        }\n        long duration = System.currentTimeMillis() - start;\n        System.out.println(\"5000000 IP checksums: \" + duration + \"ms\");\n    }\n}\n"
  },
  {
    "path": "relay-java/src/test/java/com/genymobile/gnirehtet/relay/IPv4PacketBufferTest.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.Channels;\nimport java.nio.channels.ReadableByteChannel;\n\n@SuppressWarnings(\"checkstyle:MagicNumber\")\npublic class IPv4PacketBufferTest {\n\n    private static ByteBuffer createMockPacket() {\n        ByteBuffer buffer = ByteBuffer.allocate(32);\n        writeMockPacketTo(buffer);\n        buffer.flip();\n        return buffer;\n    }\n\n    private static void writeMockPacketTo(ByteBuffer buffer) {\n        buffer.put((byte) ((4 << 4) | 5)); // versionAndIHL\n        buffer.put((byte) 0); // ToS\n        buffer.putShort((short) 32); // total length 20 + 8 + 4\n        buffer.putInt(0); // IdFlagsFragmentOffset\n        buffer.put((byte) 0); // TTL\n        buffer.put((byte) 17); // protocol (UDP)\n        buffer.putShort((short) 0); // checksum\n        buffer.putInt(0x12345678); // source address\n        buffer.putInt(0x42424242); // destination address\n\n        buffer.putShort((short) 1234); // source port\n        buffer.putShort((short) 5678); // destination port\n        buffer.putShort((short) 12); // length\n        buffer.putShort((short) 0); // checksum\n\n        buffer.putInt(0x11223344); // payload\n    }\n\n    private static ReadableByteChannel contentToChannel(ByteBuffer buffer) {\n        ByteArrayInputStream bis = new ByteArrayInputStream(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.limit());\n        return Channels.newChannel(bis);\n    }\n\n    @Test\n    public void testParseIPv4PacketBuffer() throws IOException {\n        ByteBuffer buffer = createMockPacket();\n\n        IPv4PacketBuffer packetBuffer = new IPv4PacketBuffer();\n\n        packetBuffer.readFrom(contentToChannel(buffer));\n\n        IPv4Packet packet = packetBuffer.asIPv4Packet();\n        Assert.assertNotNull(packet);\n\n        checkPacketHeaders(packet);\n    }\n\n    @Test\n    public void testParseFragmentedIPv4PacketBuffer() throws IOException {\n        ByteBuffer buffer = createMockPacket();\n\n        IPv4PacketBuffer packetBuffer = new IPv4PacketBuffer();\n\n        // onReadable the first 14 bytes\n        buffer.limit(14);\n        packetBuffer.readFrom(contentToChannel(buffer));\n\n        Assert.assertNull(packetBuffer.asIPv4Packet());\n\n        // onReadable the remaining\n        buffer.limit(32).position(14);\n        packetBuffer.readFrom(contentToChannel(buffer));\n\n        IPv4Packet packet = packetBuffer.asIPv4Packet();\n        Assert.assertNotNull(packet);\n\n        checkPacketHeaders(packet);\n    }\n\n    private static ByteBuffer createMockPackets() {\n        ByteBuffer buffer = ByteBuffer.allocate(32 * 3);\n        for (int i = 0; i < 3; ++i) {\n            writeMockPacketTo(buffer);\n        }\n        buffer.flip();\n        return buffer;\n    }\n\n    @Test\n    public void testMultiPackets() throws IOException {\n        ByteBuffer buffer = createMockPackets();\n\n        IPv4PacketBuffer packetBuffer = new IPv4PacketBuffer();\n        packetBuffer.readFrom(contentToChannel(buffer));\n\n        for (int i = 0; i < 3; ++i) {\n            IPv4Packet packet = packetBuffer.asIPv4Packet();\n            Assert.assertNotNull(packet);\n            checkPacketHeaders(packet);\n            packetBuffer.next();\n        }\n\n        // after the 3 packets have been consumed, there is nothing left\n        Assert.assertNull(packetBuffer.asIPv4Packet());\n    }\n\n    private static void checkPacketHeaders(IPv4Packet packet) {\n        IPv4Header ipv4Header = packet.getIpv4Header();\n        Assert.assertEquals(20, ipv4Header.getHeaderLength());\n        Assert.assertEquals(32, ipv4Header.getTotalLength());\n        Assert.assertEquals(IPv4Header.Protocol.UDP, ipv4Header.getProtocol());\n        Assert.assertEquals(0x12345678, ipv4Header.getSource());\n        Assert.assertEquals(0x42424242, ipv4Header.getDestination());\n\n        UDPHeader udpHeader = (UDPHeader) packet.getTransportHeader();\n        Assert.assertEquals(8, udpHeader.getHeaderLength());\n        Assert.assertEquals(1234, udpHeader.getSourcePort());\n        Assert.assertEquals(5678, udpHeader.getDestinationPort());\n    }\n}\n"
  },
  {
    "path": "relay-java/src/test/java/com/genymobile/gnirehtet/relay/IPv4PacketTest.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.nio.ByteBuffer;\n\n@SuppressWarnings(\"checkstyle:MagicNumber\")\npublic class IPv4PacketTest {\n\n    private static ByteBuffer createMockPacket() {\n        ByteBuffer buffer = ByteBuffer.allocate(32);\n\n        buffer.put((byte) ((4 << 4) | 5)); // versionAndIHL\n        buffer.put((byte) 0); // ToS\n        buffer.putShort((short) 32); // total length 20 + 8 + 4\n        buffer.putInt(0); // IdFlagsFragmentOffset\n        buffer.put((byte) 0); // TTL\n        buffer.put((byte) 17); // protocol (UDP)\n        buffer.putShort((short) 0); // checksum\n        buffer.putInt(0x12345678); // source address\n        buffer.putInt(0x42424242); // destination address\n\n        buffer.putShort((short) 1234); // source port\n        buffer.putShort((short) 5678); // destination port\n        buffer.putShort((short) 4); // length\n        buffer.putShort((short) 0); // checksum\n\n        buffer.putInt(0x11223344); // payload\n\n        return buffer;\n    }\n\n    @Test\n    public void testParseHeaders() {\n        ByteBuffer buffer = createMockPacket();\n        IPv4Packet packet = new IPv4Packet(buffer);\n\n        IPv4Header ipv4Header = packet.getIpv4Header();\n        Assert.assertTrue(ipv4Header.isSupported());\n        Assert.assertEquals(20, ipv4Header.getHeaderLength());\n        Assert.assertEquals(32, ipv4Header.getTotalLength());\n        Assert.assertEquals(IPv4Header.Protocol.UDP, ipv4Header.getProtocol());\n        Assert.assertEquals(0x12345678, ipv4Header.getSource());\n        Assert.assertEquals(0x42424242, ipv4Header.getDestination());\n\n        UDPHeader udpHeader = (UDPHeader) packet.getTransportHeader();\n        Assert.assertEquals(1234, udpHeader.getSourcePort());\n        Assert.assertEquals(5678, udpHeader.getDestinationPort());\n        Assert.assertEquals(8, udpHeader.getHeaderLength());\n\n        packet.swapSourceAndDestination();\n\n        Assert.assertEquals(0x42424242, ipv4Header.getSource());\n        Assert.assertEquals(0x12345678, ipv4Header.getDestination());\n        Assert.assertEquals(5678, udpHeader.getSourcePort());\n        Assert.assertEquals(1234, udpHeader.getDestinationPort());\n\n        int source = buffer.getInt(12);\n        int destination = buffer.getInt(16);\n        int sourcePort = Short.toUnsignedInt(buffer.getShort(20));\n        int destinationPort = Short.toUnsignedInt(buffer.getShort(22));\n\n        Assert.assertEquals(0x42424242, source);\n        Assert.assertEquals(0x12345678, destination);\n        Assert.assertEquals(5678, sourcePort);\n        Assert.assertEquals(1234, destinationPort);\n    }\n\n    @Test\n    public void testPayload() {\n        ByteBuffer buffer = createMockPacket();\n        IPv4Packet packet = new IPv4Packet(buffer);\n\n        ByteBuffer payload = packet.getPayload();\n        Assert.assertEquals(0x11223344, payload.getInt(0));\n    }\n}\n"
  },
  {
    "path": "relay-java/src/test/java/com/genymobile/gnirehtet/relay/InetAddressTest.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.net.InetAddress;\n\n@SuppressWarnings(\"checkstyle:MagicNumber\")\npublic class InetAddressTest {\n\n    @Test\n    public void testIntToInetAddress() {\n        int ip = 0x01020304;\n        InetAddress addr = Net.toInetAddress(ip);\n        Assert.assertEquals(\"1.2.3.4\", addr.getHostAddress());\n    }\n\n    @Test\n    public void testUnsignedIntToInetAddress() {\n        int ip = 0xff020304;\n        InetAddress addr = Net.toInetAddress(ip);\n        Assert.assertEquals(\"255.2.3.4\", addr.getHostAddress());\n    }\n}\n"
  },
  {
    "path": "relay-java/src/test/java/com/genymobile/gnirehtet/relay/PacketizerTest.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.Channels;\nimport java.nio.channels.ReadableByteChannel;\n\n@SuppressWarnings(\"checkstyle:MagicNumber\")\npublic class PacketizerTest {\n\n    private static ByteBuffer createMockPacket() {\n        ByteBuffer buffer = ByteBuffer.allocate(32);\n\n        buffer.put((byte) ((4 << 4) | 5)); // versionAndIHL\n        buffer.put((byte) 0); // ToS\n        buffer.putShort((short) 32); // total length 20 + 8 + 4\n        buffer.putInt(0); // IdFlagsFragmentOffset\n        buffer.put((byte) 0); // TTL\n        buffer.put((byte) 17); // protocol (UDP)\n        buffer.putShort((short) 0); // checksum\n        buffer.putInt(0x12345678); // source address\n        buffer.putInt(0x42424242); // destination address\n\n        buffer.putShort((short) 1234); // source port\n        buffer.putShort((short) 5678); // destination port\n        buffer.putShort((short) 4); // length\n        buffer.putShort((short) 0); // checksum\n\n        buffer.putInt(0x11223344); // payload\n\n        return buffer;\n    }\n\n    @Test\n    public void testMergeHeadersAndPayload() throws IOException {\n        IPv4Packet referencePacket = new IPv4Packet(createMockPacket());\n        IPv4Header ipv4Header = referencePacket.getIpv4Header();\n        TransportHeader transportHeader = referencePacket.getTransportHeader();\n\n        byte[] data = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, (byte) 0x88};\n        ReadableByteChannel channel = Channels.newChannel(new ByteArrayInputStream(data));\n\n        Packetizer packetizer = new Packetizer(ipv4Header, transportHeader);\n        IPv4Packet packet = packetizer.packetize(channel);\n        Assert.assertEquals(36, packet.getIpv4Header().getTotalLength());\n\n        ByteBuffer packetPayload = packet.getPayload();\n        Assert.assertEquals(8, packetPayload.remaining());\n        Assert.assertEquals(0x1122334455667788L, packetPayload.getLong());\n    }\n\n    @Test\n    public void testPacketizeChunks() throws IOException {\n        IPv4Packet originalPacket = new IPv4Packet(createMockPacket());\n        IPv4Header ipv4Header = originalPacket.getIpv4Header();\n        TransportHeader transportHeader = originalPacket.getTransportHeader();\n\n        byte[] data = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, (byte) 0x88};\n        ReadableByteChannel channel = Channels.newChannel(new ByteArrayInputStream(data));\n\n        Packetizer packetizer = new Packetizer(ipv4Header, transportHeader);\n\n        IPv4Packet packet = packetizer.packetize(channel, 2);\n        ByteBuffer packetPayload = packet.getPayload();\n\n        Assert.assertEquals(30, packet.getIpv4Header().getTotalLength());\n        Assert.assertEquals(2, packetPayload.remaining());\n        Assert.assertEquals(0x1122, Short.toUnsignedInt(packetPayload.getShort()));\n\n        packet = packetizer.packetize(channel, 3);\n        packetPayload = packet.getPayload();\n        Assert.assertEquals(31, packet.getIpv4Header().getTotalLength());\n        Assert.assertEquals(3, packetPayload.remaining());\n        Assert.assertEquals(0x33, packetPayload.get());\n        Assert.assertEquals(0x44, packetPayload.get());\n        Assert.assertEquals(0x55, packetPayload.get());\n\n        packet = packetizer.packetize(channel, 1024);\n        packetPayload = packet.getPayload();\n        Assert.assertEquals(31, packet.getIpv4Header().getTotalLength());\n        Assert.assertEquals(3, packetPayload.remaining());\n        Assert.assertEquals(0x66, packetPayload.get());\n        Assert.assertEquals(0x77, packetPayload.get());\n        Assert.assertEquals((byte) 0x88, packetPayload.get());\n    }\n}\n"
  },
  {
    "path": "relay-java/src/test/java/com/genymobile/gnirehtet/relay/StreamBufferTest.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.ByteChannel;\nimport java.nio.channels.Channels;\nimport java.nio.channels.WritableByteChannel;\n\n@SuppressWarnings(\"checkstyle:MagicNumber\")\npublic class StreamBufferTest {\n\n    private static ByteBuffer createChunk() {\n        byte[] data = {0, 1, 2, 3, 4, 5};\n        return ByteBuffer.wrap(data);\n    }\n\n    @Test\n    public void testSimple() throws IOException {\n        ByteBuffer buffer = createChunk();\n\n        StreamBuffer streamBuffer = new StreamBuffer(9);\n        ByteArrayOutputStream bos = new ByteArrayOutputStream();\n        WritableByteChannel channel = Channels.newChannel(bos);\n\n        streamBuffer.readFrom(buffer);\n        streamBuffer.writeTo(channel);\n\n        byte[] result = bos.toByteArray();\n        Assert.assertArrayEquals(buffer.array(), result);\n    }\n\n    static class DevNullChannel implements ByteChannel {\n\n        private int writeChunkSize;\n\n        DevNullChannel(int writeChunkSize) {\n            this.writeChunkSize = writeChunkSize;\n        }\n\n        @Override\n        public int read(ByteBuffer byteBuffer) {\n            return 0;\n        }\n\n        @Override\n        public int write(ByteBuffer byteBuffer) {\n            int consume = Math.min(writeChunkSize, byteBuffer.remaining());\n            byteBuffer.position(byteBuffer.position() + consume);\n            return consume;\n        }\n\n        @Override\n        public boolean isOpen() {\n            return false;\n        }\n\n        @Override\n        public void close() {\n            // do nothing\n        }\n    }\n\n    @Test\n    public void testCircular() throws IOException {\n        ByteBuffer buffer = createChunk();\n\n        StreamBuffer streamBuffer = new StreamBuffer(9);\n        ByteArrayOutputStream bos = new ByteArrayOutputStream();\n        WritableByteChannel channel = Channels.newChannel(bos);\n\n        // put 6 bytes\n        streamBuffer.readFrom(buffer);\n        // consume 3 bytes\n        streamBuffer.writeTo(new DevNullChannel(3));\n\n        // put test data\n        buffer.rewind();\n        streamBuffer.readFrom(buffer);\n\n        // consume 3 bytes (so that the first 6 bytes are totally consumed, and the \"tail\" position is 6)\n        streamBuffer.writeTo(new DevNullChannel(3));\n\n        // consume test data\n        streamBuffer.writeTo(channel);\n\n        // StreamBuffer is expected to break writes at circular buffer boundaries (capacity + 1)\n        // This is not a requirement, but this verifies that the implementation works as expected\n        byte[] result = bos.toByteArray();\n        byte[] expected = {0, 1, 2, 3};\n        Assert.assertArrayEquals(expected, result);\n\n        // write the remaining\n        streamBuffer.writeTo(channel);\n        result = bos.toByteArray();\n        Assert.assertArrayEquals(buffer.array(), result);\n    }\n\n    @Test\n    public void testNotEnoughSpace() throws IOException {\n        ByteBuffer buffer = createChunk();\n\n        StreamBuffer streamBuffer = new StreamBuffer(9);\n        ByteArrayOutputStream bos = new ByteArrayOutputStream();\n        WritableByteChannel channel = Channels.newChannel(bos);\n\n        streamBuffer.readFrom(buffer);\n        buffer.rewind();\n        streamBuffer.readFrom(buffer);\n\n        Assert.assertEquals(3, buffer.remaining());\n\n        streamBuffer.writeTo(channel);\n\n        byte[] result = bos.toByteArray();\n        byte[] expected = {0, 1, 2, 3, 4, 5, 0, 1, 2};\n        Assert.assertArrayEquals(expected, result);\n    }\n}\n"
  },
  {
    "path": "relay-java/src/test/java/com/genymobile/gnirehtet/relay/TCPHeaderTest.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport org.junit.Assert;\nimport org.junit.Ignore;\nimport org.junit.Test;\n\nimport java.nio.ByteBuffer;\n\n@SuppressWarnings(\"checkstyle:MagicNumber\")\npublic class TCPHeaderTest {\n\n    private static ByteBuffer createMockPacket() {\n        ByteBuffer buffer = ByteBuffer.allocate(64);\n\n        buffer.put((byte) ((4 << 4) | 5)); // versionAndIHL\n        buffer.put((byte) 0); // ToS\n        buffer.putShort((short) 44); // total length 20 + 20 + 4\n        buffer.putInt(0); // IdFlagsFragmentOffset\n        buffer.put((byte) 0); // TTL\n        buffer.put((byte) 6); // protocol (TCP)\n        buffer.putShort((short) 0); // checksum\n        buffer.putInt(0x12345678); // source address\n        buffer.putInt(0xa2a24242); // destination address\n\n        buffer.putShort((short) 0x1234); // source port\n        buffer.putShort((short) 0x5678); // destination port\n        buffer.putInt(0x111); // sequence number\n        buffer.putInt(0x222); // acknowledgment number\n        buffer.putShort((short) (5 << 12)); // data offset + flags(0)\n        buffer.putShort((short) 0); // window (don't care for these tests)\n        buffer.putShort((short) 0); // checksum\n        buffer.putShort((short) 0); // urgent pointer\n\n        buffer.putInt(0x1122eeff); // payload\n\n        return buffer;\n    }\n\n    private static ByteBuffer createMockOddPacket() {\n        ByteBuffer buffer = ByteBuffer.allocate(64);\n\n        buffer.put((byte) ((4 << 4) | 5)); // versionAndIHL\n        buffer.put((byte) 0); // ToS\n        buffer.putShort((short) 45); // total length 20 + 20 + 5\n        buffer.putInt(0); // IdFlagsFragmentOffset\n        buffer.put((byte) 0); // TTL\n        buffer.put((byte) 6); // protocol (TCP)\n        buffer.putShort((short) 0); // checksum\n        buffer.putInt(0x12345678); // source address\n        buffer.putInt(0xa2a24242); // destination address\n\n        buffer.putShort((short) 0x1234); // source port\n        buffer.putShort((short) 0x5678); // destination port\n        buffer.putInt(0x111); // sequence number\n        buffer.putInt(0x222); // acknowledgment number\n        buffer.putShort((short) (5 << 12)); // data offset + flags(0)\n        buffer.putShort((short) 0); // window (don't care for these tests)\n        buffer.putShort((short) 0); // checksum\n        buffer.putShort((short) 0); // urgent pointer\n\n        // payload\n        buffer.putInt(0x1122eeff);\n        buffer.put((byte) 0x88);\n\n        return buffer;\n    }\n\n    private static ByteBuffer createMockTCPHeader() {\n        ByteBuffer buffer = ByteBuffer.allocate(20);\n\n        buffer.putShort((short) 0x1234); // source port\n        buffer.putShort((short) 0x5678); // destination port\n        buffer.putInt(0x111); // sequence number\n        buffer.putInt(0x222); // acknowledgment number\n        buffer.putShort((short) (5 << 12)); // data offset + flags(0)\n        buffer.putShort((short) 0); // window (don't care for these tests)\n        buffer.putShort((short) 0); // checksum\n        buffer.putShort((short) 0); // urgent pointer\n\n        buffer.flip();\n        return buffer;\n    }\n\n    @Test\n    public void testEditHeaders() {\n        ByteBuffer buffer = createMockTCPHeader();\n        TCPHeader header = new TCPHeader(buffer);\n\n        header.setSourcePort(1111);\n        header.setDestinationPort(2222);\n        header.setSequenceNumber(300);\n        header.setAcknowledgementNumber(101);\n        header.setFlags(TCPHeader.FLAG_FIN | TCPHeader.FLAG_ACK);\n\n        Assert.assertEquals(1111, header.getSourcePort());\n        Assert.assertEquals(2222, header.getDestinationPort());\n        Assert.assertEquals(300, header.getSequenceNumber());\n        Assert.assertEquals(101, header.getAcknowledgementNumber());\n        Assert.assertEquals(TCPHeader.FLAG_FIN | TCPHeader.FLAG_ACK, header.getFlags());\n\n        // assert the buffer has been modified\n        int sourcePort = Short.toUnsignedInt(buffer.getShort(0));\n        int destinationPort = Short.toUnsignedInt(buffer.getShort(2));\n        int sequenceNumber = buffer.getInt(4);\n        int acknowledgementNumber = buffer.getInt(8);\n        short dataOffsetAndFlags = buffer.getShort(12);\n\n        Assert.assertEquals(1111, sourcePort);\n        Assert.assertEquals(2222, destinationPort);\n        Assert.assertEquals(300, sequenceNumber);\n        Assert.assertEquals(101, acknowledgementNumber);\n        Assert.assertEquals(0x5011, dataOffsetAndFlags);\n\n        header.swapSourceAndDestination();\n\n        Assert.assertEquals(2222, header.getSourcePort());\n        Assert.assertEquals(1111, header.getDestinationPort());\n\n        sourcePort = Short.toUnsignedInt(buffer.getShort(0));\n        destinationPort = Short.toUnsignedInt(buffer.getShort(2));\n\n        Assert.assertEquals(2222, sourcePort);\n        Assert.assertEquals(1111, destinationPort);\n    }\n\n    @Test\n    public void testComputeChecksum() {\n        ByteBuffer buffer = createMockPacket();\n        buffer.flip();\n        IPv4Packet packet = new IPv4Packet(buffer);\n        TCPHeader tcpHeader = (TCPHeader) packet.getTransportHeader();\n\n        // set a fake checksum value to assert that it is correctly computed\n        buffer.putShort(36, (short) 0x79);\n\n        tcpHeader.computeChecksum(packet.getIpv4Header(), packet.getPayload());\n\n        // pseudo-header\n        int sum = 0x1234 + 0x5678 + 0xa2a2 + 0x4242 + 0x0006 + 0x0018;\n\n        // header\n        sum += 0x1234 + 0x5678 + 0x0000 + 0x0111 + 0x0000 + 0x0222 + 0x5000 + 0x0000 + 0x0000 + 0x0000;\n\n        // payload\n        sum += 0x1122 + 0xeeff;\n\n        while ((sum & ~0xffff) != 0) {\n            sum = (sum & 0xffff) + (sum >> 16);\n        }\n        short checksum = (short) ~sum;\n\n        Assert.assertEquals(checksum, tcpHeader.getChecksum());\n    }\n\n    @Test\n    public void testComputeChecksumOddLength() {\n        ByteBuffer buffer = createMockOddPacket();\n        buffer.flip();\n\n        IPv4Packet packet = new IPv4Packet(buffer);\n        TCPHeader tcpHeader = (TCPHeader) packet.getTransportHeader();\n\n        // set a fake checksum value to assert that it is correctly computed\n        buffer.putShort(36, (short) 0x79);\n\n        tcpHeader.computeChecksum(packet.getIpv4Header(), packet.getPayload());\n\n        // pseudo-header\n        int sum = 0x1234 + 0x5678 + 0xa2a2 + 0x4242 + 0x0006 + 0x0019;\n\n        // header\n        sum += 0x1234 + 0x5678 + 0x0000 + 0x0111 + 0x0000 + 0x0222 + 0x5000 + 0x0000 + 0x0000 + 0x0000;\n\n        // payload\n        sum += 0x1122 + 0xeeff + 0x8800;\n\n        while ((sum & ~0xffff) != 0) {\n            sum = (sum & 0xffff) + (sum >> 16);\n        }\n        short checksum = (short) ~sum;\n\n        Assert.assertEquals(checksum, tcpHeader.getChecksum());\n    }\n\n    @Test\n    public void testCopyTo() {\n        ByteBuffer buffer = createMockTCPHeader();\n        TCPHeader header = new TCPHeader(buffer);\n\n        ByteBuffer target = ByteBuffer.allocate(40);\n        target.position(12);\n        TCPHeader copy = header.copyTo(target);\n        copy.setSourcePort(9999);\n\n        Assert.assertEquals(32, target.position());\n        Assert.assertEquals(\"Header must modify target\", 9999, target.getShort(12));\n        Assert.assertEquals(\"Header must not modify buffer\", 0x1234, buffer.getShort(0));\n    }\n\n    private static ByteBuffer createLongPacket() {\n        ByteBuffer buffer = ByteBuffer.allocate(2048);\n\n        buffer.put((byte) ((4 << 4) | 5)); // versionAndIHL\n        buffer.put((byte) 0); // ToS\n        buffer.putShort((short) 1240); // total length 20 + 20 + 1200\n        buffer.putInt(0); // IdFlagsFragmentOffset\n        buffer.put((byte) 0); // TTL\n        buffer.put((byte) 6); // protocol (TCP)\n        buffer.putShort((short) 0); // checksum\n        buffer.putInt(0x12345678); // source address\n        buffer.putInt(0xa2a24242); // destination address\n\n        buffer.putShort((short) 0x1234); // source port\n        buffer.putShort((short) 0x5678); // destination port\n        buffer.putInt(0x111); // sequence number\n        buffer.putInt(0x222); // acknowledgment number\n        buffer.putShort((short) (5 << 12)); // data offset + flags(0)\n        buffer.putShort((short) 0); // window (don't care for these tests)\n        buffer.putShort((short) 0); // checksum\n        buffer.putShort((short) 0); // urgent pointer\n\n        // payload\n        for (int i = 0; i < 1200; ++i) {\n            buffer.put((byte) i);\n        }\n\n        return buffer;\n    }\n\n    @Ignore // manual benchmark\n    @Test\n    public void benchComputeChecksum() {\n        ByteBuffer buffer = createLongPacket();\n        buffer.flip();\n        IPv4Packet packet = new IPv4Packet(buffer);\n        TCPHeader tcpHeader = (TCPHeader) packet.getTransportHeader();\n\n        long start = System.currentTimeMillis();\n        for (int i = 0; i < 5000000; ++i) {\n            tcpHeader.computeChecksum(packet.getIpv4Header(), packet.getPayload());\n        }\n        long duration = System.currentTimeMillis() - start;\n        System.out.println(\"5000000 TCP checksums: \" + duration + \"ms\");\n    }\n}\n"
  },
  {
    "path": "relay-java/src/test/java/com/genymobile/gnirehtet/relay/UDPHeaderTest.java",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.genymobile.gnirehtet.relay;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.nio.ByteBuffer;\n\n@SuppressWarnings(\"checkstyle:MagicNumber\")\npublic class UDPHeaderTest {\n\n    private static ByteBuffer createMockHeaders() {\n        ByteBuffer buffer = ByteBuffer.allocate(8);\n\n        buffer.putShort((short) 1234); // source port\n        buffer.putShort((short) 5678); // destination port\n        buffer.putShort((short) 42); // length\n        buffer.putShort((short) 0); // checksum\n\n        return buffer;\n    }\n\n    @Test\n    public void testParsePacketHeaders() {\n        UDPHeader header = new UDPHeader(createMockHeaders());\n        Assert.assertNotNull(\"Valid UDP header not parsed\", header);\n        Assert.assertEquals(1234, header.getSourcePort());\n        Assert.assertEquals(5678, header.getDestinationPort());\n    }\n\n    @Test\n    public void testEditHeaders() {\n        ByteBuffer buffer = createMockHeaders();\n        UDPHeader header = new UDPHeader(buffer);\n\n        header.setSourcePort(1111);\n        header.setDestinationPort(2222);\n        header.setPayloadLength(34);\n\n        Assert.assertEquals(1111, header.getSourcePort());\n        Assert.assertEquals(2222, header.getDestinationPort());\n\n        // assert the buffer has been modified\n        int sourcePort = Short.toUnsignedInt(buffer.getShort(0));\n        int destinationPort = Short.toUnsignedInt(buffer.getShort(2));\n        int length = Short.toUnsignedInt(buffer.getShort(4));\n\n        Assert.assertEquals(1111, sourcePort);\n        Assert.assertEquals(2222, destinationPort);\n        Assert.assertEquals(42, length);\n\n        header.swapSourceAndDestination();\n\n        Assert.assertEquals(2222, header.getSourcePort());\n        Assert.assertEquals(1111, header.getDestinationPort());\n\n        sourcePort = Short.toUnsignedInt(buffer.getShort(0));\n        destinationPort = Short.toUnsignedInt(buffer.getShort(2));\n\n        Assert.assertEquals(2222, sourcePort);\n        Assert.assertEquals(1111, destinationPort);\n    }\n\n    @Test\n    public void testCopyTo() {\n        ByteBuffer buffer = createMockHeaders();\n        UDPHeader header = new UDPHeader(buffer);\n\n        ByteBuffer target = ByteBuffer.allocate(32);\n        target.position(12);\n        UDPHeader copy = header.copyTo(target);\n        copy.setSourcePort(9999);\n\n        Assert.assertEquals(20, target.position());\n        Assert.assertEquals(\"Header must modify target\", 9999, target.getShort(12));\n        Assert.assertEquals(\"Header must not modify buffer\", 1234, buffer.getShort(0));\n    }\n}\n"
  },
  {
    "path": "relay-rust/.gitignore",
    "content": "target/\n**/*.rs.bk\n"
  },
  {
    "path": "relay-rust/Cargo.toml",
    "content": "[package]\nname = \"gnirehtet\"\nversion = \"2.5.1\"\nauthors = [\"Romain Vimont <rom@rom1v.com>\"]\nedition = \"2018\"\n\n[lib]\nname = \"relaylib\"\npath = \"src/lib.rs\"\n\n[dependencies]\nmio = \"0.6\"       # for async I/O\nslab = \"0.4\"      # helper for mio Tokens\nlog = \"0.4\"       # for logs\nchrono = \"0.4\"    # for formatting timestamp in logs\nbyteorder = \"1.3\" # for reading/writing binary\nrand = \"0.7\"      # for random TCP sequence number\nctrlc = { version = \"3.0\", features = [\"termination\"] }     # for handling Ctrl+C\n\n[profile.release]\nlto = true     # link-time optimization\n"
  },
  {
    "path": "relay-rust/build.gradle",
    "content": "task debug(type: Exec) {\n    commandLine 'cargo', 'build'\n}\n\ntask release(type: Exec) {\n    commandLine 'cargo', 'build', '--release'\n}\n\ntask test(type: Exec) {\n    commandLine 'cargo', 'test'\n}\n\ntask install(type: Exec) {\n    commandLine 'cargo', 'install'\n}\n\ntask clean(type: Exec) {\n    commandLine 'cargo', 'clean'\n}\n\ntask checkstyle(type: Exec) {\n    commandLine 'cargo', 'fmt', '--', '--check'\n}\n\ntask check(dependsOn: ['checkstyle', 'test'])\ntask build(dependsOn: ['check', 'debug', 'release'])\n\n// Requirements:\n//\n// Install the cross-compile toolchain (on Debian):\n//   sudo apt install gcc-mingw-w64-x86-64\n//   rustup target add x86_64-pc-windows-gnu\n//\n// Add the following lines to ~/.cargo/config:\n//   [target.x86_64-pc-windows-gnu]\n//   linker = \"x86_64-w64-mingw32-gcc\"\n//   ar = \"x86_64-w64-mingw32-gcc-ar\"\ntask releaseCrossToWindows(type: Exec) {\n    commandLine 'cargo', 'build', '--release', '--target=x86_64-pc-windows-gnu'\n}\n"
  },
  {
    "path": "relay-rust/scripts/gnirehtet-run.cmd",
    "content": "@gnirehtet.exe run\r\n@pause\r\n"
  },
  {
    "path": "relay-rust/src/adb_monitor.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse log::*;\nuse relaylib::byte_buffer::ByteBuffer;\nuse std::io::{self, Write};\nuse std::net::{SocketAddr, TcpStream};\nuse std::process;\nuse std::str;\nuse std::thread;\nuse std::time::Duration;\n\nconst TAG: &str = \"AdbMonitor\";\n\npub trait AdbMonitorCallback {\n    fn on_new_device_connected(&self, serial: &str);\n}\n\nimpl<F> AdbMonitorCallback for F\nwhere\n    F: Fn(&str),\n{\n    fn on_new_device_connected(&self, serial: &str) {\n        self(serial);\n    }\n}\npub struct AdbMonitor {\n    callback: Box<dyn AdbMonitorCallback>,\n    buf: ByteBuffer,\n    connected_devices: Vec<String>,\n}\n\nimpl AdbMonitor {\n    const TRACK_DEVICES_REQUEST: &'static [u8] = b\"0012host:track-devices\";\n    const BUFFER_SIZE: usize = 1024;\n    const RETRY_DELAY_ADB_DAEMON_OK: u64 = 1000;\n    const RETRY_DELAY_ADB_DAEMON_KO: u64 = 5000;\n\n    pub fn new(callback: Box<dyn AdbMonitorCallback>) -> Self {\n        Self {\n            callback,\n            buf: ByteBuffer::new(Self::BUFFER_SIZE),\n            connected_devices: Vec::new(),\n        }\n    }\n\n    pub fn monitor(&mut self) {\n        loop {\n            if let Err(err) = self.track_devices() {\n                error!(target: TAG, \"Failed to monitor adb devices: {}\", err);\n                Self::repair_adb_daemon();\n            }\n        }\n    }\n\n    fn track_devices(&mut self) -> io::Result<()> {\n        let adbd_addr = SocketAddr::from(([127, 0, 0, 1], 5037));\n        let mut stream = TcpStream::connect(adbd_addr)?;\n        self.track_devices_on_stream(&mut stream)\n    }\n\n    fn track_devices_on_stream(&mut self, stream: &mut TcpStream) -> io::Result<()> {\n        stream.write_all(Self::TRACK_DEVICES_REQUEST)?;\n        if self.consume_okay(stream)? {\n            loop {\n                let packet = self.next_packet(stream)?;\n                self.handle_packet(packet.as_str());\n            }\n        }\n        Ok(())\n    }\n\n    fn consume_okay(&mut self, stream: &mut TcpStream) -> io::Result<bool> {\n        while self.buf.peek().len() < 4 {\n            self.buf.read_from(stream)?;\n        }\n        let ok = b\"OKAY\" == &self.buf.peek()[0..4];\n        self.buf.consume(4);\n        Ok(ok)\n    }\n\n    fn read_packet(buf: &mut ByteBuffer) -> io::Result<Option<String>> {\n        let packet_length = Self::available_packet_length(buf.peek())?;\n        if let Some(len) = packet_length {\n            // retrieve the content and consume the packet\n            let data = Self::binary_to_string(&buf.peek()[4..len])?;\n            buf.consume(len);\n            Ok(Some(data))\n        } else {\n            Ok(None)\n        }\n    }\n\n    fn next_packet(&mut self, stream: &mut TcpStream) -> io::Result<String> {\n        loop {\n            let packet = Self::read_packet(&mut self.buf)?;\n            if let Some(packet) = packet {\n                return Ok(packet);\n            } else {\n                self.fill_buffer_from(stream)?;\n            }\n        }\n    }\n\n    fn fill_buffer_from(&mut self, stream: &mut TcpStream) -> io::Result<()> {\n        match self.buf.read_from(stream) {\n            Ok(false) => Err(io::Error::new(\n                io::ErrorKind::UnexpectedEof,\n                \"ADB daemon closed the track-devices connexion\",\n            )),\n            Ok(_) => Ok(()),\n            Err(err) => Err(err),\n        }\n    }\n\n    fn available_packet_length(input: &[u8]) -> io::Result<Option<usize>> {\n        if input.len() < 4 {\n            Ok(None)\n        } else {\n            // each packet contains 4 bytes representing the String length in hexa, followed by a\n            // list of device information;\n            // each line contains: the device serial, `\\t', the state, '\\n'\n            // for example:\n            // \"00360123456789abcdef\\tdevice\\nfedcba9876543210\\tunauthorized\\n\":\n            //  - 0036 indicates that the data is 0x36 (54) bytes length\n            //  - the device with serial 0123456789abcdef is connected\n            //  - the device with serial fedcba9876543210 is unauthorized\n            let len = Self::parse_length(&input[0..4])?;\n            if len > Self::BUFFER_SIZE as u32 {\n                return Err(io::Error::new(\n                    io::ErrorKind::InvalidInput,\n                    format!(\"Packet size should not be that big: {}\", len),\n                ));\n            }\n            if input.len() - 4usize >= len as usize {\n                Ok(Some(4usize + len as usize))\n            } else {\n                // not enough data\n                Ok(None)\n            }\n        }\n    }\n\n    fn handle_packet(&mut self, packet: &str) {\n        let current_connected_devices = self.parse_connected_devices(packet);\n        for serial in &current_connected_devices {\n            if !self.connected_devices.contains(serial) {\n                self.callback.on_new_device_connected(serial.as_str());\n            }\n        }\n        self.connected_devices = current_connected_devices;\n    }\n\n    fn parse_connected_devices(&self, packet: &str) -> Vec<String> {\n        packet\n            .lines()\n            .filter_map(|line| {\n                let mut split = line.split_whitespace();\n                if let Some(serial) = split.next() {\n                    if let Some(state) = split.next() {\n                        if state == \"device\" {\n                            return Some(serial.to_string());\n                        }\n                    }\n                }\n                None\n            })\n            .collect()\n    }\n\n    fn parse_length(data: &[u8]) -> io::Result<u32> {\n        assert!(data.len() == 4, \"Invalid length field value\");\n        let hexa = str::from_utf8(data).map_err(|err| {\n            io::Error::new(\n                io::ErrorKind::InvalidInput,\n                format!(\"Cannot read hexa length as UTF-8 ({})\", err),\n            )\n        })?;\n        u32::from_str_radix(hexa, 0x10).map_err(|err| {\n            io::Error::new(\n                io::ErrorKind::InvalidInput,\n                format!(\"Cannot parse hexa length ({})\", err),\n            )\n        })\n    }\n\n    fn repair_adb_daemon() {\n        if Self::start_adb_daemon() {\n            thread::sleep(Duration::from_millis(Self::RETRY_DELAY_ADB_DAEMON_OK));\n        } else {\n            thread::sleep(Duration::from_millis(Self::RETRY_DELAY_ADB_DAEMON_KO));\n        }\n    }\n\n    fn start_adb_daemon() -> bool {\n        info!(target: TAG, \"Restarting adb daemon\");\n        match process::Command::new(\"adb\")\n            .args(&[\"start-server\"])\n            .status()\n        {\n            Ok(exit_status) => {\n                if exit_status.success() {\n                    true\n                } else {\n                    error!(\n                        target: TAG,\n                        \"Could not restart adb daemon (exited on error)\"\n                    );\n                    false\n                }\n            }\n            Err(err) => {\n                error!(target: TAG, \"Could not restart adb daemon: {}\", err);\n                false\n            }\n        }\n    }\n\n    fn binary_to_string(data: &[u8]) -> io::Result<String> {\n        let raw_content = data.to_vec();\n        let content = String::from_utf8(raw_content);\n        if let Ok(content) = content {\n            Ok(content)\n        } else {\n            Err(io::Error::new(\n                io::ErrorKind::InvalidInput,\n                \"Track-devices string is not valid UTF-8\",\n            ))\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use std::cell::RefCell;\n    use std::rc::Rc;\n\n    #[test]\n    fn test_read_valid_packet() {\n        let mut buf = ByteBuffer::new(64);\n        let raw = \"00180123456789ABCDEF\\tdevice\\n\".as_bytes();\n\n        let mut cursor = io::Cursor::new(raw);\n        buf.read_from(&mut cursor).unwrap();\n\n        let packet = AdbMonitor::read_packet(&mut buf).unwrap().unwrap();\n        assert_eq!(\"0123456789ABCDEF\\tdevice\\n\", packet);\n    }\n\n    #[test]\n    fn test_read_valid_packets() {\n        let mut buf = ByteBuffer::new(64);\n        let raw = \"00300123456789ABCDEF\\tdevice\\nFEDCBA9876543210\\tdevice\\n\".as_bytes();\n\n        let mut cursor = io::Cursor::new(raw);\n        buf.read_from(&mut cursor).unwrap();\n\n        let packet = AdbMonitor::read_packet(&mut buf).unwrap().unwrap();\n        assert_eq!(\n            \"0123456789ABCDEF\\tdevice\\nFEDCBA9876543210\\tdevice\\n\",\n            packet\n        );\n    }\n\n    #[test]\n    fn test_read_valid_packet_with_garbage() {\n        let mut buf = ByteBuffer::new(64);\n        let raw = \"00180123456789ABCDEF\\tdevice\\ngarbage\".as_bytes();\n\n        let mut cursor = io::Cursor::new(raw);\n        buf.read_from(&mut cursor).unwrap();\n\n        let packet = AdbMonitor::read_packet(&mut buf).unwrap().unwrap();\n        assert_eq!(\"0123456789ABCDEF\\tdevice\\n\", packet);\n    }\n\n    #[test]\n    fn test_read_short_packet() {\n        let mut buf = ByteBuffer::new(64);\n        let raw = \"00180123456789ABCDEF\\tdevi\".as_bytes();\n\n        let mut cursor = io::Cursor::new(raw);\n        buf.read_from(&mut cursor).unwrap();\n\n        let packet = AdbMonitor::read_packet(&mut buf).unwrap();\n        assert!(packet.is_none());\n    }\n\n    #[test]\n    fn test_handle_packet_device() {\n        let serial = Rc::new(RefCell::new(None));\n        let serial_clone = serial.clone();\n\n        let mut monitor = AdbMonitor::new(Box::new(move |serial: &str| {\n            serial_clone.replace(Some(serial.to_string()));\n        }));\n        monitor.handle_packet(\"0123456789ABCDEF\\tdevice\\n\");\n\n        assert_eq!(\"0123456789ABCDEF\", serial.borrow().as_ref().unwrap());\n    }\n\n    #[test]\n    fn test_handle_packet_offline() {\n        let serial = Rc::new(RefCell::new(None));\n        let serial_clone = serial.clone();\n\n        let mut monitor = AdbMonitor::new(Box::new(move |serial: &str| {\n            serial_clone.replace(Some(serial.to_string()));\n        }));\n        monitor.handle_packet(\"0123456789ABCDEF\\toffline\\n\");\n\n        assert!(serial.borrow().is_none());\n    }\n\n    #[test]\n    fn test_multiple_connected_devices() {\n        let serials = Rc::new(RefCell::new(Vec::new()));\n        let serials_clone = serials.clone();\n\n        let mut monitor = AdbMonitor::new(Box::new(move |serial: &str| {\n            serials_clone.borrow_mut().push(serial.to_string());\n        }));\n        monitor.handle_packet(\"0123456789ABCDEF\\tdevice\\nFEDCBA9876543210\\tdevice\\n\");\n\n        let vec = serials.borrow();\n        assert_eq!(2, vec.len());\n        assert_eq!(\"0123456789ABCDEF\", vec[0]);\n        assert_eq!(\"FEDCBA9876543210\", vec[1]);\n    }\n\n    #[test]\n    fn test_multiple_connected_devices_with_disconnection() {\n        let serials = Rc::new(RefCell::new(Vec::new()));\n        let serials_clone = serials.clone();\n\n        let mut monitor = AdbMonitor::new(Box::new(move |serial: &str| {\n            serials_clone.borrow_mut().push(serial.to_string());\n        }));\n        monitor.handle_packet(\"0123456789ABCDEF\\tdevice\\nFEDCBA9876543210\\tdevice\\n\");\n        monitor.handle_packet(\"0123456789ABCDEF\\tdevice\\n\");\n        monitor.handle_packet(\"0123456789ABCDEF\\tdevice\\nFEDCBA9876543210\\tdevice\\n\");\n\n        let vec = serials.borrow();\n        assert_eq!(3, vec.len());\n        assert_eq!(\"0123456789ABCDEF\", vec[0]);\n        assert_eq!(\"FEDCBA9876543210\", vec[1]);\n        assert_eq!(\"FEDCBA9876543210\", vec[2]);\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/cli_args.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npub const PARAM_NONE: u8 = 0;\npub const PARAM_SERIAL: u8 = 1;\npub const PARAM_DNS_SERVERS: u8 = 1 << 1;\npub const PARAM_ROUTES: u8 = 1 << 2;\npub const PARAM_PORT: u8 = 1 << 3;\n\npub const DEFAULT_PORT: u16 = 31416;\n\npub struct CommandLineArguments {\n    serial: Option<String>,\n    dns_servers: Option<String>,\n    routes: Option<String>,\n    port: u16,\n}\n\nimpl CommandLineArguments {\n    // simple String as errors is sufficient, we never need to inspect them\n    pub fn parse<S: Into<String>>(accepted_parameters: u8, args: Vec<S>) -> Result<Self, String> {\n        let mut serial = None;\n        let mut dns_servers = None;\n        let mut routes = None;\n        let mut port = 0;\n\n        let mut iter = args.into_iter();\n        while let Some(arg) = iter.next() {\n            let arg = arg.into();\n            if (accepted_parameters & PARAM_DNS_SERVERS) != 0 && \"-d\" == arg {\n                if dns_servers.is_some() {\n                    return Err(String::from(\"DNS servers already set\"));\n                }\n                if let Some(value) = iter.next() {\n                    dns_servers = Some(value.into());\n                } else {\n                    return Err(String::from(\"Missing -d parameter\"));\n                }\n            } else if (accepted_parameters & PARAM_ROUTES) != 0 && \"-r\" == arg {\n                if routes.is_some() {\n                    return Err(String::from(\"Routes already set\"));\n                }\n                if let Some(value) = iter.next() {\n                    routes = Some(value.into());\n                } else {\n                    return Err(String::from(\"Missing -r parameter\"));\n                }\n            } else if (accepted_parameters & PARAM_PORT) != 0 && \"-p\" == arg {\n                if port != 0 {\n                    return Err(String::from(\"Port already set\"));\n                }\n                if let Some(value) = iter.next() {\n                    port = value.into().parse().unwrap();\n                    if port == 0 {\n                        return Err(String::from(\"Invalid port: 0\"));\n                    }\n                } else {\n                    return Err(String::from(\"Missing -p parameter\"));\n                }\n            } else if (accepted_parameters & PARAM_SERIAL) != 0 && serial.is_none() {\n                serial = Some(arg);\n            } else {\n                return Err(format!(\"Unexpected argument: \\\"{}\\\"\", arg));\n            }\n        }\n        if port == 0 {\n            port = DEFAULT_PORT;\n        }\n        Ok(Self {\n            serial,\n            dns_servers,\n            routes,\n            port,\n        })\n    }\n\n    pub fn serial(&self) -> Option<&str> {\n        self.serial.as_deref()\n    }\n\n    pub fn dns_servers(&self) -> Option<&str> {\n        self.dns_servers.as_deref()\n    }\n\n    pub fn routes(&self) -> Option<&str> {\n        self.routes.as_deref()\n    }\n\n    pub fn port(&self) -> u16 {\n        self.port\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    const ACCEPT_ALL: u8 = PARAM_SERIAL | PARAM_DNS_SERVERS | PARAM_ROUTES;\n\n    #[test]\n    fn test_no_args() {\n        let args = CommandLineArguments::parse(ACCEPT_ALL, Vec::<&str>::new()).unwrap();\n        assert!(args.serial.is_none());\n        assert!(args.dns_servers.is_none());\n    }\n\n    #[test]\n    fn test_serial_only() {\n        let raw_args = vec![\"myserial\"];\n        let args = CommandLineArguments::parse(ACCEPT_ALL, raw_args).unwrap();\n        assert_eq!(\"myserial\", args.serial.unwrap());\n    }\n\n    #[test]\n    fn test_invalid_paramater() {\n        let raw_args = vec![\"myserial\", \"other\"];\n        assert!(CommandLineArguments::parse(ACCEPT_ALL, raw_args).is_err());\n    }\n\n    #[test]\n    fn test_dns_servers_only() {\n        let raw_args = vec![\"-d\", \"8.8.8.8\"];\n        let args = CommandLineArguments::parse(ACCEPT_ALL, raw_args).unwrap();\n        assert!(args.serial.is_none());\n        assert_eq!(\"8.8.8.8\", args.dns_servers.unwrap());\n    }\n\n    #[test]\n    fn test_serial_and_dns_servers() {\n        let raw_args = vec![\"myserial\", \"-d\", \"8.8.8.8\"];\n        let args = CommandLineArguments::parse(ACCEPT_ALL, raw_args).unwrap();\n        assert_eq!(\"myserial\", args.serial.unwrap());\n        assert_eq!(\"8.8.8.8\", args.dns_servers.unwrap());\n    }\n\n    #[test]\n    fn test_dns_servers_and_serial() {\n        let raw_args = vec![\"-d\", \"8.8.8.8\", \"myserial\"];\n        let args = CommandLineArguments::parse(ACCEPT_ALL, raw_args).unwrap();\n        assert_eq!(\"myserial\", args.serial.unwrap());\n        assert_eq!(\"8.8.8.8\", args.dns_servers.unwrap());\n    }\n\n    #[test]\n    fn test_serial_with_no_dns_servers_parameter() {\n        let raw_args = vec![\"myserial\", \"-d\"];\n        assert!(CommandLineArguments::parse(ACCEPT_ALL, raw_args).is_err());\n    }\n\n    #[test]\n    fn test_no_dns_servers_parameter() {\n        let raw_args = vec![\"-d\"];\n        assert!(CommandLineArguments::parse(ACCEPT_ALL, raw_args).is_err());\n    }\n\n    #[test]\n    fn test_routes_parameter() {\n        let raw_args = vec![\"-r\", \"1.2.3.0/24\"];\n        let args = CommandLineArguments::parse(ACCEPT_ALL, raw_args).unwrap();\n        assert_eq!(\"1.2.3.0/24\", args.routes.unwrap());\n    }\n\n    #[test]\n    fn test_no_routes_parameter() {\n        let raw_args = vec![\"-r\"];\n        assert!(CommandLineArguments::parse(ACCEPT_ALL, raw_args).is_err());\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/execution_error.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse std::error;\nuse std::fmt;\nuse std::io;\n#[cfg(unix)]\nuse std::os::unix::process::ExitStatusExt;\nuse std::process::ExitStatus;\n\n#[derive(Debug)]\npub enum CommandExecutionError {\n    ProcessIo(ProcessIoError),\n    ProcessStatus(ProcessStatusError),\n    Io(io::Error),\n}\n\n#[derive(Debug)]\npub struct ProcessStatusError {\n    cmd: Cmd,\n    termination: Termination,\n}\n\n#[derive(Debug)]\npub struct ProcessIoError {\n    cmd: Cmd,\n    error: io::Error,\n}\n\n#[derive(Debug)]\npub struct Cmd {\n    command: String,\n    args: Vec<String>,\n}\n\n#[derive(Debug)]\npub enum Termination {\n    Value(i32),\n    #[cfg(unix)]\n    Signal(i32),\n}\n\nimpl Termination {\n    fn from(status: ExitStatus) -> Self {\n        match status.code() {\n            Some(code) => Termination::Value(code),\n            #[cfg(unix)]\n            None => Termination::Signal(status.signal().unwrap()),\n            #[cfg(not(unix))]\n            None => panic!(\"Unexpected signal termination on non-unix system\"),\n        }\n    }\n}\n\nimpl fmt::Display for Cmd {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{} {:?}\", self.command, self.args)\n    }\n}\n\nimpl Cmd {\n    pub fn new<S1, S2>(command: S1, args: Vec<S2>) -> Cmd\n    where\n        S1: Into<String>,\n        S2: Into<String>,\n    {\n        Self {\n            command: command.into(),\n            args: args.into_iter().map(Into::into).collect::<Vec<_>>(),\n        }\n    }\n}\n\nimpl ProcessStatusError {\n    pub fn new(cmd: Cmd, status: ExitStatus) -> Self {\n        Self {\n            cmd,\n            termination: Termination::from(status),\n        }\n    }\n}\n\nimpl fmt::Display for ProcessStatusError {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        match self.termination {\n            Termination::Value(code) => {\n                write!(f, \"Command {} returned with value {}\", self.cmd, code)\n            }\n            #[cfg(unix)]\n            Termination::Signal(sig) => {\n                write!(f, \"Command {} terminated by signal {}\", self.cmd, sig)\n            }\n        }\n    }\n}\n\nimpl error::Error for ProcessStatusError {}\n\nimpl ProcessIoError {\n    pub fn new(cmd: Cmd, error: io::Error) -> Self {\n        Self { cmd, error }\n    }\n}\n\nimpl fmt::Display for ProcessIoError {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"Command {} failed: {}\", self.cmd, self.error)\n    }\n}\n\nimpl error::Error for ProcessIoError {\n    fn source(&self) -> Option<&(dyn error::Error + 'static)> {\n        Some(&self.error)\n    }\n}\n\nimpl fmt::Display for CommandExecutionError {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        match *self {\n            CommandExecutionError::ProcessIo(ref err) => write!(f, \"{}\", err),\n            CommandExecutionError::ProcessStatus(ref err) => write!(f, \"{}\", err),\n            CommandExecutionError::Io(ref err) => write!(f, \"IO error: {}\", err),\n        }\n    }\n}\n\nimpl error::Error for CommandExecutionError {\n    fn source(&self) -> Option<&(dyn error::Error + 'static)> {\n        match *self {\n            CommandExecutionError::ProcessIo(ref err) => Some(err),\n            CommandExecutionError::ProcessStatus(ref err) => Some(err),\n            CommandExecutionError::Io(ref err) => Some(err),\n        }\n    }\n}\n\nimpl From<ProcessIoError> for CommandExecutionError {\n    fn from(error: ProcessIoError) -> Self {\n        CommandExecutionError::ProcessIo(error)\n    }\n}\n\nimpl From<ProcessStatusError> for CommandExecutionError {\n    fn from(error: ProcessStatusError) -> Self {\n        CommandExecutionError::ProcessStatus(error)\n    }\n}\n\nimpl From<io::Error> for CommandExecutionError {\n    fn from(error: io::Error) -> Self {\n        CommandExecutionError::Io(error)\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/lib.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nmod relay;\npub use crate::relay::byte_buffer;\n\nuse crate::relay::Relay;\nuse std::io;\n\npub fn relay(port: u16) -> io::Result<()> {\n    Relay::new(port).run()\n}\n"
  },
  {
    "path": "relay-rust/src/logger.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse chrono::prelude::Local;\nuse log::*;\nuse std::io::{self, Write};\n\nstatic LOGGER: SimpleLogger = SimpleLogger;\nconst THRESHOLD: LevelFilter = LevelFilter::Info;\n\npub struct SimpleLogger;\n\nimpl Log for SimpleLogger {\n    fn enabled(&self, metadata: &Metadata) -> bool {\n        metadata.level() <= THRESHOLD\n    }\n\n    fn log(&self, record: &Record) {\n        if self.enabled(record.metadata()) {\n            let date = Local::now();\n            let formatted_date = date.format(\"%Y-%m-%d %H:%M:%S%.3f\");\n            let msg = format!(\n                \"{} {} {}: {}\",\n                formatted_date,\n                record.level(),\n                record.target(),\n                record.args()\n            );\n            if record.level() == Level::Error {\n                eprintln!(\"{}\", msg);\n            } else {\n                println!(\"{}\", msg);\n            }\n        }\n    }\n\n    fn flush(&self) {\n        io::stdout().flush().unwrap();\n        io::stderr().flush().unwrap();\n    }\n}\n\npub fn init() -> Result<(), SetLoggerError> {\n    set_max_level(THRESHOLD);\n    set_logger(&LOGGER)\n}\n"
  },
  {
    "path": "relay-rust/src/main.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nextern crate chrono;\nextern crate ctrlc;\n#[macro_use]\nextern crate log;\nextern crate relaylib;\n\nmod adb_monitor;\nmod cli_args;\nmod execution_error;\nmod logger;\n\nuse crate::adb_monitor::AdbMonitor;\nuse crate::cli_args::CommandLineArguments;\nuse crate::execution_error::{Cmd, CommandExecutionError, ProcessIoError, ProcessStatusError};\nuse std::env;\nuse std::process::{self, exit};\nuse std::thread;\nuse std::time::Duration;\n\nconst TAG: &str = \"Main\";\nconst REQUIRED_APK_VERSION_CODE: &str = \"9\";\n\n#[inline]\nfn get_adb_path() -> String {\n    if let Some(env_adb) = std::env::var_os(\"ADB\") {\n        env_adb.into_string().expect(\"invalid ADB value\")\n    } else {\n        \"adb\".to_string()\n    }\n}\n\n#[inline]\nfn get_apk_path() -> String {\n    if let Some(env_adb) = std::env::var_os(\"GNIREHTET_APK\") {\n        env_adb.into_string().expect(\"invalid GNIREHTET_APK value\")\n    } else {\n        \"gnirehtet.apk\".to_string()\n    }\n}\n\nconst COMMANDS: &[&dyn Command] = &[\n    &InstallCommand,\n    &UninstallCommand,\n    &ReinstallCommand,\n    &RunCommand,\n    &AutorunCommand,\n    &StartCommand,\n    &AutostartCommand,\n    &StopCommand,\n    &RestartCommand,\n    &TunnelCommand,\n    &RelayCommand,\n];\n\ntrait Command {\n    fn command(&self) -> &'static str;\n    fn accepted_parameters(&self) -> u8;\n    fn description(&self) -> &'static str;\n    fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandExecutionError>;\n}\n\nstruct InstallCommand;\nstruct UninstallCommand;\nstruct ReinstallCommand;\nstruct RunCommand;\nstruct AutorunCommand;\nstruct StartCommand;\nstruct AutostartCommand;\nstruct StopCommand;\nstruct RestartCommand;\nstruct TunnelCommand;\nstruct RelayCommand;\n\nimpl Command for InstallCommand {\n    fn command(&self) -> &'static str {\n        \"install\"\n    }\n\n    fn accepted_parameters(&self) -> u8 {\n        cli_args::PARAM_SERIAL\n    }\n\n    fn description(&self) -> &'static str {\n        \"Install the client on the Android device and exit.\\n\\\n         If several devices are connected via adb, then serial must be\\n\\\n         specified.\"\n    }\n\n    fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandExecutionError> {\n        cmd_install(args.serial())\n    }\n}\n\nimpl Command for UninstallCommand {\n    fn command(&self) -> &'static str {\n        \"uninstall\"\n    }\n\n    fn accepted_parameters(&self) -> u8 {\n        cli_args::PARAM_SERIAL\n    }\n\n    fn description(&self) -> &'static str {\n        \"Uninstall the client from the Android device and exit.\\n\\\n         If several devices are connected via adb, then serial must be\\n\\\n         specified.\"\n    }\n\n    fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandExecutionError> {\n        cmd_uninstall(args.serial())\n    }\n}\n\nimpl Command for ReinstallCommand {\n    fn command(&self) -> &'static str {\n        \"reinstall\"\n    }\n\n    fn accepted_parameters(&self) -> u8 {\n        cli_args::PARAM_SERIAL\n    }\n\n    fn description(&self) -> &'static str {\n        \"Uninstall then install.\"\n    }\n\n    fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandExecutionError> {\n        cmd_reinstall(args.serial())\n    }\n}\n\nimpl Command for RunCommand {\n    fn command(&self) -> &'static str {\n        \"run\"\n    }\n\n    fn accepted_parameters(&self) -> u8 {\n        cli_args::PARAM_SERIAL\n            | cli_args::PARAM_DNS_SERVERS\n            | cli_args::PARAM_ROUTES\n            | cli_args::PARAM_PORT\n    }\n\n    fn description(&self) -> &'static str {\n        \"Enable reverse tethering for exactly one device:\\n  \\\n         - install the client if necessary;\\n  \\\n         - start the client;\\n  \\\n         - start the relay server;\\n  \\\n         - on Ctrl+C, stop both the relay server and the client.\"\n    }\n\n    fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandExecutionError> {\n        cmd_run(\n            args.serial(),\n            args.dns_servers(),\n            args.routes(),\n            args.port(),\n        )\n    }\n}\n\nimpl Command for AutorunCommand {\n    fn command(&self) -> &'static str {\n        \"autorun\"\n    }\n\n    fn accepted_parameters(&self) -> u8 {\n        cli_args::PARAM_DNS_SERVERS | cli_args::PARAM_ROUTES | cli_args::PARAM_PORT\n    }\n\n    fn description(&self) -> &'static str {\n        \"Enable reverse tethering for all devices:\\n  \\\n         - monitor devices and start clients (autostart);\\n  \\\n         - start the relay server.\"\n    }\n\n    fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandExecutionError> {\n        cmd_autorun(args.dns_servers(), args.routes(), args.port())\n    }\n}\n\nimpl Command for StartCommand {\n    fn command(&self) -> &'static str {\n        \"start\"\n    }\n\n    fn accepted_parameters(&self) -> u8 {\n        cli_args::PARAM_SERIAL\n            | cli_args::PARAM_DNS_SERVERS\n            | cli_args::PARAM_ROUTES\n            | cli_args::PARAM_PORT\n    }\n\n    fn description(&self) -> &'static str {\n        \"Start a client on the Android device and exit.\\n\\\n         If several devices are connected via adb, then serial must be\\n\\\n         specified.\\n\\\n         If -d is given, then make the Android device use the specified\\n\\\n         DNS server(s). Otherwise, use 8.8.8.8 (Google public DNS).\\n\\\n         If -r is given, then only reverse tether the specified routes.\\n\\\n         Otherwise, use 0.0.0.0/0 (redirect the whole traffic).\\n\\\n         If -p is given, then make the relay server listen on the specified\\n\\\n         port. Otherwise, use port 31416.\\n\\\n         If the client is already started, then do nothing, and ignore\\n\\\n         the other parameters.\\n\\\n         10.0.2.2 is mapped to the host 'localhost'.\"\n    }\n\n    fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandExecutionError> {\n        cmd_start(\n            args.serial(),\n            args.dns_servers(),\n            args.routes(),\n            args.port(),\n        )\n    }\n}\n\nimpl Command for AutostartCommand {\n    fn command(&self) -> &'static str {\n        \"autostart\"\n    }\n\n    fn accepted_parameters(&self) -> u8 {\n        cli_args::PARAM_DNS_SERVERS | cli_args::PARAM_ROUTES | cli_args::PARAM_PORT\n    }\n\n    fn description(&self) -> &'static str {\n        \"Listen for device connexions and start a client on every detected\\n\\\n         device.\\n\\\n         Accept the same parameters as the start command (excluding the\\n\\\n         serial, which will be taken from the detected device).\"\n    }\n\n    fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandExecutionError> {\n        cmd_autostart(args.dns_servers(), args.routes(), args.port())\n    }\n}\n\nimpl Command for StopCommand {\n    fn command(&self) -> &'static str {\n        \"stop\"\n    }\n\n    fn accepted_parameters(&self) -> u8 {\n        cli_args::PARAM_SERIAL\n    }\n\n    fn description(&self) -> &'static str {\n        \"Stop the client on the Android device and exit.\\n\\\n         If several devices are connected via adb, then serial must be\\n\\\n         specified.\"\n    }\n\n    fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandExecutionError> {\n        cmd_stop(args.serial())\n    }\n}\n\nimpl Command for RestartCommand {\n    fn command(&self) -> &'static str {\n        \"restart\"\n    }\n\n    fn accepted_parameters(&self) -> u8 {\n        cli_args::PARAM_SERIAL\n            | cli_args::PARAM_DNS_SERVERS\n            | cli_args::PARAM_ROUTES\n            | cli_args::PARAM_PORT\n    }\n\n    fn description(&self) -> &'static str {\n        \"Stop then start.\"\n    }\n\n    fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandExecutionError> {\n        cmd_stop(args.serial())?;\n        cmd_start(\n            args.serial(),\n            args.dns_servers(),\n            args.routes(),\n            args.port(),\n        )?;\n        Ok(())\n    }\n}\n\nimpl Command for TunnelCommand {\n    fn command(&self) -> &'static str {\n        \"tunnel\"\n    }\n\n    fn accepted_parameters(&self) -> u8 {\n        cli_args::PARAM_SERIAL | cli_args::PARAM_PORT\n    }\n\n    fn description(&self) -> &'static str {\n        \"Set up the 'adb reverse' tunnel.\\n\\\n         If a device is unplugged then plugged back while gnirehtet is\\n\\\n         active, resetting the tunnel is sufficient to get the\\n\\\n         connection back.\"\n    }\n\n    fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandExecutionError> {\n        cmd_tunnel(args.serial(), args.port())\n    }\n}\n\nimpl Command for RelayCommand {\n    fn command(&self) -> &'static str {\n        \"relay\"\n    }\n\n    fn accepted_parameters(&self) -> u8 {\n        cli_args::PARAM_NONE | cli_args::PARAM_PORT\n    }\n\n    fn description(&self) -> &'static str {\n        \"Start the relay server in the current terminal.\"\n    }\n\n    fn execute(&self, args: &CommandLineArguments) -> Result<(), CommandExecutionError> {\n        cmd_relay(args.port())?;\n        Ok(())\n    }\n}\n\nfn cmd_install(serial: Option<&str>) -> Result<(), CommandExecutionError> {\n    info!(target: TAG, \"Installing gnirehtet client...\");\n    exec_adb(serial, vec![\"install\".into(), \"-r\".into(), get_apk_path()])\n}\n\nfn cmd_uninstall(serial: Option<&str>) -> Result<(), CommandExecutionError> {\n    info!(target: TAG, \"Uninstalling gnirehtet client...\");\n    exec_adb(serial, vec![\"uninstall\", \"com.genymobile.gnirehtet\"])\n}\n\nfn cmd_reinstall(serial: Option<&str>) -> Result<(), CommandExecutionError> {\n    cmd_uninstall(serial)?;\n    cmd_install(serial)?;\n    Ok(())\n}\n\nfn cmd_run(\n    serial: Option<&str>,\n    dns_servers: Option<&str>,\n    routes: Option<&str>,\n    port: u16,\n) -> Result<(), CommandExecutionError> {\n    // start in parallel so that the relay server is ready when the client connects\n    async_start(serial, dns_servers, routes, port);\n\n    let ctrlc_serial = serial.map(String::from);\n    ctrlc::set_handler(move || {\n        info!(target: TAG, \"Interrupted\");\n\n        let serial = ctrlc_serial.as_ref().map(String::as_ref);\n        if let Err(err) = cmd_stop(serial) {\n            error!(target: TAG, \"Cannot stop client: {}\", err);\n        }\n\n        exit(0);\n    })\n    .expect(\"Error setting Ctrl-C handler\");\n\n    cmd_relay(port)\n}\n\nfn cmd_autorun(\n    dns_servers: Option<&str>,\n    routes: Option<&str>,\n    port: u16,\n) -> Result<(), CommandExecutionError> {\n    {\n        let autostart_dns_servers = dns_servers.map(String::from);\n        let autostart_routes = routes.map(String::from);\n        thread::spawn(move || {\n            let dns_servers = autostart_dns_servers.as_ref().map(String::as_ref);\n            let routes = autostart_routes.as_ref().map(String::as_ref);\n            if let Err(err) = cmd_autostart(dns_servers, routes, port) {\n                error!(target: TAG, \"Cannot auto start clients: {}\", err);\n            }\n        });\n    }\n\n    cmd_relay(port)\n}\n\nfn cmd_start(\n    serial: Option<&str>,\n    dns_servers: Option<&str>,\n    routes: Option<&str>,\n    port: u16,\n) -> Result<(), CommandExecutionError> {\n    if must_install_client(serial)? {\n        cmd_install(serial)?;\n        // wait a bit after the app is installed so that intent actions are correctly\n        // registered\n        thread::sleep(Duration::from_millis(500));\n    }\n\n    info!(target: TAG, \"Starting client...\");\n    cmd_tunnel(serial, port)?;\n\n    let mut adb_args = vec![\n        \"shell\",\n        \"am\",\n        \"start\",\n        \"-a\",\n        \"com.genymobile.gnirehtet.START\",\n        \"-n\",\n        \"com.genymobile.gnirehtet/.GnirehtetActivity\",\n    ];\n    if let Some(dns_servers) = dns_servers {\n        adb_args.append(&mut vec![\"--esa\", \"dnsServers\", dns_servers]);\n    }\n    if let Some(routes) = routes {\n        adb_args.append(&mut vec![\"--esa\", \"routes\", routes]);\n    }\n    exec_adb(serial, adb_args)\n}\n\nfn cmd_autostart(\n    dns_servers: Option<&str>,\n    routes: Option<&str>,\n    port: u16,\n) -> Result<(), CommandExecutionError> {\n    let start_dns_servers = dns_servers.map(String::from);\n    let start_routes = routes.map(String::from);\n    let mut adb_monitor = AdbMonitor::new(Box::new(move |serial: &str| {\n        let dns_servers = start_dns_servers.as_ref().map(String::as_ref);\n        let routes = start_routes.as_ref().map(String::as_ref);\n        async_start(Some(serial), dns_servers, routes, port)\n    }));\n    adb_monitor.monitor();\n    Ok(())\n}\n\nfn cmd_stop(serial: Option<&str>) -> Result<(), CommandExecutionError> {\n    info!(target: TAG, \"Stopping client...\");\n    exec_adb(\n        serial,\n        vec![\n            \"shell\",\n            \"am\",\n            \"start\",\n            \"-a\",\n            \"com.genymobile.gnirehtet.STOP\",\n            \"-n\",\n            \"com.genymobile.gnirehtet/.GnirehtetActivity\",\n        ],\n    )\n}\n\nfn cmd_tunnel(serial: Option<&str>, port: u16) -> Result<(), CommandExecutionError> {\n    exec_adb(\n        serial,\n        vec![\n            \"reverse\",\n            \"localabstract:gnirehtet\",\n            format!(\"tcp:{}\", port).as_str(),\n        ],\n    )\n}\n\nfn cmd_relay(port: u16) -> Result<(), CommandExecutionError> {\n    info!(target: TAG, \"Starting relay server on port {}...\", port);\n    relaylib::relay(port)?;\n    Ok(())\n}\n\nfn async_start(serial: Option<&str>, dns_servers: Option<&str>, routes: Option<&str>, port: u16) {\n    let start_serial = serial.map(String::from);\n    let start_dns_servers = dns_servers.map(String::from);\n    let start_routes = routes.map(String::from);\n    thread::spawn(move || {\n        let serial = start_serial.as_ref().map(String::as_ref);\n        let dns_servers = start_dns_servers.as_ref().map(String::as_ref);\n        let routes = start_routes.as_ref().map(String::as_ref);\n        if let Err(err) = cmd_start(serial, dns_servers, routes, port) {\n            error!(target: TAG, \"Cannot start client: {}\", err);\n        }\n    });\n}\n\nfn create_adb_args<S: Into<String>>(serial: Option<&str>, args: Vec<S>) -> Vec<String> {\n    let mut command = Vec::<String>::new();\n    if let Some(serial) = serial {\n        command.push(\"-s\".into());\n        command.push(serial.to_string());\n    }\n    for arg in args {\n        command.push(arg.into());\n    }\n    command\n}\n\nfn exec_adb<S: Into<String>>(\n    serial: Option<&str>,\n    args: Vec<S>,\n) -> Result<(), CommandExecutionError> {\n    let adb_args = create_adb_args(serial, args);\n    let adb = get_adb_path();\n    debug!(target: TAG, \"Execute: {:?} {:?}\", adb, adb_args);\n    match process::Command::new(&adb).args(&adb_args[..]).status() {\n        Ok(exit_status) => {\n            if exit_status.success() {\n                Ok(())\n            } else {\n                let cmd = Cmd::new(adb, adb_args);\n                Err(ProcessStatusError::new(cmd, exit_status).into())\n            }\n        }\n        Err(err) => {\n            let cmd = Cmd::new(adb, adb_args);\n            Err(ProcessIoError::new(cmd, err).into())\n        }\n    }\n}\n\nfn must_install_client(serial: Option<&str>) -> Result<bool, CommandExecutionError> {\n    info!(target: TAG, \"Checking gnirehtet client...\");\n    let args = create_adb_args(\n        serial,\n        vec![\"shell\", \"dumpsys\", \"package\", \"com.genymobile.gnirehtet\"],\n    );\n    let adb = get_adb_path();\n    debug!(target: TAG, \"Execute: {:?} {:?}\", adb, args);\n    match process::Command::new(&adb).args(&args[..]).output() {\n        Ok(output) => {\n            if output.status.success() {\n                // the \"regex\" crate makes the binary far bigger, so just parse the versionCode\n                // manually\n                let dumpsys = String::from_utf8_lossy(&output.stdout[..]);\n                // read the versionCode of the installed package\n                if let Some(index) = dumpsys.find(\"    versionCode=\") {\n                    let start = index + 16; // size of \"    versionCode=\\\"\"\n                    if let Some(end) = (&dumpsys[start..]).find(' ') {\n                        let installed_version_code = &dumpsys[start..start + end];\n                        Ok(installed_version_code != REQUIRED_APK_VERSION_CODE)\n                    } else {\n                        // end of versionCode value not found\n                        Ok(true)\n                    }\n                } else {\n                    // versionCode not found\n                    Ok(true)\n                }\n            } else {\n                let cmd = Cmd::new(adb, args);\n                Err(ProcessStatusError::new(cmd, output.status).into())\n            }\n        }\n        Err(err) => {\n            let cmd = Cmd::new(adb, args);\n            Err(ProcessIoError::new(cmd, err).into())\n        }\n    }\n}\n\nfn print_usage() {\n    let mut msg = \"Syntax: gnirehtet (\".to_string();\n    msg.push_str(COMMANDS[0].command());\n    for command in &COMMANDS[1..] {\n        msg.push('|');\n        msg.push_str(command.command());\n    }\n    msg.push_str(\") ...\\n\");\n    for &command in COMMANDS {\n        msg.push('\\n');\n        append_command_usage(&mut msg, command);\n    }\n    eprint!(\"{}\", msg);\n}\n\nfn append_command_usage(msg: &mut String, command: &dyn Command) {\n    msg.push_str(\"  gnirehtet \");\n    msg.push_str(command.command());\n    let accepted_parameters = command.accepted_parameters();\n    if (accepted_parameters & cli_args::PARAM_SERIAL) != 0 {\n        msg.push_str(\" [serial]\");\n    }\n    if (accepted_parameters & cli_args::PARAM_DNS_SERVERS) != 0 {\n        msg.push_str(\" [-d DNS[,DNS2,...]]\");\n    }\n    if (accepted_parameters & cli_args::PARAM_PORT) != 0 {\n        msg.push_str(\" [-p PORT]\");\n    }\n    if (accepted_parameters & cli_args::PARAM_ROUTES) != 0 {\n        msg.push_str(\" [-r ROUTE[,ROUTE2,...]]\");\n    }\n    msg.push('\\n');\n    for desc_line in command.description().split('\\n') {\n        msg.push_str(\"      \");\n        msg.push_str(desc_line);\n        msg.push('\\n');\n    }\n}\n\nfn print_command_usage(command: &dyn Command) {\n    let mut msg = String::new();\n    append_command_usage(&mut msg, command);\n    eprint!(\"{}\", msg);\n}\n\nfn main() {\n    logger::init().unwrap();\n    let mut args = env::args();\n    // args.nth(1) will consume the two first arguments (the binary name and the command name)\n    if let Some(command_name) = args.nth(1) {\n        let command = COMMANDS\n            .iter()\n            .find(|&&command| command.command() == command_name);\n        match command {\n            Some(&command) => {\n                // args now contains only the command parameters\n                let arguments =\n                    CommandLineArguments::parse(command.accepted_parameters(), args.collect());\n                match arguments {\n                    Ok(arguments) => {\n                        if let Err(err) = command.execute(&arguments) {\n                            error!(target: TAG, \"Execution error: {}\", err);\n                            exit(3);\n                        }\n                    }\n                    Err(err) => {\n                        error!(target: TAG, \"{}\", err);\n                        print_command_usage(command);\n                        exit(2);\n                    }\n                }\n            }\n            None => {\n                if command_name == \"rt\" {\n                    error!(\n                        target: TAG,\n                        \"The 'rt' command has been renamed to 'run'. Try 'gnirehtet run' instead.\"\n                    );\n                    print_command_usage(&RunCommand);\n                } else {\n                    error!(target: TAG, \"Unknown command: {}\", command_name);\n                    print_usage();\n                }\n                exit(1);\n            }\n        }\n    } else {\n        print_usage();\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/relay/binary.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse byteorder::{BigEndian, ByteOrder};\nuse std::cmp::min;\nuse std::fmt::Write;\n\nconst MAX_STRING_PACKET_SIZE: usize = 20;\n\npub fn to_byte_array(value: u32) -> [u8; 4] {\n    let mut raw = [0u8; 4];\n    BigEndian::write_u32(&mut raw, value);\n    raw\n}\n\npub fn build_packet_string(data: &[u8]) -> String {\n    let mut s = String::new();\n    let limit = min(MAX_STRING_PACKET_SIZE, data.len());\n    for (i, &byte) in data.iter().take(limit).enumerate() {\n        if i != 0 {\n            let sep = if (i % 4) == 0 { \"  \" } else { \" \" };\n            write!(&mut s, \"{}\", sep).unwrap();\n        }\n        write!(&mut s, \"{:02X}\", byte).unwrap();\n    }\n    if limit < data.len() {\n        write!(&mut s, \"  ... +{} bytes\", data.len() - limit).unwrap();\n    }\n    s\n}\n\n// only compare the data part for fat pointers (ignore the vtable part)\n// for some (buggy) reason, the vtable part may be different even if the data reference the same\n// object\n// See <https://github.com/Genymobile/gnirehtet/issues/61#issuecomment-370933770>\npub fn ptr_data_eq<T: ?Sized>(lhs: *const T, rhs: *const T) -> bool {\n    // cast to thin pointers to ignore the vtable part\n    lhs as *const () == rhs as *const ()\n}\n"
  },
  {
    "path": "relay-rust/src/relay/byte_buffer.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse std::io;\nuse std::ptr;\n\npub struct ByteBuffer {\n    buf: Box<[u8]>,\n    head: usize,\n}\n\nimpl ByteBuffer {\n    pub fn new(length: usize) -> Self {\n        Self {\n            buf: vec![0; length].into_boxed_slice(),\n            head: 0,\n        }\n    }\n\n    pub fn read_from<R: io::Read>(&mut self, source: &mut R) -> io::Result<bool> {\n        let target_slice = &mut self.buf[self.head..];\n        let r = source.read(target_slice)?;\n        self.head += r;\n        Ok(r > 0)\n    }\n\n    pub fn peek(&self) -> &[u8] {\n        &self.buf[..self.head]\n    }\n\n    pub fn peek_mut(&mut self) -> &mut [u8] {\n        &mut self.buf[..self.head]\n    }\n\n    pub fn consume(&mut self, length: usize) {\n        assert!(self.head >= length);\n        self.head -= length;\n        if self.head > 0 {\n            // some data remaining, move them to the front of the buffer\n            unsafe {\n                let buf_ptr = self.buf.as_mut_ptr();\n\n                // Before:\n                //\n                //  consumed                  old_head\n                // |        |....................|\n                //  <------>\n                //   length\n                //\n                // After:\n                //\n                //                  new_head (= old_head - length)\n                // |....................|\n                //                       <------>\n                //                        length\n                //\n                // move from [length..old_head] to [0..new_head]\n                //\n                // semantically equivalent to memmove()\n                ptr::copy(buf_ptr.add(length), buf_ptr, self.head);\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use std::io;\n\n    #[test]\n    fn produce_consume_byte_buffer() {\n        let raw = \"hello, world!\".as_bytes();\n        let mut byte_buffer = ByteBuffer::new(64);\n\n        let mut cursor = io::Cursor::new(raw);\n        byte_buffer.read_from(&mut cursor).unwrap();\n        assert_eq!(\"hello, world!\".as_bytes(), byte_buffer.peek());\n\n        byte_buffer.consume(7);\n        assert_eq!(\"world!\".as_bytes(), byte_buffer.peek());\n\n        let mut cursor = io::Cursor::new(&raw[..5]);\n        byte_buffer.read_from(&mut cursor).unwrap();\n        assert_eq!(\"world!hello\".as_bytes(), byte_buffer.peek());\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/relay/client.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse log::*;\nuse mio::net::TcpStream;\nuse mio::{Event, PollOpt, Ready, Token};\nuse std::cell::RefCell;\nuse std::io::{self, Write};\nuse std::mem;\nuse std::net::Shutdown;\nuse std::rc::Rc;\n\nuse super::binary;\nuse super::close_listener::CloseListener;\nuse super::ipv4_packet::{Ipv4Packet, MAX_PACKET_LENGTH};\nuse super::ipv4_packet_buffer::Ipv4PacketBuffer;\nuse super::packet_source::PacketSource;\nuse super::router::Router;\nuse super::selector::Selector;\nuse super::stream_buffer::StreamBuffer;\n\nconst TAG: &str = \"Client\";\n\npub struct Client {\n    id: u32,\n    stream: TcpStream,\n    interests: Ready,\n    token: Token,\n    client_to_network: Ipv4PacketBuffer,\n    network_to_client: StreamBuffer,\n    router: Router,\n    close_listener: Box<dyn CloseListener<Client>>,\n    closed: bool,\n    pending_packet_sources: Vec<Rc<RefCell<dyn PacketSource>>>,\n    // number of remaining bytes of \"id\" to send to the client before relaying any data\n    pending_id_bytes: usize,\n}\n\n/// Channel for connections to send back data immediately to the client\npub struct ClientChannel<'a> {\n    network_to_client: &'a mut StreamBuffer,\n    stream: &'a TcpStream,\n    token: Token,\n    interests: &'a mut Ready,\n}\n\nimpl<'a> ClientChannel<'a> {\n    fn new(\n        network_to_client: &'a mut StreamBuffer,\n        stream: &'a TcpStream,\n        token: Token,\n        interests: &'a mut Ready,\n    ) -> Self {\n        Self {\n            network_to_client,\n            stream,\n            token,\n            interests,\n        }\n    }\n\n    // Functionally equivalent to Client::send_to_client(), except that it does not require to\n    // mutably borrow the whole client.\n    pub fn send_to_client(\n        &mut self,\n        selector: &mut Selector,\n        ipv4_packet: &Ipv4Packet,\n    ) -> io::Result<()> {\n        if ipv4_packet.length() as usize <= self.network_to_client.remaining() {\n            self.network_to_client.read_from(ipv4_packet.raw());\n            self.update_interests(selector);\n            Ok(())\n        } else {\n            warn!(target: TAG, \"Client buffer full\");\n            Err(io::Error::new(\n                io::ErrorKind::WouldBlock,\n                \"Client buffer full\",\n            ))\n        }\n    }\n\n    fn update_interests(&mut self, selector: &mut Selector) {\n        let ready = if self.network_to_client.is_empty() {\n            Ready::readable()\n        } else {\n            Ready::readable() | Ready::writable()\n        };\n        if *self.interests != ready {\n            // interests must be changed\n            *self.interests = ready;\n            selector\n                .reregister(self.stream, self.token, ready, PollOpt::level())\n                .expect(\"Cannot register on poll\");\n        }\n    }\n}\n\nimpl Client {\n    pub fn create(\n        id: u32,\n        selector: &mut Selector,\n        stream: TcpStream,\n        close_listener: Box<dyn CloseListener<Client>>,\n    ) -> io::Result<Rc<RefCell<Self>>> {\n        // on start, we are interested only in writing (we must first send the client id)\n        let interests = Ready::writable();\n        let rc = Rc::new(RefCell::new(Self {\n            id,\n            stream,\n            interests,\n            token: Token(0), // default value, will be set afterwards\n            client_to_network: Ipv4PacketBuffer::new(),\n            network_to_client: StreamBuffer::new(16 * MAX_PACKET_LENGTH),\n            router: Router::new(),\n            closed: false,\n            close_listener,\n            pending_packet_sources: Vec::new(),\n            pending_id_bytes: 4,\n        }));\n\n        {\n            let mut self_ref = rc.borrow_mut();\n            // set client as router owner\n            self_ref.router.set_client(Rc::downgrade(&rc));\n\n            let rc2 = rc.clone();\n            // must anotate selector type: https://stackoverflow.com/a/44004103/1987178\n            let handler =\n                move |selector: &mut Selector, event| rc2.borrow_mut().on_ready(selector, event);\n            let token =\n                selector.register(&self_ref.stream, handler, interests, PollOpt::level())?;\n            self_ref.token = token;\n        }\n        Ok(rc)\n    }\n\n    pub fn id(&self) -> u32 {\n        self.id\n    }\n\n    pub fn router(&mut self) -> &mut Router {\n        &mut self.router\n    }\n\n    pub fn channel(&mut self) -> ClientChannel {\n        ClientChannel::new(\n            &mut self.network_to_client,\n            &self.stream,\n            self.token,\n            &mut self.interests,\n        )\n    }\n\n    fn close(&mut self, selector: &mut Selector) {\n        self.closed = true;\n        selector.deregister(&self.stream, self.token).unwrap();\n        // shutdown only (there is no close), the socket will be closed on drop\n        if self.stream.shutdown(Shutdown::Both).is_err() {\n            warn!(target: TAG, \"Cannot shutdown client socket\");\n        }\n        self.router.clear(selector);\n        self.close_listener.on_closed(self);\n    }\n\n    fn on_ready(&mut self, selector: &mut Selector, event: Event) {\n        #[allow(clippy::match_wild_err_arm)]\n        match self.process(selector, event) {\n            Ok(_) => (),\n            Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => {\n                debug!(target: TAG, \"Spurious event, ignoring\")\n            }\n            Err(_) => panic!(\"Unexpected unhandled error\"),\n        }\n    }\n\n    // return Err(err) with err.kind() == io::ErrorKind::WouldBlock on spurious event\n    fn process(&mut self, selector: &mut Selector, event: Event) -> io::Result<()> {\n        if !self.closed {\n            let ready = event.readiness();\n            if ready.is_writable() {\n                self.process_send(selector)?;\n            }\n            if !self.closed && ready.is_readable() {\n                self.process_receive(selector)?;\n            }\n            if !self.closed {\n                self.update_interests(selector);\n            }\n        }\n        Ok(())\n    }\n\n    // return Err(err) with err.kind() == io::ErrorKind::WouldBlock on spurious event\n    fn process_send(&mut self, selector: &mut Selector) -> io::Result<()> {\n        if self.must_send_id() {\n            match self.send_id() {\n                Ok(_) => {\n                    if self.pending_id_bytes == 0 {\n                        debug!(target: TAG, \"Client id #{} sent to client\", self.id);\n                    }\n                }\n                Err(err) => {\n                    if err.kind() == io::ErrorKind::WouldBlock {\n                        // rethrow\n                        return Err(err);\n                    }\n                    error!(target: TAG, \"Cannot write client id #{}\", self.id);\n                    self.close(selector);\n                }\n            }\n        } else {\n            match self.write() {\n                Ok(_) => self.process_pending(selector),\n                Err(err) => {\n                    error!(target: TAG, \"Cannot write: [{:?}] {}\", err.kind(), err);\n                    self.close(selector);\n                }\n            }\n        }\n        Ok(())\n    }\n\n    // return Err(err) with err.kind() == io::ErrorKind::WouldBlock on spurious event\n    fn process_receive(&mut self, selector: &mut Selector) -> io::Result<()> {\n        match self.read() {\n            Ok(true) => self.push_to_network(selector),\n            Ok(false) => {\n                debug!(target: TAG, \"EOF reached\");\n                self.close(selector);\n            }\n            Err(err) => {\n                if err.kind() == io::ErrorKind::WouldBlock {\n                    // rethrow\n                    return Err(err);\n                }\n                error!(target: TAG, \"Cannot read: [{:?}] {}\", err.kind(), err);\n                self.close(selector);\n            }\n        }\n        Ok(())\n    }\n\n    pub fn send_to_client(\n        &mut self,\n        selector: &mut Selector,\n        ipv4_packet: &Ipv4Packet,\n    ) -> io::Result<()> {\n        if ipv4_packet.length() as usize <= self.network_to_client.remaining() {\n            self.network_to_client.read_from(ipv4_packet.raw());\n            self.update_interests(selector);\n            Ok(())\n        } else {\n            warn!(target: TAG, \"Client buffer full\");\n            Err(io::Error::new(\n                io::ErrorKind::WouldBlock,\n                \"Client buffer full\",\n            ))\n        }\n    }\n\n    pub fn register_pending_packet_source(&mut self, source: Rc<RefCell<dyn PacketSource>>) {\n        self.pending_packet_sources.push(source);\n    }\n\n    fn send_id(&mut self) -> io::Result<()> {\n        assert!(self.must_send_id());\n        let raw_id = binary::to_byte_array(self.id);\n        let w = self.stream.write(&raw_id[4 - self.pending_id_bytes..])?;\n        self.pending_id_bytes -= w;\n        Ok(())\n    }\n\n    fn update_interests(&mut self, selector: &mut Selector) {\n        self.channel().update_interests(selector);\n    }\n\n    fn read(&mut self) -> io::Result<bool> {\n        self.client_to_network.read_from(&mut self.stream)\n    }\n\n    fn write(&mut self) -> io::Result<()> {\n        self.network_to_client.write_to(&mut self.stream)?;\n        Ok(())\n    }\n\n    fn push_to_network(&mut self, selector: &mut Selector) {\n        while self.push_one_packet_to_network(selector) {\n            self.client_to_network.next();\n        }\n    }\n\n    fn push_one_packet_to_network(&mut self, selector: &mut Selector) -> bool {\n        match self.client_to_network.as_ipv4_packet() {\n            Some(ref packet) => {\n                let mut client_channel = ClientChannel::new(\n                    &mut self.network_to_client,\n                    &self.stream,\n                    self.token,\n                    &mut self.interests,\n                );\n                self.router\n                    .send_to_network(selector, &mut client_channel, packet);\n                true\n            }\n            None => false,\n        }\n    }\n\n    fn process_pending(&mut self, selector: &mut Selector) {\n        let mut vec = Vec::new();\n        mem::swap(&mut self.pending_packet_sources, &mut vec);\n        for pending in vec.into_iter() {\n            let consumed = {\n                let mut source = pending.borrow_mut();\n                let result = {\n                    let ipv4_packet = source\n                        .get()\n                        .expect(\"Unexpected pending source with no packet\");\n                    self.send_to_client(selector, &ipv4_packet)\n                };\n                #[allow(clippy::match_wild_err_arm)]\n                match result {\n                    Ok(_) => {\n                        source.next(selector);\n                        true\n                    }\n                    Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => false,\n                    Err(_) => {\n                        panic!(\"Cannot send packet to client for unknown reason\");\n                    }\n                }\n            };\n            if !consumed {\n                // keep it pending\n                self.pending_packet_sources.push(pending);\n            }\n        }\n    }\n\n    pub fn clean_expired_connections(&mut self, selector: &mut Selector) {\n        self.router.clean_expired_connections(selector);\n    }\n\n    fn must_send_id(&self) -> bool {\n        self.pending_id_bytes > 0\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/relay/close_listener.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npub trait CloseListener<T> {\n    fn on_closed(&self, target: &T);\n}\n\nimpl<F, T> CloseListener<T> for F\nwhere\n    F: Fn(&T),\n{\n    fn on_closed(&self, target: &T) {\n        self(target);\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/relay/connection.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse std::fmt;\nuse std::net::SocketAddrV4;\n\nuse super::client::ClientChannel;\nuse super::ipv4_header::{Ipv4HeaderData, Protocol};\nuse super::ipv4_packet::Ipv4Packet;\nuse super::net;\nuse super::selector::Selector;\nuse super::transport_header::TransportHeaderData;\n\nconst LOCALHOST_FORWARD: u32 = 0x0A_00_02_02; // 10.0.2.2\nconst LOCALHOST: u32 = 0x7F_00_00_01; // 127.0.0.1\n\npub trait Connection {\n    fn id(&self) -> &ConnectionId;\n    fn send_to_network(\n        &mut self,\n        selector: &mut Selector,\n        client_channel: &mut ClientChannel,\n        ipv4_packet: &Ipv4Packet,\n    );\n    fn close(&mut self, selector: &mut Selector);\n    fn is_expired(&self) -> bool;\n    fn is_closed(&self) -> bool;\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct ConnectionId {\n    protocol: Protocol,\n    source_ip: u32,\n    source_port: u16,\n    destination_ip: u32,\n    destination_port: u16,\n    id_string: String,\n}\n\nimpl ConnectionId {\n    pub fn from_headers(\n        ipv4_header_data: &Ipv4HeaderData,\n        transport_header_data: &TransportHeaderData,\n    ) -> Self {\n        let source_ip = ipv4_header_data.source();\n        let source_port = transport_header_data.source_port();\n        let destination_ip = ipv4_header_data.destination();\n        let destination_port = transport_header_data.destination_port();\n        let id_string = format!(\n            \"{} -> {}\",\n            net::to_socket_addr(source_ip, source_port),\n            net::to_socket_addr(destination_ip, destination_port)\n        );\n        Self {\n            protocol: ipv4_header_data.protocol(),\n            source_ip,\n            source_port,\n            destination_ip,\n            destination_port,\n            id_string,\n        }\n    }\n\n    pub fn protocol(&self) -> Protocol {\n        self.protocol\n    }\n\n    pub fn rewritten_destination(&self) -> SocketAddrV4 {\n        let ip = if self.destination_ip == LOCALHOST_FORWARD {\n            LOCALHOST\n        } else {\n            self.destination_ip\n        };\n        net::to_socket_addr(ip, self.destination_port)\n    }\n}\n\nimpl fmt::Display for ConnectionId {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{}\", self.id_string)\n    }\n}\n\n// macros to log connection id along with the message\n\nmacro_rules! cx_format {\n    ($id:tt, $str:tt, $($arg:tt)+) => {\n        format!(concat!(\"{} \", $str), $id, $($arg)+)\n    };\n    ($id:tt, $str:tt) => {\n        format!(concat!(\"{} \", $str), $id)\n    };\n}\n\nmacro_rules! cx_trace {\n    (target: $target:expr, $id:expr, $($arg:tt)*) => {\n        log::trace!(target: $target, \"{}\", cx_format!($id, $($arg)+))\n    }\n}\n\nmacro_rules! cx_debug {\n    (target: $target:expr, $id:expr, $($arg:tt)*) => {\n        log::debug!(target: $target, \"{}\", cx_format!($id, $($arg)+))\n    }\n}\n\nmacro_rules! cx_info {\n    (target: $target:expr, $id:expr, $($arg:tt)*) => {\n        log::info!(target: $target, \"{}\", cx_format!($id, $($arg)+))\n    }\n}\n\nmacro_rules! cx_warn {\n    (target: $target:expr, $id:expr, $($arg:tt)*) => {\n        log::warn!(target: $target, \"{}\", cx_format!($id, $($arg)+))\n    }\n}\n\nmacro_rules! cx_error {\n    (target: $target:expr, $id:expr, $($arg:tt)*) => {\n        log::error!(target: $target, \"{}\", cx_format!($id, $($arg)+))\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/relay/datagram.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse mio::net::UdpSocket;\nuse std::cmp;\nuse std::io;\n\npub const MAX_DATAGRAM_LENGTH: usize = 1 << 16;\n\npub trait DatagramSender {\n    fn send(&mut self, buf: &[u8]) -> io::Result<usize>;\n}\n\npub trait DatagramReceiver {\n    fn recv(&mut self, buf: &mut [u8]) -> io::Result<usize>;\n}\n\n// Expose UdpSocket as DatagramSender\nimpl DatagramSender for UdpSocket {\n    fn send(&mut self, buf: &[u8]) -> io::Result<usize> {\n        // call the Self implementation\n        (self as &Self).send(buf)\n    }\n}\n\n// Expose UdpSocket as DatagramReceiver\nimpl DatagramReceiver for UdpSocket {\n    fn recv(&mut self, buf: &mut [u8]) -> io::Result<usize> {\n        // call the Self implementation\n        (self as &Self).recv(buf)\n    }\n}\n\n// Convert a Read to a DatagramReceiver\npub struct ReadAdapter<'a, R>\nwhere\n    R: io::Read + 'a,\n{\n    read: &'a mut R,\n    max_chunk_size: Option<usize>,\n}\n\nimpl<'a, R> ReadAdapter<'a, R>\nwhere\n    R: io::Read + 'a,\n{\n    pub fn new(read: &'a mut R, max_chunk_size: Option<usize>) -> Self {\n        Self {\n            read,\n            max_chunk_size,\n        }\n    }\n}\n\nimpl<'a, R> DatagramReceiver for ReadAdapter<'a, R>\nwhere\n    R: io::Read + 'a,\n{\n    fn recv(&mut self, buf: &mut [u8]) -> io::Result<usize> {\n        let len = if let Some(max_chunk_size) = self.max_chunk_size {\n            cmp::min(max_chunk_size, buf.len())\n        } else {\n            buf.len()\n        };\n        self.read.read(&mut buf[..len])\n    }\n}\n\n#[cfg(test)]\npub mod tests {\n    use super::*;\n\n    // Mock datagram socket to be used in other tests\n    pub struct MockDatagramSocket {\n        buf: [u8; MAX_DATAGRAM_LENGTH],\n        len: usize,\n    }\n\n    impl MockDatagramSocket {\n        pub fn new() -> Self {\n            Self {\n                buf: [0; MAX_DATAGRAM_LENGTH],\n                len: 0,\n            }\n        }\n\n        pub fn from_data(data: &[u8]) -> Self {\n            let mut mock = MockDatagramSocket::new();\n            mock.send(data).unwrap();\n            mock\n        }\n\n        pub fn data(&self) -> &[u8] {\n            &self.buf[..self.len]\n        }\n    }\n\n    impl DatagramSender for MockDatagramSocket {\n        fn send(&mut self, buf: &[u8]) -> io::Result<usize> {\n            let len = cmp::min(self.buf.len(), buf.len());\n            self.buf[..len].copy_from_slice(&buf[..len]);\n            self.len = len;\n            Ok(len)\n        }\n    }\n\n    impl DatagramReceiver for MockDatagramSocket {\n        fn recv(&mut self, buf: &mut [u8]) -> io::Result<usize> {\n            let len = cmp::min(self.len, buf.len());\n            buf[..len].copy_from_slice(&self.buf[..len]);\n            Ok(len)\n        }\n    }\n\n    #[test]\n    fn mock_send() {\n        let mut mock = MockDatagramSocket::new();\n        let data = [1, 2, 3, 4, 5];\n        let sent = mock.send(&data).unwrap();\n        assert_eq!(5, sent);\n        assert_eq!([1, 2, 3, 4, 5], mock.data());\n    }\n\n    #[test]\n    fn mock_recv() {\n        let mut mock = MockDatagramSocket::from_data(&[1, 2, 3, 4, 5]);\n        let mut buf = [0u8; 10];\n        let recved = mock.recv(&mut buf).unwrap();\n        assert_eq!(5, recved);\n        assert_eq!([1, 2, 3, 4, 5], &buf[..5]);\n    }\n\n    #[test]\n    fn read_adapter() {\n        let mut cursor = io::Cursor::new([1, 2, 3, 4, 5]);\n        let mut buf = [0u8; 10];\n        let mut adapter = ReadAdapter::new(&mut cursor, None);\n        let recved = adapter.recv(&mut buf).unwrap();\n        assert_eq!(5, recved);\n        assert_eq!([1, 2, 3, 4, 5], &buf[..5]);\n    }\n\n    #[test]\n    fn read_adapter_chunks() {\n        let mut cursor = io::Cursor::new([1, 2, 3, 4, 5]);\n        let mut buf = [0u8; 10];\n\n        {\n            let mut adapter = ReadAdapter::new(&mut cursor, Some(2));\n            let recved = adapter.recv(&mut buf).unwrap();\n            assert_eq!(2, recved);\n            assert_eq!([1, 2], &buf[..2]);\n        }\n\n        {\n            let mut adapter = ReadAdapter::new(&mut cursor, Some(1));\n            let recved = adapter.recv(&mut buf).unwrap();\n            assert_eq!(1, recved);\n            assert_eq!([3], &buf[..1]);\n        }\n\n        {\n            let mut adapter = ReadAdapter::new(&mut cursor, None);\n            let recved = adapter.recv(&mut buf).unwrap();\n            assert_eq!(2, recved);\n            assert_eq!([4, 5], &buf[..2]);\n        }\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/relay/datagram_buffer.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse byteorder::{BigEndian, ByteOrder};\nuse log::*;\nuse std::io;\n\nuse super::datagram::{DatagramSender, MAX_DATAGRAM_LENGTH};\n\nconst HEADER_LENGTH: usize = 2;\nconst MAX_BLOCK_LENGTH: usize = HEADER_LENGTH + MAX_DATAGRAM_LENGTH;\n\nconst TAG: &str = \"DatagramBuffer\";\n\n/// Circular buffer to store datagrams (preserving their boundaries).\n///\n/// ```text\n///     circularBufferLength\n/// |<------------------------->| extra space for storing the last datagram in one block\n/// +---------------------------+------+\n/// |                           |      |\n/// |[D4]     [  D1  ][ D2 ][  D3  ]   |\n/// +---------------------------+------+\n///     ^     ^\n///  head     tail\n/// ```\npub struct DatagramBuffer {\n    buf: Box<[u8]>,\n    head: usize,\n    tail: usize,\n    circular_buffer_length: usize,\n}\n\nimpl DatagramBuffer {\n    pub fn new(capacity: usize) -> Self {\n        Self {\n            buf: vec![0; capacity + MAX_BLOCK_LENGTH].into_boxed_slice(),\n            head: 0,\n            tail: 0,\n            circular_buffer_length: capacity + 1,\n        }\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.head == self.tail\n    }\n\n    pub fn has_enough_space_for(&self, datagram_length: usize) -> bool {\n        if self.head >= self.tail {\n            // there is at least the extra space for storing 1 packet\n            return true;\n        }\n        // 1 extra byte to distinguish empty vs full\n        let remaining = self.tail - self.head - 1;\n        HEADER_LENGTH + datagram_length <= remaining\n    }\n\n    pub fn write_to<S: DatagramSender>(&mut self, destination: &mut S) -> io::Result<()> {\n        assert!(\n            !self.is_empty(),\n            \"DatagramBuffer.write_to() called while empty\"\n        );\n        let length = self.read_length() as usize;\n        let source_slice = &self.buf[self.tail..self.tail + length];\n        self.tail += length;\n        if self.tail >= self.circular_buffer_length {\n            self.tail = 0;\n        }\n        let w = destination.send(source_slice)?;\n        if w != length {\n            error!(\n                target: TAG,\n                \"Cannot write the whole datagram to the buffer (only {}/{})\", w, length\n            );\n            return Err(io::Error::new(\n                io::ErrorKind::Other,\n                \"Cannot write the whole datagram\",\n            ));\n        }\n        Ok(())\n    }\n\n    pub fn read_from(&mut self, source: &[u8]) -> io::Result<()> {\n        let length = source.len();\n        assert!(\n            length <= MAX_DATAGRAM_LENGTH,\n            \"Datagram length may not be greater than {} bytes\",\n            MAX_DATAGRAM_LENGTH\n        );\n        if !self.has_enough_space_for(length) {\n            return Err(io::Error::new(\n                io::ErrorKind::Other,\n                \"Datagram buffer is full\",\n            ));\n        }\n        self.write_length(length as u16);\n        let target_slice = &mut self.buf[self.head..self.head + length];\n        target_slice.copy_from_slice(source);\n        self.head += length;\n        if self.head >= self.circular_buffer_length {\n            self.head = 0;\n        }\n        Ok(())\n    }\n\n    fn read_length(&mut self) -> u16 {\n        self.tail += 2;\n        BigEndian::read_u16(&self.buf[self.tail - 2..self.tail])\n    }\n\n    fn write_length(&mut self, length: u16) {\n        BigEndian::write_u16(&mut self.buf[self.head..self.head + 2], length);\n        self.head += 2;\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::relay::datagram::tests::MockDatagramSocket;\n\n    fn create_datagram(length: u8) -> Vec<u8> {\n        (0..length).collect()\n    }\n\n    #[test]\n    fn bufferize_datagram() {\n        let datagram = create_datagram(5);\n        let mut datagram_buffer = DatagramBuffer::new(9);\n\n        datagram_buffer.read_from(&datagram).unwrap();\n        assert_eq!(read_datagram(&mut datagram_buffer), datagram);\n    }\n\n    #[test]\n    fn split_datagrams_at_boundaries() {\n        let mut datagram_buffer = DatagramBuffer::new(32);\n\n        let datagram5 = create_datagram(5);\n        let datagram0 = create_datagram(0);\n        let datagram3 = create_datagram(3);\n        let datagram4 = create_datagram(4);\n\n        datagram_buffer.read_from(&datagram5).unwrap();\n        datagram_buffer.read_from(&datagram0).unwrap();\n        datagram_buffer.read_from(&datagram3).unwrap();\n        datagram_buffer.read_from(&datagram4).unwrap();\n\n        assert_eq!(read_datagram(&mut datagram_buffer), datagram5);\n        assert_eq!(read_datagram(&mut datagram_buffer), datagram0);\n        assert_eq!(read_datagram(&mut datagram_buffer), datagram3);\n        assert_eq!(read_datagram(&mut datagram_buffer), datagram4);\n    }\n\n    #[test]\n    fn circular() {\n        let datagram5 = create_datagram(5);\n        let datagram3 = create_datagram(3);\n\n        let mut datagram_buffer = DatagramBuffer::new(14);\n\n        // write and consume 10 bytes\n        datagram_buffer.read_from(&create_datagram(10)).unwrap();\n        {\n            // write and forget\n            let mut mock = MockDatagramSocket::new();\n            datagram_buffer.write_to(&mut mock).unwrap();\n        }\n\n        // DatagramBuffer is expected to store the whole datagram, even if it exceeds its \"capacity\"\n        datagram_buffer.read_from(&datagram5).unwrap();\n        datagram_buffer.read_from(&datagram3).unwrap();\n\n        assert_eq!(read_datagram(&mut datagram_buffer), datagram5);\n        assert_eq!(read_datagram(&mut datagram_buffer), datagram3);\n    }\n\n    fn read_datagram(datagram_buffer: &mut DatagramBuffer) -> Vec<u8> {\n        let mut mock = MockDatagramSocket::new();\n        datagram_buffer.write_to(&mut mock).unwrap();\n        mock.data().to_vec()\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/relay/interrupt.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/// Reexecute the expression if the it returns an `Interrupted` error\nmacro_rules! retry_on_intr {\n    ($e:expr) => {{\n        let result;\n        loop {\n            match $e {\n                Err(ref err) if err.kind() == io::ErrorKind::Interrupted => {\n                    continue;\n                }\n                x => {\n                    result = x;\n                    break;\n                }\n            }\n        }\n        result\n    }};\n}\n"
  },
  {
    "path": "relay-rust/src/relay/ipv4_header.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse byteorder::{BigEndian, ByteOrder};\nuse std::mem;\n\npub struct Ipv4Header<'a> {\n    raw: &'a [u8],\n    data: &'a Ipv4HeaderData,\n}\n\npub struct Ipv4HeaderMut<'a> {\n    raw: &'a mut [u8],\n    data: &'a mut Ipv4HeaderData,\n}\n\n#[derive(Clone)]\npub struct Ipv4HeaderData {\n    _version: u8,\n    header_length: u8,\n    total_length: u16,\n    protocol: Protocol,\n    source: u32,\n    destination: u32,\n}\n\n#[derive(Copy, Clone, Debug, PartialEq, Eq)]\npub enum Protocol {\n    Tcp,\n    Udp,\n    Other,\n}\n\n#[allow(dead_code)]\nimpl Ipv4HeaderData {\n    pub fn parse(raw: &[u8]) -> Self {\n        Self {\n            _version: raw[0] >> 4,\n            header_length: (raw[0] & 0xf) << 2,\n            total_length: BigEndian::read_u16(&raw[2..4]),\n            protocol: match raw[9] {\n                6 => Protocol::Tcp,\n                17 => Protocol::Udp,\n                _ => Protocol::Other,\n            },\n            source: BigEndian::read_u32(&raw[12..16]),\n            destination: BigEndian::read_u32(&raw[16..20]),\n        }\n    }\n\n    pub fn bind<'c, 'a: 'c, 'b: 'c>(&'a self, raw: &'b [u8]) -> Ipv4Header<'c> {\n        Ipv4Header::new(raw, self)\n    }\n\n    pub fn bind_mut<'c, 'a: 'c, 'b: 'c>(&'a mut self, raw: &'b mut [u8]) -> Ipv4HeaderMut<'c> {\n        Ipv4HeaderMut::new(raw, self)\n    }\n\n    pub fn header_length(&self) -> u8 {\n        self.header_length\n    }\n\n    pub fn total_length(&self) -> u16 {\n        self.total_length\n    }\n\n    pub fn protocol(&self) -> Protocol {\n        self.protocol\n    }\n\n    pub fn source(&self) -> u32 {\n        self.source\n    }\n\n    pub fn destination(&self) -> u32 {\n        self.destination\n    }\n}\n\npub fn peek_version_length(raw: &[u8]) -> Option<(u8, u16)> {\n    if raw.len() >= 4 {\n        // version is stored in the 4 first bits\n        let version = raw[0] >> 4;\n        // packet length is 16 bits starting at offset 2\n        let length = BigEndian::read_u16(&raw[2..4]);\n        Some((version, length))\n    } else {\n        None\n    }\n}\n\n// shared definition for Ipv4Header and Ipv4HeaderMut\nmacro_rules! ipv4_header_common {\n    ($name:ident, $raw_type:ty, $data_type:ty) => {\n        // for readability, declare structs manually outside the macro\n        #[allow(dead_code)]\n        impl<'a> $name<'a> {\n            pub fn new(raw: $raw_type, data: $data_type) -> Self {\n                Self {\n                    raw: raw,\n                    data: data,\n                }\n            }\n\n            pub fn raw(&self) -> &[u8] {\n                self.raw\n            }\n\n            pub fn data(&self) -> &Ipv4HeaderData {\n                self.data\n            }\n\n            pub fn header_length(&self) -> u8 {\n                self.data.header_length\n            }\n\n            pub fn total_length(&self) -> u16 {\n                self.data.total_length\n            }\n\n            pub fn protocol(&self) -> Protocol {\n                self.data.protocol\n            }\n\n            pub fn source(&self) -> u32 {\n                self.data.source\n            }\n\n            pub fn destination(&self) -> u32 {\n                self.data.destination\n            }\n        }\n    };\n}\n\nipv4_header_common!(Ipv4Header, &'a [u8], &'a Ipv4HeaderData);\nipv4_header_common!(Ipv4HeaderMut, &'a mut [u8], &'a mut Ipv4HeaderData);\n\n// additional methods for the mutable version\n#[allow(dead_code)]\nimpl<'a> Ipv4HeaderMut<'a> {\n    pub fn raw_mut(&mut self) -> &mut [u8] {\n        self.raw\n    }\n\n    pub fn data_mut(&mut self) -> &mut Ipv4HeaderData {\n        self.data\n    }\n\n    pub fn set_total_length(&mut self, total_length: u16) {\n        self.data.total_length = total_length;\n        BigEndian::write_u16(&mut self.raw[2..4], total_length);\n    }\n\n    pub fn set_source(&mut self, source: u32) {\n        self.data.source = source;\n        BigEndian::write_u32(&mut self.raw[12..16], source);\n    }\n\n    pub fn set_destination(&mut self, destination: u32) {\n        self.data.destination = destination;\n        BigEndian::write_u32(&mut self.raw[16..20], destination);\n    }\n\n    pub fn swap_source_and_destination(&mut self) {\n        mem::swap(&mut self.data.source, &mut self.data.destination);\n        for i in 12..16 {\n            self.raw.swap(i, i + 4);\n        }\n    }\n\n    fn checksum(&self) -> u16 {\n        BigEndian::read_u16(&self.raw[10..12])\n    }\n\n    fn set_checksum(&mut self, checksum: u16) {\n        BigEndian::write_u16(&mut self.raw[10..12], checksum);\n    }\n\n    pub fn update_checksum(&mut self) {\n        let j = self.data.header_length as usize / 2;\n        // skip checksum field at 10..12\n        let mut sum = (0..5)\n            .chain(6..j)\n            .map(|i| {\n                let range = 2 * i..2 * (i + 1);\n                u32::from(BigEndian::read_u16(&self.raw[range]))\n            })\n            .sum::<u32>();\n        while (sum & !0xffff) != 0 {\n            sum = (sum & 0xffff) + (sum >> 16);\n        }\n        self.set_checksum(!sum as u16);\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use byteorder::{BigEndian, WriteBytesExt};\n\n    fn create_header() -> Vec<u8> {\n        let mut raw: Vec<u8> = Vec::new();\n        raw.reserve(20);\n        raw.write_u8(4u8 << 4 | 5).unwrap(); // version_and_ihl\n        raw.write_u8(0).unwrap(); //ToS\n        raw.write_u16::<BigEndian>(28).unwrap(); // total length\n        raw.write_u32::<BigEndian>(0).unwrap(); // id_flags_fragment_offset\n        raw.write_u8(0).unwrap(); // TTL\n        raw.write_u8(17).unwrap(); // protocol (UDP)\n        raw.write_u16::<BigEndian>(0).unwrap(); // checksum\n        raw.write_u32::<BigEndian>(0x12345678).unwrap(); // source address\n        raw.write_u32::<BigEndian>(0x42424242).unwrap(); // destination address\n        raw\n    }\n\n    #[test]\n    fn parse_header() {\n        let raw = &create_header()[..];\n        let data = Ipv4HeaderData::parse(raw);\n        assert_eq!(4, data._version);\n        assert_eq!(20, data.header_length);\n        assert_eq!(28, data.total_length);\n        assert_eq!(Protocol::Udp, data.protocol);\n        assert_eq!(0x12345678, data.source);\n        assert_eq!(0x42424242, data.destination);\n    }\n\n    #[test]\n    fn edit_header() {\n        let raw = &mut create_header()[..];\n        let mut header_data = Ipv4HeaderData::parse(raw);\n        let mut header = header_data.bind_mut(raw);\n\n        header.set_source(0x87654321);\n        header.set_destination(0x24242424);\n        header.set_total_length(42);\n        assert_eq!(0x87654321, header.source());\n        assert_eq!(0x24242424, header.destination());\n        assert_eq!(42, header.total_length());\n\n        // assert that the buffer has been modified\n        let raw_source = BigEndian::read_u32(&header.raw[12..16]);\n        let raw_destination = BigEndian::read_u32(&header.raw[16..20]);\n        let raw_total_length = BigEndian::read_u16(&header.raw[2..4]);\n        assert_eq!(0x87654321, raw_source);\n        assert_eq!(0x24242424, raw_destination);\n        assert_eq!(42, raw_total_length);\n\n        header.swap_source_and_destination();\n\n        assert_eq!(0x24242424, header.source());\n        assert_eq!(0x87654321, header.destination());\n\n        let raw_source = BigEndian::read_u32(&header.raw[12..16]);\n        let raw_destination = BigEndian::read_u32(&header.raw[16..20]);\n        assert_eq!(0x24242424, raw_source);\n        assert_eq!(0x87654321, raw_destination);\n    }\n\n    #[test]\n    fn compute_checksum() {\n        let raw = &mut create_header()[..];\n        let mut header_data = Ipv4HeaderData::parse(raw);\n        let mut header = header_data.bind_mut(raw);\n\n        // set a fake checksum value to assert that it is correctly computed\n        header.set_checksum(0x79);\n\n        header.update_checksum();\n\n        let mut sum: u32 =\n            0x4500 + 0x001C + 0x0000 + 0x0000 + 0x0011 + 0x0000 + 0x1234 + 0x5678 + 0x4242 + 0x4242;\n        while (sum & !0xffff) != 0 {\n            sum = (sum & 0xffff) + (sum >> 16);\n        }\n        let sum = !sum as u16;\n        assert_eq!(sum, header.checksum());\n    }\n\n    #[test]\n    fn peek_version_length_unavailable() {\n        let raw: [u8; 0] = [];\n        assert!(peek_version_length(&raw).is_none());\n        let raw = [0x40, 2];\n        assert!(peek_version_length(&raw).is_none());\n    }\n\n    #[test]\n    fn peek_version_length_available() {\n        let raw = [4u8 << 4 | 5, 0, 0x01, 0x23];\n        let (version, length) = peek_version_length(&raw).unwrap();\n        assert_eq!(4, version);\n        assert_eq!(0x123, length);\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/relay/ipv4_packet.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse super::ipv4_header::{Ipv4Header, Ipv4HeaderData, Ipv4HeaderMut};\nuse super::transport_header::{TransportHeader, TransportHeaderData, TransportHeaderMut};\n\npub const MAX_PACKET_LENGTH: usize = 1 << 16;\n\npub struct Ipv4Packet<'a> {\n    raw: &'a mut [u8],\n    ipv4_header_data: Ipv4HeaderData,\n    transport_header_data: Option<TransportHeaderData>,\n}\n\nimpl<'a> Ipv4Packet<'a> {\n    pub fn parse(raw: &'a mut [u8]) -> Self {\n        let ipv4_header_data = Ipv4HeaderData::parse(raw);\n        let transport_header_data = {\n            let payload = &raw[ipv4_header_data.header_length() as usize..];\n            TransportHeaderData::parse(ipv4_header_data.protocol(), payload)\n        };\n        Self {\n            raw: &mut raw[..ipv4_header_data.total_length() as usize],\n            ipv4_header_data,\n            transport_header_data,\n        }\n    }\n\n    pub fn new(\n        raw: &'a mut [u8],\n        ipv4_header_data: Ipv4HeaderData,\n        transport_header_data: TransportHeaderData,\n    ) -> Self {\n        Self {\n            raw,\n            ipv4_header_data,\n            transport_header_data: Some(transport_header_data),\n        }\n    }\n\n    #[inline]\n    pub fn raw(&self) -> &[u8] {\n        self.raw\n    }\n\n    #[inline]\n    pub fn headers_data(&self) -> (&Ipv4HeaderData, Option<&TransportHeaderData>) {\n        (&self.ipv4_header_data, self.transport_header_data.as_ref())\n    }\n\n    pub fn headers(&self) -> (Ipv4Header, Option<TransportHeader>) {\n        let transport_index = self.ipv4_header_data.header_length() as usize;\n        if let Some(ref transport_header_data) = self.transport_header_data {\n            let (ipv4_header_slice, transport_slice) = self.raw.split_at(transport_index);\n            // payload_index is relative to transport\n            let payload_index = transport_header_data.header_length() as usize;\n            let transport_header_slice = &transport_slice[..payload_index];\n            let ipv4_header = self.ipv4_header_data.bind(ipv4_header_slice);\n            let transport_header = transport_header_data.bind(transport_header_slice);\n            (ipv4_header, Some(transport_header))\n        } else {\n            let ipv4_header_slice = &self.raw[..transport_index];\n            let ipv4_header = self.ipv4_header_data.bind(ipv4_header_slice);\n            (ipv4_header, None)\n        }\n    }\n\n    #[inline]\n    #[allow(dead_code)]\n    pub fn ipv4_header_data(&self) -> &Ipv4HeaderData {\n        &self.ipv4_header_data\n    }\n\n    #[inline]\n    #[allow(dead_code)]\n    pub fn ipv4_header(&self) -> Ipv4Header {\n        let slice = &self.raw[..self.ipv4_header_data.header_length() as usize];\n        self.ipv4_header_data.bind(slice)\n    }\n\n    #[inline]\n    #[allow(dead_code)]\n    pub fn ipv4_header_mut(&mut self) -> Ipv4HeaderMut {\n        let slice = &mut self.raw[..self.ipv4_header_data.header_length() as usize];\n        self.ipv4_header_data.bind_mut(slice)\n    }\n\n    #[inline]\n    #[allow(dead_code)]\n    pub fn transport_header_data(&self) -> Option<&TransportHeaderData> {\n        self.transport_header_data.as_ref()\n    }\n\n    #[inline]\n    pub fn transport_header(&self) -> Option<TransportHeader> {\n        if let Some(ref transport_header_data) = self.transport_header_data {\n            let start = self.ipv4_header_data.header_length() as usize;\n            let end = start + transport_header_data.header_length() as usize;\n            let slice = &self.raw[start..end];\n            Some(transport_header_data.bind(slice))\n        } else {\n            None\n        }\n        /*        self.transport_header_data.as_ref().map(|transport_header_data| {\n            let start = self.ipv4_header_data.header_length() as usize;\n            let end = start + transport_header_data.header_length() as usize;\n            let slice = &self.raw[start..end];\n            transport_header_data.bind(slice)\n        })*/\n    }\n\n    #[inline]\n    #[allow(dead_code)]\n    fn transport_header_mut(&mut self) -> Option<TransportHeaderMut> {\n        if let Some(ref mut transport_header_data) = self.transport_header_data {\n            let start = self.ipv4_header_data.header_length() as usize;\n            let end = start + transport_header_data.header_length() as usize;\n            let slice = &mut self.raw[start..end];\n            Some(transport_header_data.bind_mut(slice))\n        } else {\n            None\n        }\n        /*        self.transport_header_data.as_mut().map(|transport_header_data| {\n            let start = self.ipv4_header_data.header_length() as usize;\n            let end = start + transport_header_data.header_length() as usize;\n            let slice = &mut self.raw[start..end];\n            transport_header_data.bind_mut(slice)\n        })*/\n    }\n\n    /// Devide the packet into parts:\n    ///  - the IP v4 header\n    ///  - the transport header (if any)\n    ///  - the payload (if there is a transport at all)\n    #[allow(dead_code)]\n    pub fn split(&self) -> (Ipv4Header, Option<(TransportHeader, &[u8])>) {\n        let transport_index = self.ipv4_header_data.header_length() as usize;\n        if let Some(ref transport_header_data) = self.transport_header_data {\n            // payload_index is relative to transport\n            let payload_index = transport_header_data.header_length() as usize;\n            let (ipv4_header_slice, transport_slice) = self.raw.split_at(transport_index);\n            let (transport_header_slice, payload_slice) = transport_slice.split_at(payload_index);\n            let ipv4_header = self.ipv4_header_data.bind(ipv4_header_slice);\n            let transport_header = transport_header_data.bind(transport_header_slice);\n            (ipv4_header, Some((transport_header, payload_slice)))\n        } else {\n            let ipv4_header_slice = &self.raw[..transport_index];\n            let ipv4_header = self.ipv4_header_data.bind(ipv4_header_slice);\n            (ipv4_header, None)\n        }\n    }\n\n    /// Devide the packet into mutable parts:\n    ///  - the IP v4 header\n    ///  - the transport header (if any)\n    ///  - the payload (if there is a transport at all)\n    pub fn split_mut(&mut self) -> (Ipv4HeaderMut, Option<(TransportHeaderMut, &mut [u8])>) {\n        let transport_index = self.ipv4_header_data.header_length() as usize;\n        if let Some(ref mut transport_header_data) = self.transport_header_data {\n            // payload_index is relative to transport\n            let payload_index = transport_header_data.header_length() as usize;\n            let (ipv4_header_slice, transport_slice) = self.raw.split_at_mut(transport_index);\n            let (transport_header_slice, payload_slice) =\n                transport_slice.split_at_mut(payload_index);\n            let ipv4_header = self.ipv4_header_data.bind_mut(ipv4_header_slice);\n            let transport_header = transport_header_data.bind_mut(transport_header_slice);\n            (ipv4_header, Some((transport_header, payload_slice)))\n        } else {\n            let ipv4_header_slice = &mut self.raw[..transport_index];\n            let ipv4_header = self.ipv4_header_data.bind_mut(ipv4_header_slice);\n            (ipv4_header, None)\n        }\n    }\n\n    #[inline]\n    pub fn is_valid(&self) -> bool {\n        self.transport_header_data.is_some()\n    }\n\n    #[inline]\n    pub fn length(&self) -> u16 {\n        self.ipv4_header_data.total_length()\n    }\n\n    pub fn payload(&self) -> Option<&[u8]> {\n        self.transport_header_data\n            .as_ref()\n            .map(|transport_header_data| {\n                let range = self.ipv4_header_data.header_length() as usize\n                    + transport_header_data.header_length() as usize..;\n                &self.raw[range]\n            })\n    }\n\n    pub fn compute_checksums(&mut self) {\n        let (mut ipv4_header, transport) = self.split_mut();\n        ipv4_header.update_checksum();\n        if let Some((mut transport_header, payload)) = transport {\n            transport_header.update_checksum(ipv4_header.data(), payload);\n        }\n    }\n\n    /*#[inline]\n    pub fn swap_source_and_destination(&mut self) {\n        self.ipv4_header_mut().swap_source_and_destination();\n        if let Some(mut transport_header) = self.transport_header_mut() {\n            transport_header.swap_source_and_destination();\n        }\n    }*/\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::relay::ipv4_header::Protocol;\n    use byteorder::{BigEndian, WriteBytesExt};\n\n    fn create_packet() -> Vec<u8> {\n        let mut raw = Vec::new();\n        raw.reserve(32);\n\n        raw.write_u8(4u8 << 4 | 5).unwrap(); // version_and_ihl\n        raw.write_u8(0).unwrap(); //ToS\n        raw.write_u16::<BigEndian>(32).unwrap(); // total length 20 + 8 + 4\n        raw.write_u32::<BigEndian>(0).unwrap(); // id_flags_fragment_offset\n        raw.write_u8(0).unwrap(); // TTL\n        raw.write_u8(17).unwrap(); // protocol (UDP)\n        raw.write_u16::<BigEndian>(0).unwrap(); // checksum\n        raw.write_u32::<BigEndian>(0x12345678).unwrap(); // source address\n        raw.write_u32::<BigEndian>(0x42424242).unwrap(); // destination address\n\n        raw.write_u16::<BigEndian>(1234).unwrap(); // source port\n        raw.write_u16::<BigEndian>(5678).unwrap(); // destination port\n        raw.write_u16::<BigEndian>(4).unwrap(); // length\n        raw.write_u16::<BigEndian>(0).unwrap(); // checksum\n\n        raw.write_u32::<BigEndian>(0x11223344).unwrap(); // payload\n\n        raw\n    }\n\n    #[test]\n    fn parse_headers() {\n        let raw = &mut create_packet()[..];\n        let ipv4_packet = Ipv4Packet::parse(raw);\n\n        {\n            let ipv4_header = ipv4_packet.ipv4_header();\n            assert_eq!(20, ipv4_header.header_length());\n            assert_eq!(32, ipv4_header.total_length());\n            assert_eq!(Protocol::Udp, ipv4_header.protocol());\n            assert_eq!(0x12345678, ipv4_header.source());\n            assert_eq!(0x42424242, ipv4_header.destination());\n\n            if let Some(&TransportHeaderData::Udp(ref udp_header)) =\n                ipv4_packet.transport_header_data()\n            {\n                assert_eq!(1234, udp_header.source_port());\n                assert_eq!(5678, udp_header.destination_port());\n            } else {\n                panic!(\"No UDP transport header\");\n            }\n        }\n    }\n\n    #[test]\n    fn payload() {\n        let raw = &mut create_packet()[..];\n        let ipv4_packet = Ipv4Packet::parse(raw);\n        assert_eq!([0x11, 0x22, 0x33, 0x44], ipv4_packet.payload().unwrap());\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/relay/ipv4_packet_buffer.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse super::binary;\nuse super::byte_buffer::ByteBuffer;\nuse super::ipv4_header;\nuse super::ipv4_packet::{Ipv4Packet, MAX_PACKET_LENGTH};\n\nuse log::*;\nuse std::io;\n\npub struct Ipv4PacketBuffer {\n    buf: ByteBuffer,\n}\n\nimpl Ipv4PacketBuffer {\n    pub fn new() -> Self {\n        Self {\n            buf: ByteBuffer::new(MAX_PACKET_LENGTH),\n        }\n    }\n\n    pub fn read_from<R: io::Read>(&mut self, source: &mut R) -> io::Result<bool> {\n        self.buf.read_from(source)\n    }\n\n    fn available_packet_length(&self) -> Option<u16> {\n        let data = self.buf.peek();\n        trace!(\"Parse packet: {}\", binary::build_packet_string(data));\n        if let Some((version, length)) = ipv4_header::peek_version_length(data) {\n            assert!(version == 4, \"Not an Ipv4 packet, version={}\", version);\n            if length as usize <= data.len() {\n                // full packet available\n                Some(length)\n            } else {\n                // no full packet available\n                None\n            }\n        } else {\n            // no packet\n            None\n        }\n    }\n\n    pub fn as_ipv4_packet(&mut self) -> Option<Ipv4Packet> {\n        if self.available_packet_length().is_some() {\n            let data = self.buf.peek_mut();\n            Some(Ipv4Packet::parse(data))\n        } else {\n            None\n        }\n    }\n\n    pub fn next(&mut self) {\n        // remove the packet in front of the buffer\n        let length = self\n            .available_packet_length()\n            .expect(\"next() called while there was no packet\") as usize;\n        self.buf.consume(length);\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::relay::ipv4_header::Protocol;\n    use crate::relay::transport_header::TransportHeaderData;\n    use byteorder::{BigEndian, WriteBytesExt};\n    use std::io;\n\n    fn create_packet() -> Vec<u8> {\n        let mut raw = Vec::new();\n        write_packet_to(&mut raw);\n        raw\n    }\n\n    fn write_packet_to(raw: &mut Vec<u8>) {\n        raw.write_u8(4u8 << 4 | 5).unwrap();\n        raw.write_u8(0).unwrap(); // ToS\n        raw.write_u16::<BigEndian>(32).unwrap(); // total length 20 + 8 + 4\n        raw.write_u32::<BigEndian>(0).unwrap(); // id_flags_fragment_offset\n        raw.write_u8(0).unwrap(); // TTL\n        raw.write_u8(17).unwrap(); // protocol (UDP)\n        raw.write_u16::<BigEndian>(0).unwrap(); // checksum\n        raw.write_u32::<BigEndian>(0x12345678).unwrap(); // source address\n        raw.write_u32::<BigEndian>(0x42424242).unwrap(); // destination address\n\n        raw.write_u16::<BigEndian>(1234).unwrap(); // source port\n        raw.write_u16::<BigEndian>(5678).unwrap(); // destination port\n        raw.write_u16::<BigEndian>(12).unwrap(); // length\n        raw.write_u16::<BigEndian>(0).unwrap(); // checksum\n\n        raw.write_u32::<BigEndian>(0x11223344).unwrap(); // payload\n    }\n\n    fn write_another_packet_to(raw: &mut Vec<u8>) {\n        raw.write_u8(4u8 << 4 | 5).unwrap();\n        raw.write_u8(0).unwrap(); // ToS\n        raw.write_u16::<BigEndian>(29).unwrap(); // total length 20 + 8 + 1\n        raw.write_u32::<BigEndian>(0).unwrap(); // id_flags_fragment_offset\n        raw.write_u8(0).unwrap(); // TTL\n        raw.write_u8(17).unwrap(); // protocol (UDP)\n        raw.write_u16::<BigEndian>(0).unwrap(); // checksum\n        raw.write_u32::<BigEndian>(0x11111111).unwrap(); // source address\n        raw.write_u32::<BigEndian>(0x22222222).unwrap(); // destination address\n\n        raw.write_u16::<BigEndian>(1111).unwrap(); // source port\n        raw.write_u16::<BigEndian>(2222).unwrap(); // destination port\n        raw.write_u16::<BigEndian>(9).unwrap(); // length\n        raw.write_u16::<BigEndian>(0).unwrap(); // checksum\n\n        raw.write_u8(0x99).unwrap(); // payload\n    }\n\n    fn check_packet_headers(ipv4_packet: &Ipv4Packet) {\n        let ipv4_header = ipv4_packet.ipv4_header();\n        assert_eq!(20, ipv4_header.header_length());\n        assert_eq!(32, ipv4_header.total_length());\n        assert_eq!(Protocol::Udp, ipv4_header.protocol());\n        assert_eq!(0x12345678, ipv4_header.source());\n        assert_eq!(0x42424242, ipv4_header.destination());\n\n        if let Some(&TransportHeaderData::Udp(ref udp_header)) = ipv4_packet.transport_header_data()\n        {\n            assert_eq!(1234, udp_header.source_port());\n            assert_eq!(5678, udp_header.destination_port());\n        } else {\n            panic!(\"No UDP transport header\");\n        }\n    }\n\n    fn check_another_packet_headers(ipv4_packet: &Ipv4Packet) {\n        let ipv4_header = ipv4_packet.ipv4_header();\n        assert_eq!(20, ipv4_header.header_length());\n        assert_eq!(29, ipv4_header.total_length());\n        assert_eq!(Protocol::Udp, ipv4_header.protocol());\n        assert_eq!(0x11111111, ipv4_header.source());\n        assert_eq!(0x22222222, ipv4_header.destination());\n\n        if let Some(&TransportHeaderData::Udp(ref udp_header)) = ipv4_packet.transport_header_data()\n        {\n            assert_eq!(1111, udp_header.source_port());\n            assert_eq!(2222, udp_header.destination_port());\n        } else {\n            panic!(\"No UDP transport header\");\n        }\n    }\n\n    #[test]\n    fn parse_ipv4_packet_buffer() {\n        let raw = create_packet();\n        let mut packet_buffer = Ipv4PacketBuffer::new();\n\n        let mut cursor = io::Cursor::new(raw);\n        packet_buffer.read_from(&mut cursor).unwrap();\n\n        let packet = packet_buffer.as_ipv4_packet().unwrap();\n        check_packet_headers(&packet);\n    }\n\n    #[test]\n    fn parse_fragmented_ipv4_packet_buffer() {\n        let raw = create_packet();\n        let mut packet_buffer = Ipv4PacketBuffer::new();\n\n        let mut cursor = io::Cursor::new(&raw[..14]);\n        packet_buffer.read_from(&mut cursor).unwrap();\n\n        assert!(packet_buffer.as_ipv4_packet().is_none());\n\n        let mut cursor = io::Cursor::new(&raw[14..]);\n        packet_buffer.read_from(&mut cursor).unwrap();\n\n        let packet = packet_buffer.as_ipv4_packet().unwrap();\n        check_packet_headers(&packet);\n    }\n\n    fn create_multi_packets() -> Vec<u8> {\n        let mut raw = Vec::new();\n        write_packet_to(&mut raw);\n        write_another_packet_to(&mut raw);\n        write_packet_to(&mut raw);\n        raw\n    }\n\n    #[test]\n    fn parse_multi_packets() {\n        let raw = create_multi_packets();\n        let mut packet_buffer = Ipv4PacketBuffer::new();\n\n        let mut cursor = io::Cursor::new(raw);\n        packet_buffer.read_from(&mut cursor).unwrap();\n\n        check_packet_headers(&packet_buffer.as_ipv4_packet().unwrap());\n        packet_buffer.next();\n        check_another_packet_headers(&packet_buffer.as_ipv4_packet().unwrap());\n        packet_buffer.next();\n        check_packet_headers(&packet_buffer.as_ipv4_packet().unwrap());\n        packet_buffer.next();\n\n        assert!(packet_buffer.as_ipv4_packet().is_none());\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/relay/mod.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npub use self::relay::Relay;\npub mod byte_buffer;\n\nmod binary;\nmod client;\nmod close_listener;\n#[macro_use]\nmod connection;\nmod datagram;\nmod datagram_buffer;\n#[macro_use]\nmod interrupt;\nmod ipv4_header;\nmod ipv4_packet;\nmod ipv4_packet_buffer;\nmod net;\nmod packet_source;\nmod packetizer;\n#[allow(clippy::module_inception)] // relay.rs is in relay/\nmod relay;\nmod router;\nmod selector;\nmod stream_buffer;\nmod tcp_connection;\nmod tcp_header;\nmod transport_header;\nmod tunnel_server;\nmod udp_connection;\nmod udp_header;\n"
  },
  {
    "path": "relay-rust/src/relay/net.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse super::binary;\nuse std::net::{Ipv4Addr, SocketAddrV4};\n\npub fn to_addr(ipv4: u32) -> Ipv4Addr {\n    let raw = binary::to_byte_array(ipv4);\n    Ipv4Addr::new(raw[0], raw[1], raw[2], raw[3])\n}\n\npub fn to_socket_addr(ipv4: u32, port: u16) -> SocketAddrV4 {\n    let addr = to_addr(ipv4);\n    SocketAddrV4::new(addr, port)\n}\n"
  },
  {
    "path": "relay-rust/src/relay/packet_source.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse super::ipv4_packet::Ipv4Packet;\nuse super::selector::Selector;\n\n/// Source that may produce packets.\n///\n/// When a `TcpConnection` sends a packet to the `Client` while its buffers are full, then it\n/// fails. To recover, once some space becomes available, the `Client` must pull the available\n/// packets.\n///\n/// This trait provides the abstraction of a packet source from which it can pull packets.\n///\n/// It is implemented by `TcpConnection`.\npub trait PacketSource {\n    fn get(&mut self) -> Option<Ipv4Packet>;\n    fn next(&mut self, selector: &mut Selector);\n}\n"
  },
  {
    "path": "relay-rust/src/relay/packetizer.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse std::io;\n\nuse super::datagram::{DatagramReceiver, ReadAdapter};\nuse super::ipv4_header::{Ipv4Header, Ipv4HeaderData, Ipv4HeaderMut};\nuse super::ipv4_packet::{Ipv4Packet, MAX_PACKET_LENGTH};\nuse super::transport_header::{TransportHeader, TransportHeaderData, TransportHeaderMut};\n\n/// Convert from level 5 to level 3 by appending correct IP and transport headers.\npub struct Packetizer {\n    buffer: Box<[u8; MAX_PACKET_LENGTH]>,\n    transport_index: usize,\n    payload_index: usize,\n    ipv4_header_data: Ipv4HeaderData,\n    transport_header_data: TransportHeaderData,\n}\n\nimpl Packetizer {\n    pub fn new(\n        reference_ipv4_header: &Ipv4Header,\n        reference_transport_header: &TransportHeader,\n    ) -> Self {\n        let mut buffer = Box::new([0; MAX_PACKET_LENGTH]);\n\n        let transport_index = reference_ipv4_header.header_length() as usize;\n        let payload_index = transport_index + reference_transport_header.header_length() as usize;\n\n        let mut ipv4_header_data = reference_ipv4_header.data().clone();\n        let mut transport_header_data = reference_transport_header.data_clone();\n\n        {\n            let ipv4_header_raw = &mut buffer[..transport_index];\n            ipv4_header_raw.copy_from_slice(reference_ipv4_header.raw());\n            let mut ipv4_header = ipv4_header_data.bind_mut(ipv4_header_raw);\n            ipv4_header.swap_source_and_destination();\n        }\n\n        {\n            let transport_header_raw = &mut buffer[transport_index..payload_index];\n            transport_header_raw.copy_from_slice(reference_transport_header.raw());\n            let mut transport_header = transport_header_data.bind_mut(transport_header_raw);\n            transport_header.swap_source_and_destination();\n        }\n\n        Self {\n            buffer,\n            transport_index,\n            payload_index,\n            ipv4_header_data,\n            transport_header_data,\n        }\n    }\n\n    pub fn packetize_empty_payload(&mut self) -> Ipv4Packet {\n        self.build(0)\n    }\n\n    pub fn packetize<R: DatagramReceiver>(&mut self, source: &mut R) -> io::Result<Ipv4Packet> {\n        let r = source.recv(&mut self.buffer[self.payload_index..])?;\n        let ipv4_packet = self.build(r as u16);\n        Ok(ipv4_packet)\n    }\n\n    /// Packetize from stream (`Read`) source.\n    ///\n    /// `Ok(Some(_))` when packet is available\n    /// `Ok(None)` on EOF (read 0 byte)\n    /// `Err(_)` on error\n    pub fn packetize_read<R: io::Read>(\n        &mut self,\n        source: &mut R,\n        max_chunk_size: Option<usize>,\n    ) -> io::Result<Option<Ipv4Packet>> {\n        let mut adapter = ReadAdapter::new(source, max_chunk_size);\n        let r = adapter.recv(&mut self.buffer[self.payload_index..])?;\n        let option = if r > 0 {\n            let ipv4_packet = self.build(r as u16);\n            Some(ipv4_packet)\n        } else {\n            None\n        };\n        Ok(option)\n    }\n\n    pub fn ipv4_header_mut(&mut self) -> Ipv4HeaderMut {\n        let raw = &mut self.buffer[..self.transport_index];\n        self.ipv4_header_data.bind_mut(raw)\n    }\n\n    pub fn transport_header_mut(&mut self) -> TransportHeaderMut {\n        let raw = &mut self.buffer[self.transport_index..self.payload_index];\n        self.transport_header_data.bind_mut(raw)\n    }\n\n    fn build(&mut self, payload_length: u16) -> Ipv4Packet {\n        let total_length = self.payload_index as u16 + payload_length;\n\n        self.ipv4_header_mut().set_total_length(total_length);\n        self.transport_header_mut()\n            .set_payload_length(payload_length);\n\n        let mut ipv4_packet = Ipv4Packet::new(\n            &mut self.buffer[..total_length as usize],\n            self.ipv4_header_data.clone(),\n            self.transport_header_data.clone(),\n        );\n        ipv4_packet.compute_checksums();\n        ipv4_packet\n    }\n\n    pub fn inflate(&mut self, packet_length: u16) -> Ipv4Packet {\n        Ipv4Packet::new(\n            &mut self.buffer[..packet_length as usize],\n            self.ipv4_header_data.clone(),\n            self.transport_header_data.clone(),\n        )\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::relay::datagram::tests::MockDatagramSocket;\n    use byteorder::{BigEndian, WriteBytesExt};\n    use std::io;\n\n    fn create_packet() -> Vec<u8> {\n        let mut raw = Vec::new();\n        raw.write_u8(4u8 << 4 | 5).unwrap();\n        raw.write_u8(0).unwrap(); // ToS\n        raw.write_u16::<BigEndian>(32).unwrap(); // total length 20 + 8 + 4\n        raw.write_u32::<BigEndian>(0).unwrap(); // id_flags_fragment_offset\n        raw.write_u8(0).unwrap(); // TTL\n        raw.write_u8(17).unwrap(); // protocol (UDP)\n        raw.write_u16::<BigEndian>(0).unwrap(); // checksum\n        raw.write_u32::<BigEndian>(0x12345678).unwrap(); // source address\n        raw.write_u32::<BigEndian>(0x42424242).unwrap(); // destination address\n\n        raw.write_u16::<BigEndian>(1234).unwrap(); // source port\n        raw.write_u16::<BigEndian>(5678).unwrap(); // destination port\n        raw.write_u16::<BigEndian>(12).unwrap(); // length\n        raw.write_u16::<BigEndian>(0).unwrap(); // checksum\n\n        raw.write_u32::<BigEndian>(0x11223344).unwrap(); // payload\n        raw\n    }\n\n    #[test]\n    fn merge_headers_and_payload() {\n        let raw = &mut create_packet()[..];\n        let reference_packet = Ipv4Packet::parse(raw);\n\n        let data = [0x11u8, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88];\n        let mut mock = MockDatagramSocket::from_data(&data);\n\n        let ipv4_header = reference_packet.ipv4_header();\n        let transport_header = reference_packet.transport_header().unwrap();\n        let mut packetizer = Packetizer::new(&ipv4_header, &transport_header);\n\n        let packet = packetizer.packetize(&mut mock).unwrap();\n        assert_eq!(36, packet.ipv4_header_data().total_length());\n        assert_eq!(data, &packet.raw()[28..36]);\n    }\n\n    #[test]\n    fn last_packet() {\n        let raw = &mut create_packet()[..];\n        let reference_packet = Ipv4Packet::parse(raw);\n\n        let data = [0x11u8, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88];\n        let mut mock = MockDatagramSocket::from_data(&data);\n\n        let ipv4_header = reference_packet.ipv4_header();\n        let transport_header = reference_packet.transport_header().unwrap();\n        let mut packetizer = Packetizer::new(&ipv4_header, &transport_header);\n\n        let packet_length = packetizer.packetize(&mut mock).unwrap().length();\n        let packet = packetizer.inflate(packet_length);\n        assert_eq!(36, packet.ipv4_header_data().total_length());\n        assert_eq!(data, &packet.raw()[28..36]);\n    }\n\n    #[test]\n    fn packetize_chunks() {\n        let raw = &mut create_packet()[..];\n        let reference_packet = Ipv4Packet::parse(raw);\n\n        let data = [0x11u8, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88];\n        let mut cursor = io::Cursor::new(&data);\n\n        let ipv4_header = reference_packet.ipv4_header();\n        let transport_header = reference_packet.transport_header().unwrap();\n        let mut packetizer = Packetizer::new(&ipv4_header, &transport_header);\n\n        {\n            let packet = packetizer\n                .packetize_read(&mut cursor, Some(2))\n                .unwrap()\n                .unwrap();\n            assert_eq!(30, packet.ipv4_header_data().total_length());\n            assert_eq!([0x11, 0x22], packet.payload().unwrap());\n        }\n\n        {\n            let packet = packetizer\n                .packetize_read(&mut cursor, Some(3))\n                .unwrap()\n                .unwrap();\n            assert_eq!(31, packet.ipv4_header_data().total_length());\n            assert_eq!([0x33, 0x44, 0x55], packet.payload().unwrap());\n        }\n\n        {\n            let packet = packetizer\n                .packetize_read(&mut cursor, Some(1024))\n                .unwrap()\n                .unwrap();\n            assert_eq!(31, packet.ipv4_header_data().total_length());\n            assert_eq!([0x66, 0x77, 0x88], packet.payload().unwrap());\n        }\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/relay/relay.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse chrono::Local;\nuse log::*;\nuse mio::Events;\nuse std::cell::RefCell;\nuse std::cmp::max;\nuse std::io;\nuse std::rc::Rc;\nuse std::time::Duration;\n\nuse super::selector::Selector;\nuse super::tunnel_server::TunnelServer;\nuse super::udp_connection::IDLE_TIMEOUT_SECONDS;\n\nconst TAG: &str = \"Relay\";\nconst CLEANING_INTERVAL_SECONDS: i64 = 60;\n\npub struct Relay {\n    port: u16,\n}\n\nimpl Relay {\n    pub fn new(port: u16) -> Self {\n        Self { port }\n    }\n\n    pub fn run(&self) -> io::Result<()> {\n        let mut selector = Selector::create().unwrap();\n        let tunnel_server = TunnelServer::create(self.port, &mut selector)?;\n        info!(target: TAG, \"Relay server started\");\n        self.poll_loop(&mut selector, &tunnel_server)\n    }\n\n    fn poll_loop(\n        &self,\n        selector: &mut Selector,\n        tunnel_server: &Rc<RefCell<TunnelServer>>,\n    ) -> io::Result<()> {\n        let mut events = Events::with_capacity(1024);\n        // no connection may expire before the UDP idle timeout delay\n        let mut next_cleaning_deadline = Local::now().timestamp() + IDLE_TIMEOUT_SECONDS as i64;\n        loop {\n            retry_on_intr!({\n                let timeout_seconds = max(0, next_cleaning_deadline - Local::now().timestamp());\n                let timeout = Some(Duration::new(timeout_seconds as u64, 0));\n                selector.poll(&mut events, timeout)\n            })?;\n\n            let now = Local::now().timestamp();\n            if now >= next_cleaning_deadline {\n                tunnel_server.borrow_mut().clean_up(selector);\n                next_cleaning_deadline = now + CLEANING_INTERVAL_SECONDS;\n            } else if events.is_empty() {\n                debug!(\n                    target: TAG,\n                    \"Spurious wakeup: poll() returned without any event\"\n                );\n                continue;\n            }\n\n            selector.run_handlers(&events);\n        }\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/relay/router.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse log::*;\nuse std::cell::RefCell;\nuse std::io;\nuse std::rc::{Rc, Weak};\n\nuse super::binary;\nuse super::client::{Client, ClientChannel};\nuse super::connection::{Connection, ConnectionId};\nuse super::ipv4_header::Protocol;\nuse super::ipv4_packet::Ipv4Packet;\nuse super::selector::Selector;\nuse super::tcp_connection::TcpConnection;\nuse super::udp_connection::UdpConnection;\n\nconst TAG: &str = \"Router\";\n\npub struct Router {\n    client: Weak<RefCell<Client>>,\n    // there are typically only few connections per client, HashMap would be less efficient\n    connections: Vec<Rc<RefCell<dyn Connection>>>,\n}\n\nimpl Router {\n    pub fn new() -> Self {\n        Self {\n            client: Weak::new(),\n            connections: Vec::new(),\n        }\n    }\n\n    // expose client initialization after construction to break cyclic initialization dependencies\n    pub fn set_client(&mut self, client: Weak<RefCell<Client>>) {\n        self.client = client;\n    }\n\n    pub fn send_to_network(\n        &mut self,\n        selector: &mut Selector,\n        client_channel: &mut ClientChannel,\n        ipv4_packet: &Ipv4Packet,\n    ) {\n        if ipv4_packet.is_valid() {\n            match self.connection(selector, ipv4_packet) {\n                Ok(index) => {\n                    let closed = {\n                        let connection_ref = &self.connections[index];\n                        let mut connection = connection_ref.borrow_mut();\n                        connection.send_to_network(selector, client_channel, ipv4_packet);\n                        if connection.is_closed() {\n                            debug!(\n                                target: TAG,\n                                \"Removing connection from router: {}\",\n                                connection.id()\n                            );\n                            true\n                        } else {\n                            false\n                        }\n                    };\n                    if closed {\n                        // the connection is closed, remove it\n                        self.connections.swap_remove(index);\n                    }\n                }\n                Err(err) => error!(target: TAG, \"Cannot create route, dropping packet: {}\", err),\n            }\n        } else {\n            warn!(target: TAG, \"Dropping invalid packet\");\n            if log_enabled!(target: TAG, Level::Trace) {\n                trace!(\n                    target: TAG,\n                    \"{}\",\n                    binary::build_packet_string(ipv4_packet.raw())\n                );\n            }\n        }\n    }\n\n    fn connection(\n        &mut self,\n        selector: &mut Selector,\n        ipv4_packet: &Ipv4Packet,\n    ) -> io::Result<usize> {\n        let (ipv4_header_data, transport_header_data) = ipv4_packet.headers_data();\n        let transport_header_data = transport_header_data.expect(\"No transport\");\n        let id = ConnectionId::from_headers(ipv4_header_data, transport_header_data);\n        let index = match self.find_index(&id) {\n            Some(index) => index,\n            None => {\n                let connection =\n                    Self::create_connection(selector, id, self.client.clone(), ipv4_packet)?;\n                let index = self.connections.len();\n                self.connections.push(connection);\n                index\n            }\n        };\n        Ok(index)\n    }\n\n    fn create_connection(\n        selector: &mut Selector,\n        id: ConnectionId,\n        client: Weak<RefCell<Client>>,\n        ipv4_packet: &Ipv4Packet,\n    ) -> io::Result<Rc<RefCell<dyn Connection>>> {\n        let (ipv4_header, transport_header) = ipv4_packet.headers();\n        let transport_header = transport_header.expect(\"No transport\");\n        match id.protocol() {\n            Protocol::Tcp => Ok(TcpConnection::create(\n                selector,\n                id,\n                client,\n                ipv4_header,\n                transport_header,\n            )?),\n            Protocol::Udp => Ok(UdpConnection::create(\n                selector,\n                id,\n                client,\n                ipv4_header,\n                transport_header,\n            )?),\n            p => Err(io::Error::new(\n                io::ErrorKind::Other,\n                format!(\"Unsupported protocol: {:?}\", p),\n            )),\n        }\n    }\n\n    fn find_index(&self, id: &ConnectionId) -> Option<usize> {\n        self.connections\n            .iter()\n            .position(|connection| connection.borrow().id() == id)\n    }\n\n    pub fn remove(&mut self, connection: &dyn Connection) {\n        let index = self\n            .connections\n            .iter()\n            .position(|item| {\n                // compare (thin) pointers to find the connection to remove\n                binary::ptr_data_eq(connection, item.as_ptr())\n            })\n            .expect(\"Removing an unknown connection\");\n        debug!(\n            target: TAG,\n            \"Self-removing connection from router: {}\",\n            connection.id()\n        );\n        self.connections.swap_remove(index);\n    }\n\n    pub fn clear(&mut self, selector: &mut Selector) {\n        for connection in &mut self.connections {\n            connection.borrow_mut().close(selector);\n        }\n        self.connections.clear();\n    }\n\n    pub fn clean_expired_connections(&mut self, selector: &mut Selector) {\n        // remove the last items first, otherwise i might not be less than len() on swap_remove(i)\n        for i in (0..self.connections.len()).rev() {\n            let expired = {\n                let mut connection = self.connections[i].borrow_mut();\n                if connection.is_expired() {\n                    debug!(\n                        target: TAG,\n                        \"Removing expired connection from router: {}\",\n                        connection.id()\n                    );\n                    connection.close(selector);\n                    true\n                } else {\n                    false\n                }\n            };\n            if expired {\n                self.connections.swap_remove(i);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/relay/selector.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse log::*;\nuse mio::{Event, Evented, Events, Poll, PollOpt, Ready, Token};\nuse slab::Slab;\nuse std::io;\nuse std::rc::Rc;\nuse std::time::Duration;\n\nconst TAG: &str = \"Selector\";\n\npub trait EventHandler {\n    fn on_ready(&self, selector: &mut Selector, event: Event);\n}\n\nimpl<F> EventHandler for F\nwhere\n    F: Fn(&mut Selector, Event),\n{\n    fn on_ready(&self, selector: &mut Selector, event: Event) {\n        self(selector, event);\n    }\n}\n\npub struct Selector {\n    poll: Poll,\n    handlers: Slab<Rc<dyn EventHandler>>,\n    // tokens to be removed after all the current poll events are executed\n    tokens_to_remove: Vec<Token>,\n}\n\nimpl Selector {\n    pub fn create() -> io::Result<Self> {\n        Ok(Self {\n            poll: Poll::new()?,\n            handlers: Slab::with_capacity(1024),\n            tokens_to_remove: Vec::new(),\n        })\n    }\n\n    pub fn register<E, H>(\n        &mut self,\n        handle: &E,\n        handler: H,\n        interest: Ready,\n        opts: PollOpt,\n    ) -> io::Result<Token>\n    where\n        E: Evented + ?Sized,\n        H: EventHandler + 'static,\n    {\n        let token = Token(self.handlers.insert(Rc::new(handler)));\n        if let Err(err) = self.poll.register(handle, token, interest, opts) {\n            // remove the token we just added\n            self.handlers.remove(token.0);\n            Err(err)\n        } else {\n            Ok(token)\n        }\n    }\n\n    pub fn reregister<E>(\n        &mut self,\n        handle: &E,\n        token: Token,\n        interest: Ready,\n        opts: PollOpt,\n    ) -> io::Result<()>\n    where\n        E: Evented + ?Sized,\n    {\n        self.poll.reregister(handle, token, interest, opts)\n    }\n\n    pub fn deregister<E>(&mut self, handle: &E, token: Token) -> io::Result<()>\n    where\n        E: Evented + ?Sized,\n    {\n        self.poll.deregister(handle)?;\n        // remove them before next poll()\n        self.tokens_to_remove.push(token);\n        Ok(())\n    }\n\n    fn clean_removed_tokens(&mut self) {\n        for &token in &self.tokens_to_remove {\n            self.handlers.remove(token.0);\n        }\n        self.tokens_to_remove.clear();\n    }\n\n    pub fn poll(&mut self, events: &mut Events, timeout: Option<Duration>) -> io::Result<usize> {\n        self.poll.poll(events, timeout)\n    }\n\n    pub fn run_handlers(&mut self, events: &Events) {\n        for event in events {\n            debug!(target: TAG, \"event={:?}\", event);\n            let handler = self\n                .handlers\n                .get_mut(event.token().0)\n                .expect(\"Token not found\")\n                .clone();\n            handler.on_ready(self, event);\n        }\n\n        // remove the tokens marked as removed\n        self.clean_removed_tokens();\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/relay/stream_buffer.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse std::io;\n\n/// Circular buffer to store a stream. Read/write boundaries are not preserved.\npub struct StreamBuffer {\n    buf: Box<[u8]>,\n    head: usize,\n    tail: usize,\n}\n\nimpl StreamBuffer {\n    pub fn new(capacity: usize) -> Self {\n        Self {\n            buf: vec![0; capacity + 1].into_boxed_slice(),\n            head: 0,\n            tail: 0,\n        }\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.head == self.tail\n    }\n\n    pub fn size(&self) -> usize {\n        if self.head < self.tail {\n            self.head + self.buf.len() - self.tail\n        } else {\n            self.head - self.tail\n        }\n    }\n\n    pub fn capacity(&self) -> usize {\n        self.buf.len() - 1\n    }\n\n    pub fn remaining(&self) -> usize {\n        self.capacity() - self.size()\n    }\n\n    pub fn write_to<W: io::Write>(&mut self, destination: &mut W) -> io::Result<usize> {\n        if self.head == self.tail {\n            // buffer is empty, nothing to do\n            Ok(0)\n        } else {\n            let w;\n            if self.head > self.tail {\n                let source_slice = &self.buf[self.tail..self.head];\n                w = destination.write(source_slice)?;\n                self.tail += w;\n            } else {\n                // self.head < self.tail\n                let source_slice = &self.buf[self.tail..];\n                w = destination.write(source_slice)?;\n                self.tail = (self.tail + w) % self.buf.len();\n            }\n            self.optimize();\n            Ok(w)\n        }\n    }\n\n    pub fn read_from(&mut self, source: &[u8]) {\n        assert!(\n            source.len() <= self.remaining(),\n            \"StreamBuffer is full, check remaining() before calling read_from()\"\n        );\n        let source_len = source.len();\n        let buf_len = self.buf.len();\n        if source_len <= buf_len - self.head {\n            let target_slice = &mut self.buf[self.head..self.head + source_len];\n            target_slice.copy_from_slice(source);\n        } else {\n            {\n                // fill until the right-end of the buffer\n                let target_slice = &mut self.buf[self.head..];\n                let source_slice = &source[..buf_len - self.head];\n                target_slice.copy_from_slice(source_slice);\n            }\n            // fill the remaining from the left-end of the buffer\n            let target_slice = &mut self.buf[..self.head + source_len - buf_len];\n            let source_slice = &source[buf_len - self.head..];\n            target_slice.copy_from_slice(source_slice);\n        }\n        self.head = (self.head + source_len) % buf_len;\n    }\n\n    /// To avoid unnecessary copies, StreamBuffer writes at most until the \"end\" of the circular\n    /// buffer, which is suboptimal (it could have written more data if they have been contiguous).\n    ///\n    /// In order to minimize the occurrence of this event, reset the head and tail to 0 when the\n    /// buffer is empty (no copy is involved).\n    ///\n    /// This is especially useful when the StreamBuffer is used to read/write one packet at a time,\n    /// so the \"end\" of the buffer is guaranteed to never be reached.\n    fn optimize(&mut self) {\n        if self.is_empty() {\n            self.head = 0;\n            self.tail = 0;\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    fn create_data() -> Vec<u8> {\n        (0..6).collect()\n    }\n\n    #[test]\n    fn bufferize_data() {\n        let data = create_data();\n        let mut stream_buffer = StreamBuffer::new(9);\n\n        let mut cursor = io::Cursor::new(Vec::new());\n        stream_buffer.read_from(&data);\n        stream_buffer.write_to(&mut cursor).unwrap();\n\n        assert_eq!(cursor.get_ref(), &data);\n    }\n\n    #[test]\n    fn circular() {\n        let data = create_data();\n        let mut stream_buffer = StreamBuffer::new(9);\n\n        // put 6 bytes\n        stream_buffer.read_from(&data);\n        // consume 3 bytes\n        read_some(&mut stream_buffer, 3);\n\n        // put test data\n        stream_buffer.read_from(&data);\n        // consume 3 bytes (so that the first 6 bytes are consumed, and the \"tail\" position is 6)\n        read_some(&mut stream_buffer, 3);\n\n        // consume test data\n        let result = read(&mut stream_buffer);\n\n        // StreamBuffer is expected to break writes at circular buffer boundaries (capacity + 1)\n        // This is not a requirement, but this verifies that the implementation works as expected\n        assert_eq!([0u8, 1, 2, 3], &result[..]);\n\n        // consume the remaining\n        let result = read(&mut stream_buffer);\n        assert_eq!([4u8, 5], &result[..]);\n    }\n\n    #[test]\n    fn just_enough_space() {\n        let data = create_data();\n        let mut stream_buffer = StreamBuffer::new(9);\n\n        // fill the buffer twice\n        stream_buffer.read_from(&data);\n        assert_eq!(3, stream_buffer.remaining());\n        stream_buffer.read_from(&[0, 1, 2]);\n\n        let result = read(&mut stream_buffer);\n        assert_eq!([0, 1, 2, 3, 4, 5, 0, 1, 2], &result[..]);\n    }\n\n    fn read_some(stream_buffer: &mut StreamBuffer, bytes: usize) -> Vec<u8> {\n        let mut vec = vec![0u8; bytes];\n        {\n            let mut cursor = io::Cursor::new(&mut vec[..bytes]);\n            stream_buffer.write_to(&mut cursor).unwrap();\n        }\n        vec\n    }\n\n    fn read(stream_buffer: &mut StreamBuffer) -> Vec<u8> {\n        let mut cursor = io::Cursor::new(Vec::new());\n        stream_buffer.write_to(&mut cursor).unwrap();\n        cursor.into_inner()\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/relay/tcp_connection.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse log::*;\nuse mio::net::TcpStream;\nuse mio::{Event, PollOpt, Ready, Token};\nuse rand::random;\nuse std::cell::RefCell;\nuse std::cmp;\nuse std::io;\nuse std::num::Wrapping;\nuse std::rc::{Rc, Weak};\n\nuse super::binary;\nuse super::client::{Client, ClientChannel};\nuse super::connection::{Connection, ConnectionId};\nuse super::ipv4_header::Ipv4Header;\nuse super::ipv4_packet::{Ipv4Packet, MAX_PACKET_LENGTH};\nuse super::packet_source::PacketSource;\nuse super::packetizer::Packetizer;\nuse super::selector::Selector;\nuse super::stream_buffer::StreamBuffer;\nuse super::tcp_header::{self, TcpHeader, TcpHeaderMut};\nuse super::transport_header::{TransportHeader, TransportHeaderMut};\n\nconst TAG: &str = \"TcpConnection\";\n\n// same value as GnirehtetService.MTU in the client\nconst MTU: u16 = 0x4000;\n// 20 bytes for IP headers, 20 bytes for TCP headers\nconst MAX_PAYLOAD_LENGTH: u16 = MTU - 20 - 20 as u16;\n\npub struct TcpConnection {\n    self_weak: Weak<RefCell<TcpConnection>>,\n    id: ConnectionId,\n    client: Weak<RefCell<Client>>,\n    stream: TcpStream,\n    interests: Ready,\n    token: Token,\n    client_to_network: StreamBuffer,\n    network_to_client: Packetizer,\n    packet_for_client_length: Option<u16>,\n    closed: bool,\n    tcb: Tcb,\n}\n\n// Transport Control Block\nstruct Tcb {\n    state: TcpState,\n    syn_sequence_number: u32,\n    sequence_number: Wrapping<u32>,\n    acknowledgement_number: Wrapping<u32>,\n    their_acknowledgement_number: u32,\n    fin_sequence_number: Option<u32>,\n    fin_received: bool,\n    client_window: u16,\n}\n\n// See RFC793: <https://tools.ietf.org/html/rfc793#page-23>\n#[derive(Debug, PartialEq, Eq)]\nenum TcpState {\n    Init,\n    SynSent,\n    SynReceived,\n    Established,\n    CloseWait,\n    LastAck,\n    Closing,\n    FinWait1,\n    FinWait2,\n}\n\nimpl TcpState {\n    fn is_connected(&self) -> bool {\n        self != &TcpState::Init && self != &TcpState::SynSent && self != &TcpState::SynReceived\n    }\n\n    fn is_closed(&self) -> bool {\n        self == &TcpState::FinWait1\n            || self == &TcpState::FinWait2\n            || self == &TcpState::Closing\n            || self == &TcpState::LastAck\n    }\n}\n\nimpl Tcb {\n    fn new() -> Self {\n        Self {\n            state: TcpState::Init,\n            syn_sequence_number: 0,\n            sequence_number: Wrapping(0),\n            acknowledgement_number: Wrapping(0),\n            their_acknowledgement_number: 0,\n            fin_sequence_number: None,\n            fin_received: false,\n            client_window: 0,\n        }\n    }\n\n    fn remaining_client_window(&self) -> u16 {\n        let wrapped_remaining = Wrapping(self.their_acknowledgement_number)\n            + Wrapping(u32::from(self.client_window))\n            - self.sequence_number;\n        let remaining = wrapped_remaining.0;\n        if remaining <= u32::from(self.client_window) {\n            remaining as u16\n        } else {\n            0\n        }\n    }\n\n    fn numbers(&self) -> String {\n        format!(\n            \"(seq={}, ack={})\",\n            self.sequence_number, self.acknowledgement_number\n        )\n    }\n}\n\nimpl TcpConnection {\n    #[allow(clippy::needless_pass_by_value)] // semantically, headers are consumed\n    pub fn create(\n        selector: &mut Selector,\n        id: ConnectionId,\n        client: Weak<RefCell<Client>>,\n        ipv4_header: Ipv4Header,\n        transport_header: TransportHeader,\n    ) -> io::Result<Rc<RefCell<Self>>> {\n        cx_info!(target: TAG, id, \"Open\");\n        let stream = Self::create_stream(&id)?;\n\n        let tcp_header = Self::tcp_header_of_transport(transport_header);\n\n        // shrink the TCP options to pass a minimal refrence header to the packetizer\n        let mut shrinked_tcp_header_raw = [0u8; 20];\n        shrinked_tcp_header_raw.copy_from_slice(&tcp_header.raw()[..20]);\n        let mut shrinked_tcp_header_data = tcp_header.data().clone();\n        {\n            let mut shrinked_tcp_header =\n                shrinked_tcp_header_data.bind_mut(&mut shrinked_tcp_header_raw);\n            shrinked_tcp_header.shrink_options();\n            assert_eq!(20, shrinked_tcp_header.header_length());\n        }\n\n        let shrinked_transport_header = shrinked_tcp_header_data\n            .bind(&shrinked_tcp_header_raw)\n            .into();\n\n        let packetizer = Packetizer::new(&ipv4_header, &shrinked_transport_header);\n\n        // interests will be set on the first packet received\n        // set the initial value now so that they won't need to be updated\n        let interests = Ready::writable();\n        let rc = Rc::new(RefCell::new(Self {\n            self_weak: Weak::new(),\n            id,\n            client,\n            stream,\n            interests,\n            token: Token(0), // default value, will be set afterwards\n            client_to_network: StreamBuffer::new(4 * MAX_PACKET_LENGTH),\n            network_to_client: packetizer,\n            packet_for_client_length: None,\n            closed: false,\n            tcb: Tcb::new(),\n        }));\n\n        {\n            let mut self_ref = rc.borrow_mut();\n\n            // keep a shared reference to this\n            self_ref.self_weak = Rc::downgrade(&rc);\n\n            let rc2 = rc.clone();\n            // must annotate selector type: https://stackoverflow.com/a/44004103/1987178\n            let handler =\n                move |selector: &mut Selector, event| rc2.borrow_mut().on_ready(selector, event);\n            let token =\n                selector.register(&self_ref.stream, handler, interests, PollOpt::level())?;\n            self_ref.token = token;\n        }\n        Ok(rc)\n    }\n\n    fn create_stream(id: &ConnectionId) -> io::Result<TcpStream> {\n        TcpStream::connect(&id.rewritten_destination().into())\n    }\n\n    fn remove_from_router(&self) {\n        // route is embedded in router which is embedded in client: the client necessarily exists\n        let client_rc = self.client.upgrade().expect(\"Expected client not found\");\n        let mut client = client_rc.borrow_mut();\n        client.router().remove(self);\n    }\n\n    fn on_ready(&mut self, selector: &mut Selector, event: Event) {\n        #[allow(clippy::match_wild_err_arm)]\n        match self.process(selector, event) {\n            Ok(_) => (),\n            Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => {\n                cx_debug!(target: TAG, self.id, \"Spurious event, ignoring\")\n            }\n            Err(_) => panic!(\"Unexpected unhandled error\"),\n        }\n    }\n    // return Err(err) with err.kind() == io::ErrorKind::WouldBlock on spurious event\n    fn process(&mut self, selector: &mut Selector, event: Event) -> io::Result<()> {\n        if !self.closed {\n            let ready = event.readiness();\n            if ready.is_readable() || ready.is_writable() {\n                if ready.is_writable() {\n                    if self.tcb.state == TcpState::SynSent {\n                        // writable is first triggered when the stream is connected\n                        self.process_connect(selector);\n                    } else {\n                        self.process_send(selector)?;\n                    }\n                }\n                if !self.closed && ready.is_readable() {\n                    self.process_receive(selector)?;\n                }\n                if !self.closed {\n                    self.update_interests(selector);\n                }\n            } else {\n                cx_debug!(target: TAG, self.id, \"received ready = {:?}\", ready);\n                // error or hup\n                self.close(selector);\n            }\n            if self.closed {\n                // on_ready is not called from the router, so the connection must remove itself\n                self.remove_from_router();\n            }\n        }\n        Ok(())\n    }\n\n    // return Err(err) with err.kind() == io::ErrorKind::WouldBlock on spurious event\n    fn process_send(&mut self, selector: &mut Selector) -> io::Result<()> {\n        match self.client_to_network.write_to(&mut self.stream) {\n            Ok(w) => {\n                if w != 0 {\n                    self.tcb.acknowledgement_number += Wrapping(w as u32);\n\n                    if self.tcb.fin_received && self.client_to_network.is_empty() {\n                        let client_rc = self.client.upgrade().expect(\"Expected client not found\");\n                        let mut client = client_rc.borrow_mut();\n                        cx_debug!(\n                            target: TAG,\n                            self.id,\n                            \"No more pending data, process the pending FIN\"\n                        );\n                        self.do_handle_fin(selector, &mut client.channel());\n                    } else {\n                        cx_debug!(\n                            target: TAG,\n                            self.id,\n                            \"Sending ACK {} to client\",\n                            self.tcb.numbers()\n                        );\n                        self.send_empty_packet_to_client(selector, tcp_header::FLAG_ACK);\n                    }\n                } else {\n                    self.close(selector);\n                }\n            }\n            Err(err) => {\n                if err.kind() == io::ErrorKind::WouldBlock {\n                    // rethrow\n                    return Err(err);\n                }\n                cx_error!(\n                    target: TAG,\n                    self.id,\n                    \"Cannot write: [{:?}] {}\",\n                    err.kind(),\n                    err\n                );\n                self.send_empty_packet_to_client(selector, tcp_header::FLAG_RST);\n                self.close(selector);\n            }\n        }\n        Ok(())\n    }\n\n    // return Err(err) with err.kind() == io::ErrorKind::WouldBlock on spurious event\n    fn process_receive(&mut self, selector: &mut Selector) -> io::Result<()> {\n        assert!(\n            self.packet_for_client_length.is_none(),\n            \"A pending packet was not sent\"\n        );\n        let remaining_client_window = self.tcb.remaining_client_window();\n        assert!(\n            remaining_client_window > 0,\n            \"process_received() must not be called when window == 0\"\n        );\n        let max_payload_length =\n            Some(cmp::min(remaining_client_window, MAX_PAYLOAD_LENGTH) as usize);\n        Self::update_headers(\n            &mut self.network_to_client,\n            &self.tcb,\n            tcp_header::FLAG_ACK | tcp_header::FLAG_PSH,\n        );\n        match self\n            .network_to_client\n            .packetize_read(&mut self.stream, max_payload_length)\n        {\n            Ok(Some(ipv4_packet)) => {\n                match Self::send_to_client(&self.client, selector, &ipv4_packet) {\n                    Ok(_) => {\n                        let len = ipv4_packet.payload().unwrap().len();\n                        cx_debug!(\n                            target: TAG,\n                            self.id,\n                            \"Packet ({} bytes) sent to client {}\",\n                            len,\n                            self.tcb.numbers()\n                        );\n                        self.tcb.sequence_number += Wrapping(len as u32);\n                    }\n                    Err(_) => {\n                        // ask to the client to pull when its buffer is not full\n                        let client_rc = self.client.upgrade().expect(\"Expected client not found\");\n                        let mut client = client_rc.borrow_mut();\n                        let self_rc = self.self_weak.upgrade().unwrap();\n                        client.register_pending_packet_source(self_rc);\n                        self.packet_for_client_length = Some(ipv4_packet.length());\n                    }\n                };\n            }\n            Ok(None) => {\n                self.eof(selector);\n            }\n            Err(err) => {\n                if err.kind() == io::ErrorKind::WouldBlock {\n                    // rethrow\n                    return Err(err);\n                }\n                cx_error!(\n                    target: TAG,\n                    self.id,\n                    \"Cannot read: [{:?}] {}\",\n                    err.kind(),\n                    err\n                );\n                self.send_empty_packet_to_client(selector, tcp_header::FLAG_RST);\n                self.close(selector);\n            }\n        }\n        Ok(())\n    }\n\n    fn process_connect(&mut self, selector: &mut Selector) {\n        assert_eq!(self.tcb.state, TcpState::SynSent);\n        self.tcb.state = TcpState::SynReceived;\n        cx_debug!(target: TAG, self.id, \"State = {:?}\", self.tcb.state);\n        self.send_empty_packet_to_client(selector, tcp_header::FLAG_SYN | tcp_header::FLAG_ACK);\n        self.tcb.sequence_number += Wrapping(1); // SYN counts for 1 byte\n    }\n\n    fn send_to_client(\n        client: &Weak<RefCell<Client>>,\n        selector: &mut Selector,\n        ipv4_packet: &Ipv4Packet,\n    ) -> io::Result<()> {\n        let client_rc = client.upgrade().expect(\"Expected client not found\");\n        let mut client = client_rc.borrow_mut();\n        client.send_to_client(selector, &ipv4_packet)\n    }\n\n    /// Borrow self.client and send empty packet to it\n    ///\n    /// To be used if called by on_ready() (so the client is not borrowed yet).\n    fn send_empty_packet_to_client(&mut self, selector: &mut Selector, flags: u16) {\n        let client_rc = self.client.upgrade().expect(\"Expected client not found\");\n        let mut client = client_rc.borrow_mut();\n        self.reply_empty_packet_to_client(selector, &mut client.channel(), flags)\n    }\n\n    /// Send empty packet to the client channel (that already borrows the client)\n    ///\n    /// To be used if called by send_to_network() (called by the client, so it is already\n    /// borrowed).\n    fn reply_empty_packet_to_client(\n        &mut self,\n        selector: &mut Selector,\n        client_channel: &mut ClientChannel,\n        flags: u16,\n    ) {\n        let ipv4_packet = Self::create_empty_response_packet(\n            &self.id,\n            &mut self.network_to_client,\n            &self.tcb,\n            flags,\n        );\n        if let Err(err) = client_channel.send_to_client(selector, &ipv4_packet) {\n            // losing such an empty packet will not break the TCP connection\n            cx_warn!(\n                target: TAG,\n                self.id,\n                \"Cannot send packet to client: {}\",\n                err\n            );\n        }\n    }\n\n    fn eof(&mut self, selector: &mut Selector) {\n        self.send_empty_packet_to_client(selector, tcp_header::FLAG_FIN | tcp_header::FLAG_ACK);\n        self.tcb.fin_sequence_number = Some(self.tcb.sequence_number.0);\n        self.tcb.sequence_number += Wrapping(1); // FIN counts for 1 byte\n        self.tcb.state = if self.tcb.state == TcpState::CloseWait {\n            TcpState::LastAck\n        } else {\n            TcpState::FinWait1\n        };\n        cx_debug!(target: TAG, self.id, \"State = {:?}\", self.tcb.state);\n    }\n\n    #[inline]\n    fn tcp_header_of_transport(transport_header: TransportHeader) -> TcpHeader {\n        if let TransportHeader::Tcp(tcp_header) = transport_header {\n            tcp_header\n        } else {\n            panic!(\"Not a TCP header\");\n        }\n    }\n\n    #[inline]\n    fn tcp_header_of_transport_mut(transport_header: TransportHeaderMut) -> TcpHeaderMut {\n        if let TransportHeaderMut::Tcp(tcp_header) = transport_header {\n            tcp_header\n        } else {\n            panic!(\"Not a TCP header\");\n        }\n    }\n\n    #[inline]\n    fn tcp_header_of_packet<'a>(ipv4_packet: &'a Ipv4Packet) -> TcpHeader<'a> {\n        if let Some(TransportHeader::Tcp(tcp_header)) = ipv4_packet.transport_header() {\n            tcp_header\n        } else {\n            panic!(\"Not a TCP packet\");\n        }\n    }\n\n    fn update_headers(packetizer: &mut Packetizer, tcb: &Tcb, flags: u16) {\n        let mut tcp_header = Self::tcp_header_of_transport_mut(packetizer.transport_header_mut());\n        tcp_header.set_sequence_number(tcb.sequence_number.0);\n        tcp_header.set_acknowledgement_number(tcb.acknowledgement_number.0);\n        tcp_header.set_flags(flags);\n    }\n\n    fn handle_packet(\n        &mut self,\n        selector: &mut Selector,\n        client_channel: &mut ClientChannel,\n        ipv4_packet: &Ipv4Packet,\n    ) {\n        let tcp_header = Self::tcp_header_of_packet(ipv4_packet);\n        if self.tcb.state == TcpState::Init {\n            self.handle_first_packet(selector, client_channel, ipv4_packet);\n            return;\n        }\n\n        if tcp_header.is_syn() {\n            self.handle_duplicate_syn(selector, client_channel, ipv4_packet);\n            return;\n        }\n\n        let expected_packet =\n            (self.tcb.acknowledgement_number + Wrapping(self.client_to_network.size() as u32)).0;\n        if tcp_header.sequence_number() != expected_packet {\n            // ignore packet already received or out-of-order, retransmission is already\n            // managed by both sides\n            cx_warn!(\n                target: TAG,\n                self.id,\n                \"Ignoring packet {} (acking {}); expecting {}; flags={}\",\n                tcp_header.sequence_number(),\n                tcp_header.acknowledgement_number(),\n                expected_packet,\n                tcp_header.flags()\n            );\n            return;\n        }\n\n        self.tcb.client_window = tcp_header.window();\n        self.tcb.their_acknowledgement_number = tcp_header.acknowledgement_number();\n\n        cx_debug!(\n            target: TAG,\n            self.id,\n            \"Receiving expected packet {} (flags={})\",\n            tcp_header.sequence_number(),\n            tcp_header.flags()\n        );\n\n        if tcp_header.is_rst() {\n            self.close(selector);\n            return;\n        }\n\n        if tcp_header.is_ack() {\n            cx_debug!(\n                target: TAG,\n                self.id,\n                \"Client acked {}\",\n                tcp_header.acknowledgement_number()\n            );\n\n            self.handle_ack(selector, client_channel, ipv4_packet);\n        }\n\n        if tcp_header.is_fin() {\n            self.handle_fin(selector, client_channel);\n        }\n\n        if let Some(fin_sequence_number) = self.tcb.fin_sequence_number {\n            if tcp_header.acknowledgement_number() == fin_sequence_number + 1 {\n                cx_debug!(target: TAG, self.id, \"Received ACK of FIN\");\n                self.handle_fin_ack(selector);\n            }\n        }\n    }\n\n    fn handle_first_packet(\n        &mut self,\n        selector: &mut Selector,\n        client_channel: &mut ClientChannel,\n        ipv4_packet: &Ipv4Packet,\n    ) {\n        cx_debug!(target: TAG, self.id, \"handle_first_packet()\");\n        let tcp_header = Self::tcp_header_of_packet(ipv4_packet);\n        if tcp_header.is_syn() {\n            let their_sequence_number = tcp_header.sequence_number();\n            self.tcb.acknowledgement_number = Wrapping(their_sequence_number) + Wrapping(1);\n            self.tcb.syn_sequence_number = their_sequence_number;\n\n            self.tcb.sequence_number = Wrapping(random::<u32>());\n            cx_debug!(\n                target: TAG,\n                self.id,\n                \"Initialized seq={}; ack={}\",\n                self.tcb.sequence_number,\n                self.tcb.acknowledgement_number\n            );\n            self.tcb.client_window = tcp_header.window();\n            self.tcb.state = TcpState::SynSent;\n            cx_debug!(target: TAG, self.id, \"State = {:?}\", self.tcb.state);\n        } else {\n            cx_warn!(\n                target: TAG,\n                self.id,\n                \"Unexpected first packet {}; acking {}; flags={}\",\n                tcp_header.sequence_number(),\n                tcp_header.acknowledgement_number(),\n                tcp_header.flags()\n            );\n            // make a RST in the window client\n            self.tcb.sequence_number = Wrapping(tcp_header.acknowledgement_number());\n            self.reply_empty_packet_to_client(selector, client_channel, tcp_header::FLAG_RST);\n            self.close(selector);\n        }\n    }\n\n    fn handle_duplicate_syn(\n        &mut self,\n        selector: &mut Selector,\n        client_channel: &mut ClientChannel,\n        ipv4_packet: &Ipv4Packet,\n    ) {\n        let tcp_header = Self::tcp_header_of_packet(ipv4_packet);\n        let their_sequence_number = tcp_header.sequence_number();\n        if self.tcb.state == TcpState::SynSent {\n            // the connection is not established yet, we can accept this packet as if it were the\n            // first SYN\n            self.tcb.syn_sequence_number = their_sequence_number;\n            self.tcb.acknowledgement_number = Wrapping(their_sequence_number) + Wrapping(1);\n        } else if their_sequence_number != self.tcb.syn_sequence_number {\n            // duplicate SYN with different sequence number\n            self.reply_empty_packet_to_client(selector, client_channel, tcp_header::FLAG_RST);\n            self.close(selector);\n        }\n    }\n\n    fn handle_fin(&mut self, selector: &mut Selector, client_channel: &mut ClientChannel) {\n        cx_debug!(\n            target: TAG,\n            self.id,\n            \"Received a FIN from the client {}\",\n            self.tcb.numbers()\n        );\n\n        self.tcb.fin_received = true;\n        if self.client_to_network.is_empty() {\n            cx_debug!(\n                target: TAG,\n                self.id,\n                \"No pending data, process the FIN immediately\"\n            );\n            self.do_handle_fin(selector, client_channel);\n        }\n        // otherwise, the FIN will be processed once client_to_network is empty\n    }\n\n    fn do_handle_fin(&mut self, selector: &mut Selector, client_channel: &mut ClientChannel) {\n        self.tcb.acknowledgement_number += Wrapping(1); // received FIN counts for 1 byte\n\n        if self.tcb.state == TcpState::Established {\n            self.reply_empty_packet_to_client(\n                selector,\n                client_channel,\n                tcp_header::FLAG_FIN | tcp_header::FLAG_ACK,\n            );\n            self.tcb.fin_sequence_number = Some(self.tcb.sequence_number.0);\n            self.tcb.sequence_number += Wrapping(1); // FIN counts for 1 byte\n                                                     // the connection will be closed by RAII, so switch immediately to LastAck\n                                                     // (bypass CloseWait)\n            self.tcb.state = TcpState::LastAck;\n            cx_debug!(target: TAG, self.id, \"State = {:?}\", self.tcb.state);\n        } else if self.tcb.state == TcpState::FinWait1 {\n            self.reply_empty_packet_to_client(selector, client_channel, tcp_header::FLAG_ACK);\n            self.tcb.state = TcpState::Closing;\n            cx_debug!(target: TAG, self.id, \"State = {:?}\", self.tcb.state);\n        } else if self.tcb.state == TcpState::FinWait2 {\n            self.reply_empty_packet_to_client(selector, client_channel, tcp_header::FLAG_ACK);\n            self.close(selector);\n        } else {\n            cx_warn!(\n                target: TAG,\n                self.id,\n                \"Received FIN was state was {:?}\",\n                self.tcb.state\n            );\n        }\n    }\n\n    fn handle_fin_ack(&mut self, selector: &mut Selector) {\n        if self.tcb.state == TcpState::LastAck || self.tcb.state == TcpState::Closing {\n            self.close(selector);\n        } else if self.tcb.state == TcpState::FinWait1 {\n            self.tcb.state = TcpState::FinWait2;\n            cx_debug!(target: TAG, self.id, \"State = {:?}\", self.tcb.state);\n        } else if self.tcb.state != TcpState::FinWait2 {\n            cx_warn!(\n                target: TAG,\n                self.id,\n                \"Received FIN ACK while state was {:?}\",\n                self.tcb.state\n            );\n        }\n    }\n\n    fn handle_ack(\n        &mut self,\n        _selector: &mut Selector,\n        _client_channel: &mut ClientChannel,\n        ipv4_packet: &Ipv4Packet,\n    ) {\n        cx_debug!(target: TAG, self.id, \"handle_ack()\");\n        if self.tcb.state == TcpState::SynReceived {\n            self.tcb.state = TcpState::Established;\n            cx_debug!(target: TAG, self.id, \"State = {:?}\", self.tcb.state);\n            return;\n        }\n\n        if log_enabled!(target: TAG, Level::Trace) {\n            cx_trace!(\n                target: TAG,\n                self.id,\n                \"{}\",\n                binary::build_packet_string(ipv4_packet.raw())\n            );\n        }\n\n        let payload = ipv4_packet.payload().expect(\"No payload\");\n        if payload.is_empty() {\n            // no data to transmit\n            return;\n        }\n\n        if self.client_to_network.remaining() < payload.len() {\n            cx_warn!(target: TAG, self.id, \"Not enough space, dropping packet\");\n            return;\n        }\n\n        self.client_to_network.read_from(payload);\n        // data will be ACKed once written to the network socket\n    }\n\n    fn create_empty_response_packet<'a>(\n        id: &ConnectionId,\n        packetizer: &'a mut Packetizer,\n        tcb: &Tcb,\n        flags: u16,\n    ) -> Ipv4Packet<'a> {\n        Self::update_headers(packetizer, tcb, flags);\n        cx_debug!(\n            target: TAG,\n            id,\n            \"Forging empty response (flags={}) {}\",\n            flags,\n            tcb.numbers()\n        );\n        if (flags & tcp_header::FLAG_ACK) != 0 {\n            cx_debug!(target: TAG, id, \"Acking {}\", tcb.numbers());\n        }\n        let ipv4_packet = packetizer.packetize_empty_payload();\n        if log_enabled!(target: TAG, Level::Trace) {\n            cx_trace!(\n                target: TAG,\n                id,\n                \"{}\",\n                binary::build_packet_string(ipv4_packet.raw())\n            );\n        }\n        ipv4_packet\n    }\n\n    fn update_interests(&mut self, selector: &mut Selector) {\n        assert!(!self.closed);\n        let mut ready = Ready::empty();\n        if self.tcb.state == TcpState::SynSent {\n            // waiting for connectable\n            ready = Ready::writable()\n        } else {\n            if self.may_read() {\n                ready |= Ready::readable()\n            }\n            if self.may_write() {\n                ready |= Ready::writable()\n            }\n        }\n        cx_debug!(target: TAG, self.id, \"interests: {:?}\", ready);\n        if self.interests != ready {\n            // interests must be changed\n            self.interests = ready;\n            selector\n                .reregister(&self.stream, self.token, ready, PollOpt::level())\n                .expect(\"Cannot register on poll\");\n        }\n    }\n\n    fn may_read(&self) -> bool {\n        if !self.tcb.state.is_connected() || self.tcb.state.is_closed() {\n            return false;\n        }\n        if self.packet_for_client_length.is_some() {\n            // a packet is already pending\n            return false;\n        }\n        self.tcb.remaining_client_window() > 0\n    }\n\n    fn may_write(&self) -> bool {\n        !self.client_to_network.is_empty()\n    }\n}\n\nimpl Connection for TcpConnection {\n    fn id(&self) -> &ConnectionId {\n        &self.id\n    }\n\n    fn send_to_network(\n        &mut self,\n        selector: &mut Selector,\n        client_channel: &mut ClientChannel,\n        ipv4_packet: &Ipv4Packet,\n    ) {\n        self.handle_packet(selector, client_channel, ipv4_packet);\n        if !self.closed {\n            self.update_interests(selector);\n        }\n    }\n\n    fn close(&mut self, selector: &mut Selector) {\n        cx_info!(target: TAG, self.id, \"Close\");\n        self.closed = true;\n        if let Err(err) = selector.deregister(&self.stream, self.token) {\n            // do not panic, this can happen in mio\n            // see <https://github.com/Genymobile/gnirehtet/issues/136>\n            cx_warn!(\n                target: TAG,\n                self.id,\n                \"Fail to deregister TCP stream: {:?}\",\n                err\n            );\n        }\n        // socket will be closed by RAII\n    }\n\n    fn is_expired(&self) -> bool {\n        // no external timeout expiration\n        false\n    }\n\n    fn is_closed(&self) -> bool {\n        self.closed\n    }\n}\n\nimpl PacketSource for TcpConnection {\n    fn get(&mut self) -> Option<Ipv4Packet> {\n        if let Some(len) = self.packet_for_client_length {\n            Some(self.network_to_client.inflate(len))\n        } else {\n            None\n        }\n    }\n\n    fn next(&mut self, selector: &mut Selector) {\n        let len = self\n            .packet_for_client_length\n            .expect(\"next() called on empty packet source\");\n        cx_debug!(\n            target: TAG,\n            self.id,\n            \"Deferred packet ({} bytes) sent to client {}\",\n            len,\n            self.tcb.numbers()\n        );\n        self.tcb.sequence_number += Wrapping(u32::from(len));\n        self.packet_for_client_length = None;\n        self.update_interests(selector);\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/relay/tcp_header.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse super::ipv4_header::Ipv4HeaderData;\nuse byteorder::{BigEndian, ByteOrder};\nuse std::mem;\n\npub struct TcpHeader<'a> {\n    raw: &'a [u8],\n    data: &'a TcpHeaderData,\n}\n\npub struct TcpHeaderMut<'a> {\n    raw: &'a mut [u8],\n    data: &'a mut TcpHeaderData,\n}\n\n#[derive(Clone)]\npub struct TcpHeaderData {\n    source_port: u16,\n    destination_port: u16,\n    sequence_number: u32,\n    acknowledgement_number: u32,\n    header_length: u8,\n    flags: u16,\n    window: u16,\n}\n\npub const FLAG_FIN: u16 = 1;\npub const FLAG_SYN: u16 = 1 << 1;\npub const FLAG_RST: u16 = 1 << 2;\npub const FLAG_PSH: u16 = 1 << 3;\npub const FLAG_ACK: u16 = 1 << 4;\n\n#[allow(dead_code)]\nimpl TcpHeaderData {\n    pub fn parse(raw: &[u8]) -> Self {\n        let data_offset_and_flags = BigEndian::read_u16(&raw[12..14]);\n        Self {\n            source_port: BigEndian::read_u16(&raw[0..2]),\n            destination_port: BigEndian::read_u16(&raw[2..4]),\n            sequence_number: BigEndian::read_u32(&raw[4..8]),\n            acknowledgement_number: BigEndian::read_u32(&raw[8..12]),\n            header_length: ((data_offset_and_flags & 0xF000) >> 10) as u8,\n            flags: data_offset_and_flags & 0x1FF,\n            window: BigEndian::read_u16(&raw[14..16]),\n        }\n    }\n\n    #[inline]\n    pub fn bind<'c, 'a: 'c, 'b: 'c>(&'a self, raw: &'b [u8]) -> TcpHeader<'c> {\n        TcpHeader::new(raw, self)\n    }\n\n    #[inline]\n    pub fn bind_mut<'c, 'a: 'c, 'b: 'c>(&'a mut self, raw: &'b mut [u8]) -> TcpHeaderMut<'c> {\n        TcpHeaderMut::new(raw, self)\n    }\n\n    #[inline]\n    pub fn header_length(&self) -> u8 {\n        self.header_length\n    }\n\n    #[inline]\n    pub fn source_port(&self) -> u16 {\n        self.source_port\n    }\n\n    #[inline]\n    pub fn destination_port(&self) -> u16 {\n        self.destination_port\n    }\n\n    #[inline]\n    pub fn sequence_number(&self) -> u32 {\n        self.sequence_number\n    }\n\n    #[inline]\n    pub fn acknowledgement_number(&self) -> u32 {\n        self.acknowledgement_number\n    }\n\n    #[inline]\n    pub fn window(&self) -> u16 {\n        self.window\n    }\n\n    #[inline]\n    pub fn flags(&self) -> u16 {\n        self.flags\n    }\n\n    #[inline]\n    pub fn is_fin(&self) -> bool {\n        self.flags & FLAG_FIN != 0\n    }\n\n    #[inline]\n    pub fn is_syn(&self) -> bool {\n        self.flags & FLAG_SYN != 0\n    }\n\n    #[inline]\n    pub fn is_rst(&self) -> bool {\n        self.flags & FLAG_RST != 0\n    }\n\n    #[inline]\n    pub fn is_psh(&self) -> bool {\n        self.flags & FLAG_PSH != 0\n    }\n\n    #[inline]\n    pub fn is_ack(&self) -> bool {\n        self.flags & FLAG_ACK != 0\n    }\n}\n\n// shared definition for UdpHeader and UdpHeaderMut\nmacro_rules! tcp_header_common {\n    ($name:ident, $raw_type:ty, $data_type:ty) => {\n        // for readability, declare structs manually outside the macro\n        #[allow(dead_code)]\n        impl<'a> $name<'a> {\n            pub fn new(raw: $raw_type, data: $data_type) -> Self {\n                Self {\n                    raw: raw,\n                    data: data,\n                }\n            }\n\n            #[inline]\n            pub fn raw(&self) -> &[u8] {\n                self.raw\n            }\n\n            #[inline]\n            pub fn data(&self) -> &TcpHeaderData {\n                self.data\n            }\n\n            #[inline]\n            pub fn header_length(&self) -> u8 {\n                self.data.header_length\n            }\n\n            #[inline]\n            pub fn source_port(&self) -> u16 {\n                self.data.source_port\n            }\n\n            #[inline]\n            pub fn destination_port(&self) -> u16 {\n                self.data.destination_port\n            }\n\n            #[inline]\n            pub fn sequence_number(&self) -> u32 {\n                self.data.sequence_number\n            }\n\n            #[inline]\n            pub fn acknowledgement_number(&self) -> u32 {\n                self.data.acknowledgement_number\n            }\n\n            #[inline]\n            pub fn window(&self) -> u16 {\n                self.data.window\n            }\n\n            #[inline]\n            pub fn flags(&self) -> u16 {\n                self.data.flags\n            }\n\n            #[inline]\n            pub fn is_fin(&self) -> bool {\n                self.data.is_fin()\n            }\n\n            #[inline]\n            pub fn is_syn(&self) -> bool {\n                self.data.is_syn()\n            }\n\n            #[inline]\n            pub fn is_rst(&self) -> bool {\n                self.data.is_rst()\n            }\n\n            #[inline]\n            pub fn is_psh(&self) -> bool {\n                self.data.is_psh()\n            }\n\n            #[inline]\n            pub fn is_ack(&self) -> bool {\n                self.data.is_ack()\n            }\n        }\n    };\n}\n\ntcp_header_common!(TcpHeader, &'a [u8], &'a TcpHeaderData);\ntcp_header_common!(TcpHeaderMut, &'a mut [u8], &'a mut TcpHeaderData);\n\n// additional methods for the mutable version\n#[allow(dead_code)]\nimpl<'a> TcpHeaderMut<'a> {\n    #[inline]\n    pub fn raw_mut(&mut self) -> &mut [u8] {\n        self.raw\n    }\n\n    #[inline]\n    pub fn data_mut(&mut self) -> &mut TcpHeaderData {\n        self.data\n    }\n\n    #[inline]\n    pub fn set_source_port(&mut self, source_port: u16) {\n        self.data.source_port = source_port;\n        BigEndian::write_u16(&mut self.raw[0..2], source_port);\n    }\n\n    #[inline]\n    pub fn set_destination_port(&mut self, destination_port: u16) {\n        self.data.destination_port = destination_port;\n        BigEndian::write_u16(&mut self.raw[2..4], destination_port);\n    }\n\n    pub fn swap_source_and_destination(&mut self) {\n        mem::swap(&mut self.data.source_port, &mut self.data.destination_port);\n        for i in 0..2 {\n            self.raw.swap(i, i + 2);\n        }\n    }\n\n    #[inline]\n    pub fn set_sequence_number(&mut self, sequence_number: u32) {\n        self.data.sequence_number = sequence_number;\n        BigEndian::write_u32(&mut self.raw[4..8], sequence_number);\n    }\n\n    #[inline]\n    pub fn set_acknowledgement_number(&mut self, acknowledgement_number: u32) {\n        self.data.acknowledgement_number = acknowledgement_number;\n        BigEndian::write_u32(&mut self.raw[8..12], acknowledgement_number);\n    }\n\n    #[inline]\n    pub fn set_flags(&mut self, flags: u16) {\n        self.data.flags = flags;\n        let mut data_offset_and_flags = BigEndian::read_u16(&self.raw[12..14]);\n        data_offset_and_flags = data_offset_and_flags & 0xFE00 | flags & 0x1FF;\n\n        BigEndian::write_u16(&mut self.raw[12..14], data_offset_and_flags);\n    }\n\n    #[inline]\n    pub fn shrink_options(&mut self) {\n        self.set_data_offset(5);\n    }\n\n    #[inline]\n    fn set_data_offset(&mut self, data_offset: u8) {\n        let mut data_offset_and_flags = BigEndian::read_u16(&self.raw[12..14]);\n        data_offset_and_flags = data_offset_and_flags & 0x0FFF | (u16::from(data_offset) << 12);\n        BigEndian::write_u16(&mut self.raw[12..14], data_offset_and_flags);\n        self.data.header_length = data_offset << 2;\n    }\n\n    #[inline]\n    fn checksum(&self) -> u16 {\n        BigEndian::read_u16(&self.raw[16..18])\n    }\n\n    #[inline]\n    fn set_checksum(&mut self, checksum: u16) {\n        BigEndian::write_u16(&mut self.raw[16..18], checksum);\n    }\n\n    pub fn update_checksum(&mut self, ipv4_header_data: &Ipv4HeaderData, payload: &[u8]) {\n        // pseudo-header checksum (cf rfc793 section 3.1)\n        let source = ipv4_header_data.source();\n        let destination = ipv4_header_data.destination();\n        let transport_length =\n            ipv4_header_data.total_length() - u16::from(ipv4_header_data.header_length());\n\n        let header_length = self.header_length();\n        debug_assert!(header_length % 2 == 0 && header_length >= 20);\n\n        let payload_length = transport_length - u16::from(header_length);\n        debug_assert_eq!(\n            payload_length as usize,\n            payload.len(),\n            \"Payload length does not match\"\n        );\n\n        let mut sum = 6u32; // protocol: TCP = 6\n        sum += source >> 16;\n        sum += source & 0xFFFF;\n        sum += destination >> 16;\n        sum += destination & 0xFFFF;\n        sum += u32::from(transport_length);\n\n        // reset checksum field, so that it can be added with other bytes\n        self.set_checksum(0);\n\n        // checksum computation is the most CPU-intensive task in gnirehtet\n        // prefer optimization over readability/safety\n\n        let mut hsum = 0; // high-order bytes sum\n\n        unsafe {\n            let mut p = self.raw.as_ptr();\n            let end = p.offset(header_length as isize);\n            while p < end {\n                hsum += u32::from(*p);\n                sum += u32::from(*p.offset(1));\n                p = p.offset(2);\n            }\n\n            let mut p = payload.as_ptr();\n            // -1 to ignore the last if payload_length is odd\n            let end = p.offset(payload_length as isize - 1);\n            while p < end {\n                hsum += u32::from(*p);\n                sum += u32::from(*p.offset(1));\n                p = p.offset(2);\n            }\n            if payload_length % 2 != 0 {\n                // if payload length is odd, the last byte is considered high-order\n                hsum += u32::from(*payload.get_unchecked((payload_length - 1) as usize));\n            }\n        }\n\n        // add high-order bytes sum to the global sum\n        sum += hsum << 8;\n\n        while (sum & !0xFFFF) != 0 {\n            sum = (sum & 0xFFFF) + (sum >> 16);\n        }\n        self.set_checksum(!sum as u16);\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::relay::ipv4_packet::Ipv4Packet;\n    use crate::relay::transport_header::TransportHeaderMut;\n    use byteorder::{BigEndian, WriteBytesExt};\n\n    fn create_packet() -> Vec<u8> {\n        let mut raw = Vec::new();\n        raw.reserve(44);\n\n        raw.write_u8(4u8 << 4 | 5).unwrap(); // version_and_ihl\n        raw.write_u8(0).unwrap(); //ToS\n        raw.write_u16::<BigEndian>(44).unwrap(); // total length\n        raw.write_u32::<BigEndian>(0).unwrap(); // id_flags_fragment_offset\n        raw.write_u8(0).unwrap(); // TTL\n        raw.write_u8(6).unwrap(); // protocol (TCP)\n        raw.write_u16::<BigEndian>(0).unwrap(); // checksum\n        raw.write_u32::<BigEndian>(0x12345678).unwrap(); // source address\n        raw.write_u32::<BigEndian>(0xA2A24242).unwrap(); // destination address\n\n        raw.write_u16::<BigEndian>(0x1234).unwrap(); // source port\n        raw.write_u16::<BigEndian>(0x5678).unwrap(); // destination port\n        raw.write_u32::<BigEndian>(0x111).unwrap(); // sequence number\n        raw.write_u32::<BigEndian>(0x222).unwrap(); // acknowledgement number\n        raw.write_u16::<BigEndian>(5 << 12).unwrap(); // data offset + flags(0)\n        raw.write_u16::<BigEndian>(0).unwrap(); // window (don't care for these tests)\n        raw.write_u16::<BigEndian>(0).unwrap(); // checksum\n        raw.write_u16::<BigEndian>(0).unwrap(); // urgent pointer\n\n        raw.write_u32::<BigEndian>(0x1122EEFF).unwrap(); // payload\n\n        raw\n    }\n\n    fn create_odd_packet() -> Vec<u8> {\n        let mut raw = Vec::new();\n        raw.reserve(45);\n\n        raw.write_u8(4u8 << 4 | 5).unwrap(); // version_and_ihl\n        raw.write_u8(0).unwrap(); //ToS\n        raw.write_u16::<BigEndian>(45).unwrap(); // total length\n        raw.write_u32::<BigEndian>(0).unwrap(); // id_flags_fragment_offset\n        raw.write_u8(0).unwrap(); // TTL\n        raw.write_u8(6).unwrap(); // protocol (TCP)\n        raw.write_u16::<BigEndian>(0).unwrap(); // checksum\n        raw.write_u32::<BigEndian>(0x12345678).unwrap(); // source address\n        raw.write_u32::<BigEndian>(0xA2A24242).unwrap(); // destination address\n\n        raw.write_u16::<BigEndian>(0x1234).unwrap(); // source port\n        raw.write_u16::<BigEndian>(0x5678).unwrap(); // destination port\n        raw.write_u32::<BigEndian>(0x111).unwrap(); // sequence number\n        raw.write_u32::<BigEndian>(0x222).unwrap(); // acknowledgement number\n        raw.write_u16::<BigEndian>(5 << 12).unwrap(); // data offset + flags(0)\n        raw.write_u16::<BigEndian>(0).unwrap(); // window (don't care for these tests)\n        raw.write_u16::<BigEndian>(0).unwrap(); // checksum\n        raw.write_u16::<BigEndian>(0).unwrap(); // urgent pointer\n\n        // payload\n        raw.write_u32::<BigEndian>(0x1122EEFF).unwrap();\n        raw.write_u8(0x88).unwrap();\n\n        raw\n    }\n\n    fn create_empty_packet() -> Vec<u8> {\n        let mut raw = Vec::new();\n        raw.reserve(40);\n\n        raw.write_u8(4u8 << 4 | 5).unwrap(); // version_and_ihl\n        raw.write_u8(0).unwrap(); //ToS\n        raw.write_u16::<BigEndian>(40).unwrap(); // total length\n        raw.write_u32::<BigEndian>(0).unwrap(); // id_flags_fragment_offset\n        raw.write_u8(0).unwrap(); // TTL\n        raw.write_u8(6).unwrap(); // protocol (TCP)\n        raw.write_u16::<BigEndian>(0).unwrap(); // checksum\n        raw.write_u32::<BigEndian>(0x12345678).unwrap(); // source address\n        raw.write_u32::<BigEndian>(0xA2A24242).unwrap(); // destination address\n\n        raw.write_u16::<BigEndian>(0x1234).unwrap(); // source port\n        raw.write_u16::<BigEndian>(0x5678).unwrap(); // destination port\n        raw.write_u32::<BigEndian>(0x111).unwrap(); // sequence number\n        raw.write_u32::<BigEndian>(0x222).unwrap(); // acknowledgement number\n        raw.write_u16::<BigEndian>(5 << 12).unwrap(); // data offset + flags(0)\n        raw.write_u16::<BigEndian>(0).unwrap(); // window (don't care for these tests)\n        raw.write_u16::<BigEndian>(0).unwrap(); // checksum\n        raw.write_u16::<BigEndian>(0).unwrap(); // urgent pointer\n\n        raw\n    }\n\n    fn create_tcp_header() -> Vec<u8> {\n        let mut raw = Vec::new();\n        raw.reserve(20);\n\n        raw.write_u16::<BigEndian>(0x1234).unwrap(); // source port\n        raw.write_u16::<BigEndian>(0x5678).unwrap(); // destination port\n        raw.write_u32::<BigEndian>(0x111).unwrap(); // sequence number\n        raw.write_u32::<BigEndian>(0x222).unwrap(); // acknowledgement number\n        raw.write_u16::<BigEndian>(5 << 12).unwrap(); // data offset + flags(0)\n        raw.write_u16::<BigEndian>(0).unwrap(); // window (don't care for these tests)\n        raw.write_u16::<BigEndian>(0).unwrap(); // checksum\n        raw.write_u16::<BigEndian>(0).unwrap(); // urgent pointer\n\n        raw\n    }\n\n    #[test]\n    fn edit_header() {\n        let raw = &mut create_tcp_header()[..];\n        let mut header_data = TcpHeaderData::parse(raw);\n        let mut header = header_data.bind_mut(raw);\n\n        header.set_source_port(1111);\n        header.set_destination_port(2222);\n        header.set_sequence_number(300);\n        header.set_acknowledgement_number(101);\n        header.set_flags(FLAG_FIN | FLAG_ACK);\n\n        assert_eq!(1111, header.source_port());\n        assert_eq!(2222, header.destination_port());\n        assert_eq!(300, header.sequence_number());\n        assert_eq!(101, header.acknowledgement_number());\n        assert_eq!(FLAG_FIN | FLAG_ACK, header.flags());\n\n        {\n            let raw = header.raw();\n            // assert that the buffer has been modified\n            let raw_source_port = BigEndian::read_u16(&raw[0..2]);\n            let raw_destination_port = BigEndian::read_u16(&raw[2..4]);\n            let raw_sequence_number = BigEndian::read_u32(&raw[4..8]);\n            let raw_acknowledgement_number = BigEndian::read_u32(&raw[8..12]);\n            let raw_data_offset_and_flags = BigEndian::read_u16(&raw[12..14]);\n\n            assert_eq!(1111, raw_source_port);\n            assert_eq!(2222, raw_destination_port);\n            assert_eq!(300, raw_sequence_number);\n            assert_eq!(101, raw_acknowledgement_number);\n            assert_eq!(0x5011, raw_data_offset_and_flags);\n        }\n\n        header.swap_source_and_destination();\n\n        assert_eq!(2222, header.source_port());\n        assert_eq!(1111, header.destination_port());\n\n        let raw = header.raw();\n        let raw_source_port = BigEndian::read_u16(&raw[0..2]);\n        let raw_destination_port = BigEndian::read_u16(&raw[2..4]);\n        assert_eq!(2222, raw_source_port);\n        assert_eq!(1111, raw_destination_port);\n    }\n\n    #[test]\n    fn compute_checksum() {\n        let raw = &mut create_packet()[..];\n        let mut ipv4_packet = Ipv4Packet::parse(raw);\n        let (ipv4_header, mut transport) = ipv4_packet.split_mut();\n        if let Some((TransportHeaderMut::Tcp(ref mut tcp_header), ref payload)) = transport {\n            // set a fake checksum value to assert that it is correctly computed\n            tcp_header.set_checksum(0x79);\n            tcp_header.update_checksum(ipv4_header.data(), payload);\n            let checksum = tcp_header.checksum();\n\n            let expected_checksum = {\n                // pseudo-header\n                let mut sum: u32 = 0x1234 + 0x5678 + 0xA2A2 + 0x4242 + 0x0006 + 0x0018;\n\n                // header\n                sum += 0x1234\n                    + 0x5678\n                    + 0x0000\n                    + 0x0111\n                    + 0x0000\n                    + 0x0222\n                    + 0x5000\n                    + 0x0000\n                    + 0x0000\n                    + 0x0000;\n\n                // payload\n                sum += 0x1122 + 0xEEFF;\n\n                while (sum & !0xFFFF) != 0 {\n                    sum = (sum & 0xFFFF) + (sum >> 16);\n                }\n                !sum as u16\n            };\n\n            assert_eq!(expected_checksum, checksum);\n        } else {\n            panic!(\"Not a TCP packet\");\n        }\n    }\n\n    #[test]\n    fn compute_checksum_odd() {\n        let raw = &mut create_odd_packet()[..];\n        let mut ipv4_packet = Ipv4Packet::parse(raw);\n        let (ipv4_header, mut transport) = ipv4_packet.split_mut();\n        if let Some((TransportHeaderMut::Tcp(ref mut tcp_header), ref payload)) = transport {\n            // set a fake checksum value to assert that it is correctly computed\n            tcp_header.set_checksum(0x79);\n            tcp_header.update_checksum(ipv4_header.data(), payload);\n            let checksum = tcp_header.checksum();\n\n            let expected_checksum = {\n                // pseudo-header\n                let mut sum: u32 = 0x1234 + 0x5678 + 0xA2A2 + 0x4242 + 0x0006 + 0x0019;\n\n                // header\n                sum += 0x1234\n                    + 0x5678\n                    + 0x0000\n                    + 0x0111\n                    + 0x0000\n                    + 0x0222\n                    + 0x5000\n                    + 0x0000\n                    + 0x0000\n                    + 0x0000;\n\n                // payload\n                sum += 0x1122 + 0xEEFF + 0x8800;\n\n                while (sum & !0xFFFF) != 0 {\n                    sum = (sum & 0xFFFF) + (sum >> 16);\n                }\n                !sum as u16\n            };\n\n            assert_eq!(expected_checksum, checksum);\n        } else {\n            panic!(\"Not a TCP packet\");\n        }\n    }\n\n    #[test]\n    fn compute_checksum_empty_payload() {\n        let raw = &mut create_empty_packet()[..];\n        let mut ipv4_packet = Ipv4Packet::parse(raw);\n        let (ipv4_header, mut transport) = ipv4_packet.split_mut();\n        if let Some((TransportHeaderMut::Tcp(ref mut tcp_header), ref payload)) = transport {\n            // set a fake checksum value to assert that it is correctly computed\n            tcp_header.set_checksum(0x79);\n            tcp_header.update_checksum(ipv4_header.data(), payload);\n            let checksum = tcp_header.checksum();\n\n            let expected_checksum = {\n                // pseudo-header\n                let mut sum: u32 = 0x1234 + 0x5678 + 0xA2A2 + 0x4242 + 0x0006 + 0x0014;\n\n                // header\n                sum += 0x1234\n                    + 0x5678\n                    + 0x0000\n                    + 0x0111\n                    + 0x0000\n                    + 0x0222\n                    + 0x5000\n                    + 0x0000\n                    + 0x0000\n                    + 0x0000;\n\n                while (sum & !0xFFFF) != 0 {\n                    sum = (sum & 0xFFFF) + (sum >> 16);\n                }\n                !sum as u16\n            };\n\n            assert_eq!(expected_checksum, checksum);\n        } else {\n            panic!(\"Not a TCP packet\");\n        }\n    }\n\n    fn create_long_packet() -> Vec<u8> {\n        let mut raw = Vec::new();\n        raw.reserve(45);\n\n        raw.write_u8(4u8 << 4 | 5).unwrap(); // version_and_ihl\n        raw.write_u8(0).unwrap(); //ToS\n        raw.write_u16::<BigEndian>(1240).unwrap(); // total length\n        raw.write_u32::<BigEndian>(0).unwrap(); // id_flags_fragment_offset\n        raw.write_u8(0).unwrap(); // TTL\n        raw.write_u8(6).unwrap(); // protocol (TCP)\n        raw.write_u16::<BigEndian>(0).unwrap(); // checksum\n        raw.write_u32::<BigEndian>(0x12345678).unwrap(); // source address\n        raw.write_u32::<BigEndian>(0xA2A24242).unwrap(); // destination address\n\n        raw.write_u16::<BigEndian>(0x1234).unwrap(); // source port\n        raw.write_u16::<BigEndian>(0x5678).unwrap(); // destination port\n        raw.write_u32::<BigEndian>(0x111).unwrap(); // sequence number\n        raw.write_u32::<BigEndian>(0x222).unwrap(); // acknowledgement number\n        raw.write_u16::<BigEndian>(5 << 12).unwrap(); // data offset + flags(0)\n        raw.write_u16::<BigEndian>(0).unwrap(); // window (don't care for these tests)\n        raw.write_u16::<BigEndian>(0).unwrap(); // checksum\n        raw.write_u16::<BigEndian>(0).unwrap(); // urgent pointer\n\n        // payload\n        for i in 0u16..1200 {\n            raw.write_u8(i as u8).unwrap();\n        }\n\n        raw\n    }\n\n    // built-in rust bench is still unstable, use a simple test instead\n    // run with: cargo test bench_checksum --release -- --nocapture\n    // manual benchmark\n    #[ignore]\n    #[test]\n    fn bench_checksum() {\n        let raw = &mut create_long_packet()[..];\n        let mut ipv4_packet = Ipv4Packet::parse(raw);\n        let (ipv4_header, mut transport) = ipv4_packet.split_mut();\n        if let Some((TransportHeaderMut::Tcp(ref mut tcp_header), ref payload)) = transport {\n            use std::time::Instant;\n            let start = Instant::now();\n            for _ in 0..5000000 {\n                tcp_header.update_checksum(ipv4_header.data(), payload);\n            }\n            let duration = start.elapsed();\n            let ms = duration.as_secs() * 1000 + duration.subsec_nanos() as u64 / 1000000;\n            println!(\"5000000 TCP checksums: {}ms\", ms);\n        } else {\n            panic!(\"Not a TCP packet\");\n        }\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/relay/transport_header.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse super::ipv4_header::{Ipv4HeaderData, Protocol};\nuse super::tcp_header::{TcpHeader, TcpHeaderData, TcpHeaderMut};\nuse super::udp_header::{UdpHeader, UdpHeaderData, UdpHeaderMut, UDP_HEADER_LENGTH};\n\npub enum TransportHeader<'a> {\n    Tcp(TcpHeader<'a>),\n    Udp(UdpHeader<'a>),\n}\n\npub enum TransportHeaderMut<'a> {\n    Tcp(TcpHeaderMut<'a>),\n    Udp(UdpHeaderMut<'a>),\n}\n\n#[derive(Clone)]\npub enum TransportHeaderData {\n    Tcp(TcpHeaderData),\n    Udp(UdpHeaderData),\n}\n\n#[allow(dead_code)]\nimpl TransportHeaderData {\n    pub fn parse(protocol: Protocol, raw: &[u8]) -> Option<Self> {\n        match protocol {\n            Protocol::Udp => Some(UdpHeaderData::parse(raw).into()),\n            Protocol::Tcp => Some(TcpHeaderData::parse(raw).into()),\n            _ => None,\n        }\n    }\n\n    #[inline]\n    pub fn bind<'c, 'a: 'c, 'b: 'c>(&'a self, raw: &'b [u8]) -> TransportHeader<'c> {\n        TransportHeader::new(raw, self)\n    }\n\n    #[inline]\n    pub fn bind_mut<'c, 'a: 'c, 'b: 'c>(&'a mut self, raw: &'b mut [u8]) -> TransportHeaderMut<'c> {\n        TransportHeaderMut::new(raw, self)\n    }\n\n    #[inline]\n    pub fn source_port(&self) -> u16 {\n        match *self {\n            TransportHeaderData::Tcp(ref tcp_header_data) => tcp_header_data.source_port(),\n            TransportHeaderData::Udp(ref udp_header_data) => udp_header_data.source_port(),\n        }\n    }\n\n    #[inline]\n    pub fn destination_port(&self) -> u16 {\n        match *self {\n            TransportHeaderData::Tcp(ref tcp_header_data) => tcp_header_data.destination_port(),\n            TransportHeaderData::Udp(ref udp_header_data) => udp_header_data.destination_port(),\n        }\n    }\n\n    #[inline]\n    pub fn header_length(&self) -> u8 {\n        match *self {\n            TransportHeaderData::Tcp(ref tcp_header_data) => tcp_header_data.header_length(),\n            TransportHeaderData::Udp(_) => UDP_HEADER_LENGTH,\n        }\n    }\n}\n\nimpl<'a> TransportHeader<'a> {\n    pub fn new(raw: &'a [u8], data: &'a TransportHeaderData) -> Self {\n        match *data {\n            TransportHeaderData::Tcp(ref tcp_header_data) => tcp_header_data.bind(raw).into(),\n            TransportHeaderData::Udp(ref udp_header_data) => udp_header_data.bind(raw).into(),\n        }\n    }\n}\n\nimpl<'a> TransportHeaderMut<'a> {\n    pub fn new(raw: &'a mut [u8], data: &'a mut TransportHeaderData) -> Self {\n        match *data {\n            TransportHeaderData::Tcp(ref mut tcp_header_data) => {\n                tcp_header_data.bind_mut(raw).into()\n            }\n            TransportHeaderData::Udp(ref mut udp_header_data) => {\n                udp_header_data.bind_mut(raw).into()\n            }\n        }\n    }\n}\n\n// shared definition for TransportHeader and TransportHeaderMut\nmacro_rules! transport_header_common {\n    ($name:ident, $raw_type:ty, $data_type:ty) => {\n        // for readability, declare structs manually outside the macro\n        #[allow(dead_code)]\n        impl<'a> $name<'a> {\n            #[inline]\n            pub fn raw(&self) -> &[u8] {\n                match *self {\n                    $name::Tcp(ref tcp_header) => tcp_header.raw(),\n                    $name::Udp(ref udp_header) => udp_header.raw(),\n                }\n            }\n\n            #[inline]\n            pub fn data_clone(&self) -> TransportHeaderData {\n                match *self {\n                    $name::Tcp(ref tcp_header) => tcp_header.data().clone().into(),\n                    $name::Udp(ref udp_header) => udp_header.data().clone().into(),\n                }\n            }\n\n            #[inline]\n            pub fn source_port(&self) -> u16 {\n                match *self {\n                    $name::Tcp(ref tcp_header) => tcp_header.data().source_port(),\n                    $name::Udp(ref udp_header) => udp_header.data().source_port(),\n                }\n            }\n\n            #[inline]\n            pub fn destination_port(&self) -> u16 {\n                match *self {\n                    $name::Tcp(ref tcp_header) => tcp_header.data().destination_port(),\n                    $name::Udp(ref udp_header) => udp_header.data().destination_port(),\n                }\n            }\n\n            #[inline]\n            pub fn header_length(&self) -> u8 {\n                match *self {\n                    $name::Tcp(ref tcp_header) => tcp_header.data().header_length(),\n                    $name::Udp(_) => UDP_HEADER_LENGTH,\n                }\n            }\n        }\n    };\n}\n\ntransport_header_common!(TransportHeader, &'a [u8], &'a TransportHeaderData);\ntransport_header_common!(\n    TransportHeaderMut,\n    &'a mut [u8],\n    &'a mut TransportHeaderData\n);\n\n// additional methods for the mutable version\n#[allow(dead_code)]\nimpl<'a> TransportHeaderMut<'a> {\n    #[inline]\n    pub fn raw_mut(&mut self) -> &mut [u8] {\n        match *self {\n            TransportHeaderMut::Tcp(ref mut tcp_header) => tcp_header.raw_mut(),\n            TransportHeaderMut::Udp(ref mut udp_header) => udp_header.raw_mut(),\n        }\n    }\n\n    #[inline]\n    pub fn swap_source_and_destination(&mut self) {\n        match *self {\n            TransportHeaderMut::Tcp(ref mut tcp_header) => tcp_header.swap_source_and_destination(),\n            TransportHeaderMut::Udp(ref mut udp_header) => udp_header.swap_source_and_destination(),\n        }\n    }\n\n    #[inline]\n    pub fn set_payload_length(&mut self, payload_length: u16) {\n        #[allow(clippy::single_match)]\n        match *self {\n            TransportHeaderMut::Udp(ref mut udp_header) => {\n                udp_header.set_payload_length(payload_length)\n            }\n            _ => (), // TCP does not store its payload length\n        }\n    }\n\n    #[inline]\n    pub fn update_checksum(&mut self, ipv4_header_data: &Ipv4HeaderData, payload: &[u8]) {\n        match *self {\n            TransportHeaderMut::Tcp(ref mut tcp_header) => {\n                tcp_header.update_checksum(ipv4_header_data, payload)\n            }\n            TransportHeaderMut::Udp(ref mut udp_header) => {\n                udp_header.update_checksum(ipv4_header_data, payload)\n            }\n        }\n    }\n}\n\nimpl From<TcpHeaderData> for TransportHeaderData {\n    fn from(tcp_header_data: TcpHeaderData) -> TransportHeaderData {\n        TransportHeaderData::Tcp(tcp_header_data)\n    }\n}\n\nimpl From<UdpHeaderData> for TransportHeaderData {\n    fn from(udp_header_data: UdpHeaderData) -> TransportHeaderData {\n        TransportHeaderData::Udp(udp_header_data)\n    }\n}\n\nimpl<'a> From<TcpHeader<'a>> for TransportHeader<'a> {\n    fn from(tcp_header: TcpHeader) -> TransportHeader {\n        TransportHeader::Tcp(tcp_header)\n    }\n}\n\nimpl<'a> From<UdpHeader<'a>> for TransportHeader<'a> {\n    fn from(udp_header: UdpHeader) -> TransportHeader {\n        TransportHeader::Udp(udp_header)\n    }\n}\n\nimpl<'a> From<TcpHeaderMut<'a>> for TransportHeaderMut<'a> {\n    fn from(tcp_header: TcpHeaderMut) -> TransportHeaderMut {\n        TransportHeaderMut::Tcp(tcp_header)\n    }\n}\n\nimpl<'a> From<UdpHeaderMut<'a>> for TransportHeaderMut<'a> {\n    fn from(udp_header: UdpHeaderMut) -> TransportHeaderMut {\n        TransportHeaderMut::Udp(udp_header)\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/relay/tunnel_server.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse log::*;\nuse mio::net::TcpListener;\nuse mio::{Event, PollOpt, Ready};\nuse std::cell::RefCell;\nuse std::io;\nuse std::net::{Ipv4Addr, SocketAddr};\nuse std::ptr;\nuse std::rc::{Rc, Weak};\n\nuse super::client::Client;\nuse super::selector::Selector;\n\nconst TAG: &str = \"TunnelServer\";\n\npub struct TunnelServer {\n    self_weak: Weak<RefCell<TunnelServer>>,\n    clients: Vec<Rc<RefCell<Client>>>,\n    tcp_listener: TcpListener,\n    next_client_id: u32,\n}\n\nimpl TunnelServer {\n    pub fn create(port: u16, selector: &mut Selector) -> io::Result<Rc<RefCell<Self>>> {\n        let tcp_listener = Self::start_socket(port)?;\n        let rc = Rc::new(RefCell::new(Self {\n            self_weak: Weak::new(),\n            clients: Vec::new(),\n            tcp_listener,\n            next_client_id: 0,\n        }));\n\n        // keep a shared reference to this\n        rc.borrow_mut().self_weak = Rc::downgrade(&rc);\n\n        let rc2 = rc.clone();\n        // must anotate selector type: https://stackoverflow.com/a/44004103/1987178\n        let handler =\n            move |selector: &mut Selector, event| rc2.borrow_mut().on_ready(selector, event);\n        selector.register(\n            &rc.borrow().tcp_listener,\n            handler,\n            Ready::readable(),\n            PollOpt::edge(),\n        )?;\n        Ok(rc)\n    }\n\n    fn start_socket(port: u16) -> io::Result<TcpListener> {\n        let localhost = Ipv4Addr::new(127, 0, 0, 1).into();\n        let addr = SocketAddr::new(localhost, port);\n        let server = TcpListener::bind(&addr)?;\n        Ok(server)\n    }\n\n    fn on_ready(&mut self, selector: &mut Selector, _: Event) {\n        match self.accept_client(selector) {\n            Ok(_) => debug!(target: TAG, \"New client accepted\"),\n            Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => {\n                debug!(target: TAG, \"Spurious event, ignoring\");\n            }\n            Err(err) => error!(target: TAG, \"Cannot accept client: {}\", err),\n        }\n    }\n\n    fn accept_client(&mut self, selector: &mut Selector) -> io::Result<()> {\n        let (stream, _) = self.tcp_listener.accept()?;\n        let client_id = self.next_client_id;\n        self.next_client_id += 1;\n        let weak = self.self_weak.clone();\n        let on_client_closed = Box::new(move |client: &Client| {\n            if let Some(rc) = weak.upgrade() {\n                let mut tunnel_server = rc.borrow_mut();\n                tunnel_server.remove_client(client);\n            } else {\n                warn!(\n                    target: TAG,\n                    \"on_client_closed called but no tunnel_server available\"\n                );\n            }\n        });\n        let client = Client::create(client_id, selector, stream, on_client_closed)?;\n        self.clients.push(client);\n        info!(target: TAG, \"Client #{} connected\", client_id);\n        Ok(())\n    }\n\n    fn remove_client(&mut self, client: &Client) {\n        info!(target: TAG, \"Client #{} disconnected\", client.id());\n        let index = self\n            .clients\n            .iter()\n            .position(|item| {\n                // compare pointers to find the client to remove\n                ptr::eq(client, item.as_ptr())\n            })\n            .expect(\"Trying to remove an unknown client\");\n        self.clients.swap_remove(index);\n    }\n\n    pub fn clean_up(&mut self, selector: &mut Selector) {\n        for client in &self.clients {\n            client.borrow_mut().clean_expired_connections(selector);\n        }\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/relay/udp_connection.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse log::*;\nuse mio::net::UdpSocket;\nuse mio::{Event, PollOpt, Ready, Token};\nuse std::cell::RefCell;\nuse std::io;\nuse std::net::{Ipv4Addr, SocketAddr};\nuse std::rc::{Rc, Weak};\nuse std::time::Instant;\n\nuse super::binary;\nuse super::client::{Client, ClientChannel};\nuse super::connection::{Connection, ConnectionId};\nuse super::datagram_buffer::DatagramBuffer;\nuse super::ipv4_header::Ipv4Header;\nuse super::ipv4_packet::{Ipv4Packet, MAX_PACKET_LENGTH};\nuse super::packetizer::Packetizer;\nuse super::selector::Selector;\nuse super::transport_header::TransportHeader;\n\nconst TAG: &str = \"UdpConnection\";\n\npub const IDLE_TIMEOUT_SECONDS: u64 = 2 * 60;\n\npub struct UdpConnection {\n    id: ConnectionId,\n    client: Weak<RefCell<Client>>,\n    socket: UdpSocket,\n    interests: Ready,\n    token: Token,\n    client_to_network: DatagramBuffer,\n    network_to_client: Packetizer,\n    closed: bool,\n    idle_since: Instant,\n}\n\nimpl UdpConnection {\n    #[allow(clippy::needless_pass_by_value)] // semantically, headers are consumed\n    pub fn create(\n        selector: &mut Selector,\n        id: ConnectionId,\n        client: Weak<RefCell<Client>>,\n        ipv4_header: Ipv4Header,\n        transport_header: TransportHeader,\n    ) -> io::Result<Rc<RefCell<Self>>> {\n        cx_info!(target: TAG, id, \"Open\");\n        let socket = Self::create_socket(&id)?;\n        let packetizer = Packetizer::new(&ipv4_header, &transport_header);\n        let interests = Ready::readable();\n        let rc = Rc::new(RefCell::new(Self {\n            id,\n            client,\n            socket,\n            interests,\n            token: Token(0), // default value, will be set afterwards\n            client_to_network: DatagramBuffer::new(4 * MAX_PACKET_LENGTH),\n            network_to_client: packetizer,\n            closed: false,\n            idle_since: Instant::now(),\n        }));\n\n        {\n            let mut self_ref = rc.borrow_mut();\n\n            let rc2 = rc.clone();\n            // must anotate selector type: https://stackoverflow.com/a/44004103/1987178\n            let handler =\n                move |selector: &mut Selector, event| rc2.borrow_mut().on_ready(selector, event);\n            let token =\n                selector.register(&self_ref.socket, handler, interests, PollOpt::level())?;\n            self_ref.token = token;\n        }\n        Ok(rc)\n    }\n\n    fn create_socket(id: &ConnectionId) -> io::Result<UdpSocket> {\n        let autobind_addr = SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 0);\n        let udp_socket = UdpSocket::bind(&autobind_addr)?;\n        udp_socket.connect(id.rewritten_destination().into())?;\n        Ok(udp_socket)\n    }\n\n    fn remove_from_router(&self) {\n        // route is embedded in router which is embedded in client: the client necessarily exists\n        let client_rc = self.client.upgrade().expect(\"Expected client not found\");\n        let mut client = client_rc.borrow_mut();\n        client.router().remove(self);\n    }\n\n    fn on_ready(&mut self, selector: &mut Selector, event: Event) {\n        #[allow(clippy::match_wild_err_arm)]\n        match self.process(selector, event) {\n            Ok(_) => (),\n            Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => {\n                cx_debug!(target: TAG, self.id, \"Spurious event, ignoring\")\n            }\n            Err(_) => panic!(\"Unexpected unhandled error\"),\n        }\n    }\n\n    // return Err(err) with err.kind() == io::ErrorKind::WouldBlock on spurious event\n    fn process(&mut self, selector: &mut Selector, event: Event) -> io::Result<()> {\n        if !self.closed {\n            self.touch();\n            let ready = event.readiness();\n            if ready.is_readable() || ready.is_writable() {\n                if ready.is_writable() {\n                    self.process_send(selector)?;\n                }\n                if !self.closed && ready.is_readable() {\n                    self.process_receive(selector)?;\n                }\n                if !self.closed {\n                    self.update_interests(selector);\n                }\n            } else {\n                // error or hup\n                self.close(selector);\n            }\n            if self.closed {\n                // on_ready is not called from the router, so the connection must remove itself\n                self.remove_from_router();\n            }\n        }\n        Ok(())\n    }\n\n    // return Err(err) with err.kind() == io::ErrorKind::WouldBlock on spurious event\n    fn process_send(&mut self, selector: &mut Selector) -> io::Result<()> {\n        match self.write() {\n            Ok(_) => (),\n            Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => {\n                cx_debug!(target: TAG, self.id, \"Spurious event, ignoring\")\n            }\n            Err(err) => {\n                if err.kind() == io::ErrorKind::WouldBlock {\n                    // rethrow\n                    return Err(err);\n                }\n                cx_error!(\n                    target: TAG,\n                    self.id,\n                    \"Cannot write: [{:?}] {}\",\n                    err.kind(),\n                    err\n                );\n                self.close(selector);\n            }\n        }\n        Ok(())\n    }\n\n    // return Err(err) with err.kind() == io::ErrorKind::WouldBlock on spurious event\n    fn process_receive(&mut self, selector: &mut Selector) -> io::Result<()> {\n        match self.read(selector) {\n            Ok(_) => (),\n            Err(err) => {\n                if err.kind() == io::ErrorKind::WouldBlock {\n                    // rethrow\n                    return Err(err);\n                }\n                cx_error!(\n                    target: TAG,\n                    self.id,\n                    \"Cannot read: [{:?}] {}\",\n                    err.kind(),\n                    err\n                );\n                self.close(selector);\n            }\n        }\n        Ok(())\n    }\n\n    fn read(&mut self, selector: &mut Selector) -> io::Result<()> {\n        let ipv4_packet = self.network_to_client.packetize(&mut self.socket)?;\n        let client_rc = self.client.upgrade().expect(\"Expected client not found\");\n        match client_rc\n            .borrow_mut()\n            .send_to_client(selector, &ipv4_packet)\n        {\n            Ok(_) => {\n                cx_debug!(\n                    target: TAG,\n                    self.id,\n                    \"Packet ({} bytes) sent to client\",\n                    ipv4_packet.length()\n                );\n                if log_enabled!(target: TAG, Level::Trace) {\n                    cx_trace!(\n                        target: TAG,\n                        self.id,\n                        \"{}\",\n                        binary::build_packet_string(ipv4_packet.raw())\n                    );\n                }\n            }\n            Err(_) => cx_warn!(target: TAG, self.id, \"Cannot send to client, drop packet\"),\n        }\n        Ok(())\n    }\n\n    fn write(&mut self) -> io::Result<()> {\n        self.client_to_network.write_to(&mut self.socket)?;\n        Ok(())\n    }\n\n    fn update_interests(&mut self, selector: &mut Selector) {\n        let ready = if self.client_to_network.is_empty() {\n            Ready::readable()\n        } else {\n            Ready::readable() | Ready::writable()\n        };\n        cx_debug!(target: TAG, self.id, \"interests: {:?}\", ready);\n        if self.interests != ready {\n            // interests must be changed\n            self.interests = ready;\n            selector\n                .reregister(&self.socket, self.token, ready, PollOpt::level())\n                .expect(\"Cannot register on poll\");\n        }\n    }\n\n    fn touch(&mut self) {\n        self.idle_since = Instant::now();\n    }\n}\n\nimpl Connection for UdpConnection {\n    fn id(&self) -> &ConnectionId {\n        &self.id\n    }\n\n    fn send_to_network(\n        &mut self,\n        selector: &mut Selector,\n        _: &mut ClientChannel,\n        ipv4_packet: &Ipv4Packet,\n    ) {\n        match self\n            .client_to_network\n            .read_from(ipv4_packet.payload().expect(\"No payload\"))\n        {\n            Ok(_) => {\n                self.update_interests(selector);\n            }\n            Err(err) => cx_warn!(\n                target: TAG,\n                self.id,\n                \"Cannot send to network, drop packet: {}\",\n                err\n            ),\n        }\n    }\n\n    fn close(&mut self, selector: &mut Selector) {\n        cx_info!(target: TAG, self.id, \"Close\");\n        self.closed = true;\n        if let Err(err) = selector.deregister(&self.socket, self.token) {\n            // do not panic, this can happen in mio\n            // see <https://github.com/Genymobile/gnirehtet/issues/136>\n            cx_warn!(\n                target: TAG,\n                self.id,\n                \"Fail to deregister UDP stream: {:?}\",\n                err\n            );\n        }\n        // socket will be closed by RAII\n    }\n\n    fn is_expired(&self) -> bool {\n        self.idle_since.elapsed().as_secs() > IDLE_TIMEOUT_SECONDS\n    }\n\n    fn is_closed(&self) -> bool {\n        self.closed\n    }\n}\n"
  },
  {
    "path": "relay-rust/src/relay/udp_header.rs",
    "content": "/*\n * Copyright (C) 2017 Genymobile\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nuse super::ipv4_header::Ipv4HeaderData;\nuse byteorder::{BigEndian, ByteOrder};\nuse std::mem;\n\npub const UDP_HEADER_LENGTH: u8 = 8;\n\npub struct UdpHeader<'a> {\n    raw: &'a [u8],\n    data: &'a UdpHeaderData,\n}\n\npub struct UdpHeaderMut<'a> {\n    raw: &'a mut [u8],\n    data: &'a mut UdpHeaderData,\n}\n\n#[derive(Clone)]\npub struct UdpHeaderData {\n    source_port: u16,\n    destination_port: u16,\n}\n\n#[allow(dead_code)]\nimpl UdpHeaderData {\n    pub fn parse(raw: &[u8]) -> Self {\n        Self {\n            source_port: BigEndian::read_u16(&raw[0..2]),\n            destination_port: BigEndian::read_u16(&raw[2..4]),\n        }\n    }\n\n    #[inline]\n    pub fn bind<'c, 'a: 'c, 'b: 'c>(&'a self, raw: &'b [u8]) -> UdpHeader<'c> {\n        UdpHeader::new(raw, self)\n    }\n\n    #[inline]\n    pub fn bind_mut<'c, 'a: 'c, 'b: 'c>(&'a mut self, raw: &'b mut [u8]) -> UdpHeaderMut<'c> {\n        UdpHeaderMut::new(raw, self)\n    }\n\n    #[inline]\n    pub fn source_port(&self) -> u16 {\n        self.source_port\n    }\n\n    #[inline]\n    pub fn destination_port(&self) -> u16 {\n        self.destination_port\n    }\n}\n\n// shared definition for UdpHeader and UdpHeaderMut\nmacro_rules! udp_header_common {\n    ($name:ident, $raw_type:ty, $data_type:ty) => {\n        // for readability, declare structs manually outside the macro\n        #[allow(dead_code)]\n        impl<'a> $name<'a> {\n            pub fn new(raw: $raw_type, data: $data_type) -> Self {\n                Self {\n                    raw: raw,\n                    data: data,\n                }\n            }\n\n            #[inline]\n            pub fn raw(&self) -> &[u8] {\n                self.raw\n            }\n\n            #[inline]\n            pub fn data(&self) -> &UdpHeaderData {\n                self.data\n            }\n\n            #[inline]\n            pub fn source_port(&self) -> u16 {\n                self.data.source_port\n            }\n\n            #[inline]\n            pub fn destination_port(&self) -> u16 {\n                self.data.destination_port\n            }\n        }\n    };\n}\n\nudp_header_common!(UdpHeader, &'a [u8], &'a UdpHeaderData);\nudp_header_common!(UdpHeaderMut, &'a mut [u8], &'a mut UdpHeaderData);\n\n// additional methods for the mutable version\n#[allow(dead_code)]\nimpl<'a> UdpHeaderMut<'a> {\n    #[inline]\n    pub fn raw_mut(&mut self) -> &mut [u8] {\n        self.raw\n    }\n\n    #[inline]\n    pub fn data_mut(&mut self) -> &mut UdpHeaderData {\n        self.data\n    }\n\n    #[inline]\n    pub fn set_source_port(&mut self, source_port: u16) {\n        self.data.source_port = source_port;\n        BigEndian::write_u16(&mut self.raw[0..2], source_port);\n    }\n\n    #[inline]\n    pub fn set_destination_port(&mut self, destination_port: u16) {\n        self.data.destination_port = destination_port;\n        BigEndian::write_u16(&mut self.raw[2..4], destination_port);\n    }\n\n    pub fn swap_source_and_destination(&mut self) {\n        mem::swap(&mut self.data.source_port, &mut self.data.destination_port);\n        for i in 0..2 {\n            self.raw.swap(i, i + 2);\n        }\n    }\n\n    #[inline]\n    pub fn set_payload_length(&mut self, payload_length: u16) {\n        let total_length = u16::from(UDP_HEADER_LENGTH) + payload_length;\n        BigEndian::write_u16(&mut self.raw[4..6], total_length);\n    }\n\n    #[inline]\n    fn set_checksum(&mut self, checksum: u16) {\n        BigEndian::write_u16(&mut self.raw[6..8], checksum);\n    }\n\n    #[inline]\n    pub fn update_checksum(&mut self, _ipv4_header_data: &Ipv4HeaderData, _payload: &[u8]) {\n        // disable checksum validation\n        self.set_checksum(0);\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use byteorder::{BigEndian, WriteBytesExt};\n\n    fn create_header() -> Vec<u8> {\n        let mut raw = Vec::new();\n        raw.reserve(8);\n        raw.write_u16::<BigEndian>(1234).unwrap(); // source port\n        raw.write_u16::<BigEndian>(5678).unwrap(); // destination port\n        raw.write_u16::<BigEndian>(42).unwrap(); // length\n        raw.write_u16::<BigEndian>(0).unwrap(); // checksum\n        raw\n    }\n\n    #[test]\n    fn parse_header() {\n        let raw = &create_header()[..];\n        let data = UdpHeaderData::parse(raw);\n        assert_eq!(1234, data.source_port());\n        assert_eq!(5678, data.destination_port());\n    }\n\n    #[test]\n    fn edit_header() {\n        let raw = &mut create_header()[..];\n        let mut header_data = UdpHeaderData::parse(raw);\n        let mut header = header_data.bind_mut(raw);\n\n        header.set_source_port(1111);\n        header.set_destination_port(2222);\n        header.set_payload_length(34);\n        assert_eq!(1111, header.source_port());\n        assert_eq!(2222, header.destination_port());\n\n        {\n            let raw = header.raw();\n            // assert that the buffer has been modified\n            let raw_source_port = BigEndian::read_u16(&raw[0..2]);\n            let raw_destination_port = BigEndian::read_u16(&raw[2..4]);\n            let raw_total_length = BigEndian::read_u16(&raw[4..6]);\n            assert_eq!(1111, raw_source_port);\n            assert_eq!(2222, raw_destination_port);\n            assert_eq!(34 + 8, raw_total_length);\n        }\n\n        header.swap_source_and_destination();\n\n        assert_eq!(2222, header.source_port());\n        assert_eq!(1111, header.destination_port());\n\n        let raw = header.raw();\n        let raw_source_port = BigEndian::read_u16(&raw[0..2]);\n        let raw_destination_port = BigEndian::read_u16(&raw[2..4]);\n        assert_eq!(2222, raw_source_port);\n        assert_eq!(1111, raw_destination_port);\n    }\n}\n"
  },
  {
    "path": "release",
    "content": "#!/bin/bash\n# Make a gnirehtet release.\n#\n# Put your keystore properties into ~/.gradle/gradle.properties\n# (check app/build.gradle)\n\nset -e # fail on error\n\nGRADLE=${GRADLE:-./gradlew} # set value ./gradlew if not set\nDIST=dist\nTARGET_DIR_JAVA=gnirehtet-java\nTARGET_DIR_RUST_LINUX=gnirehtet-rust-linux64\nTARGET_DIR_RUST_WIN=gnirehtet-rust-win64\n\nversion=$(git describe --tags --always)\nTARGET_JAVA=\"$TARGET_DIR_JAVA-$version\".zip\nTARGET_RUST_LINUX=\"$TARGET_DIR_RUST_LINUX-$version\".zip\nTARGET_RUST_WIN=\"$TARGET_DIR_RUST_WIN-$version\".zip\n\n\"$GRADLE\" checkAll releaseAll releaseRustWindows\n\nrm -rf \"$DIST\"\n\nfor dir in \"$TARGET_DIR_JAVA\" \"$TARGET_DIR_RUST_LINUX\" \"$TARGET_DIR_RUST_WIN\"\ndo\n    mkdir -p \"$DIST/$dir\"\n    cp app/build/outputs/apk/release/gnirehtet-release.apk \"$DIST/$dir\"/gnirehtet.apk\ndone\n\ncp relay-java/build/libs/gnirehtet.jar \\\n   relay-java/scripts/gnirehtet \\\n   relay-java/scripts/gnirehtet.cmd \\\n   relay-java/scripts/gnirehtet-run.cmd \\\n   \"$DIST/$TARGET_DIR_JAVA\"/\ncp relay-rust/target/release/gnirehtet \"$DIST/$TARGET_DIR_RUST_LINUX\"/\nstrip \"$DIST/$TARGET_DIR_RUST_LINUX\"/gnirehtet  # remove symbols for smaller binary\ncp relay-rust/target/x86_64-pc-windows-gnu/release/gnirehtet.exe \\\n   relay-rust/scripts/gnirehtet-run.cmd \\\n   \"$DIST/$TARGET_DIR_RUST_WIN\"/\n\ncd \"$DIST\"\nzip -r \"$TARGET_JAVA\" \"$TARGET_DIR_JAVA\"\nzip -r \"$TARGET_RUST_LINUX\" \"$TARGET_DIR_RUST_LINUX\"\nzip -r \"$TARGET_RUST_WIN\" \"$TARGET_DIR_RUST_WIN\"\nsha256sum \"$TARGET_JAVA\" \"$TARGET_RUST_LINUX\" \"$TARGET_RUST_WIN\" > SHA256SUMS.txt\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':app', ':relay-java', ':relay-rust'\n"
  }
]