From 531e33715f36af653c9c32be09bf8fa41d0bf2e3 Mon Sep 17 00:00:00 2001
From: Anton Weber <anton@antweb.me>
Date: Wed, 7 Aug 2019 22:54:11 +0200
Subject: [PATCH] docs(ble): Add file transfer documentation

---
 Documentation/bluetooth/file-transfer.rst    | 189 +++++++++++++++++++
 Documentation/index.rst                      |   6 +
 Documentation/static/ble-low-effort-flow.dia | Bin 0 -> 2331 bytes
 Documentation/static/ble-low-effort-flow.png | Bin 0 -> 13295 bytes
 4 files changed, 195 insertions(+)
 create mode 100644 Documentation/bluetooth/file-transfer.rst
 create mode 100644 Documentation/static/ble-low-effort-flow.dia
 create mode 100644 Documentation/static/ble-low-effort-flow.png

diff --git a/Documentation/bluetooth/file-transfer.rst b/Documentation/bluetooth/file-transfer.rst
new file mode 100644
index 00000000..c098c6cd
--- /dev/null
+++ b/Documentation/bluetooth/file-transfer.rst
@@ -0,0 +1,189 @@
+Bluetooth File Transfer
+=======================
+
+.. warning::
+    The file transfer specification is still work in progress
+
+File transfer to the card10 is implemented using the custom Low Effort File
+Transfer Protocol.
+
+BLE Service
+-----------
+The service consists of two GATT characteristics that act as a bidrectional
+link, similar to many BLE UART implementations. The two channels are seen from
+the Central perspective and hence named Central TX and Central RX.
+
+The current draft uses following service specification:
+
+- Service
+
+  UUID: 00422342-2342-2342-2342-234223422342
+
+- Central TX characteristic:
+
+  UUID: 01422342-2342-2342-2342-234223422342
+  write
+
+- Central RX characteristic:
+
+  UUID 02422342-2342-2342-2342-234223422342
+  read, notify
+
+Low Effort File Transfer Protocol
+---------------------------------
+(Version 1)
+
+This protocol was designed to strike a balance between ease of implementation
+and reasonable levels of functionality.
+
+Features:
+
+- File push from Central (e.g. Android) to Peripheral (card10)
+- Path and file name support
+- Chunked data transfer for variable MTUs
+- CRC32 error-detection
+- Basic error handling
+
+All communication between Central and Peripheral is packet based. The first
+byte specifies the packet type using a char followed by an optional CRC and/or
+payload, depending on the packet type.
+
+START:
+
+===== ====
+  0   1-N
+----- ----
+  s   path
+===== ====
+
+START_ACK:
+
+===== ===
+  0   1-4
+----- ---
+  S   CRC
+===== ===
+
+CHUNK:
+
+===== ====== =======
+  0     1-4    4-N
+----- ------ -------
+  c   offset payload
+===== ====== =======
+
+CHUNK_ACK:
+
+===== ===
+  0   1-4
+----- ---
+  C   CRC
+===== ===
+
+FINISH:
+
+=== ===
+ 0
+--- ---
+ f
+=== ===
+
+FINISH_ACK:
+
+=== ===
+ 0
+--- ---
+ F
+=== ===
+
+ERROR:
+
+=== ===
+ 0
+--- ---
+ e
+=== ===
+
+
+ERROR_ACK:
+
+=== ===
+ 0
+--- ---
+ E
+=== ===
+
+Flow
+----
+
+The file transfer process can be described as a series of states from the view
+of the Central role:
+
+
+.. image:: ../static/ble-low-effort-flow.png
+
+
+``IDLE`` state:
+
+    - Send ``START`` to initiate transfer
+
+``START_SENT`` state:
+
+    - Wait for ``START_ACK``
+
+``SEND_READY`` state:
+
+    - Send first ``CHUNK``
+
+``CHUNK_SENT`` state:
+
+    - Wait for ``CHUNK_ACK``
+
+``SEND_READY`` state:
+
+    - Repeat previous two steps until all data is sent
+    - If the last chunk was sent, send ``FINISH``
+
+``FINISH_SENT`` state:
+
+    - Wait for ``FINISH_ACK``
+
+After ``FINISH_ACK`` was received, the transfer is complete and the process can
+return to ``IDLE``.
+
+Error Handling
+--------------
+Three types of errors are currently supported:
+
+- CRC errors:
+
+  If an ``ACK`` packet contains a CRC that fails the verification, then the
+  original packet must be retransmitted. If three consecutive attempts to
+  send a packet fail, then the transfer is aborted.
+
+
+- ACK timeouts:
+
+  If the Central does not receive a required ``ACK`` within 10 seconds, then
+  the original packet must be retransmitted. If three consecutive attempts to
+  send a packet fail, then the transfer is aborted.
+
+
+- Unexpected response:
+
+  All steps in the flow described above have exactly one expected response.
+  If any other packet is received, then the transfer is aborted.
+
+Aborting Transfer
+-----------------
+To abort the transfer, the Central role sends an ``ERROR`` packet and returns
+to ``IDLE`` after receiving the ``ERROR_ACK``.
+If the Peripheral role aborts the transfer, i.e. the Central receives an
+``ERROR`` at any point, then it responds with ``ERROR_ACK`` and returns to
+``IDLE``
+
+.. warning::
+    As this is a custom file transfer protocol developed under less than ideal
+    circumstances, it does not provide any guarantees, especially not regarding
+    reliability or security. The protocol assumes a secure link and a
+    trustworthy peer, amongst many other things. Use with caution.
diff --git a/Documentation/index.rst b/Documentation/index.rst
index 50fdc4af..a5be0e5c 100644
--- a/Documentation/index.rst
+++ b/Documentation/index.rst
@@ -48,6 +48,12 @@ Last but not least, if you want to start hacking the lower-level firmware, the
    epicardium/api
    epicardium-guide
 
+.. toctree::
+   :maxdepth: 1
+   :caption: Bluetooth
+
+   bluetooth/file-transfer
+
 Indices and tables
 ==================
 
diff --git a/Documentation/static/ble-low-effort-flow.dia b/Documentation/static/ble-low-effort-flow.dia
new file mode 100644
index 0000000000000000000000000000000000000000..7dfc86360869474e57806bf5da189e728d210876
GIT binary patch
literal 2331
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
Biv|Dy

literal 0
HcmV?d00001

diff --git a/Documentation/static/ble-low-effort-flow.png b/Documentation/static/ble-low-effort-flow.png
new file mode 100644
index 0000000000000000000000000000000000000000..da33b299973316817a8e16574564d50dd9af7aaa
GIT binary patch
literal 13295
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
WW5X+acm7{{P^87>ALczU@cCaeL@WOQ

literal 0
HcmV?d00001

-- 
GitLab