<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>mtp &amp;mdash; Gerrit Niezen</title>
    <link>https://gerritniezen.com/tag:mtp</link>
    <description>Maker of open-source software and hardware.</description>
    <pubDate>Thu, 30 Apr 2026 04:46:57 +0000</pubDate>
    <image>
      <url>https://i.snap.as/aMPXpIot.png</url>
      <title>mtp &amp;mdash; Gerrit Niezen</title>
      <link>https://gerritniezen.com/tag:mtp</link>
    </image>
    <item>
      <title>Taking a deep dive into MTP: Part 5</title>
      <link>https://gerritniezen.com/taking-a-deep-dive-into-mtp-part-5?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[This is the final instalment of my second series of blog posts on MTP. I now have my code working in both the browser and Node.js, where it connects to an Android device, reads the latest file and saves it locally.&#xA;&#xA;First off, I had to write some code to figure out whether we&#39;re in the browser, in Node.js or in Electron:&#xA;&#xA;let isBrowser = null;&#xA;&#xA;if (typeof navigator !== &#39;undefined&#39;) {&#xA;  const userAgent = navigator.userAgent.toLowerCase();&#xA;  isBrowser = userAgent.indexOf(&#39; electron/&#39;) === -1 &amp;&amp; typeof window !== &#39;undefined&#39;;&#xA;} else {&#xA;  // Node.js process&#xA;  isBrowser = false;&#xA;}&#xA;&#xA;I&#39;m sure there are modules out there that do this better, but it works for now. Now we have to load some modules for Node.js when we&#39;re not in the browser:&#xA;&#xA;let usb, fs = null;&#xA;&#xA;if (!isBrowser) {&#xA;  // For Node.js and Electron&#xA;  usb = require(&#39;webusb&#39;).usb;&#xA;  EventTarget = require(&#39;events&#39;);&#xA;  fs = require(&#39;fs&#39;);&#xA;} else {&#xA;  usb = navigator.usb; // Yay, we&#39;re using WebUSB!&#xA;}&#xA;&#xA;We also need to be able to save the file to disk in both the browser and Node.js:&#xA;&#xA;const array = new Uint8Array(data.payload);&#xA;&#xA;if (isBrowser) {&#xA;  const file = new Blob([array]);&#xA;  const a = document.createElement(&#39;a&#39;),&#xA;  url = URL.createObjectURL(file);&#xA;  a.href = url;&#xA;  a.download = this.filename;&#xA;  document.body.appendChild(a);&#xA;  a.click();&#xA;  setTimeout(function() {&#xA;    document.body.removeChild(a);&#xA;    window.URL.revokeObjectURL(url);&#xA;  }, 0);&#xA;} else {&#xA;  fs.writeFileSync(this.filename, array);&#xA;}&#xA;&#xA;We also need to deal with event listeners differently in the browser and Node.js:&#xA;&#xA;const initMTP = () =  {&#xA;  const mtp = new Mtp(0x0e8d, 0x201d);&#xA;&#xA;  if (isBrowser) {&#xA;    mtp.addEventListener(&#39;error&#39;, err =  console.log(&#39;Error&#39;, err));&#xA;&#xA;    mtp.addEventListener(&#39;ready&#39;, async () =  {&#xA;      mtp.addEventListener(&#39;data&#39;, (e) =  mtp.dataHandler(e.detail));&#xA;      await mtp.openSession();&#xA;    } );&#xA;  } else {&#xA;    mtp.on(&#39;error&#39;, err =  console.log(&#39;Error&#39;, err));&#xA;    mtp.on(&#39;ready&#39;, async () =  {&#xA;      mtp.on(&#39;data&#39;, (data) =  mtp.dataHandler(data));&#xA;      await mtp.openSession();&#xA;    });&#xA;  }&#xA;};&#xA;&#xA;And finally, WebUSB requires a user interaction before it will show the permission prompt, so we need:&#xA;&#xA;if (isBrowser) {&#xA;  document.addEventListener(&#39;DOMContentLoaded&#39;, event =  {&#xA;    let button = document.getElementById(&#39;connect&#39;);&#xA;&#xA;    button.addEventListener(&#39;click&#39;, async() =  {&#xA;      initMTP();&#xA;    });&#xA; });&#xA;} else {&#xA;  initMTP();&#xA;}&#xA;&#xA;So, that&#39;s it! Over on GitHub I have the full implementation. If you run it in Node.js, it will save the newest file on your Android device to disk. If you run it in the browser, it will do the same after you click the Connect button:&#xA;&#xA;html&#xA;  head&#xA;    titleMTP over WebUSB/title&#xA;    script src=&#34;mtp.js&#34;/script&#xA;  /head&#xA;  body&#xA;    button id=&#34;connect&#34;Connect/button&#xA;  /body&#xA;/html&#xA;&#xA;---&#xA;I’m publishing this as part of 100 Days To Offload. You can join in yourself by visiting https://100daystooffload.com.&#xA;&#xA;#100DaysToOffload #day54 #mtp&#xA;&#xA;iComment on this post/i&#xD;&#xA;div id=&#34;cusdis_thread&#34;/div]]&gt;</description>
      <content:encoded><![CDATA[<p>This is the final instalment of my <a href="https://gerritniezen.com/tag:mtp">second series of blog posts on MTP</a>. I now have my code working in both the browser and Node.js, where it connects to an Android device, reads the latest file and saves it locally.</p>

<p>First off, I had to write some code to figure out whether we&#39;re in the browser, in Node.js or in Electron:</p>

<pre><code class="language-js">let isBrowser = null;

if (typeof navigator !== &#39;undefined&#39;) {
  const userAgent = navigator.userAgent.toLowerCase();
  isBrowser = userAgent.indexOf(&#39; electron/&#39;) === -1 &amp;&amp; typeof window !== &#39;undefined&#39;;
} else {
  // Node.js process
  isBrowser = false;
}
</code></pre>

<p>I&#39;m sure there are modules out there that do this better, but it works for now. Now we have to load some modules for Node.js when we&#39;re not in the browser:</p>

<pre><code class="language-js">let usb, fs = null;

if (!isBrowser) {
  // For Node.js and Electron
  usb = require(&#39;webusb&#39;).usb;
  EventTarget = require(&#39;events&#39;);
  fs = require(&#39;fs&#39;);
} else {
  usb = navigator.usb; // Yay, we&#39;re using WebUSB!
}
</code></pre>

<p>We also need to be able to save the file to disk in both the browser and Node.js:</p>

<pre><code class="language-js">const array = new Uint8Array(data.payload);

if (isBrowser) {
  const file = new Blob([array]);
  const a = document.createElement(&#39;a&#39;),
  url = URL.createObjectURL(file);
  a.href = url;
  a.download = this.filename;
  document.body.appendChild(a);
  a.click();
  setTimeout(function() {
    document.body.removeChild(a);
    window.URL.revokeObjectURL(url);
  }, 0);
} else {
  fs.writeFileSync(this.filename, array);
}
</code></pre>

<p>We also need to deal with event listeners differently in the browser and Node.js:</p>

<pre><code class="language-js">const initMTP = () =&gt; {
  const mtp = new Mtp(0x0e8d, 0x201d);

  if (isBrowser) {
    mtp.addEventListener(&#39;error&#39;, err =&gt; console.log(&#39;Error&#39;, err));

    mtp.addEventListener(&#39;ready&#39;, async () =&gt; {
      mtp.addEventListener(&#39;data&#39;, (e) =&gt; mtp.dataHandler(e.detail));
      await mtp.openSession();
    } );
  } else {
    mtp.on(&#39;error&#39;, err =&gt; console.log(&#39;Error&#39;, err));
    mtp.on(&#39;ready&#39;, async () =&gt; {
      mtp.on(&#39;data&#39;, (data) =&gt; mtp.dataHandler(data));
      await mtp.openSession();
    });
  }
};
</code></pre>

<p>And finally, WebUSB requires a user interaction before it will show the permission prompt, so we need:</p>

<pre><code class="language-js">if (isBrowser) {
  document.addEventListener(&#39;DOMContentLoaded&#39;, event =&gt; {
    let button = document.getElementById(&#39;connect&#39;);

    button.addEventListener(&#39;click&#39;, async() =&gt; {
      initMTP();
    });
 });
} else {
  initMTP();
}
</code></pre>

<p>So, that&#39;s it! Over on <a href="https://github.com/tidepool-org/node-mtp/blob/pure-js/mtp.js">GitHub</a> I have the full implementation. If you run it in Node.js, it will save the newest file on your Android device to disk. If you run it in the browser, it will do the same after you click the Connect button:</p>

<pre><code class="language-html">&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;MTP over WebUSB&lt;/title&gt;
    &lt;script src=&#34;mtp.js&#34;&gt;&lt;/script&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;button id=&#34;connect&#34;&gt;Connect&lt;/button&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>

<hr/>

<p>I’m publishing this as part of 100 Days To Offload. You can join in yourself by visiting <a href="https://100daystooffload.com">https://100daystooffload.com</a>.</p>

<p><a href="https://gerritniezen.com/tag:100DaysToOffload" class="hashtag"><span>#</span><span class="p-category">100DaysToOffload</span></a> <a href="https://gerritniezen.com/tag:day54" class="hashtag"><span>#</span><span class="p-category">day54</span></a> <a href="https://gerritniezen.com/tag:mtp" class="hashtag"><span>#</span><span class="p-category">mtp</span></a></p>

<p><i>Comment on this post</i>
<div id="cusdis_thread" id="cusdis_thread"></div></p>
]]></content:encoded>
      <guid>https://gerritniezen.com/taking-a-deep-dive-into-mtp-part-5</guid>
      <pubDate>Mon, 06 Jul 2020 15:12:58 +0000</pubDate>
    </item>
    <item>
      <title>Taking a deep dive into MTP: Part 4</title>
      <link>https://gerritniezen.com/taking-a-deep-dive-into-mtp-part-4?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[In the previous post on MTP we looked at how to retrieve the name of a file on an Android device using MTP. Let&#39;s see how to decode that filename:&#xA;&#xA;const array = new Uint8Array(data.payload);&#xA;const decoder = new TextDecoder(&#39;utf-16le&#39;);&#xA;filename = decoder.decode(array.subarray(1, array.byteLength - 2));&#xA;console.log(&#39;Filename:&#39;, filename);&#xA;&#xA;In short, we need to remove the length byte at the beginning and the zero terminator bytes at the end, and then decode it as UTF-16LE. To retrieve the file itself, we use the following (where CODE.GETOBJECT.value is 0x1009):&#xA;&#xA;const getFile = {&#xA;  type: 1,&#xA;  code: CODE.GETOBJECT.value,&#xA;  payload: [objectHandle],&#xA;};&#xA;await device.transferOut(0x01, buildContainerPacket(getFile));&#xA;&#xA;All that&#39;s left to do is to save the data that is returned to a file! (OK, I also need to write some code that handle files that are larger than the buffer we use for transferIn.)&#xA;&#xA;All the code is currently on a branch on GitHub:&#xA;https://github.com/tidepool-org/node-mtp/blob/pure-js/mtp.js&#xA;&#xA;Once I have it all working, I hope to replace the Node.js-specific bits so that it can run in the browser using WebUSB, and ideally have a proof-of-concept available on the web.&#xA;&#xA;---&#xA;I’m publishing this as part of 100 Days To Offload. You can join in yourself by visiting https://100daystooffload.com.&#xA;&#xA;#100DaysToOffload #day53 #mtp&#xA;&#xA;iComment on this post/i&#xD;&#xA;div id=&#34;cusdis_thread&#34;/div]]&gt;</description>
      <content:encoded><![CDATA[<p>In the <a href="https://gerritniezen.com/taking-a-deep-dive-into-mtp-part-3">previous post on MTP</a> we looked at how to retrieve the name of a file on an Android device using MTP. Let&#39;s see how to decode that filename:</p>

<pre><code class="language-js">const array = new Uint8Array(data.payload);
const decoder = new TextDecoder(&#39;utf-16le&#39;);
filename = decoder.decode(array.subarray(1, array.byteLength - 2));
console.log(&#39;Filename:&#39;, filename);
</code></pre>

<p>In short, we need to remove the length byte at the beginning and the zero terminator bytes at the end, and then decode it as UTF-16LE. To retrieve the file itself, we use the following (where <code>CODE.GET_OBJECT.value</code> is <code>0x1009</code>):</p>

<pre><code class="language-js">const getFile = {
  type: 1,
  code: CODE.GET_OBJECT.value,
  payload: [objectHandle],
};
await device.transferOut(0x01, buildContainerPacket(getFile));
</code></pre>

<p>All that&#39;s left to do is to save the data that is returned to a file! (OK, I also need to write some code that handle files that are larger than the buffer we use for <code>transferIn</code>.)</p>

<p>All the code is currently on a branch on GitHub:
<a href="https://github.com/tidepool-org/node-mtp/blob/pure-js/mtp.js">https://github.com/tidepool-org/node-mtp/blob/pure-js/mtp.js</a></p>

<p>Once I have it all working, I hope to replace the Node.js-specific bits so that it can run in the browser using WebUSB, and ideally have a proof-of-concept available on the web.</p>

<hr/>

<p>I’m publishing this as part of 100 Days To Offload. You can join in yourself by visiting <a href="https://100daystooffload.com">https://100daystooffload.com</a>.</p>

<p><a href="https://gerritniezen.com/tag:100DaysToOffload" class="hashtag"><span>#</span><span class="p-category">100DaysToOffload</span></a> <a href="https://gerritniezen.com/tag:day53" class="hashtag"><span>#</span><span class="p-category">day53</span></a> <a href="https://gerritniezen.com/tag:mtp" class="hashtag"><span>#</span><span class="p-category">mtp</span></a></p>

<p><i>Comment on this post</i>
<div id="cusdis_thread" id="cusdis_thread"></div></p>
]]></content:encoded>
      <guid>https://gerritniezen.com/taking-a-deep-dive-into-mtp-part-4</guid>
      <pubDate>Fri, 03 Jul 2020 15:46:10 +0000</pubDate>
    </item>
    <item>
      <title>Taking a deep dive into MTP: Part 3</title>
      <link>https://gerritniezen.com/taking-a-deep-dive-into-mtp-part-3?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[In the previous post I looked at connecting to an Android device over WebUSB and opening an MTP session.&#xA;&#xA;Let&#39;s have a look at how to see what objects (directories, files etc.) are available on the device. Before we get started, let&#39;s define some of the operation and response codes that we&#39;ll need, as well as some possible container types:&#xA;&#xA;const CODE = {&#xA;  OPENSESSION: { value: 0x1002, name: &#39;OpenSession&#39; },&#xA;  CLOSESESSION: { value: 0x1003, name: &#39;CloseSession&#39; },&#xA;  GETOBJECTHANDLES: { value: 0x1007, name: &#39;GetObjectHandles&#39;},&#xA;  OK: { value: 0x2001, name: &#39;OK&#39;},&#xA;  INVALIDPARAMETER: { value: 0x201D, name: &#39;Invalid parameter&#39;},&#xA;  INVALIDOBJECTPROPFORMAT: { value: 0xA802, name: &#39;InvalidObjectPropFormat&#39;},&#xA;  GETOBJECTPROPVALUE: { value: 0x9803, name: &#39;GetObjectPropValue&#39; },&#xA;};&#xA;&#xA;onst TYPE = [&#xA;  &#39;undefined&#39;,&#xA;  &#39;Command Block&#39;,&#xA;  &#39;Data Block&#39;,&#xA;  &#39;Response Block&#39;,&#xA;  &#39;Event Block&#39;&#xA;];&#xA;&#xA;We also need to be able to parse container packets:&#xA;&#xA;const parseContainerPacket = (bytes, length) =  {&#xA;  const fields = {&#xA;    type : TYPE[bytes.getUint16(4, true)],&#xA;    code : getName(CODE, bytes.getUint16(6, true)),&#xA;    transactionID : bytes.getUint32(8, true),&#xA;    payload : [],&#xA;  };&#xA;&#xA;  for (let i = 12; i &lt; length; i += 4) {&#xA;    fields.payload.push(bytes.getUint32(i, true));&#xA;  }&#xA;&#xA;  return fields;&#xA;};&#xA;&#xA;The getName() helper function looks like this:&#xA;&#xA;const getName = (list, idx) =  {&#xA;  for (let i in list) {&#xA;    if (list[i].value === idx) {&#xA;      return list[i].name;&#xA;    }&#xA;  }&#xA;  return &#39;unknown&#39;;&#xA;};&#xA;&#xA;To get the object handles, we do the following:&#xA;&#xA;  const getObjectHandles = {&#xA;    type: 1, // command block&#xA;    code: CODE.GETOBJECTHANDLES.value,&#xA;    payload: [0xFFFFFFFF, 0, 0xFFFFFFFF], // get all&#xA;  };&#xA;  data = buildContainerPacket(getObjectHandles, 4);&#xA;  result = await device.transferOut(0x01, data);&#xA;  console.log(&#39;result:&#39;, result);&#xA;  let { payload } = await receiveData();&#xA;&#xA;Now we have an array of object handles. We can use the object handle to retrieve the filename, using the &#34;Object File Name&#34; object property code 0xDC07:&#xA;&#xA;const getFilename = {&#xA;    type: 1,&#xA;    code: CODE.GETOBJECTPROPVALUE.value,&#xA;    payload: [0x2B, 0xDC07], // object handle and object property code&#xA;  };&#xA;  result = await device.transferOut(0x01, buildContainerPacket(getFilename));&#xA;  console.log(&#39;result:&#39;, result);&#xA;  let { payload } = await receiveData();&#xA;&#xA;The payload is a 16-bit unicode string containing the file name. Nice!&#xA;&#xA;---&#xA;I’m publishing this as part of 100 Days To Offload. You can join in yourself by visiting https://100daystooffload.com.&#xA;&#xA;#100DaysToOffload #day51 #mtp&#xA;&#xA;iComment on this post/i&#xD;&#xA;div id=&#34;cusdisthread&#34;/div]]&gt;</description>
      <content:encoded><![CDATA[<p>In the <a href="https://gerritniezen.com/taking-a-deep-dive-into-mtp-part-2">previous post</a> I looked at connecting to an Android device over WebUSB and opening an MTP session.</p>

<p>Let&#39;s have a look at how to see what objects (directories, files etc.) are available on the device. Before we get started, let&#39;s define some of the operation and response codes that we&#39;ll need, as well as some possible container types:</p>

<pre><code class="language-js">const CODE = {
  OPEN_SESSION: { value: 0x1002, name: &#39;OpenSession&#39; },
  CLOSE_SESSION: { value: 0x1003, name: &#39;CloseSession&#39; },
  GET_OBJECT_HANDLES: { value: 0x1007, name: &#39;GetObjectHandles&#39;},
  OK: { value: 0x2001, name: &#39;OK&#39;},
  INVALID_PARAMETER: { value: 0x201D, name: &#39;Invalid parameter&#39;},
  INVALID_OBJECTPROP_FORMAT: { value: 0xA802, name: &#39;Invalid_ObjectProp_Format&#39;},
  GET_OBJECT_PROP_VALUE: { value: 0x9803, name: &#39;GetObjectPropValue&#39; },
};

onst TYPE = [
  &#39;undefined&#39;,
  &#39;Command Block&#39;,
  &#39;Data Block&#39;,
  &#39;Response Block&#39;,
  &#39;Event Block&#39;
];
</code></pre>

<p>We also need to be able to parse container packets:</p>

<pre><code class="language-js">const parseContainerPacket = (bytes, length) =&gt; {
  const fields = {
    type : TYPE[bytes.getUint16(4, true)],
    code : getName(CODE, bytes.getUint16(6, true)),
    transactionID : bytes.getUint32(8, true),
    payload : [],
  };

  for (let i = 12; i &lt; length; i += 4) {
    fields.payload.push(bytes.getUint32(i, true));
  }

  return fields;
};
</code></pre>

<p>The <code>getName()</code> helper function looks like this:</p>

<pre><code class="language-js">const getName = (list, idx) =&gt; {
  for (let i in list) {
    if (list[i].value === idx) {
      return list[i].name;
    }
  }
  return &#39;unknown&#39;;
};
</code></pre>

<p>To get the object handles, we do the following:</p>

<pre><code class="language-js">  const getObjectHandles = {
    type: 1, // command block
    code: CODE.GET_OBJECT_HANDLES.value,
    payload: [0xFFFFFFFF, 0, 0xFFFFFFFF], // get all
  };
  data = buildContainerPacket(getObjectHandles, 4);
  result = await device.transferOut(0x01, data);
  console.log(&#39;result:&#39;, result);
  let { payload } = await receiveData();
</code></pre>

<p>Now we have an array of object handles. We can use the object handle to retrieve the filename, using the “Object File Name” object property code <code>0xDC07</code>:</p>

<pre><code class="language-js">const getFilename = {
    type: 1,
    code: CODE.GET_OBJECT_PROP_VALUE.value,
    payload: [0x2B, 0xDC07], // object handle and object property code
  };
  result = await device.transferOut(0x01, buildContainerPacket(getFilename));
  console.log(&#39;result:&#39;, result);
  let { payload } = await receiveData();
</code></pre>

<p>The payload is a 16-bit unicode string containing the file name. Nice!</p>

<hr/>

<p>I’m publishing this as part of 100 Days To Offload. You can join in yourself by visiting <a href="https://100daystooffload.com">https://100daystooffload.com</a>.</p>

<p><a href="https://gerritniezen.com/tag:100DaysToOffload" class="hashtag"><span>#</span><span class="p-category">100DaysToOffload</span></a> <a href="https://gerritniezen.com/tag:day51" class="hashtag"><span>#</span><span class="p-category">day51</span></a> <a href="https://gerritniezen.com/tag:mtp" class="hashtag"><span>#</span><span class="p-category">mtp</span></a></p>

<p><i>Comment on this post</i>
<div id="cusdis_thread" id="cusdis_thread"></div></p>
]]></content:encoded>
      <guid>https://gerritniezen.com/taking-a-deep-dive-into-mtp-part-3</guid>
      <pubDate>Wed, 24 Jun 2020 15:51:10 +0000</pubDate>
    </item>
    <item>
      <title>Taking a deep dive into MTP: Part 2</title>
      <link>https://gerritniezen.com/taking-a-deep-dive-into-mtp-part-2?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[Last time I started looking at how to implement a subset of the MTP specification, in order to read files from Android devices. Let&#39;s start by writing some code to talk to the device over WebUSB:&#xA;&#xA;const device = await usb.requestDevice({&#xA;  filters: [&#xA;    {&#xA;      vendorId: 3725,&#xA;      productId: 8221,&#xA;    }&#xA;  ]&#xA;});&#xA;&#xA;await device.open();&#xA;&#xA;if (device.configuration === null) {&#xA;  console.log(&#39;selectConfiguration&#39;);&#xA;  await device.selectConfiguration(1);&#xA;}&#xA;await device.claimInterface(0);&#xA;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):&#xA;&#xA;const buildContainerPacket = (container, payloadLength) =  {&#xA;  const packetLength = 12 + payloadLength;&#xA;  const buf = new ArrayBuffer(packetLength);&#xA;  const bytes = new Uint8Array(buf);&#xA;  let ctr = struct.pack(bytes, 0, &#39;issi&#39;, packetLength, container.type, container.code, container.transactionID);&#xA;  if (payloadLength   0) {&#xA;    ctr += struct.copyBytes(bytes, ctr, container.payload, payloadLength);&#xA;  }&#xA;&#xA;  return buf;&#xA;};&#xA;&#xA;To open a new session, we fill the container object with the right info and send it off via the bulk pipe:&#xA;&#xA;  const openSession = {&#xA;    type: 1, // command block&#xA;    code: 0x1002, // open session&#xA;    transactionID: 0,&#xA;    payload: 1, // session ID&#xA;  };&#xA;  let data = buildContainerPacket(openSession, 4);&#xA;  let result = await device.transferOut(0x01, data);&#xA;&#xA;To read the response, I&#39;m creating a receiveData() function:&#xA;&#xA;  const receiveData = async () =  {&#xA;    const timeoutID = setTimeout(async() =  {&#xA;      console.warn(&#39;Device not connected&#39;);&#xA;    }, 5000);&#xA;&#xA;    console.log(&#39;Receiving...&#39;);&#xA;&#xA;    let incoming = await device.transferIn(0x01, 1024);&#xA;&#xA;    if (incoming.data.byteLength   0) {&#xA;      clearTimeout(timeoutID);&#xA;&#xA;      const [length] = new Uint32Array(incoming.data.buffer, 0, 1);&#xA;      console.log(&#39;Length:&#39;, length);&#xA;&#xA;      // TODO: if length !== incoming.data.byteLength, read more data&#xA;    }&#xA;  };&#xA;&#xA;To actually receive some data, we call &#xA;&#xA;await receiveData();&#xA;&#xA;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:&#xA;&#xA;Length: 0x0C000000 - 12 (in little-endian format)&#xA;Type: 0x0300 - Response block&#xA;0x0120, or 0x2001 in big-endian - response code OK&#xA;0x0000000000 - transaction ID&#xA;&#xA;So, we&#39;re able to successfully open a session!&#xA;&#xA;PS: Hey, I&#39;m halfway through #100DaysToOffload already! 🎉️&#xA;&#xA;---&#xA;I’m publishing this as part of 100 Days To Offload. You can join in yourself by visiting https://100daystooffload.com.&#xA;&#xA;#100DaysToOffload #day50 #mtp&#xA;&#xA;iComment on this post/i&#xD;&#xA;div id=&#34;cusdis_thread&#34;/div]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="https://gerritniezen.com/taking-a-deep-dive-into-mtp">Last time</a> I started looking at how to implement a subset of the MTP specification, in order to read files from Android devices. Let&#39;s start by writing some code to talk to the device over WebUSB:</p>

<pre><code class="language-js">const device = await usb.requestDevice({
  filters: [
    {
      vendorId: 3725,
      productId: 8221,
    }
  ]
});

await device.open();

if (device.configuration === null) {
  console.log(&#39;selectConfiguration&#39;);
  await device.selectConfiguration(1);
}
await device.claimInterface(0);
</code></pre>

<p>MTP uses <em>containers</em> to structure what the various commands and responses look like. Here is how to build a container packet (and note that <code>struct.pack()</code> is part of a <a href="https://github.com/tidepool-org/uploader/blob/master/lib/struct.js">convenience library</a>):</p>

<pre><code class="language-js">const buildContainerPacket = (container, payloadLength) =&gt; {
  const packetLength = 12 + payloadLength;
  const buf = new ArrayBuffer(packetLength);
  const bytes = new Uint8Array(buf);
  let ctr = struct.pack(bytes, 0, &#39;issi&#39;, packetLength, container.type, container.code, container.transactionID);
  if (payloadLength &gt; 0) {
    ctr += struct.copyBytes(bytes, ctr, container.payload, payloadLength);
  }

  return buf;
};
</code></pre>

<p>To open a new session, we fill the <code>container</code> object with the right info and send it off via the bulk pipe:</p>

<pre><code class="language-js">  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);
</code></pre>

<p>To read the response, I&#39;m creating a <code>receiveData()</code> function:</p>

<pre><code class="language-js">  const receiveData = async () =&gt; {
    const timeoutID = setTimeout(async() =&gt; {
      console.warn(&#39;Device not connected&#39;);
    }, 5000);

    console.log(&#39;Receiving...&#39;);

    let incoming = await device.transferIn(0x01, 1024);

    if (incoming.data.byteLength &gt; 0) {
      clearTimeout(timeoutID);

      const [length] = new Uint32Array(incoming.data.buffer, 0, 1);
      console.log(&#39;Length:&#39;, length);

      // TODO: if length !== incoming.data.byteLength, read more data
    }
  };
</code></pre>

<p>To actually receive some data, we call</p>

<pre><code class="language-js">await receiveData();
</code></pre>

<p>and then parse the response (<code>0c 00 00 00 03 00 01 20 01 00 00 00</code>), which is also in the same container format:</p>
<ul><li>Length: <code>0x0C000000</code> – 12 (in little-endian format)</li>
<li>Type: <code>0x0300</code> – Response block</li>
<li><code>0x0120</code>, or <code>0x2001</code> in big-endian – response code <code>OK</code></li>
<li><code>0x0000000000</code> – transaction ID</li></ul>

<p>So, we&#39;re able to successfully open a session!</p>

<p>PS: Hey, I&#39;m halfway through <a href="https://gerritniezen.com/tag:100DaysToOffload" class="hashtag"><span>#</span><span class="p-category">100DaysToOffload</span></a> already! 🎉️</p>

<hr/>

<p>I’m publishing this as part of 100 Days To Offload. You can join in yourself by visiting <a href="https://100daystooffload.com">https://100daystooffload.com</a>.</p>

<p><a href="https://gerritniezen.com/tag:100DaysToOffload" class="hashtag"><span>#</span><span class="p-category">100DaysToOffload</span></a> <a href="https://gerritniezen.com/tag:day50" class="hashtag"><span>#</span><span class="p-category">day50</span></a> <a href="https://gerritniezen.com/tag:mtp" class="hashtag"><span>#</span><span class="p-category">mtp</span></a></p>

<p><i>Comment on this post</i>
<div id="cusdis_thread" id="cusdis_thread"></div></p>
]]></content:encoded>
      <guid>https://gerritniezen.com/taking-a-deep-dive-into-mtp-part-2</guid>
      <pubDate>Mon, 22 Jun 2020 15:24:08 +0000</pubDate>
    </item>
    <item>
      <title>Taking a deep dive into MTP</title>
      <link>https://gerritniezen.com/taking-a-deep-dive-into-mtp?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[I&#39;ve done a 5-part series on using MTP on MacOS with Node.js in 2018. I also did a 3-part series on using MTP on Windows with Node.js just last month. The learnings from all these posts have been incorporated into my node-mtp Node.js library.&#xA;&#xA;The node-mtp library is essentially a wrapper around the libmtp library, which means it doesn&#39;t work in the browser. Given that I eventually want this to work in the browser, and that I&#39;m struggling to get libmtp built on 32-bit Windows, I decided to write a subset of the MTP protocol myself on top of WebUSB. &#xA;&#xA;The MTP protocol spec itself can be downloaded for free from the USB Implementer&#39;s Forum. It&#39;s a 282-page document, but doesn&#39;t itself describe what the packet structure looks like. For that, you need the earlier PTP protocol that it builds on, which has been standardized as ISO 15740. Luckily the USB Implementer&#39;s Forum also has the PTP over USB spec, so you don&#39;t have to buy the ISO spec to be able to implement it. Before I discovered the PTP over USB spec, this USB device fuzzing code was pretty helpful to start decoding what&#39;s going on.&#xA;&#xA;When you connect to an MTP device over USB, you should look for an interface with three endpoints, one of which will be a bulk transfer endpoint. I captured some packets in Wireshark while running Android File Transfer on Linux, which has a nice CLI tool called aft-mtp-cli.&#xA;&#xA;The first bulk transfer packet sent to the device from the computer looks like this:&#xA;&#xA;10000000010002100000000001000000&#xA;&#xA;According to the PTP over USB spec, the structure is as follows:&#xA;&#xA;Container Length (4 bytes)&#xA;Container Type (2 bytes)&#xA;Code (2 bytes)&#xA;Transaction ID (4 bytes)&#xA;Payload&#xA;&#xA;Now we can see that10000000010002100000000001000000 can be broken down into:&#xA;&#xA;Length: 0x10000000 - 16 (in little-endian format)&#xA;Type: 0x0100 - Command Block&#xA;0x0210, or 0x1002 in big-endian - operation code OpenSession&#xA;0x00000000 - Transaction ID&#xA;0x01000000 - Payload, parameter 1: Session ID&#xA;&#xA;Next up, actually using WebUSB to start a new MTP session on the device and read some data!&#xA;&#xA;---&#xA;I’m publishing this as part of 100 Days To Offload. You can join in yourself by visiting https://100daystooffload.com.&#xA;&#xA;#100DaysToOffload #day49 #mtp&#xA; &#xA;&#xA;iComment on this post/i&#xD;&#xA;div id=&#34;cusdisthread&#34;/div]]&gt;</description>
      <content:encoded><![CDATA[<p>I&#39;ve done a <a href="https://gerritniezen.com/tag:nodemtp">5-part series</a> on using MTP on MacOS with Node.js in 2018. I also did a <a href="https://gerritniezen.com/tag:libmtp">3-part series</a> on using MTP on Windows with Node.js just last month. The learnings from all these posts have been incorporated into my <a href="https://github.com/tidepool-org/node-mtp">node-mtp</a> Node.js library.</p>

<p>The node-mtp library is essentially a wrapper around the <a href="https://github.com/libmtp/libmtp">libmtp</a> library, which means it doesn&#39;t work in the browser. Given that I eventually want this to work in the browser, and that I&#39;m struggling to get libmtp built on 32-bit Windows, I decided to write a subset of the MTP protocol myself on top of <a href="https://wicg.github.io/webusb/">WebUSB</a>.</p>

<p>The MTP protocol spec itself can be <a href="https://www.usb.org/sites/default/files/MTPv1_1.zip">downloaded for free</a> from the USB Implementer&#39;s Forum. It&#39;s a 282-page document, but doesn&#39;t itself describe what the packet structure looks like. For that, you need the earlier PTP protocol that it builds on, which has been standardized as <a href="https://www.iso.org/standard/63602.html">ISO 15740</a>. Luckily the USB Implementer&#39;s Forum also has the <a href="https://www.usb.org/document-library/still-image-capture-device-definition-10-and-errata-16-mar-2007">PTP over USB spec</a>, so you don&#39;t have to buy the ISO spec to be able to implement it. Before I discovered the PTP over USB spec, this <a href="https://github.com/ollseg/usb-device-fuzzing/blob/master/USBFuzz/MTP.py">USB device fuzzing code</a> was pretty helpful to start decoding what&#39;s going on.</p>

<p>When you connect to an MTP device over USB, you should look for an interface with three endpoints, one of which will be a bulk transfer endpoint. I captured some packets in Wireshark while running <a href="https://github.com/whoozle/android-file-transfer-linux/">Android File Transfer</a> on Linux, which has a nice CLI tool called <code>aft-mtp-cli</code>.</p>

<p>The first bulk transfer packet sent to the device from the computer looks like this:</p>

<p><code>10000000010002100000000001000000</code></p>

<p>According to the PTP over USB spec, the structure is as follows:</p>
<ul><li>Container Length (4 bytes)</li>
<li>Container Type (2 bytes)</li>
<li>Code (2 bytes)</li>
<li>Transaction ID (4 bytes)</li>
<li>Payload</li></ul>

<p>Now we can see that<code>10000000010002100000000001000000</code> can be broken down into:</p>
<ul><li>Length: <code>0x10000000</code> – 16 (in little-endian format)</li>
<li>Type: <code>0x0100</code> – Command Block</li>
<li><code>0x0210</code>, or <code>0x1002</code> in big-endian – operation code <code>OpenSession</code></li>
<li><code>0x00000000</code> – Transaction ID</li>
<li><code>0x01000000</code> – Payload, parameter 1: Session ID</li></ul>

<p>Next up, actually using WebUSB to start a new MTP session on the device and read some data!</p>

<hr/>

<p>I’m publishing this as part of 100 Days To Offload. You can join in yourself by visiting <a href="https://100daystooffload.com">https://100daystooffload.com</a>.</p>

<p><a href="https://gerritniezen.com/tag:100DaysToOffload" class="hashtag"><span>#</span><span class="p-category">100DaysToOffload</span></a> <a href="https://gerritniezen.com/tag:day49" class="hashtag"><span>#</span><span class="p-category">day49</span></a> <a href="https://gerritniezen.com/tag:mtp" class="hashtag"><span>#</span><span class="p-category">mtp</span></a></p>

<p><i>Comment on this post</i>
<div id="cusdis_thread" id="cusdis_thread"></div></p>
]]></content:encoded>
      <guid>https://gerritniezen.com/taking-a-deep-dive-into-mtp</guid>
      <pubDate>Thu, 18 Jun 2020 15:30:59 +0000</pubDate>
    </item>
  </channel>
</rss>