diff --git a/app/src/main/java/chilladvanced/Logger.kt b/app/src/main/java/chilladvanced/Logger.kt new file mode 100644 index 0000000..3de7d78 --- /dev/null +++ b/app/src/main/java/chilladvanced/Logger.kt @@ -0,0 +1,165 @@ +package chilladvanced + +import android.util.Log +import com.google.gson.Gson +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import java.io.IOException +import java.net.HttpURLConnection +import java.net.Proxy +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicLong + +/** + * Defining variable types. + * + * @param url the type of a urls in logs. + * @param method the type of a metods in logs. + * @param userId the type of a user_id in logs. + * @param unixtime the type of a time logging. + * @param inputTraffic the type of a incoming traffic in logs. + * @param outputTraffic the type of a outgoing traffic in logs. + * @param duration the type of a url in logs. + */ + +data class RequestLog( + val url: String, + val method: String, + val userId: String, + val unixtime: Long, + val inputTraffic: Long, + val outputTraffic: Long, + val duration: Long, +) + +typealias Host = String + +typealias Timestamp = Long + +/** + * Object containing the main functions used + */ + +object Logger { + + @JvmStatic + val userId: UUID = UUID.randomUUID() + + @JvmStatic + private val storage = mutableSetOf() + + @JvmStatic + val sendLogsLock = Mutex() + + @JvmStatic + val gson = Gson() + + @JvmStatic + val map: ConcurrentHashMap> = ConcurrentHashMap() + + @JvmStatic + var lastUpdate = AtomicLong() + + + /** + * Parameters accepted by the logger. + * + * @param host the name of request host + * @param method the name method that usnig for network request + * @param duration the time in millisecond that network connection spend to + * @param inputTraffic the traffic in bytest that connection sends to server + * @param outputTraffic the traffic in bytest that connection received from server + */ + @JvmStatic + fun log( + host: String, + method: NETWORK_METHODS, + duration: Long, + inputTraffic: Long, + outputTraffic: Long + ) { + val methodWithFilter = map[host]?.first ?: method + val currentTimeMillis = System.currentTimeMillis() + if (currentTimeMillis - lastUpdate.get() > 10000) { + map.filterValues { (currentTimeMillis - it.second) > 10000 }.keys.forEach(map::remove) + lastUpdate.set(currentTimeMillis); + } + val l = RequestLog( + host, methodWithFilter.name, userId.toString(), + currentTimeMillis, inputTraffic, outputTraffic, duration + ) + synchronized(storage) { + storage += l + } + } + + /** + * Registers an expected call to the host from a supplied method. + * + * @param host the name of host that connection will be sent + * @param method the name of network method that will be invocated + */ + @JvmStatic + fun register(host: String, method: NETWORK_METHODS) { + map[host] = Pair(method, System.currentTimeMillis()); + } + + /** + * frequency of sending statistic to server + */ + val SEND_LOG_TIME_SECOND = 5 + + /** + * Run logger. + */ + @JvmStatic + fun runSending() { + Thread { + while (true) { + Thread.sleep(SEND_LOG_TIME_SECOND.toLong() * 1000) + sendLog() + } + }.start() + } + + /** + * Getting logs to the server. + * + */ + @JvmStatic + private fun sendLog() { + val lgs = synchronized(storage) { + storage.toList() + } + + if (lgs.isEmpty()) { + return + } + + /** + * Connecting to the server and getting logs. + * @throws IOException if failed to log network request. + */ + + try { + Log.i("Logger", "log object: $lgs") + val connection = java.net.URL("https://d.kbats.ru/util/user-add") + .openConnection(Proxy.NO_PROXY) as HttpURLConnection + connection.requestMethod = "POST" + connection.setRequestProperty("Content-Type", "application/json; charset=utf-8") + + val info = gson.toJson(lgs) + Log.i("Logger", "sending log info: $info") + connection.outputStream.write(info.toByteArray()) + + val text = connection.inputStream.bufferedReader().readText() + + synchronized(storage) { + storage -= lgs.toSet() + } + } catch (e: IOException) { + Log.e("Logger", "failed to log network request, caused error $e") + } + } +} diff --git a/app/src/main/java/chilladvanced/NETWORK_METHODS.kt b/app/src/main/java/chilladvanced/NETWORK_METHODS.kt new file mode 100644 index 0000000..c393d46 --- /dev/null +++ b/app/src/main/java/chilladvanced/NETWORK_METHODS.kt @@ -0,0 +1,11 @@ +package chilladvanced + +enum class NETWORK_METHODS { + UNKNOWN, + EXO_PLAYER, + OK_HTTP_CLIENT, + URL_CONNECTION, + FRESCO, + NATIVE, + +} \ No newline at end of file diff --git a/app/src/main/java/chilladvanced/NativeLibraryTracker.kt b/app/src/main/java/chilladvanced/NativeLibraryTracker.kt new file mode 100644 index 0000000..ce8ba7b --- /dev/null +++ b/app/src/main/java/chilladvanced/NativeLibraryTracker.kt @@ -0,0 +1,74 @@ +package chilladvanced + +import android.Manifest +import android.app.Activity +import android.content.Context +import android.content.pm.PackageManager +import android.net.TrafficStats +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.core.content.PermissionChecker +import androidx.core.content.PermissionChecker.checkSelfPermission +import com.google.android.exoplayer2.util.Log + + +class NativeLibraryTracker() { +// val currentThread: Int +// val totalTxBytes = TrafficStats.getTotalTxBytes() +// init { +// currentThread = TrafficStats.getUidRxBytes() +// TrafficStats.getThreadStatsTag() +// val totalRxBytes = TrafficStats..getTotalRxBytes() +// } + + var trafficStatsTx = 0L + var started: Boolean = false + var trafficStatsRx = 0L + var uid = -1; + + fun start(context: Activity) { + // May be we got permission for another task + try { + if (checkSelfPermission( + context, + Manifest.permission.READ_PHONE_STATE + ) == PermissionChecker.PERMISSION_GRANTED + ) { + + + uid = context.applicationInfo.uid + TrafficStats.setThreadStatsTag(uid) + trafficStatsRx = TrafficStats.getTotalRxBytes() + trafficStatsTx = TrafficStats.getTotalTxBytes() + + } + } catch (e: Exception) { + Log.w( + "NativeLibraryTracker", + "Something went wrong in NativeLibraryTracker: " + e.message + ) + } + } + + fun stop(context: Context) { + try { + if (started && checkSelfPermission( + context, + Manifest.permission.READ_PHONE_STATE + ) == PermissionChecker.PERMISSION_GRANTED + ) { + TrafficStats.setThreadStatsTag(uid) + Logger.log( + "", NETWORK_METHODS.NATIVE, 0, + TrafficStats.getTotalRxBytes() - trafficStatsRx, + TrafficStats.getTotalTxBytes() - trafficStatsTx + ) + } + } catch (e: Exception) { + Log.w( + "NativeLibraryTracker", + "Something went wrong in NativeLibraryTracker: " + e.message + ) + } + } +} diff --git a/app/src/main/java/chilladvanced/NetworkLogger.kt b/app/src/main/java/chilladvanced/NetworkLogger.kt new file mode 100644 index 0000000..3b06095 --- /dev/null +++ b/app/src/main/java/chilladvanced/NetworkLogger.kt @@ -0,0 +1,67 @@ +package chilladvanced + +/** + * Network logging sdk main object + * + * @author chill advanced team + */ +object NetworkLogger { + + /** + * Initialize and run network logger + * + * This method setups and starts network proxy for logging all traffic in android application + */ + fun initializeAndRunLogging(proxyPortHttp: Int = 3128, proxyPortAnother: Int = 3129) { + setProxyHostHttp("127.0.0.1") + setProxyHostAnother("127.0.0.1") + setProxyPortHttp("$proxyPortHttp") + setProxyPortAnother("$proxyPortAnother") + + ProxyServer( + proxyPortHttp, + SocketConnectHttpChecker.Companion::connectAndCountTraffic + ).startServer() + ProxyServer( + proxyPortAnother, + SocketConnect.Companion::connectAndCountTraffic + ).startServer() + + Logger.runSending() + } + + private fun setProxyHostHttp(host: String) { + System.setProperty("http.proxyHost", host); + } + + private fun setProxyPortHttp(host: String) { + System.setProperty("http.proxyPort", host); + } + + private fun setProxyHostAnother(host: String) { + System.setProperty("https.proxyHost", host); + System.setProperty("ftp.proxyHost", host); + System.setProperty("socksProxyHost", host); + } + + private fun setProxyPortAnother(port: String) { + System.setProperty("https.proxyPort", port); + System.setProperty("ftp.proxyPort", port); + System.setProperty("socksProxyPort", port); + } + + private fun delayShutdownEvent(millis: Long) { + Thread.setDefaultUncaughtExceptionHandler { t, e -> // perform any necessary cleanup or shutdown tasks here + println("Uncaught exception occurred: " + e.message) + try { + // delay the shutdown for 2 seconds + Thread.sleep(millis) + } catch (ex: InterruptedException) { + // handle the InterruptedException if necessary + ex.printStackTrace() + } + // shut down the program + System.exit(1) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chilladvanced/OkHttpClientWithStat.kt b/app/src/main/java/chilladvanced/OkHttpClientWithStat.kt new file mode 100644 index 0000000..16417b9 --- /dev/null +++ b/app/src/main/java/chilladvanced/OkHttpClientWithStat.kt @@ -0,0 +1,13 @@ +package chilladvanced + +import android.util.Log +import okhttp3.Call +import okhttp3.OkHttpClient +import okhttp3.Request + +class OkHttpClientWithStat(val okHttpClient: OkHttpClient) { + fun newCall(request: Request): Call { + Logger.register(request.url.host, NETWORK_METHODS.OK_HTTP_CLIENT) + return okHttpClient.newCall(request) + } +} \ No newline at end of file diff --git a/app/src/main/java/chilladvanced/ProxyServer.kt b/app/src/main/java/chilladvanced/ProxyServer.kt new file mode 100644 index 0000000..a6d83c0 --- /dev/null +++ b/app/src/main/java/chilladvanced/ProxyServer.kt @@ -0,0 +1,399 @@ +package chilladvanced + +import android.util.Log +import chilladvanced.SocketConnect.Companion.connectAndCountTraffic +import com.google.common.collect.Lists +import java.io.IOException +import java.io.InputStream +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.Proxy +import java.net.ProxySelector +import java.net.ServerSocket +import java.net.Socket +import java.net.SocketException +import java.net.URI +import java.net.URISyntaxException +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import java.util.function.BiConsumer + +/** + * @hide + */ +class ProxyServer(port: Int, val connector: ConnectAndCountTrafficInterface) : Thread() { + private val threadExecutor: ExecutorService = Executors.newCachedThreadPool() + var mIsRunning = false + private var serverSocket: ServerSocket? = null + val port: Int + + private inner class ProxyConnection(private val connection: Socket) : Runnable { + override fun run() { + val urlStringReq: String + try { + val requestLine = getLine(connection.getInputStream()) + Log.i(TAG, "REQUEST ON PROXY: $requestLine") + val splitLine = + requestLine.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + if (splitLine.size < 3) { + connection.close() + return + } + Log.v(TAG, " -> REQUEST: $requestLine") + val requestType = splitLine[0] + var urlString = splitLine[1] + urlStringReq = urlString + val httpVersion = splitLine[2] + var url: URI? = null + val host: String + var port: Int + if (requestType == CONNECT) { + val hostPortSplit = + urlString.split(":".toRegex()).dropLastWhile { it.isEmpty() } + .toTypedArray() + host = hostPortSplit[0] + // Use default SSL port if not specified. Parse it otherwise + port = if (hostPortSplit.size < 2) { + 443 + } else { + try { + hostPortSplit[1].toInt() + } catch (nfe: NumberFormatException) { + connection.close() + return + } + } + urlString = "Https://$host:$port" + } else { + try { + url = URI(urlString) + host = url.host + port = url.port + if (port < 0) { + port = 80 + } + } catch (e: URISyntaxException) { + connection.close() + return + } + } + var list: List = Lists.newArrayList() + try { + list = ProxySelector.getDefault().select(URI(urlString)) + } catch (e: URISyntaxException) { + e.printStackTrace() + } + var server: Socket? = null + for (proxy in list) { + try { + var forward = false + if (proxy != Proxy.NO_PROXY) { + // Only Inets created by PacProxySelector. + val inetSocketAddress = proxy.address() as InetSocketAddress + server = Socket( + inetSocketAddress.hostName, + inetSocketAddress.port + ) + if ("127.0.0.1" == InetAddress.getByName( + inetSocketAddress.hostName + ).hostAddress != true + ) { + server = Socket( + inetSocketAddress.hostName, + inetSocketAddress.port + ) + sendLine(server, requestLine) + forward = true + } + } + if (forward != true) { + server = Socket(host, port) + if (requestType == CONNECT) { + Log.v(TAG, " -> CONNECT: $host:$port") + skipToRequestBody(connection) + // No proxy to respond so we must. + sendLine(connection, HTTP_OK) + } else { + // Proxying the request directly to the origin server. + Log.v(TAG, " -> DIRECT: $host:$port") + sendAugmentedRequestToHost( + connection, server, + requestType, url, httpVersion + ) + } + } + } catch (ioe: IOException) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Unable to connect to proxy $proxy", ioe) + } + } + if (server != null) { + break + } + } + if (list.isEmpty()) { + server = Socket(host, port) + if (requestType == CONNECT) { + skipToRequestBody(connection) + // No proxy to respond so we must. + sendLine(connection, HTTP_OK) + } else { + // Proxying the request directly to the origin server. + sendAugmentedRequestToHost( + connection, server, + requestType, url, httpVersion + ) + } + } + val start = LongArray(1) + start[0] = System.currentTimeMillis() + // Pass data back and forth until complete. + if (server != null) { + connector( + connection, + server, { inputTraffic, outputTraffic -> + Logger.log( + host, NETWORK_METHODS.UNKNOWN, 0, + inputTraffic, outputTraffic + ); + }) { + Logger.log( + host, NETWORK_METHODS.UNKNOWN, System.currentTimeMillis() - start[0], + 0, 0 + ) + start[0] = System.currentTimeMillis() + } + } + } catch (e: Exception) { + Log.d(TAG, "Problem Proxying", e) + } + try { + connection.close() + } catch (ioe: IOException) { + // Do nothing + } + } + + /** + * Sends HTTP request-line (i.e. the first line in the request) + * that contains absolute path of a given absolute URI. + * + * @param server server to send the request to. + * @param requestType type of the request, a.k.a. HTTP method. + * @param absoluteUri absolute URI which absolute path should be extracted. + * @param httpVersion version of HTTP, e.g. HTTP/1.1. + * @throws IOException if the request-line cannot be sent. + */ + @Throws(IOException::class) + private fun sendRequestLineWithPath( + server: Socket, requestType: String, + absoluteUri: URI?, httpVersion: String + ) { + val absolutePath = getAbsolutePathFromAbsoluteURI(absoluteUri) + val outgoingRequestLine = String.format( + "%s %s %s", + requestType, absolutePath, httpVersion + ) + sendLine(server, outgoingRequestLine) + } + + /** + * Extracts absolute path form a given URI. E.g., passing + * `http://google.com:80/execute?query=cat#top` + * will result in `/execute?query=cat#top`. + * + * @param uri URI which absolute path has to be extracted, + * @return the absolute path of the URI, + */ + private fun getAbsolutePathFromAbsoluteURI(uri: URI?): String { + val rawPath = uri!!.rawPath + val rawQuery = uri.rawQuery + val rawFragment = uri.rawFragment + val absolutePath = StringBuilder() + if (rawPath != null) { + absolutePath.append(rawPath) + } else { + absolutePath.append("/") + } + if (rawQuery != null) { + absolutePath.append("?").append(rawQuery) + } + if (rawFragment != null) { + absolutePath.append("#").append(rawFragment) + } + return absolutePath.toString() + } + + @Throws(IOException::class) + private fun getLine(inputStream: InputStream): String { + val buffer = StringBuilder() + var byteBuffer = inputStream.read() + if (byteBuffer < 0) return "" + do { + if (byteBuffer != '\r'.code) { + buffer.append(byteBuffer.toChar()) + } + byteBuffer = inputStream.read() + } while (byteBuffer != '\n'.code && byteBuffer >= 0) + return buffer.toString() + } + + @Throws(IOException::class) + private fun sendLine(socket: Socket, line: String) { + val os = socket.getOutputStream() + os.write(line.toByteArray()) + os.write('\n'.code) + os.flush() + } + + /** + * Reads from socket until an empty line is read which indicates the end of HTTP headers. + * + * @param socket socket to read from. + * @throws IOException if an exception took place during the socket read. + */ + @Throws(IOException::class) + private fun skipToRequestBody(socket: Socket) { + while (getLine(socket.getInputStream()).length != 0); + } + + /** + * Sends an augmented request to the final host (DIRECT connection). + * + * @param src socket to read HTTP headers from.The socket current position should point + * to the beginning of the HTTP header section. + * @param dst socket to write the augmented request to. + * @param httpMethod original request http method. + * @param uri original request absolute URI. + * @param httpVersion original request http version. + * @throws IOException if an exception took place during socket reads or writes. + */ + @Throws(IOException::class) + private fun sendAugmentedRequestToHost( + src: Socket, dst: Socket, + httpMethod: String, uri: URI?, httpVersion: String + ) { + sendRequestLineWithPath(dst, httpMethod, uri, httpVersion) + filterAndForwardRequestHeaders(src, dst) + + // Currently the proxy does not support keep-alive connections; therefore, + // the proxy has to request the destination server to close the connection + // after the destination server sent the response. + sendLine(dst, "Connection: close") + + // Sends and empty line that indicates termination of the header section. + sendLine(dst, "") + } + + /** + * Forwards original request headers filtering out the ones that have to be removed. + * + * @param src source socket that contains original request headers. + * @param dst destination socket to send the filtered headers to. + * @throws IOException if the data cannot be read from or written to the sockets. + */ + @Throws(IOException::class) + private fun filterAndForwardRequestHeaders(src: Socket, dst: Socket) { + var line: String + do { + line = getLine(src.getInputStream()) + if (line.length > 0 && !shouldRemoveHeaderLine(line)) { + sendLine(dst, line) + } + } while (line.length > 0) + } + + /** + * Returns true if a given header line has to be removed from the original request. + * + * @param line header line that should be analysed. + * @return true if the header line should be removed and not forwarded to the destination. + */ + private fun shouldRemoveHeaderLine(line: String): Boolean { + val colIndex = line.indexOf(":") + if (colIndex != -1) { + val headerName = line.substring(0, colIndex).trim { it <= ' ' } + if (headerName.regionMatches( + 0, HEADER_CONNECTION, 0, + HEADER_CONNECTION.length, ignoreCase = true + ) + || headerName.regionMatches( + 0, HEADER_PROXY_CONNECTION, + 0, HEADER_PROXY_CONNECTION.length, ignoreCase = true + ) + ) { + return true + } + } + return false + } + } + + init { + this.port = port + } + + override fun run() { + try { + serverSocket = ServerSocket(port) + while (mIsRunning) { + try { + val socket = serverSocket!!.accept() + // Only receive local connections. + if (socket.inetAddress.isLoopbackAddress) { + val parser = ProxyConnection(socket) + threadExecutor.execute(parser) + } else { + socket.close() + } + } catch (e: IOException) { + e.printStackTrace() + } + } + } catch (e: SocketException) { + Log.e(TAG, "Failed to start proxy server", e) + } catch (e1: IOException) { + Log.e(TAG, "Failed to start proxy server", e1) + } + mIsRunning = false + } + + @Synchronized + fun startServer() { + mIsRunning = true + start() + } + + @Synchronized + fun stopServer() { + mIsRunning = false + if (serverSocket != null) { + try { + serverSocket!!.close() + serverSocket = null + } catch (e: IOException) { + e.printStackTrace() + } + } + } + + val isBound: Boolean + get() = port != -1 + + companion object { + private const val CONNECT = "CONNECT" + private const val HTTP_OK = "HTTP/1.1 200 OK\n" + private const val TAG = "ProxyServer" + + // HTTP Headers + private const val HEADER_CONNECTION = "connection" + private const val HEADER_PROXY_CONNECTION = "proxy-connection" + } +} + +typealias ConnectAndCountTrafficInterface = ( + first: Socket, + second: Socket, + consumer: BiConsumer, + runnable: Runnable +) -> Unit \ No newline at end of file diff --git a/app/src/main/java/chilladvanced/SocketConnect.kt b/app/src/main/java/chilladvanced/SocketConnect.kt new file mode 100644 index 0000000..31500df --- /dev/null +++ b/app/src/main/java/chilladvanced/SocketConnect.kt @@ -0,0 +1,69 @@ +package chilladvanced + +import android.util.Log +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream +import java.net.Socket +import java.util.function.BiConsumer + +/** + * @hide + */ +class SocketConnect(from: Socket, to: Socket) : Thread() { + private val from: InputStream + private val to: OutputStream + private var trafficCounter: Long = 0 + + init { + this.from = from.getInputStream() + this.to = to.getOutputStream() + start() + } + + override fun run() { + val buffer = ByteArray(512) + try { + while (true) { + val r = from.read(buffer) + if (r < 0) { + break + } + Log.d("Socket", String(buffer, 0, r)) + trafficCounter += r.toLong() + to.write(buffer, 0, r) + } + from.close() + to.close() + } catch (io: IOException) { + } + } + + companion object { + @JvmStatic + fun connectAndCountTraffic( + first: Socket, + second: Socket, + consumer: BiConsumer, + runnable: Runnable + ) { + try { + val sc1 = SocketConnect(first, second) + val sc2 = SocketConnect(second, first) + while (sc1.isAlive || sc2.isAlive) { + try { + sleep(1000); + } catch (e: InterruptedException) { + } + consumer.accept(sc2.trafficCounter, sc1.trafficCounter) + sc2.trafficCounter = 0 + sc1.trafficCounter = 0 + } + runnable.run() + } catch (e: IOException) { + e.printStackTrace() + } + } + } +} + diff --git a/app/src/main/java/chilladvanced/SocketConnectHttpChecker.kt b/app/src/main/java/chilladvanced/SocketConnectHttpChecker.kt new file mode 100644 index 0000000..3668f55 --- /dev/null +++ b/app/src/main/java/chilladvanced/SocketConnectHttpChecker.kt @@ -0,0 +1,143 @@ +package chilladvanced + +import android.util.Log +import okhttp3.internal.wait +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream +import java.lang.Integer.min +import java.lang.StringBuilder +import java.net.Socket +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicLong +import java.util.function.BiConsumer + +/** + * @hide + */ +class SocketConnectHttpChecker(from: Socket, to: Socket, val addJson: Runnable) : Thread() { + private val from: InputStream + private val to: OutputStream + private val trafficCounter: AtomicLong = AtomicLong(0) + + init { + this.from = from.getInputStream() + this.to = to.getOutputStream() + start() + } + + override fun run() { + Thread.getDefaultUncaughtExceptionHandler() + val buffer = ByteArray(512) + try { + var scanner = HttpScanner() + while (true) { + val r = from.read( + buffer, 0, min( + buffer.size, + if (scanner.bodySize > 0) scanner.bodySize - scanner.bodyIndex else buffer.size + ) + ) + if (r < 0) { + break + } + trafficCounter.addAndGet(r.toLong()) + to.write(buffer, 0, r) + scanner.parse(buffer, 0, r) + if (scanner.bodyIndex == scanner.bodySize) { + scanner = HttpScanner() + } + } + from.close() + to.close() + } catch (io: IOException) { + } + } + + class HttpScanner() { + var method: String? = null + get() = field + var request: String? = null + get() = field + var bodySize = 0 + get() = field + var bodyIndex = -1 + get() = field + private val contentLengthKeyString = "Content-Length: ".toByteArray() + private var contentLengthKeyIndex = 0 + private val contentLengthValue = StringBuilder() + private var readContentLengthValue = false; + private var bodySeparatorIndex = 0 + private var bodySeparator = "\r\n\r\n".toByteArray() + + fun parse(bytes: ByteArray, offset: Int, size: Int) { + for (i in offset until offset + size) { + if (contentLengthKeyIndex >= 0 && + contentLengthKeyIndex < contentLengthKeyString.size - 1 + ) { + if (bytes[i] != contentLengthKeyString[contentLengthKeyIndex++]) { + contentLengthKeyIndex = 0 + } + } else if (contentLengthKeyIndex == contentLengthKeyString.size - 1) { + if (bytes[i] != contentLengthKeyString[contentLengthKeyIndex++]) { + contentLengthKeyIndex = 0 + } else { + readContentLengthValue = true; + } + } else if (readContentLengthValue && '0' <= bytes[i].toInt().toChar() + && bytes[i].toInt().toChar() <= '9' + ) { + contentLengthValue.append(bytes[i].toInt().toChar()) + } else if (readContentLengthValue) { + readContentLengthValue = false + bodySize = Integer.parseInt(contentLengthValue.toString()) + } + if (bodySeparatorIndex < bodySeparator.size - 1 && + bytes[i] != bodySeparator[bodySeparatorIndex++] + ) { + bodySeparatorIndex = 0 + } else if (bodySeparatorIndex == bodySeparator.size - 1) { + if (bytes[i] != bodySeparator[bodySeparatorIndex++]) { + bodySeparatorIndex = 0 + } else { + bodyIndex = 0; + } + } + if (bodyIndex >= 0 && bodyIndex < bodySize) { + bodyIndex++ + } + } + } + } + + companion object { + @JvmStatic + fun connectAndCountTraffic( + first: Socket, + second: Socket, + consumerTraffic: BiConsumer, + consumerTime: Runnable + ) { + try { + val jsons1 = AtomicInteger(0) + val jsons2 = AtomicInteger(0) + val sc1 = SocketConnectHttpChecker(first, second) + { if (jsons1.incrementAndGet() <= jsons2.get()) consumerTime.run() } + val sc2 = SocketConnectHttpChecker(second, first) + { if (jsons2.incrementAndGet() <= jsons1.get()) consumerTime.run() } + while (sc1.isAlive || sc2.isAlive) { + try { + sleep(1000); + } catch (e: InterruptedException) { + } + consumerTraffic.accept( + sc1.trafficCounter.getAndSet(0), + sc1.trafficCounter.getAndSet(0) + ) + } + } catch (e: IOException) { + e.printStackTrace() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chilladvanced/com/facebook/imagepipeline/request/ImageRequestWithStat.kt b/app/src/main/java/chilladvanced/com/facebook/imagepipeline/request/ImageRequestWithStat.kt new file mode 100644 index 0000000..9080064 --- /dev/null +++ b/app/src/main/java/chilladvanced/com/facebook/imagepipeline/request/ImageRequestWithStat.kt @@ -0,0 +1,13 @@ +package chilladvanced + +import com.facebook.imagepipeline.request.ImageRequest +import com.facebook.imagepipeline.request.ImageRequestBuilder + +class ImageRequestWithStat(build: ImageRequestBuilder) : ImageRequest(build) { + init { + // TODO + Logger.register(super.getSourceUri()?.host ?: "", NETWORK_METHODS.FRESCO) + + } + +} diff --git a/app/src/main/java/chilladvanced/com/google/android/exoplayer2/ExoPlayerWithStat.kt b/app/src/main/java/chilladvanced/com/google/android/exoplayer2/ExoPlayerWithStat.kt new file mode 100644 index 0000000..d8dab9e --- /dev/null +++ b/app/src/main/java/chilladvanced/com/google/android/exoplayer2/ExoPlayerWithStat.kt @@ -0,0 +1,19 @@ +package chilladvanced.com.google.android.exoplayer2 + +import chilladvanced.Logger +import chilladvanced.NETWORK_METHODS +import com.google.android.exoplayer2.ExoPlayer +import com.google.android.exoplayer2.MediaItem + +class ExoPlayerWithStat(val exoPlayer: ExoPlayer) : ExoPlayer by exoPlayer { + override fun prepare() { + exoPlayer.prepare() + val mediaSource: MediaItem? = exoPlayer.currentMediaItem + Logger.register( + mediaSource?.requestMetadata?.mediaUri?.host ?: " ", + NETWORK_METHODS.EXO_PLAYER + ) + } + + +} \ No newline at end of file diff --git a/app/src/main/java/chilladvanced/java/net/URLWithStat.kt b/app/src/main/java/chilladvanced/java/net/URLWithStat.kt new file mode 100644 index 0000000..9d5e230 --- /dev/null +++ b/app/src/main/java/chilladvanced/java/net/URLWithStat.kt @@ -0,0 +1,51 @@ +package chilladvanced.java.net + +import chilladvanced.Logger +import chilladvanced.NETWORK_METHODS +import java.io.IOException +import java.io.InputStream +import java.io.Serializable +import java.net.Proxy +import java.net.URI +import java.net.URISyntaxException +import java.net.URL +import java.net.URLConnection + +class URLWithStat(private val url: URL) : Serializable { + val port: Int = url.port + val defaultPort: Int = url.defaultPort + val protocol: String? = url.protocol + val host: String? = url.host + val file: String? = url.file + val ref: String? = url.ref + override fun equals(other: Any?): Boolean = url == other + override fun hashCode(): Int = url.hashCode() + fun sameFile(other: URL?): Boolean = url.sameFile(other) + override fun toString(): String = url.toString() + fun toExternalForm(): String? = url.toExternalForm() + + @Throws(URISyntaxException::class) + fun toURI(): URI? = url.toURI() + + //TODO + @Throws(IOException::class) + fun openConnection(): URLConnection { + Logger.register(url.host, NETWORK_METHODS.URL_CONNECTION) + return url.openConnection() + } + + @Throws(IOException::class) + fun openConnection(proxy: Proxy?): URLConnection { + Logger.register(url.host, NETWORK_METHODS.URL_CONNECTION) + return url.openConnection(proxy); + } + + @Throws(IOException::class) + fun openStream(): InputStream? = url.openStream() + + @get:Throws(IOException::class) + val content: Any? = url.content + + @Throws(IOException::class) + fun getContent(classes: Array?>?): Any? = url.getContent(classes) +} \ No newline at end of file diff --git a/app/src/main/java/ru/ok/android/itmohack2023/ExoPlayerActivity.kt b/app/src/main/java/ru/ok/android/itmohack2023/ExoPlayerActivity.kt index 9aba99b..0e9ff2d 100644 --- a/app/src/main/java/ru/ok/android/itmohack2023/ExoPlayerActivity.kt +++ b/app/src/main/java/ru/ok/android/itmohack2023/ExoPlayerActivity.kt @@ -2,6 +2,7 @@ package ru.ok.android.itmohack2023 import android.os.Bundle import androidx.appcompat.app.AppCompatActivity +import chilladvanced.com.google.android.exoplayer2.ExoPlayerWithStat import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.source.hls.HlsMediaSource @@ -21,7 +22,7 @@ class ExoPlayerActivity : AppCompatActivity() { } private fun preparePlayer() { - exoPlayer = ExoPlayer.Builder(this).build() + exoPlayer = ExoPlayerWithStat(ExoPlayer.Builder(this).build()) exoPlayer?.playWhenReady = true val playerView = findViewById(R.id.playerView) playerView.player = exoPlayer diff --git a/app/src/main/java/ru/ok/android/itmohack2023/FrescoActivity.kt b/app/src/main/java/ru/ok/android/itmohack2023/FrescoActivity.kt index 55e9d6e..c64432f 100644 --- a/app/src/main/java/ru/ok/android/itmohack2023/FrescoActivity.kt +++ b/app/src/main/java/ru/ok/android/itmohack2023/FrescoActivity.kt @@ -3,6 +3,7 @@ package ru.ok.android.itmohack2023 import android.net.Uri import android.os.Bundle import androidx.appcompat.app.AppCompatActivity +import chilladvanced.ImageRequestWithStat import com.facebook.drawee.view.SimpleDraweeView import com.facebook.imagepipeline.request.ImageRequestBuilder import com.google.android.material.button.MaterialButton @@ -30,11 +31,10 @@ class FrescoActivity : AppCompatActivity() { private fun getAnimal() { val animal = SAMPLE_URIS_PNG[position] - val request = ImageRequestBuilder + val request = ImageRequestWithStat( ImageRequestBuilder .newBuilderWithSource(Uri.parse(animal)) .disableDiskCache() - .disableMemoryCache() - .build() + .disableMemoryCache()) animalsView.setImageRequest(request) position = (0..5).random() } diff --git a/app/src/main/java/ru/ok/android/itmohack2023/JNIActivity.kt b/app/src/main/java/ru/ok/android/itmohack2023/JNIActivity.kt index 864295a..59b45b0 100644 --- a/app/src/main/java/ru/ok/android/itmohack2023/JNIActivity.kt +++ b/app/src/main/java/ru/ok/android/itmohack2023/JNIActivity.kt @@ -3,6 +3,7 @@ package ru.ok.android.itmohack2023 import android.os.Bundle import android.widget.TextView import androidx.appcompat.app.AppCompatActivity +import chilladvanced.NativeLibraryTracker import org.json.JSONArray import org.json.JSONObject @@ -12,7 +13,10 @@ class JNIActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_jniactivity) Threads.ioPool.execute { + val nativeTracker = NativeLibraryTracker(); + nativeTracker.start(this) var result = nativeFunction() ?: return@execute + nativeTracker.stop(this) result = result.dropWhile { it != '{' } val textJson = JSONObject(result) diff --git a/app/src/main/java/ru/ok/android/itmohack2023/MainActivity.kt b/app/src/main/java/ru/ok/android/itmohack2023/MainActivity.kt index 2a4528e..184f6b7 100644 --- a/app/src/main/java/ru/ok/android/itmohack2023/MainActivity.kt +++ b/app/src/main/java/ru/ok/android/itmohack2023/MainActivity.kt @@ -4,10 +4,17 @@ import android.content.Intent import android.os.Bundle import android.view.View import androidx.appcompat.app.AppCompatActivity +import chilladvanced.NetworkLogger class MainActivity : AppCompatActivity() { + + + + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + NetworkLogger.initializeAndRunLogging() + setContentView(R.layout.activity_main) findViewById(R.id.url_connection).setOnClickListener { startActivity(Intent(this, UrlConnectionActivity::class.java)) diff --git a/app/src/main/java/ru/ok/android/itmohack2023/OkHttpActivity.kt b/app/src/main/java/ru/ok/android/itmohack2023/OkHttpActivity.kt index cec982e..ce10d0e 100644 --- a/app/src/main/java/ru/ok/android/itmohack2023/OkHttpActivity.kt +++ b/app/src/main/java/ru/ok/android/itmohack2023/OkHttpActivity.kt @@ -5,6 +5,7 @@ import android.view.ViewGroup import android.widget.Space import android.widget.TextView import androidx.appcompat.app.AppCompatActivity +import chilladvanced.OkHttpClientWithStat import okhttp3.OkHttpClient import okhttp3.Request import org.json.JSONArray @@ -43,6 +44,6 @@ class OkHttpActivity : AppCompatActivity() { val request: Request = Request.Builder() .url(url) .build() - OkHttpClient().newCall(request).execute().use { response -> return response.body?.string() } + OkHttpClientWithStat(OkHttpClient()).newCall(request).execute().use { response -> return response.body?.string() } } } \ No newline at end of file diff --git a/app/src/main/java/ru/ok/android/itmohack2023/UrlConnectionActivity.kt b/app/src/main/java/ru/ok/android/itmohack2023/UrlConnectionActivity.kt index da727f8..96918eb 100644 --- a/app/src/main/java/ru/ok/android/itmohack2023/UrlConnectionActivity.kt +++ b/app/src/main/java/ru/ok/android/itmohack2023/UrlConnectionActivity.kt @@ -6,6 +6,7 @@ import android.widget.Space import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import org.json.JSONArray +import chilladvanced.java.net.URLWithStat import java.net.URL class UrlConnectionActivity : AppCompatActivity() { @@ -15,7 +16,7 @@ class UrlConnectionActivity : AppCompatActivity() { val list = findViewById(R.id.list) Threads.ioPool.execute { - val connection = URL("https://cat-fact.herokuapp.com/facts").openConnection() + val connection = URLWithStat(URL("https://cat-fact.herokuapp.com/facts")).openConnection() val text = connection.getInputStream().bufferedReader().readText() val textJson = JSONArray(text) for (i in 0 until textJson.length()) {