A brilliant demonstration of first-principles engineering that bypasses legacy protocol bottlenecks with low-level systems knowledge. It turns a common technical frustration into an elegant, high-performance solution.
Inmersión profunda
Prerrequisito
- No hay datos disponibles.
Próximos pasos
- No hay datos disponibles.
Inmersión profunda
Android MTP is Broken. So I Built My Own Protocol.Añadido:
Hi everyone, I'm Vishnu Srivastava, the creator of Socket Sweeper. So, So, yeah, let's uh look at what happened really and why I did all of this over a weekend. So, a few weeks ago, my dad hands me his Android phone and says, "Can you clean this up? I'm out of storage." And I said, "Sure." Uh plug it into my Mac, um fire up Open MTP, and try to browse his photos folder, and it just hangs for minutes. So, the MTP connection is like completely frozen on a normal folder with a few hundred photos. So, and that's when I realized that I got to do something about it, and I Now, I write C++, I write Rust, I've built TCP servers from scratch before, and I'm like sitting here watching a progress bar like at dial-up speed. And so, yeah. So, that's how I So, I spent the weekend building a replacement for it. Um it's called Socket Sweeper, and it's and and it completely bypasses Android's MTP protocol.
So, here's a USB cable.
Uh I'll show the demo in a while, but before that, um let's look at why MTP's broken, and also a little shout-out to like Screen Copy.
Uh if you've ever used Screen Copy, SCRCPY uh it's called, and it's a tool that lets you mirror your Android screen to your PC, and you might already know the trick. Uh Screen Copy works by pushing a small native uh binary uh onto your Android device via ADB, and then communicating with the with it over a local socket. So, that architecture is very genius.
Uh like huge shout out to Genymobile and the scrcpy project. And that's directly what inspired me to take the same approach, but for file system access instead of um screen mirror.
So, you push a daemon talk over a TCP tunnel uh bypass everything in between. So, let me show you how it works.
So, yeah, let's uh scroll to the architecture uh diagram in my readme.
And yeah, so So, I could context on why MTP is fundamentally broken. So, it's not just slow, the protocol itself is a problem.
So, so like MTP was built in the 2000s for MP3 players and it's a request-response thing. So, every file stat, every directory listing is a separate USB round trip. So, if you have 500 photos, that's like 500 individual requests one at a time. So, yeah, there's no batching and there's no pipelining. So, it's quite slow. And on Android, MTP doesn't even talk directly to the file system. So, it goes through the media store uh a SQLite database, which can be stale, re-indexing, or just hanging. So, I've got a couple pictures to show.
So, that's how it is and yeah, so like it's So, I guess we basically skips all of that and it pushes a C++ binary that calls opendir and readdir, which are basically open directory and read directory like raw kernel system calls, and streams the entire tree back as one JSON blob over TCP. So, like let me just demo and >> [snorts] >> yeah, like then I'll walk through every line of code. Uh so, let's open the apps. Uh it's right over here.
And here's my phone. I connected to my phone.
Okay, it's connected and I have ADB debugging on in developer options. And I hit the connect.
So, there's a little terminal thing over here which shows everything. So, that took like 6.9 seconds. Uh that's quite fast. And so, we got 39.3 GB total size, 50,000 files, 1657 directories.
And here's the tree map for like all the files in here. Um but like every block is proportional to the storage uh it's using. So, I can click on it and like drill down like what I can like delete. So, let's open screen recordings. I have a couple of big screen recordings here. Let's try to nuke this.
Uh I can just delete it. And yep, and it's basically gone from my phone. So, it's it took a couple seconds, but yeah, it re-scanned everything uh because it has to do it to get the new JSON again. So, yeah. So, that's uh kernel-level remove all happening on the phone. So, there's no MTP involved. And I now let me just show you how this actually works. Let's So, I can just disconnect.
And let's go back to the code.
So yeah, let's look at the code.
Um so this is the entire engine uh daemon.cpp.
It's one file, 363 lines in total, and let's go from top to bottom.
Uh up top we have the includes uh the dirent.h is for directory traversal.
And the sys/socket.h is And this is also the arpa/inet.h.
Both are for TCP networking. And sys/stat.h is for file metadata. And like all our POSIX headers, this is raw Linux C, so no frameworks involved.
And let's go to the configuration section, a namespace here.
So here's the configuration namespace.
Port 5050 binds to 127.0.0.1.
That's localhost only, so the daemon is never exposed to the phone's Wi-Fi network. And the default scan root is /sdcard. So listen backlog of four, max recursion depth of 64, and a 30 seconds uh recursive I mean, receive timeout.
So let's look at the global section, and this volatiles sig_atomic_t g_running is uh set to one. This is the shutdown flag, so when we get as sigint or sigterm, uh this flips to zero, and the main accept loop exits cleanly.
And we have uh networking and streaming helpers. So so now this is interesting.
The JSON stream class, instead of calling send for every tiny JSON token, this buffer writes into a 64 kilobyte buffer. So, you can see on line 44 here um buffer reserves 64 into 1024.
So, when the buffer fills up, it flushes by calling send all.
And which is uh which is a a tight loop um which I wrote [snorts] somewhere here, right over here.
And yeah, so it's a tight loop.
You can see that on line 71 that keeps calling send with message no signal, which is right over here. And until every byte is out. So, it handles um I'm sorry.
So, it handles error number E I N T R.
Uh that's when a signal like E interrupt uh when a signal interrupts the system call and the destructor on line 40 somewhere which we saw just now. And so, this is the destructor um calls flush uh automatically. So, even if you forget um the R A I I like handles everything.
Um this is dramatically like this dramatically reduces its kernel context switches.
And so, let's get to This is all uh how the data model works.
And let's look at There's not much to look at here, but there is a lot more interesting over here. So, let's look at the scan function.
So, this is the heart of the whole project, the scan function.
Uh this Let's look at this right here. So, this is subtle, but very important. Uh at zero depth we call stat which follow a sim links. So, that's because it /sdcard on Android is actually a sim link to storage /emulated/0, but at every other depth we call lstat which does not follow sim links. Uh that prevents like infinite loops from sim link cycles.
And let's look at line 178.
Here, if it's a sim link at depth greater than zero, uh we skip it entirely. And in line 180, we have is if it's a regular file, um like if it's a regular file, grab the size from st_size and increment the file counter and add to total size and we're done with it.
And scroll down a little bit. Here, we have open dir. This is a POSIX call that opens a directory for reading. And then in line 198, we have readdir which is um in a while loop. So, which gives us each entry one by one. So, yeah. So, in what line 199, we have uh yeah, we skip zero like I'm sorry, we skip the dot and the double dot entries by uh checking uh the raw D name characters.
And we go to 204, line 204 to 206.
So, uh, we go to line 204 and 206. In between, we have if the kernel gives us DT link uh, in the dirent struct, we skip it directly, like immediately. And that's a fast path. We don't even need to lstat it uh, at all. And at 210, we have a file node child. And it's a recursive call. Uh, we scan each child and accumulate its size into the parent node on the s- 211 line. And then lines 216 to 219, we have like after scanning everything, we sort children by size. Uh, this in the descending order. So, >> [snorts] >> that's why the biggest space hogs always appear first in the tree map.
And coming to 224, uh, to the end of the code, I think we can look at all of this at once.
Uh, the handle client function um, in line 225, reads exactly one command per connection. So, and in line 229, um, what do we what do we have? Uh, receive line. So, it reads byte by byte until it hits a new line. And then it dispatches ping and returns a pong.
Uh, scan triggers um, the full file system traversal. And the delete uh, we have here uses standard file system remove all for recursive deletion and shut down flips uh, G running to zero.
And let's scroll a little bit and we find, yeah, we look at the main now.
Uh, so here's main, um, lines 311 and two 313 are important. We register signal handlers. Uh, so sig pipe is set to sig ITN. So that's critical. Uh, it prevents the daemon from crashing if the PC like disconnects mid transfer like whatever we're doing. And in lines 315 to I think 335, yes, the whole thing. What we're basically doing is a standard TCP server setup.
Uh, it creates a socket and set It creates a socket and we set, uh, so re, uh, reuse adder. Um, which is right over here.
And what we do is so we we can like rebind immediately after a crash.
Uh, like bind and listen.
Um, then what do we have here?
Let's go to 340 and till 357.
Um, we have there's the accept loop. Uh, notice the line 345.
Here we use a select, uh, with a 1-second timeout instead of blocking on accept directly. So that way the G running flag gets checked every second. Uh, so sig term leads to a clean shutdown, uh, instead of a hung process.
Uh so, that's about it for uh the daemon.cpp.
Let's look at the other two files.
lib.rs, um and main.rs. Let's start with the main.
It's simple, very simple. Let me uh This is the Tauri entry point. Like, it's just seven lines. All it does is um suppress the window console window, well, like the Windows console window in release mode, on line two, which you can see right over here. And then it calls the Tauri lib app lib run. And like, that's about it. The everything else interesting is in lib.rs. So, let's go to here.
Uh here's uh the Rust bridge. Uh top of the file, the wire protocol is documented right here in the comments.
Uh ping, scan, and shutdown uh text commands are newline terminated.
And let's go to the lines 21 to 25.
These are constants.
Uh port 5050, same as the daemon.
Uh the daemon gets pushed to uh data local temp sock sweep daemon. That's the only path on Android where the ADB shell user can write and execute binaries.
Um connect timeout is 5 seconds, and read timeout is 120 seconds, because scans on phones with 100 thousands of files can take a while.
And let's go to line 31 to 51. So, this entire thing is get bundled binary. So, this resolves bundled binaries from Tauri's uh resource directory. Uh on Windows, it tries the.exe extension first. Um And so, it finds adb.exe and then falls back to the extensionless name for Linux arm daemon binary. Uh one function handles both the platforms. Uh let's look at line 57.
And this is the ADB helper. So, uh it runs any ADB command using uh the standard process command.
Captures uh standard out and standard error uh and then does smart error detection. And let's go to line 67.
And till 73. So, this uh this is the interesting part um because if the output contains no devices or device not found, instead of dumping raw ADB errors at the user, it returns a clean connect your phone via USB message. So, same for unauthorized, it tells you to check the confirmation dialog. And this is why the UI can show helpful error messages instead of like cryptic ADB output, which people wouldn't understand.
And let's look at the TCP helper.
Uh so, this is the daemon command.
This is the entire TCP networking layer.
And in line 88, we have TCP stream connect timeout. So, uh to 127.0.0.1:5050 with uh 5-second timeout. And in lines 94 to 96, we set a 120-second read timeout. And lines 98 to 105.
Uh ensure the command ends with a new line, uh then write all to send it.
And we have this 107 to 110.
So, here's the read strategy. Uh we pre-allocate a 1 MB VEC and then call read to end. So, this blocks until the daemon closes the connection. And which like it does after sending the response and gives us the entire JSON payload in one shot. So, yeah, that's a what we aim for. And in line 112, we convert it to uh UTF-8 string.
Um like trim white spaces and return.
That's it. Uh 28 lines for the entire networking layer.
And then we move to this uh the init daemon orchestration. Uh so, this is the big one. Uh this is the function that fires when you click connect. Uh in 128 and 129, we have resolve both uh bundled binaries ADB and daemon.
And in line 131 to 142, uh we verify ADB works, then run ADB devices and parse the output to count the connected devices. If zero, it returns an error.
And we go to 145, uh the pkill uh any zombie daemon from uh a previous session. Um This the let_equals uh means we ignore errors. It's It's fine if there's nothing to kill.
Um And then we go to 148.
Um we're for This is the Android 11 scoped storage bypass, uh like appops set com.android.shell manage_external_storage allow. So, without this the daemon can't see the full file system.
And we go to 152 154.
Here we have the ADB push. The the daemon binary then we do the CHMOD + X thing.
And we go to 156 to 158.
We kill again then sleep 300 seconds bell 10 suspenders make sure the old process is fully dead before we start a new one. And we go to 160 to 163.
This is the launch we launch the daemon with and wait up redirect output to dev null and backgrounded and echo dollar exclamation mark to ask I like to capture the process ID. And we go to line 166.
This is a critical line ADB forward TCP 5050 TCP 5050. This creates the USB tunnel any TCP connection to local host 5050 on the PC gets routed through the USB cable to local host 5050 on the phone. So, let's look at lines 168 to 181.
So, this is the ping retry loop.
It's 15 attempts and 150 milliseconds apart. It tries to send ping over the tunnel and breaks on the first successful pong.
So, this handles the race condition where the daemon hasn't finished calling bind and listen yet. So, maximum rate is about 20 2.
25 seconds. Uh 2.25 seconds. And let's go to line 183 to 185. This is if all 15 attempts fail, we return an error. And otherwise, we return the daemon process ID and ping response as JSON.
So, let's go to 192.
And it goes up to 237. Yeah, this is till the end of the file.
So, run scan on line 183, uh just sends a scan/path to uh the daemon. And ping daemon sends ping. And delete item um basically sends delete a delete/path.
Um so, that's about it. And also, on line 207, the stop daemon uh sends shutdown to the daemon. Uh removes the ADB port forward and deletes the binary from the device. So, uh unwrap or let else on the shutdown um call means it won't crash if the daemon is already dead. Uh so, yeah, that's uh it. And we come to the Tauri entry point. So, this uh Altix command registered in the invoke handler, which we have right here.
Uh so, that's about everything. And yeah, that's about everything. And yeah, let's go back to the GitHub page.
Um So, yeah. So, like I pushed this to GitHub about 2 weeks ago, I think. And it's right now at 34 stars.
Um I have this multiple forks. Uh senior engineer just leaving comments on the architecture.
And let's go to releases.
Uh And so, yeah, it ships with a full CI pipeline, GitHub Actions, cross-compiles the C++ daemon for arm64 and with the Android NDK, and builds the Rust bridge, bundles the React front end, and produces installers for macOS, Windows, and Linux fully automated on every push.
And so, yeah, so that's Socket Sweep um C++ daemon doing raw POSIX IO on Android talking over TCP through USB uh orchestrated by Rust and rendered by React. Heavily inspired by scrcpy's architecture, so same idea of pushing a native binary and communicating over local socket, but applied to file system access instead of screen mirroring.
Um So, yeah.
Uh built in a weekend because my dad's phone was slow.
Um if you're a recruiter or engineering manager, uh this is the kind of systems work I love, uh low-level, cross-platform, and performance-critical.
I'm 22, actively looking for back-end and system engineering system engineering roles. And uh LinkedIn and email will be in the description. If you're a developer, repo link is below.
Star it, fork it, or just try it on your own. Um thanks for watching.
Videos Relacionados
Agentforce NOW AMA: Build with React and Salesforce Multi-Framework
SalesforceDevs
490 views•2026-05-28
How agent o11y differs from traditional o11y — Phil Hetzel, Braintrust
aiDotEngineer
450 views•2026-05-28
Re: 🗣️📍theprophedu📍2026 GST 103 CLASS (E-EXAM REVISION)
theprophedu
636 views•2026-06-04
WEB TECHNOLOGIES UNIT-2 | Degree 4th sem BCOM Computers web technologies unit-2 full explanation💯✅
LearnwithSahera
1K views•2026-05-29
More tests are always better? How to use AI to identify tests that bring little value
Alliance4Qualification
335 views•2026-05-29
Search Algorithms Explained in 60 Seconds! 🤖💨
samarthtuliofficial
218 views•2026-06-01
People of Game of Thrones using JavaScript DOM
AltCampus
296 views•2026-05-30
Instagram accounts got PWNed
EricParker
13K views•2026-06-03











