Taking a deep dive into MTP: Part 2
Last time I started looking at how to implement a subset of the MTP specification, in order to read files from Android devices. Let's start by writing some code to talk to the device over WebUSB:
const device = await usb.requestDevice({
filters: [
{
vendorId: 3725,
productId: 8221,
}
]
});
await device.open();
if (device.configuration === null) {
console.log('selectConfiguration');
await device.selectConfiguration(1);
}
await device.claimInterface(0);
MTP uses containers to structure what the various commands and responses look like. Here is how to build a container packet (and note that struct.pack()
is part of a convenience library):
const buildContainerPacket = (container, payloadLength) => {
const packetLength = 12 + payloadLength;
const buf = new ArrayBuffer(packetLength);
const bytes = new Uint8Array(buf);
let ctr = struct.pack(bytes, 0, 'issi', packetLength, container.type, container.code, container.transactionID);
if (payloadLength > 0) {
ctr += struct.copyBytes(bytes, ctr, container.payload, payloadLength);
}
return buf;
};
To open a new session, we fill the container
object with the right info and send it off via the bulk pipe:
const openSession = {
type: 1, // command block
code: 0x1002, // open session
transactionID: 0,
payload: 1, // session ID
};
let data = buildContainerPacket(openSession, 4);
let result = await device.transferOut(0x01, data);
To read the response, I'm creating a receiveData()
function:
const receiveData = async () => {
const timeoutID = setTimeout(async() => {
console.warn('Device not connected');
}, 5000);
console.log('Receiving...');
let incoming = await device.transferIn(0x01, 1024);
if (incoming.data.byteLength > 0) {
clearTimeout(timeoutID);
const [length] = new Uint32Array(incoming.data.buffer, 0, 1);
console.log('Length:', length);
// TODO: if length !== incoming.data.byteLength, read more data
}
};
To actually receive some data, we call
await receiveData();
and then parse the response (0c 00 00 00 03 00 01 20 01 00 00 00
), which is also in the same container format:
- Length:
0x0C000000
– 12 (in little-endian format) - Type:
0x0300
– Response block 0x0120
, or0x2001
in big-endian – response codeOK
0x0000000000
– transaction ID
So, we're able to successfully open a session!
PS: Hey, I'm halfway through #100DaysToOffload already! 🎉️
I’m publishing this as part of 100 Days To Offload. You can join in yourself by visiting https://100daystooffload.com.