11package com.mg4.control.update
22
3+ import com.mg4.control.debug.AppLogger
34import kotlinx.coroutines.Dispatchers
45import kotlinx.coroutines.withContext
56import java.io.File
@@ -13,6 +14,8 @@ import java.net.URL
1314 */
1415object ApkDownloader {
1516
17+ private const val TAG = " MG4_UPDATE"
18+
1619 /* * Plafond de taille : l'APK fait ~10 Mo, au-delà on refuse plutôt que remplir le disque. */
1720 private const val MAX_APK_BYTES = 100L * 1024 * 1024
1821
@@ -61,6 +64,30 @@ object ApkDownloader {
6164 }
6265
6366 conn.disconnect()
67+
68+ // ⚠️ Un transfert peut s'arreter SANS lever d'exception : connexion coupee, keep-alive
69+ // rompu, reseau de la voiture qui decroche. La boucle se termine alors normalement et
70+ // laisse un fichier court mais non vide, que `length() > 0` laissait passer.
71+ //
72+ // Cet APK tronque partait ensuite au verificateur de signature, qui n'y lisait aucun
73+ // certificat et annoncait « Signature de l'APK non conforme ». Le symptome accusait la
74+ // cle de signature alors que la cause etait le reseau : compter les octets evite
75+ // d'envoyer enqueter sur la mauvaise piste.
76+ if (totalBytes > 0 && downloaded != totalBytes) {
77+ AppLogger .w(TAG , " Telechargement incomplet : $downloaded /$totalBytes octets " +
78+ " — APK rejete (reseau interrompu, ce n'est PAS un probleme de signature)" )
79+ destFile.delete()
80+ return @withContext false
81+ }
82+ if (downloaded == 0L ) {
83+ AppLogger .w(TAG , " Telechargement vide — APK rejete" )
84+ destFile.delete()
85+ return @withContext false
86+ }
87+ // sha256 + taille : a comparer avec les lignes publiees par la CI (etape
88+ // "Verify signature schemes" de beta.yml). Deux empreintes identiques prouvent que
89+ // l'octet a octet est intact ; une divergence designe le transfert, pas la signature.
90+ AppLogger .i(TAG , " APK telecharge : $downloaded octets, sha256=${sha256(destFile)} " )
6491 destFile.exists() && destFile.length() > 0
6592
6693 } catch (_: Exception ) {
@@ -75,6 +102,19 @@ object ApkDownloader {
75102 * accepter une rétrogradation https → http ou un hôte arbitraire.
76103 * Renvoie null si un saut sort de la liste d'origines autorisées.
77104 */
105+ /* * Empreinte du fichier recu, pour la confronter a celle publiee par la CI. */
106+ private fun sha256 (f : File ): String = try {
107+ val md = java.security.MessageDigest .getInstance(" SHA-256" )
108+ f.inputStream().use { input ->
109+ val buf = ByteArray (8_192 )
110+ var read: Int
111+ while (input.read(buf).also { read = it } != - 1 ) md.update(buf, 0 , read)
112+ }
113+ md.digest().joinToString(" " ) { " %02x" .format(it) }
114+ } catch (e: Exception ) {
115+ " (illisible: ${e.message} )"
116+ }
117+
78118 private fun openConnection (urlStr : String , depth : Int = 0): HttpURLConnection ? {
79119 if (! ApkUrlPolicy .isAllowedLogged(urlStr, " Redirection" )) return null
80120 val conn = (URL (urlStr).openConnection() as HttpURLConnection ).apply {
0 commit comments