FirmwareAutoDumper.java 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. package de.nplusc.izc.senabitwiggler;
  2. import io.github.ma1uta.matrix.client.StandaloneClient;
  3. import io.github.ma1uta.matrix.client.model.room.CreateRoomRequest;
  4. import io.github.ma1uta.matrix.event.content.RoomPowerLevelsContent;
  5. import io.github.ma1uta.matrix.impl.exception.MatrixException;
  6. import org.apache.logging.log4j.LogManager;
  7. import org.apache.logging.log4j.Logger;
  8. import org.yaml.snakeyaml.DumperOptions;
  9. import org.yaml.snakeyaml.TypeDescription;
  10. import org.yaml.snakeyaml.Yaml;
  11. import org.yaml.snakeyaml.constructor.Constructor;
  12. import org.yaml.snakeyaml.nodes.Tag;
  13. import org.yaml.snakeyaml.representer.Representer;
  14. import javax.swing.plaf.nimbus.State;
  15. import java.io.*;
  16. import java.nio.file.CopyOption;
  17. import java.nio.file.Files;
  18. import java.nio.file.StandardCopyOption;
  19. import java.text.DateFormat;
  20. import java.text.SimpleDateFormat;
  21. import java.util.*;
  22. public class FirmwareAutoDumper
  23. {
  24. private static final Logger l = LogManager.getLogger();
  25. static Statefile state = null;
  26. static File outfolder_base;
  27. public static final boolean IA_UPLOAD_ENABLED = false;
  28. public static final boolean DUMMY_MODE = false;
  29. public static PrintStream dummydownloadfake = null;
  30. public static HashMap<String,FirmwareVersion> supprCache = new HashMap<>();
  31. public static void pullFirmwares(File statefile, File outfolder, boolean deepmode)
  32. {
  33. outfolder_base=outfolder;
  34. try{
  35. if(DUMMY_MODE)
  36. {
  37. dummydownloadfake = new PrintStream(new FileOutputStream(new File(outfolder,"dummy.log")));
  38. }
  39. Representer representer = new Representer();
  40. representer.addClassTag(Firmware.class, new Tag("!FW"));
  41. representer.addClassTag(FirmwareVersion.class, new Tag("!FWV"));
  42. Yaml y = new Yaml(representer, new DumperOptions());
  43. if(statefile.exists())
  44. {
  45. try {
  46. Constructor constructor = new Constructor();
  47. constructor.addTypeDescription(new TypeDescription(Firmware.class, "!FW"));
  48. constructor.addTypeDescription(new TypeDescription(FirmwareVersion.class, "!FWV"));
  49. Yaml internal = new Yaml(constructor);
  50. state = (Statefile) internal.load(new FileReader(statefile));
  51. } catch (FileNotFoundException e) {
  52. l.catching(e);
  53. }
  54. }
  55. else
  56. {
  57. state = new Statefile();
  58. state.setFirmwares(new LinkedHashMap<>());
  59. }
  60. File outfolder_tmp = new File(outfolder,"tmp");
  61. if(!DUMMY_MODE)
  62. {
  63. new File(outfolder_tmp,"Firmware").delete();
  64. }
  65. File downloadFolderBase = new File(outfolder_base,"downloadstage");
  66. downloadFolderBase.mkdirs();
  67. File fw = new File(outfolder_tmp,"Firmware");
  68. outfolder_tmp.mkdirs();
  69. if(!fw.exists()) // should only happen in dummy mode or if something zarfed out while dev
  70. {
  71. Utils.runTool(outfolder_tmp,
  72. "wget",
  73. "http://firmware.sena.com/senabluetoothmanager/Firmware"
  74. );
  75. }
  76. BufferedReader br = new BufferedReader(new FileReader(fw));
  77. final HashMap<String,String> basenamelookup = new LinkedHashMap<>();
  78. // filename --> real basename
  79. br.lines().forEach((line)->
  80. {
  81. String[] linesplitted = line.split(":");
  82. String FWFileName;
  83. String FWBAsename = "ZÖINKS!";
  84. if(linesplitted.length==4)
  85. {
  86. FWBAsename = linesplitted[0]+"-"+linesplitted[2];
  87. FWFileName = linesplitted[3];
  88. }
  89. else if(linesplitted.length==3)
  90. {
  91. FWBAsename = linesplitted[0]+"-NOLNG";
  92. FWFileName = linesplitted[2];
  93. }
  94. else
  95. {
  96. return;
  97. }
  98. if(basenamelookup.containsKey(FWFileName))
  99. {
  100. return; //languageless file, no need to handle multiple times.
  101. }
  102. else
  103. {
  104. basenamelookup.put(FWFileName,FWBAsename);
  105. }
  106. if(!state.getFirmwares().containsKey(FWBAsename))
  107. {
  108. Firmware tmp = new Firmware();
  109. tmp.setDeviceId(FWBAsename);
  110. tmp.setInitialDLDone(false);
  111. tmp.setVersions(new LinkedHashMap<>());
  112. state.getFirmwares().put(FWBAsename,tmp);
  113. }
  114. Firmware f = state.getFirmwares().get(FWBAsename);
  115. try {
  116. l.info("Processing line:"+line+"\n Basename:"+FWBAsename);
  117. int[] fwnumber = splitVersionAndPullFW(FWFileName,f.getVersions(),deepmode);
  118. f.setMajor(fwnumber[0]);
  119. f.setMinor(fwnumber[1]);
  120. f.setPatch(fwnumber[2]);
  121. } catch (IOException e) {
  122. e.printStackTrace();
  123. }
  124. });
  125. br.close();
  126. y.dump(state,new FileWriter(statefile));
  127. if(!DUMMY_MODE)
  128. {
  129. new File(outfolder_tmp,"Firmware").delete();
  130. pushToMatrix();
  131. }
  132. if(DUMMY_MODE)
  133. {
  134. dummydownloadfake.close();
  135. }
  136. y.dump(state,new FileWriter(statefile));
  137. }
  138. catch (IOException ex)
  139. {
  140. ex.printStackTrace();
  141. }
  142. }
  143. private static void pushToMatrix()
  144. {
  145. if(!EntryPoint.c.isMatrixEnabled())
  146. {
  147. //skip all if matrix is off.
  148. return;
  149. }
  150. StandaloneClient mxClient = new StandaloneClient.Builder().domain(EntryPoint.c.getMatrixDomain()).build();
  151. mxClient.auth().login(EntryPoint.c.getMatrixUser(), EntryPoint.c.getMatrixPassword().toCharArray());
  152. if(state.getGeneralRoomId()==null)
  153. {
  154. l.info("Create Matrix Room General");
  155. CreateRoomRequest r = new CreateRoomRequest();
  156. r.setName("Sena Full Logstream");
  157. r.setRoomAliasName("SenaFirmwareAll");
  158. RoomPowerLevelsContent lvls = new RoomPowerLevelsContent();
  159. lvls.setEventsDefault((byte)1); //readonly except for the BitWiggler
  160. r.setPowerLevelContentOverride(lvls);
  161. r.setVisibility("public");
  162. state.setGeneralRoomId(mxClient.room().create(r).getRoomId());
  163. }
  164. if(state.getOtherRoomId()==null)
  165. {
  166. l.info("Create Matrix Room Catchall");
  167. CreateRoomRequest r = new CreateRoomRequest();
  168. r.setName("Sena Other Logstream");
  169. r.setRoomAliasName("SenaFirmwareOther");
  170. RoomPowerLevelsContent lvls = new RoomPowerLevelsContent();
  171. lvls.setEventsDefault((byte)1); //readonly except for the BitWiggler
  172. r.setPowerLevelContentOverride(lvls);
  173. r.setVisibility("public");
  174. state.setOtherRoomId(mxClient.room().create(r).getRoomId());
  175. }
  176. state.getFirmwares().forEach((k,v)->
  177. {
  178. if(v.getRoomid()==null)
  179. {
  180. l.info("Create Matrix Room for:"+k);
  181. CreateRoomRequest r = new CreateRoomRequest();
  182. r.setName("Sena Logstream: "+v.getDeviceId());
  183. r.setRoomAliasName("SenaStream"+k);
  184. RoomPowerLevelsContent lvls = new RoomPowerLevelsContent();
  185. lvls.setEventsDefault((byte)1); //readonly except for the BitWiggler
  186. r.setPowerLevelContentOverride(lvls);
  187. r.setVisibility("public");
  188. while(v.getRoomid()==null)
  189. {
  190. try {
  191. v.setRoomid(mxClient.room().create(r).getRoomId());
  192. }
  193. catch(MatrixException e)
  194. {
  195. }
  196. if(v.getRoomid()==null)
  197. {
  198. try {
  199. Thread.sleep(2000);
  200. } catch (InterruptedException e) {
  201. e.printStackTrace();
  202. }
  203. }
  204. }
  205. }
  206. List<String> keys = new ArrayList<>(v.getVersions().keySet());
  207. keys.sort((x,y)->
  208. {
  209. var vr = v.getVersions();
  210. var version1 = vr.get(x);
  211. var version2 = vr.get(y);
  212. if(version1.getMajor()< version2.getMajor()) {
  213. return -1;
  214. }
  215. if(version1.getMajor()> version2.getMajor()) {
  216. return 1;
  217. }
  218. if(version1.getMinor()< version2.getMinor()) {
  219. return -1;
  220. }
  221. if(version1.getMinor()> version2.getMinor()) {
  222. return 1;
  223. }
  224. if(version1.getPatch()< version2.getPatch()) {
  225. return -1;
  226. }
  227. if(version1.getPatch()> version2.getPatch()) {
  228. return 1;
  229. }
  230. return 0;
  231. });
  232. for (var v2p : keys) {
  233. final var v2 = v.getVersions().get(v2p); //HAXX
  234. LinkedList<Runnable> messageActions = new LinkedList<>();
  235. if(!v2.isFiller())
  236. {
  237. String version = v2.getMajor()+"."+v2.getMinor()+"."+v2.getPatch();
  238. Date ts = v2.getServerCreationDate();
  239. TimeZone tz = TimeZone.getTimeZone("UTC");
  240. DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'"); // Quoted "Z" to indicate UTC, no timezone offset
  241. df.setTimeZone(tz);
  242. String date = df.format(ts);
  243. String msg = "Detected new Version: "+version+" for "+k+" uploaded at: "+date;
  244. if(v2.isHidden())
  245. {
  246. msg = "Guessed new hidden Version: "+version+" for "+k+" uploaded at: "+date;
  247. }
  248. if((!v2.isHidden()&&v2.getNotificationState()==MatrixState.SENT_PRELIM)||v2.getNotificationState()==MatrixState.TODO)
  249. {
  250. final String msg2 = msg;
  251. messageActions.add(()->
  252. {
  253. l.info("Push: "+msg2);
  254. mxClient.event().sendMessage(v.getRoomid(), msg2);
  255. mxClient.event().sendMessage(state.getGeneralRoomId(), msg2);
  256. v2.setNotificationState(v2.isHidden()?MatrixState.SENT_PRELIM:MatrixState.SENT);
  257. });
  258. }
  259. }
  260. messageActions.iterator().forEachRemaining(runnable -> runnable.run());
  261. }
  262. });
  263. mxClient.auth().logout();
  264. }
  265. private static int[] splitVersionAndPullFW(String filename,HashMap<String,FirmwareVersion> knownVersions, boolean deepmode) throws IOException {
  266. String[] magic = filename.split("-v");
  267. boolean fuckingoddity = false;
  268. boolean hackfix50R_SR = false;
  269. if(magic.length==1)
  270. {
  271. fuckingoddity = true;
  272. magic = filename.split("_v");
  273. if(magic.length==1)
  274. {
  275. hackfix50R_SR = true;
  276. magic = filename.split("SRv");
  277. }
  278. }
  279. String version = magic[1];
  280. String[] vsplit = version.split("\\.");
  281. String suffix = "";
  282. int major=0,minor=0,patch=0;
  283. major=Integer.valueOf(vsplit[0]);
  284. if(vsplit.length==4)
  285. {
  286. minor = Integer.valueOf(vsplit[1]);
  287. patch = Integer.valueOf(vsplit[2].substring(0,1));
  288. if(vsplit[2].length()>1)
  289. {
  290. suffix = vsplit[2].substring(1)+"."+vsplit[3];
  291. }
  292. else
  293. {
  294. suffix = "."+vsplit[3];
  295. }
  296. }
  297. else
  298. {
  299. if(vsplit[1].length()>1)
  300. {
  301. minor = Integer.valueOf(vsplit[1].substring(0,1));
  302. suffix = vsplit[1].substring(1)+"."+vsplit[2];
  303. }
  304. else
  305. {
  306. minor = Integer.valueOf(vsplit[1].substring(0,1));
  307. suffix = "."+vsplit[2];
  308. }
  309. }
  310. String prefix = magic[0]+(fuckingoddity?"_":"-")+"v";
  311. if(hackfix50R_SR)
  312. {
  313. prefix = magic[0]+"SRv";
  314. }
  315. //suffix = suffix+".img";
  316. scanDownwards(major,minor,patch,prefix,suffix,knownVersions);
  317. //scanning upwards for "poking" for undocumented stuff
  318. if(deepmode)
  319. {
  320. //String synthesizedFileName = prefix+(major)+"."+minor+"."+(patch)+suffix;
  321. String synthesizedFileName = prefix+(major+1)+"."+0+"."+(0)+suffix;
  322. String synthesizedFileNameShort = prefix+(major+1)+"."+0+suffix;
  323. peekNext(synthesizedFileName,synthesizedFileNameShort,major+1,0,0,knownVersions);
  324. synthesizedFileName = prefix+(major)+"."+(minor+1)+"."+(0)+suffix;
  325. synthesizedFileNameShort = prefix+(major)+"."+(minor+1)+suffix;
  326. peekNext(synthesizedFileName,synthesizedFileNameShort,major,minor+1,0,knownVersions);
  327. synthesizedFileName = prefix+(major)+"."+minor+"."+(patch+1)+suffix;
  328. peekNext(synthesizedFileName,null,major,minor,patch+1,knownVersions);
  329. }
  330. return new int[]{major,minor,patch};
  331. }
  332. private static void scanDownwards(
  333. int major,
  334. int minor,
  335. int patch,
  336. String prefix,
  337. String suffix,
  338. HashMap<String,FirmwareVersion> knownVersions) throws IOException {
  339. l.info(major+"--"+minor+"--"+patch);
  340. List<FirmwareVersion> temp = new ArrayList<>();
  341. l.info(prefix+"."+major+"."+minor+"."+patch+"."+suffix);
  342. while(major>=1&&minor>=0&&patch>=0) //older versions won't be bruteforced, check for existence in the index prevents a redownload.
  343. {
  344. String synthesizedFileName = prefix+(major)+"."+minor+"."+(patch)+suffix;
  345. if(knownVersions.containsKey(synthesizedFileName)) //skipping redownload and setting it as "not hidden" if it was a hidden version
  346. {
  347. knownVersions.get(synthesizedFileName).setHidden(false);
  348. }
  349. else
  350. {
  351. FirmwareVersion f = getFirmware(major,minor,patch,false,synthesizedFileName);
  352. knownVersions.put(synthesizedFileName,f);
  353. }
  354. if(patch==0)
  355. {
  356. synthesizedFileName = prefix+(major)+"."+minor+suffix;
  357. if(knownVersions.containsKey(synthesizedFileName)) //skipping redownload and setting it as "not hidden" if it was a hidden version
  358. {
  359. knownVersions.get(synthesizedFileName).setHidden(false);
  360. }
  361. else
  362. {
  363. FirmwareVersion f2 = getFirmware(major,minor,patch,false,synthesizedFileName);
  364. knownVersions.put(synthesizedFileName,f2);
  365. }
  366. }
  367. patch--;
  368. if(patch<0)
  369. {
  370. patch=9;
  371. minor--;
  372. }
  373. if(minor<0)
  374. {
  375. minor=9;
  376. major--;
  377. }
  378. }/**/
  379. }
  380. private static FirmwareVersion getFirmware(int major, int minor, int patch, boolean hidden, String fwname) throws IOException {
  381. FirmwareVersion f = new FirmwareVersion();
  382. f.setFilename(fwname);
  383. f.setHidden(hidden);
  384. f.setMajor(major);
  385. f.setMinor(minor);
  386. f.setPatch(patch);
  387. File downloadFolderBase = new File(outfolder_base,"downloadstage");
  388. if(supprCache.containsKey(fwname)) //leaves a "fake entry" when 2 different deviceIDs would lead to the same filename
  389. {
  390. l.info("Fakerfuck");
  391. FirmwareVersion f2 = supprCache.get(fwname);
  392. f.setFiller(false);
  393. f.setServerCreationDate(f2.getServerCreationDate());
  394. return f;
  395. }
  396. supprCache.put(fwname,f); // banning a filename from rescan/redownload
  397. //if(true) return f;
  398. if(!DUMMY_MODE)
  399. {
  400. Utils.runTool(downloadFolderBase,
  401. "wget",
  402. "http://firmware.sena.com/senabluetoothmanager/"+fwname
  403. );
  404. }
  405. else
  406. {
  407. dummydownloadfake.println("http://firmware.sena.com/senabluetoothmanager/"+fwname);
  408. }
  409. l.info("New Version spotted:"+fwname);
  410. File dlf = new File(downloadFolderBase,fwname);
  411. File dst = new File(outfolder_base,fwname);
  412. if(dlf.exists())
  413. {
  414. f.setFiller(false);
  415. f.setServerCreationDate(new Date(dlf.lastModified()));
  416. if(!dst.exists())
  417. {
  418. Files.copy(dlf.toPath(),dst.toPath());
  419. }
  420. }
  421. else
  422. {
  423. f.setFiller(true);
  424. f.setServerCreationDate(new Date(0));
  425. }
  426. return f;
  427. }
  428. private static void peekNext(String synthesizedFileName, String synthesizedFileNameShort, int major, int minor, int patch, HashMap<String,FirmwareVersion> knownVersions) throws IOException {
  429. l.info("peekstate:");
  430. l.info(knownVersions.containsKey(synthesizedFileName));
  431. l.info(knownVersions.containsKey(synthesizedFileNameShort));
  432. if(!(knownVersions.containsKey(synthesizedFileName)||knownVersions.containsKey(synthesizedFileNameShort))) // only re-poking if nothing was there yet.
  433. {
  434. FirmwareVersion nextMajor = getFirmware(major,minor,patch,true,synthesizedFileName);
  435. if(nextMajor.isFiller())
  436. {
  437. if(synthesizedFileNameShort != null)
  438. {
  439. nextMajor = getFirmware(major,minor,patch,true,synthesizedFileNameShort);
  440. synthesizedFileName = synthesizedFileNameShort;
  441. }
  442. }
  443. l.info("peekstate:"+nextMajor.isFiller());
  444. if(!nextMajor.isFiller())
  445. {
  446. l.info("SUCCESS for:"+synthesizedFileName);
  447. knownVersions.put(synthesizedFileName,nextMajor);
  448. }
  449. }
  450. }
  451. }