Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions app/windows/runner/ble.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ namespace {
const guid kService{0xc46b12c3, 0x1dee, 0x482d, {0xa4, 0x90, 0xb1, 0x85, 0x21, 0x23, 0x12, 0xfb}};
const guid kMessage{0xc46b12c4, 0x1dee, 0x482d, {0xa4, 0x90, 0xb1, 0x85, 0x21, 0x23, 0x12, 0xfb}};
constexpr size_t kMaxMessage = 512; // BLE-03
constexpr uint8_t kBleVersion = 1; // its messages' first byte (bible §6.6)
constexpr auto kAnswerWithin = std::chrono::seconds(25); // BLE-11: under the 30 s of a GATT transaction
constexpr int64_t kSeenEveryMs = 5000;

Expand Down Expand Up @@ -177,6 +178,12 @@ fire_and_forget Ble::Start(bool advertise, bool scan, Done done) {
GattServiceProviderAdvertisingParameters params;
params.IsConnectable(true);
params.IsDiscoverable(false);
// One byte of service data, the same on every computer (the BLE version): without it, the
// Galaxy A57's scan filtered on the service never sees this computer, which a TECNO
// does (3 November 2026: 0 results in 15 s without it, 11 and 12 with it).
DataWriter version;
version.WriteByte(kBleVersion);
params.ServiceData(version.DetachBuffer());
provider.StartAdvertising(params);
provider_ = provider;
}
Expand Down
2 changes: 1 addition & 1 deletion docs/medxfer-bible-technique-v8.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ Seul circule un réseau créé par medXfer pour l'occasion, au mot de passe tir
| Id | Exigence | Statut |
|---|---|---|
| `BLE-01` | **Rôle.** BLE ne sert qu'à mettre deux appareils sur un même réseau. Il ne porte ni fichier, ni alias, ni nom (`DISC-06`) ; la session s'ouvre sur le Wi-Fi, en TLS (§7). Le cœur produit et lit les messages ; la plateforme les porte. Sans BLE : le QR et le PIN. | ✅ cœur et API (WP-D.2 : `pkg/pairing/ble.go`, actions `ble_server`, `ble_client`, `pair_ble`) ; plateformes : Android et Windows (WP-D.3) |
| `BLE-02` | **Annonce.** L'annonce ne porte que l'UUID de service `c46b12c3-1dee-482d-a490-b185212312fb` : ni nom (Windows : `IsDiscoverable` faux), ni donnée de service, ni identifiant stable. L'adresse est l'adresse aléatoire du système. La caractéristique « message » est `c46b12c4-1dee-482d-a490-b185212312fb`. Annonce et balayage, filtré sur l'UUID, suivent `DISC-05` : seulement quand l'utilisateur cherche ou attend un appareil, l'application ouverte (plan J6d, D3). | ✅ Android (WP-D.3, `Ble.kt`) : UUID seul, recherche filtrée sur le service, en pause pendant une session ; Windows (WP-D.3, `ble.cpp`) : `GattServiceProvider` caché, recherche filtrée ; un téléphone appairé en Bluetooth avec le PC n'est pas lu (plan J6d) |
| `BLE-02` | **Annonce.** L'annonce ne porte que l'UUID de service `c46b12c3-1dee-482d-a490-b185212312fb` : ni nom (Windows : `IsDiscoverable` faux), ni donnée de service propre à l'appareil, ni identifiant stable (Windows ajoute un octet de donnée de service, la version des messages, le même partout : sans lui, la recherche filtrée du Galaxy A57 ne voit pas le PC). L'adresse est l'adresse aléatoire du système. La caractéristique « message » est `c46b12c4-1dee-482d-a490-b185212312fb`. Annonce et balayage, filtré sur l'UUID, suivent `DISC-05` : seulement quand l'utilisateur cherche ou attend un appareil, l'application ouverte (plan J6d, D3). | ✅ Android (WP-D.3, `Ble.kt`) : UUID seul, recherche filtrée sur le service, en pause pendant une session ; Windows (WP-D.3, `ble.cpp`) : `GattServiceProvider` caché, recherche filtrée |
| `BLE-03` | **Messages.** Format, tailles et réponses du §6.6 ; 512 octets au plus. Une version inconnue, une taille fausse, un champ de trop ou de moins : `refuse`, connexion fermée. Aucune taille de MTU n'est exigée. R répond dans la version de I s'il la connaît. | ✅ cœur (WP-D.2) : `pkg/pairing/ble.go`, `FuzzBLEReceive` |
| `BLE-04` | **Reconnaissance.** `hello` porte un défi frais. `hello_reply` porte toujours 16 étiquettes, dans un ordre aléatoire, le suffixe et `c_R`. Seuls les appareils de confiance mutuelle qui ont une clé `K_AB` ont une étiquette ; oublier un appareil retire la sienne. `c_R` vaut pour cette connexion, 60 s au plus. | ✅ cœur (WP-D.2) |
| `BLE-05` | **Appareil de confiance.** `trusted` porte `tag(fp_I, c_R)` et la demande chiffrée. Chaque côté ne calcule qu'avec la clé de sa propre liste : l'échange exige la confiance des deux côtés (`PAIR-09`). Aucune question à l'utilisateur de R, qui voit l'appareil se connecter. | ✅ cœur (WP-D.2) |
Expand Down
6 changes: 4 additions & 2 deletions docs/medxfer-plan-j6d.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,11 @@ Essayé, sans effet : un verrou Wi-Fi « haute performance » sur le TECNO. Le j
| Fermeture de l'application sur le PC | ✅ réseau quitté, profil supprimé |
| 1 Gio du PC vers le TECNO, écran du TECNO éteint | ❌ coupé vers 44 % ; l'application affiche pourtant « le transfert continue » et sa barre avance |

**Suite de l'essai, l'après-midi du 3 novembre** :
- **L'A57 ne voyait pas l'annonce de l'application Windows** : sans donnée de service, sa recherche filtrée sur le service ne la voit jamais (0 résultat en 15 s, avec la sonde), et la voit avec un octet (11 et 12). L'annonce porte maintenant un octet, la version des messages BLE, le même sur tous les PC. L'A57 voit alors le PC, le reconnaît comme appareil de confiance, et le touchant, met les deux sur son groupe à 5 GHz (canal 48) ; la session s'ouvre. À la fin, le PC revient seul au Wi-Fi où il était (« TECNO POP 7 »).
- **Le téléphone appairé (#258) ne se reproduit pas** avec un appairage refait : 4 échanges sur 4 avec la sonde, en 0,9 à 2 s, et l'application reconnaît l'A57 appairé. L'ancien appairage était sans doute abîmé : l'A57 journalisait « BOND_BONDED » dans un état stable, et annonçait sous son adresse d'identité, ce qu'il ne fait plus. À revoir en WP-D.6 avec d'autres appareils appairés.

**Reste ouvert** :
- un téléphone appairé en Bluetooth avec le PC : le PC n'en lit pas le service. Les utilisateurs appairent souvent leur téléphone ; à corriger avant WP-D.6 ;
- l'A57 ne voit pas l'annonce de l'application Windows, alors qu'il voit celle de la sonde et que le TECNO voit celle de l'application. Le PC touchant l'A57 suffit pour l'instant ;
- un transfert mort affiché comme en cours, après une coupure ;
- la coupure écran éteint (#256), cette fois le TECNO créant le réseau.

Expand Down
31 changes: 22 additions & 9 deletions scripts/ble-probe/ble-probe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#include <set>
#include <string>
#include <thread>

#include <winrt/Windows.Devices.Enumeration.h>
#include <vector>

using namespace winrt;
Expand Down Expand Up @@ -141,7 +143,9 @@ int Serve(int seconds, size_t reply) {
ap.IsDiscoverable(std::getenv("PROBE_HIDDEN") == nullptr);
// 10 bytes of service data: what is left in 31 once the flags and the 128-bit UUID are in.
try {
ap.ServiceData(Bytes(10, 0x10));
// PROBE_DATA=n: n bytes of service data (0: none).
size_t n = std::getenv("PROBE_DATA") ? std::strtoul(std::getenv("PROBE_DATA"), nullptr, 10) : 10;
if (n > 0) ap.ServiceData(Bytes(n, 0x10));
} catch (hresult_error const& e) {
Log("service data refused: %ls", e.message().c_str());
}
Expand Down Expand Up @@ -216,20 +220,28 @@ int Exchange(uint64_t addr, size_t bytes) {
if (!dev) { Log("device %s not found", Addr(addr).c_str()); return 1; }
auto t1 = Clock::now();
Log("device found, connection status %d", static_cast<int>(dev.ConnectionStatus()));
auto session = GattSession::FromDeviceIdAsync(dev.BluetoothDeviceId()).get();
Log("session opened");
session.MaintainConnection(true);
auto all = dev.GetGattServicesAsync(BluetoothCacheMode::Uncached).get();
// Variants for a phone paired with this computer (#258): PROBE_NOSESSION, no GattSession;
// PROBE_CACHED, the system's cache; PROBE_WAIT=ms before the discovery.
GattSession session{nullptr};
if (!std::getenv("PROBE_NOSESSION")) {
session = GattSession::FromDeviceIdAsync(dev.BluetoothDeviceId()).get();
Log("session opened");
session.MaintainConnection(true);
}
if (auto wait = std::getenv("PROBE_WAIT")) std::this_thread::sleep_for(std::chrono::milliseconds(std::atoi(wait)));
const auto mode = std::getenv("PROBE_CACHED") ? BluetoothCacheMode::Cached : BluetoothCacheMode::Uncached;
Log("paired: %d", dev.DeviceInformation().Pairing().IsPaired() ? 1 : 0);
auto all = dev.GetGattServicesAsync(mode).get();
Log("all services: %s, %u", Status(all.Status()), all.Status() == GattCommunicationStatus::Success ? all.Services().Size() : 0);
if (all.Status() == GattCommunicationStatus::Success)
for (auto const& s : all.Services()) Log(" service %08x-...", s.Uuid().Data1);
auto sr = dev.GetGattServicesForUuidAsync(kService, BluetoothCacheMode::Uncached).get();
auto sr = dev.GetGattServicesForUuidAsync(kService, mode).get();
auto t2 = Clock::now();
if (sr.Status() != GattCommunicationStatus::Success || sr.Services().Size() == 0) {
Log("service: %s, %u found", Status(sr.Status()), sr.Services().Size());
return 1;
}
auto cr = sr.Services().GetAt(0).GetCharacteristicsForUuidAsync(kMessage, BluetoothCacheMode::Uncached).get();
auto cr = sr.Services().GetAt(0).GetCharacteristicsForUuidAsync(kMessage, mode).get();
if (cr.Status() != GattCommunicationStatus::Success || cr.Characteristics().Size() == 0) {
Log("characteristic: %s", Status(cr.Status()));
return 1;
Expand All @@ -241,12 +253,13 @@ int Exchange(uint64_t addr, size_t bytes) {
auto rd = ch.ReadValueAsync(BluetoothCacheMode::Uncached).get();
auto t5 = Clock::now();
auto ms = [](Clock::time_point a, Clock::time_point b) { return std::chrono::duration<double, std::milli>(b - a).count(); };
Log("device %.0f ms, services %.0f ms, characteristic %.0f ms, max pdu %u", ms(t0, t1), ms(t1, t2), ms(t2, t3), session.MaxPduSize());
Log("device %.0f ms, services %.0f ms, characteristic %.0f ms, max pdu %u", ms(t0, t1), ms(t1, t2), ms(t2, t3),
session ? session.MaxPduSize() : 0);
Log("write %zu bytes: %s in %.0f ms", bytes, Status(wr.Status()), ms(t3, t4));
Log("read: %s, %u bytes in %.0f ms (%s)", Status(rd.Status()), rd.Status() == GattCommunicationStatus::Success ? rd.Value().Length() : 0,
ms(t4, t5), rd.Status() == GattCommunicationStatus::Success ? Hex(rd.Value()).c_str() : "");
Log("total %.0f ms", ms(t0, t5));
session.Close();
if (session) session.Close();
dev.Close();
return 0;
}
Expand Down
Loading