-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
1205 lines (576 loc) · 455 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Android Bluetooth App Development</title>
<link href="/2023/10/09/APP%20Development/android-bluetooth/"/>
<url>/2023/10/09/APP%20Development/android-bluetooth/</url>
<content type="html"><![CDATA[<h1 id="Android-Package"><a href="#Android-Package" class="headerlink" title="Android Package"></a>Android Package</h1><p>android.bluetooth.le</p><h1 id="Manifest"><a href="#Manifest" class="headerlink" title="Manifest"></a>Manifest</h1><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">manifest</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">uses-feature</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:name</span>=<span class="string">"android.hardware.bluetooth_le"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:required</span>=<span class="string">"true"</span> /></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.BLUETOOTH"</span> <span class="attr">android:maxSdkVersion</span>=<span class="string">"30"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.BLUETOOTH_ADMIN"</span> <span class="attr">android:maxSdkVersion</span>=<span class="string">"30"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.ACCESS_FINE_LOCATION"</span> <span class="attr">android:maxSdkVersion</span>=<span class="string">"30"</span>/></span></span><br><span class="line"></span><br><span class="line"> <span class="comment"><!-- Request Target API >= 31, Bluetooth permissions. --></span></span><br><span class="line"> <span class="tag"><<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.BLUETOOTH_SCAN"</span> <span class="attr">android:usesPermissionFlags</span> = <span class="string">"neverForLocation"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.BLUETOOTH_CONNECT"</span> /></span></span><br><span class="line"> ...</span><br><span class="line"><span class="tag"></<span class="name">manifest</span>></span></span><br></pre></td></tr></table></figure><h1 id="Phone-Bluetooth-State"><a href="#Phone-Bluetooth-State" class="headerlink" title="Phone Bluetooth State"></a>Phone Bluetooth State</h1><ul><li>BluetoothAdapter<br>Pop up warning message if bluetooth setting is turned off.<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">checkBLESetting</span><span class="params">()</span></span>: <span class="built_in">Boolean</span>{</span><br><span class="line"> <span class="comment">//Check if phone support bluetooth and bluetooth is on</span></span><br><span class="line"> <span class="keyword">val</span> bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()</span><br><span class="line"> <span class="keyword">if</span> (!bluetoothAdapter.isEnabled){</span><br><span class="line"></span><br><span class="line"> mAlertDlg = AlertDialog.Builder(<span class="keyword">this</span>)</span><br><span class="line"> .setMessage(<span class="string">"Please turn on Bluetooth setting!"</span>)</span><br><span class="line"> .setTitle(<span class="string">""</span>)</span><br><span class="line"> .setPositiveButton(<span class="string">"OK"</span>) { _, _ -></span><br><span class="line"> startActivity(Intent(Settings.ACTION_BLUETOOTH_SETTINGS))</span><br><span class="line"> mAlertDlg = <span class="literal">null</span></span><br><span class="line"> }</span><br><span class="line"> .setNeutralButton(<span class="string">"Cancel"</span>) { _, _ -></span><br><span class="line"> mAlertDlg = <span class="literal">null</span></span><br><span class="line"> }</span><br><span class="line"> .create()</span><br><span class="line"></span><br><span class="line"> mAlertDlg?.show()</span><br><span class="line"> mAlertDlg?.getButton(AlertDialog.BUTTON_NEUTRAL)?.setTextColor(ContextCompat.getColor(<span class="keyword">this</span>, R.color.blue));</span><br><span class="line"> mAlertDlg?.getButton(AlertDialog.BUTTON_POSITIVE)?.setTextColor(ContextCompat.getColor(<span class="keyword">this</span>, R.color.blue));</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h1 id="Check-amp-request-for-Permission"><a href="#Check-amp-request-for-Permission" class="headerlink" title="Check & request for Permission"></a>Check & request for Permission</h1><ul><li><p>Request for BLE permission</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">requestPermissions</span><span class="params">(requestCode: <span class="type">Int</span>)</span></span> {</span><br><span class="line"> <span class="keyword">if</span> (Build.VERSION.SDK_INT > <span class="number">30</span>) {</span><br><span class="line"> requirePermission(</span><br><span class="line"> arrayOf(</span><br><span class="line"> Manifest.permission.BLUETOOTH_CONNECT,</span><br><span class="line"> Manifest.permission.BLUETOOTH_SCAN,</span><br><span class="line"> ), requestCode = requestCode</span><br><span class="line"> )</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> requirePermission(</span><br><span class="line"> arrayOf(</span><br><span class="line"> Manifest.permission.BLUETOOTH,</span><br><span class="line"> Manifest.permission.BLUETOOTH_ADMIN,</span><br><span class="line"> Manifest.permission.ACCESS_FINE_LOCATION</span><br><span class="line"> ), requestCode = requestCode</span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>Check for BLE permission</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">checkBLEPermission</span><span class="params">()</span></span>: <span class="built_in">Boolean</span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">//Check permissions for ACCESS_FINE_LOCATION if API < 30</span></span><br><span class="line"> <span class="comment">//Check permissions for BLUETOOTH_SCAN if API > 30</span></span><br><span class="line"> <span class="keyword">if</span> (Build.VERSION.SDK_INT > <span class="number">30</span>){</span><br><span class="line"></span><br><span class="line"> <span class="keyword">when</span> (PermissionChecker.checkSelfPermission(</span><br><span class="line"> <span class="keyword">this</span>,</span><br><span class="line"> Manifest.permission.BLUETOOTH_SCAN</span><br><span class="line"> )) {</span><br><span class="line"> PackageManager.PERMISSION_GRANTED -> {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> -> {</span><br><span class="line"> <span class="keyword">if</span> (shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH_SCAN)) {</span><br><span class="line"> <span class="comment">// ask user to grant the permission and redirect to the app setting </span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line"> }</span><br><span class="line"> requestPermissions(<span class="number">1</span>)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }<span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">when</span> (PermissionChecker.checkSelfPermission(</span><br><span class="line"> <span class="keyword">this</span>,</span><br><span class="line"> Manifest.permission.ACCESS_FINE_LOCATION</span><br><span class="line"> )) {</span><br><span class="line"> PackageManager.PERMISSION_GRANTED -> {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> -> {</span><br><span class="line"> <span class="keyword">if</span> (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) {</span><br><span class="line"> <span class="comment">// ask user to grant the permission and redirect to the app setting </span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line"> }</span><br><span class="line"> requestPermissions(<span class="number">1</span>)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h1 id="Start-amp-Stop-Scan"><a href="#Start-amp-Stop-Scan" class="headerlink" title="Start & Stop Scan"></a>Start & Stop Scan</h1><ul><li><p>BluetoothLeScanner</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">val</span> mBluetoothLeScanner: BluetoothLeScanner</span><br><span class="line"> <span class="keyword">get</span>() {</span><br><span class="line"> <span class="keyword">val</span> bluetoothManager = applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) <span class="keyword">as</span> BluetoothManager</span><br><span class="line"> <span class="keyword">val</span> bluetoothAdapter = bluetoothManager.adapter</span><br><span class="line"> <span class="keyword">return</span> bluetoothAdapter.bluetoothLeScanner</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></li><li><p>ScanCallback</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">val</span> mScanCallback = <span class="keyword">object</span> : ScanCallback() {</span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onScanResult</span><span class="params">(callbackType: <span class="type">Int</span>, result: <span class="type">ScanResult</span>?)</span></span> {</span><br><span class="line"> <span class="keyword">super</span>.onScanResult(callbackType, result)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> device = result?.device</span><br><span class="line"> <span class="keyword">val</span> deviceName = result?.device?.name</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (deviceName != <span class="literal">null</span> && deviceName != <span class="string">""</span>){</span><br><span class="line"> Log.d(</span><br><span class="line"> <span class="string">"ScanDeviceActivity"</span>,</span><br><span class="line"> <span class="string">"onScanResult(): <span class="subst">${result?.device?.address}</span> - <span class="subst">${result?.device?.name}</span> <span class="subst">${result.rssi}</span>"</span></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onScanFailed</span><span class="params">(errorCode: <span class="type">Int</span>)</span></span> {</span><br><span class="line"> <span class="keyword">super</span>.onScanFailed(errorCode)</span><br><span class="line"> Log.d(<span class="string">"ttt"</span>, <span class="string">"onScanFailed: <span class="variable">$errorCode</span>"</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>Start scan</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mBluetoothLeScanner.startScan(mScanCallback)</span><br></pre></td></tr></table></figure></li><li><p>Stop scan</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mBluetoothLeScanner.stopScan(mScanCallback)</span><br></pre></td></tr></table></figure></li></ul><h1 id="Connect-amp-Get-servers-amp-characteristics"><a href="#Connect-amp-Get-servers-amp-characteristics" class="headerlink" title="Connect & Get servers & characteristics"></a>Connect & Get servers & characteristics</h1><ul><li><p>BluetoothGattServerCallback</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">val</span> gattCallback = <span class="keyword">object</span> : BluetoothGattCallback() {</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onConnectionStateChange</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function"> gatt: <span class="type">BluetoothGatt</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> status: <span class="type">Int</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> newState: <span class="type">Int</span></span></span></span><br><span class="line"><span class="params"><span class="function"> )</span></span> {</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">val</span> intentAction: String</span><br><span class="line"> <span class="keyword">when</span> (newState) {</span><br><span class="line"> </span><br><span class="line"> BluetoothProfile.STATE_CONNECTED -> {</span><br><span class="line"> gatt.discoverServices()</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> BluetoothProfile.STATE_DISCONNECTED -> {</span><br><span class="line"> </span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// New services discovered</span></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onServicesDiscovered</span><span class="params">(gatt: <span class="type">BluetoothGatt</span>, status: <span class="type">Int</span>)</span></span> {</span><br><span class="line"> <span class="keyword">when</span> (status) {</span><br><span class="line"> BluetoothGatt.GATT_SUCCESS -> {</span><br><span class="line"> <span class="keyword">val</span> gattServices = gatt.services</span><br><span class="line"> gattServices.forEach { gattService -></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">val</span> uuid = gattService.uuid.toString()</span><br><span class="line"> <span class="keyword">val</span> gattCharacteristics = gattService.characteristics</span><br><span class="line"> </span><br><span class="line"> gattCharacteristics.forEach { gattCharacteristic -></span><br><span class="line"> uuid = gattCharacteristic.uuid.toString()</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Result of a characteristic read operation</span></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onCharacteristicRead</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function"> gatt: <span class="type">BluetoothGatt</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> characteristic: <span class="type">BluetoothGattCharacteristic</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> status: <span class="type">Int</span></span></span></span><br><span class="line"><span class="params"><span class="function"> )</span></span> {</span><br><span class="line"> <span class="keyword">when</span> (status) {</span><br><span class="line"> BluetoothGatt.GATT_SUCCESS -> {</span><br><span class="line"> </span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onCharacteristicRead</span><span class="params">(BluetoothGatt gatt, </span></span></span><br><span class="line"><span class="params"><span class="function"> BluetoothGattCharacteristic characteristic, byte[] value, int status)</span></span>{</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onCharacteristicChanged</span><span class="params">(BluetoothGatt gatt, </span></span></span><br><span class="line"><span class="params"><span class="function"> BluetoothGattCharacteristic characteristic, byte[] value)</span></span>{</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onCharacteristicWrite</span><span class="params">(BluetoothGatt gatt, </span></span></span><br><span class="line"><span class="params"><span class="function"> BluetoothGattCharacteristic characteristic, int status)</span></span>{</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>Connect</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> bluetoothGatt = device.connectGatt(<span class="keyword">this</span>, <span class="literal">false</span>, gattCallback)</span><br></pre></td></tr></table></figure></li><li><p>Reconnect</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">bluetoothGatt?.let { gatt -></span><br><span class="line"> gatt.connect()</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>Disconnect</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">bluetoothGatt?.let { gatt -></span><br><span class="line"> gatt.disconnect()</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>Close</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">bluetoothGatt?.let { gatt -></span><br><span class="line"> gatt.close()</span><br><span class="line"> bluetoothGatt = <span class="literal">null</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h1 id="Read-amp-Write-operations"><a href="#Read-amp-Write-operations" class="headerlink" title="Read & Write operations"></a>Read & Write operations</h1><ul><li><p>Read</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">bluetoothGatt?.let{ gatt -></span><br><span class="line"> gatt.readCharacteristic(rxChar)</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>Write</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">bluetoothGatt?.let{ gatt -></span><br><span class="line"> gatt.writeCharacteristic(txChar, value, WRITE_TYPE_DEFAULT)</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>Set notification</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">bluetoothGatt?.let{ gatt -></span><br><span class="line"> gatt.setCharacteristicNotification(rxChar, <span class="literal">true</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h1 id="References"><a href="#References" class="headerlink" title="References"></a>References</h1><p><a href="https://developer.android.com/reference/android/bluetooth/le/package-summary">https://developer.android.com/reference/android/bluetooth/le/package-summary</a></p><p><a href="https://developer.android.com/reference/android/bluetooth/BluetoothGatt">https://developer.android.com/reference/android/bluetooth/BluetoothGatt</a></p><p><a href="https://developer.android.com/reference/android/bluetooth/BluetoothGattCallback">https://developer.android.com/reference/android/bluetooth/BluetoothGattCallback</a></p><p><a href="https://developer.android.com/reference/android/bluetooth/le/BluetoothLeScanner">https://developer.android.com/reference/android/bluetooth/le/BluetoothLeScanner</a></p><p><a href="https://developer.android.com/reference/android/bluetooth/le/ScanCallback">https://developer.android.com/reference/android/bluetooth/le/ScanCallback</a></p><p><a href="https://developer.android.com/reference/android/bluetooth/BluetoothManager">https://developer.android.com/reference/android/bluetooth/BluetoothManager</a></p><p><a href="https://developer.android.com/reference/android/bluetooth/BluetoothGattServerCallback">https://developer.android.com/reference/android/bluetooth/BluetoothGattServerCallback</a></p>]]></content>
<categories>
<category> APP Development </category>
</categories>
<tags>
<tag> Bluetooth </tag>
</tags>
</entry>
<entry>
<title>iOS Bluetooth App Development</title>
<link href="/2023/10/09/APP%20Development/ios-bluetooth/"/>
<url>/2023/10/09/APP%20Development/ios-bluetooth/</url>
<content type="html"><![CDATA[<h1 id="Framework"><a href="#Framework" class="headerlink" title="Framework"></a>Framework</h1><p>CoreBluetooth.Framework</p><h1 id="Privacy"><a href="#Privacy" class="headerlink" title="Privacy"></a>Privacy</h1><p>Privacy - Bluetooth Always Usage Description</p><h1 id="Check-Bluetooth-State"><a href="#Check-Bluetooth-State" class="headerlink" title="Check Bluetooth State"></a>Check Bluetooth State</h1><p>Init CBCentralManager object and will get the bluetooth state from the CBCentralManagerDelegate. Must handle the bluetooth power off state. </p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">func</span> <span class="title function_">centralManagerDidUpdateState</span>(<span class="keyword">_</span> <span class="params">central</span>: <span class="type">CBCentralManager</span>){</span><br><span class="line"> <span class="keyword">var</span> state : <span class="type">String</span></span><br><span class="line"> <span class="keyword">switch</span>(central.state){</span><br><span class="line"> <span class="keyword">case</span> .poweredOn:</span><br><span class="line"> <span class="keyword">case</span> .poweredOff:</span><br><span class="line"> <span class="keyword">case</span> .resetting:</span><br><span class="line"> <span class="keyword">case</span> .unauthorized:</span><br><span class="line"> <span class="keyword">case</span> .unsupported:</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><h1 id="Check-amp-ask-for-Bluetooth-Permission"><a href="#Check-amp-ask-for-Bluetooth-Permission" class="headerlink" title="Check & ask for Bluetooth Permission"></a>Check & ask for Bluetooth Permission</h1><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> <span class="type">CBPeripheralManager</span>.authorization <span class="operator">==</span> .denied{</span><br><span class="line"> <span class="comment">// ask user to grant the bluetooth permission in app setting. </span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="Scan-for-Bluetooth"><a href="#Scan-for-Bluetooth" class="headerlink" title="Scan for Bluetooth"></a>Scan for Bluetooth</h1><ul><li><p>Init CBCentralManager object.</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">centralManager <span class="operator">=</span> <span class="type">CBCentralManager</span>(delegate: <span class="keyword">self</span>, <span class="literal">nil</span>)</span><br></pre></td></tr></table></figure></li><li><p>Start scan</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">centralManager <span class="operator">=</span> <span class="type">CBCentralManager</span>(delegate: <span class="keyword">self</span>, queue: centralQueue)</span><br><span class="line">centralManager.scanForPeripherals(withServices: serviceUUIDs, options: <span class="literal">nil</span>)</span><br></pre></td></tr></table></figure></li><li><p>Handle the function below in the CBCentralManagerDelegate</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">func</span> <span class="title function_">centralManager</span>(<span class="keyword">_</span> <span class="params">central</span>: <span class="type">CBCentralManager</span>, </span><br><span class="line"> <span class="params">didDiscover</span> <span class="params">peripheral</span>: <span class="type">CBPeripheral</span>, </span><br><span class="line"> <span class="params">advertisementData</span>: [<span class="params">String</span> : <span class="keyword">Any</span>], </span><br><span class="line"> <span class="params">rssi</span> <span class="params">RSSI</span>: <span class="type">NSNumber</span>){</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>Stop scan</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">centralManager.stopScan()</span><br></pre></td></tr></table></figure></li></ul><h1 id="Connect-amp-Disconnect-with-device"><a href="#Connect-amp-Disconnect-with-device" class="headerlink" title="Connect & Disconnect with device"></a>Connect & Disconnect with device</h1><ul><li><p>Connect with a peripheral</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">centralManager.connect(peripheral, options: <span class="literal">nil</span>)</span><br></pre></td></tr></table></figure></li><li><p>Disconnect with a peripheral</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">centralManager.cancelPeripheralConnection(peripheral)</span><br></pre></td></tr></table></figure></li><li><p>Handle the functions below in the CBCentralManagerDelegate</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">func</span> <span class="title function_">centralManager</span>(<span class="keyword">_</span> <span class="params">central</span>: <span class="type">CBCentralManager</span>, </span><br><span class="line"> <span class="params">didConnect</span> <span class="params">peripheral</span>: <span class="type">CBPeripheral</span>){</span><br><span class="line"> </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">func</span> <span class="title function_">centralManager</span>(<span class="keyword">_</span> <span class="params">central</span>: <span class="type">CBCentralManager</span>, </span><br><span class="line"> <span class="params">didFailToConnect</span> <span class="params">peripheral</span>: <span class="type">CBPeripheral</span>, <span class="params">error</span>: <span class="type">Error</span>?){</span><br><span class="line"> </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">func</span> <span class="title function_">centralManager</span>(<span class="keyword">_</span> <span class="params">central</span>: <span class="type">CBCentralManager</span>, </span><br><span class="line"> <span class="params">didDisconnectPeripheral</span> <span class="params">peripheral</span>: <span class="type">CBPeripheral</span>, <span class="params">error</span>: <span class="type">Error</span>?){</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>Handle the functions below in the CBPeripheralDelegate</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">peripheralDidUpdateRSSI</span>(<span class="keyword">_</span> <span class="params">peripheral</span>: <span class="type">CBPeripheral</span>, <span class="params">error</span>: <span class="type">Error</span>?){</span><br><span class="line"> bluetoothPeripheral.readRSSI()</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">func</span> <span class="title function_">peripheral</span>(<span class="keyword">_</span> <span class="params">peripheral</span>: <span class="type">CBPeripheral</span>, <span class="params">didReadRSSI</span> <span class="params">RSSI</span>: <span class="type">NSNumber</span>, <span class="params">error</span>: <span class="type">Error</span>?){</span><br><span class="line"> <span class="comment">//print("rssi = \(RSSI)")</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h1 id="Get-Connected-device-services-amp-characteristics"><a href="#Get-Connected-device-services-amp-characteristics" class="headerlink" title="Get Connected device services & characteristics"></a>Get Connected device services & characteristics</h1><ul><li>Discover services<figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">peripheral.discoverServices([<span class="type">ServiceUUID</span>])</span><br></pre></td></tr></table></figure></li><li>Discover characteristics and handle the functions below in the CBPeripheralDelegate<figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">peripheral</span>(<span class="keyword">_</span> <span class="params">peripheral</span>: <span class="type">CBPeripheral</span>, <span class="params">didDiscoverServices</span> <span class="params">error</span>: <span class="type">Error</span>?){</span><br><span class="line"> <span class="keyword">guard</span> error <span class="operator">==</span> <span class="literal">nil</span> <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//Handle the error</span></span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> aService: <span class="type">CBService</span> <span class="keyword">in</span> peripheral.services<span class="operator">!</span> {</span><br><span class="line"> <span class="keyword">if</span> aService.uuid.isEqual(<span class="type">YourServiceUUID</span>) {</span><br><span class="line"> peripheral.discoverCharacteristics(<span class="literal">nil</span>, for: aService)</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">func</span> <span class="title function_">peripheral</span>(<span class="keyword">_</span> <span class="params">peripheral</span>: <span class="type">CBPeripheral</span>, <span class="params">didDiscoverCharacteristicsFor</span> <span class="params">service</span>: <span class="type">CBService</span>, <span class="params">error</span>: <span class="type">Error</span>?){</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">guard</span> error <span class="operator">==</span> <span class="literal">nil</span> <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//Handle the error</span></span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> service.uuid.isEqual(<span class="type">YourServiceUUID</span>) {</span><br><span class="line"> <span class="keyword">for</span> aCharacteristic : <span class="type">CBCharacteristic</span> <span class="keyword">in</span> service.characteristics<span class="operator">!</span> {</span><br><span class="line"> <span class="keyword">if</span> aCharacteristic.uuid.isEqual(<span class="type">YourTXCharacteristicUUID</span>) {</span><br><span class="line"> logw(<span class="string">"TX Characteristic found"</span>)</span><br><span class="line"> theTXCharacteristic <span class="operator">=</span> aCharacteristic</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> aCharacteristic.uuid.isEqual(<span class="type">YourRXCharacteristicUUID</span>) {</span><br><span class="line"> logw(<span class="string">"RX Characteristic found"</span>)</span><br><span class="line"> theRXCharacteristic <span class="operator">=</span> aCharacteristic</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h1 id="Read-amp-Write-operations"><a href="#Read-amp-Write-operations" class="headerlink" title="Read & Write operations"></a>Read & Write operations</h1><ul><li><p>Read data</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">If successful, the peripheral then calls the peripheral(_:didUpdateValueFor:error:) method of its delegate object whenever the characteristic value changes.</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="type">Peripheral</span>.setNotifyValue(<span class="literal">true</span>, for: theRXCharacteristic)</span><br></pre></td></tr></table></figure></li><li><p>Write data</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">peripheral.writeValue(data, for: txChar, type: .withResponse)</span><br></pre></td></tr></table></figure></li><li><p>Handle the functions below in the CBPeripheralDelegate</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">peripheral</span>(<span class="keyword">_</span> <span class="params">peripheral</span>: <span class="type">CBPeripheral</span>, <span class="params">didUpdateValueFor</span> <span class="params">characteristic</span>: <span class="type">CBCharacteristic</span>, <span class="params">error</span>: <span class="type">Error</span>?){</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">guard</span> error <span class="operator">==</span> <span class="literal">nil</span> <span class="keyword">else</span> {</span><br><span class="line"> logw(<span class="string">"Updating characteristic has failed <span class="subst">\(error<span class="operator">?</span>.localizedDescription <span class="operator">??</span> <span class="string">""</span>)</span>"</span>)</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// try to print a friendly string of received bytes if they can be parsed as UTF8</span></span><br><span class="line"> <span class="keyword">guard</span> <span class="keyword">let</span> dataReceived <span class="operator">=</span> characteristic.value <span class="keyword">else</span> {</span><br><span class="line"> logw(<span class="string">"Notification received from: <span class="subst">\(characteristic.uuid.uuidString)</span>, with empty value"</span>)</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//let str : String = NSString(data: dataReceived, encoding: String.Encoding.utf8.rawValue)! as String</span></span><br><span class="line"> logw(<span class="string">"<span class="subst">\(Date().timeIntervalSince1970<span class="operator">*</span><span class="number">1000</span>)</span> didReceiveData <span class="subst">\(dataReceived.count)</span> from characteristic <span class="subst">\(characteristic.uuid.uuidString)</span>"</span>)</span><br><span class="line"> <span class="operator">...</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">func</span> <span class="title function_">peripheral</span>(<span class="keyword">_</span> <span class="params">peripheral</span>: <span class="type">CBPeripheral</span>, <span class="params">didWriteValueFor</span> <span class="params">characteristic</span>: <span class="type">CBCharacteristic</span>, <span class="params">error</span>: <span class="type">Error</span>?){</span><br><span class="line"> <span class="keyword">guard</span> error <span class="operator">==</span> <span class="literal">nil</span> <span class="keyword">else</span> {</span><br><span class="line"> logw(<span class="string">"Writing value to characteristic failed <span class="subst">\(error<span class="operator">?</span>.localizedDescription <span class="operator">??</span> <span class="string">""</span>)</span>"</span>)</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//delegate?.writeDone?(ret: true)</span></span><br><span class="line"> logw(<span class="string">"Data has written to characteristic: <span class="subst">\(characteristic.uuid.uuidString)</span>"</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h1 id="References"><a href="#References" class="headerlink" title="References"></a>References</h1><p><a href="https://developer.apple.com/documentation/corebluetooth/cbcentralmanager">https://developer.apple.com/documentation/corebluetooth/cbcentralmanager</a></p><p><a href="https://developer.apple.com/documentation/corebluetooth/cbperipheral">https://developer.apple.com/documentation/corebluetooth/cbperipheral</a></p><p><a href="https://developer.apple.com/documentation/corebluetooth/cbcentralmanagerdelegate">https://developer.apple.com/documentation/corebluetooth/cbcentralmanagerdelegate</a></p><p><a href="https://developer.apple.com/documentation/corebluetooth/cbperipheraldelegate">https://developer.apple.com/documentation/corebluetooth/cbperipheraldelegate</a></p><p><a href="https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/CoreBluetoothBackgroundProcessingForIOSApps/PerformingTasksWhileYourAppIsInTheBackground.html">https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/CoreBluetoothBackgroundProcessingForIOSApps/PerformingTasksWhileYourAppIsInTheBackground.html</a></p>]]></content>
<categories>
<category> APP Development </category>
</categories>
<tags>
<tag> Bluetooth </tag>
</tags>
</entry>
<entry>
<title>iOS ARKit Practice</title>
<link href="/2023/09/27/Projects/iOS-ARKit/"/>
<url>/2023/09/27/Projects/iOS-ARKit/</url>
<content type="html"><![CDATA[<h1 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h1><blockquote><p>This is a practice iOS app that utilizes the ARKit framework for attaching virtual objects to horizontal planes, performing image recognition, and conducting 3D object detection.</p></blockquote><h1 id="APP-TestFlight-installation-QRCode"><a href="#APP-TestFlight-installation-QRCode" class="headerlink" title="APP TestFlight installation QRCode"></a>APP TestFlight installation QRCode</h1><p>You can install the app from TestFlight by using your iPhone/iPad to scan the QR code below.<br><img src="/img/arkit/qrcode.png" width="40%"></p><h1 id="Main-functions"><a href="#Main-functions" class="headerlink" title="Main functions"></a>Main functions</h1><h2 id="🔸-Attaching-virtual-object-to-a-horizontal-plane"><a href="#🔸-Attaching-virtual-object-to-a-horizontal-plane" class="headerlink" title="🔸 Attaching virtual object to a horizontal plane"></a>🔸 Attaching virtual object to a horizontal plane</h2><p>Run the app and click the “Place 3D Object on a Horizontal Surface” button. When you point the phone camera lens at the tabletop, an astronaut figurine will appear, and you can rotate the figurine by swiping right or left.<br>APP Screen capture<br><img src="/img/arkit/demo-3.jpeg"></p><h2 id="🔸-Attaching-virtual-object-to-a-specific-image-image-recognition"><a href="#🔸-Attaching-virtual-object-to-a-specific-image-image-recognition" class="headerlink" title="🔸 Attaching virtual object to a specific image (image recognition)"></a>🔸 Attaching virtual object to a specific image (image recognition)</h2><p>Run the app and click the “Recognize 2D Image & Generate 3D Model” button. When you point the phone camera lens at the images below, different 3D model will appear.<br><img src="/img/arkit/coffee.jpg"><br><img src="/img/arkit/spaceship.jpg"><br><img src="/img/arkit/viking.png"><br>APP Screen capture<br><img src="/img/arkit/demo-2.jpeg"></p><h2 id="🔸-3D-object-detection"><a href="#🔸-3D-object-detection" class="headerlink" title="🔸 3D object detection"></a>🔸 3D object detection</h2><p>The APP can detect three objects below. Once the object is detected, the APP will add a label to mark the object.</p><ul><li>正哲生技厚味蘇打餅. You can buy it from Carrefour.</li><li>優識立New Lutein. You can buy it from Costco.</li><li>Viking Lander model. You can print out the 3D model of the Viking lander. 3D printing and assembly document can be found at <a href="https://nasa3d.arc.nasa.gov/detail/viking-lander">https://nasa3d.arc.nasa.gov/detail/viking-lander</a></li></ul><p>APP Screen capture<br><img src="/img/arkit/demo-1.jpeg"></p><h1 id="Development-IDE-amp-languages-amp-frameworks"><a href="#Development-IDE-amp-languages-amp-frameworks" class="headerlink" title="Development IDE & languages & frameworks"></a>Development IDE & languages & frameworks</h1><blockquote><p>XCode, Swift, UIkit, ARKit</p></blockquote><h1 id="Demo-Video"><a href="#Demo-Video" class="headerlink" title="Demo Video"></a>Demo Video</h1><div class="video-container"><iframe src="https://www.youtube.com/embed/qrDFb4gNoRI" frameborder="0" loading="lazy" allowfullscreen></iframe></div>]]></content>
<categories>
<category> Projects </category>
</categories>
</entry>
<entry>
<title>Update the Android APP which uses nordic DFU</title>
<link href="/2023/08/16/APP%20Development/update-android-using-dfu/"/>
<url>/2023/08/16/APP%20Development/update-android-using-dfu/</url>
<content type="html"><![CDATA[<h2 id="New-target-API-requirement-from-Google"><a href="#New-target-API-requirement-from-Google" class="headerlink" title="New target API requirement from Google"></a>New target API requirement from Google</h2><p><a href="https://developer.android.com/google/play/requirements/target-sdk">https://developer.android.com/google/play/requirements/target-sdk</a></p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">android {</span><br><span class="line"> compileSdkVersion <span class="number">33</span></span><br><span class="line"> buildToolsVersion <span class="string">"33.0.2"</span></span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="android-exported"><a href="#android-exported" class="headerlink" title="android:exported"></a>android:exported</h2><p><a href="https://developer.android.com/about/versions/12/behavior-changes-12">https://developer.android.com/about/versions/12/behavior-changes-12</a><br>For Android 12 or higher, set “android:exported” attribute to “true” in the launch activity.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><activity android:name=".activity.SplashActivity" android:exported="true"></span><br></pre></td></tr></table></figure><h2 id="New-bluetooth-permissions-from-Google"><a href="#New-bluetooth-permissions-from-Google" class="headerlink" title="New bluetooth permissions from Google"></a>New bluetooth permissions from Google</h2><p><a href="https://developer.android.com/guide/topics/connectivity/bluetooth/permissions">https://developer.android.com/guide/topics/connectivity/bluetooth/permissions</a><br>For Android 12 lower, ACCESS_FINE_LOCATION & ACCESS_COARSE_LOCATION is required.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><manifest></span><br><span class="line"> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /></span><br><span class="line"> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /></span><br><span class="line"></span><br><span class="line"> ...</span><br><span class="line"></manifest></span><br></pre></td></tr></table></figure><p>For Android 12 or higher, add the new permissions below.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><manifest></span><br><span class="line"> <uses-permission android:name="android.permission.BLUETOOTH"</span><br><span class="line"> android:maxSdkVersion="30" /></span><br><span class="line"> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"</span><br><span class="line"> android:maxSdkVersion="30" /></span><br><span class="line"></span><br><span class="line"> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /></span><br><span class="line"> <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /></span><br><span class="line"> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /></span><br><span class="line"></span><br><span class="line"> ...</span><br><span class="line"></manifest></span><br></pre></td></tr></table></figure><p>Request for the BLE related permissions<br><a href="https://developer.android.com/training/permissions/requesting">https://developer.android.com/training/permissions/requesting</a></p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (Build.VERSION.SDK_INT < <span class="number">31</span>) {</span><br><span class="line"> requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,</span><br><span class="line"> Manifest.permission.ACCESS_FINE_LOCATION),</span><br><span class="line"> <span class="number">2</span>)</span><br><span class="line">}<span class="keyword">else</span>{</span><br><span class="line"> requestPermissions(arrayOf(Manifest.permission.BLUETOOTH_CONNECT,</span><br><span class="line"> Manifest.permission.BLUETOOTH_SCAN,</span><br><span class="line"> Manifest.permission.BLUETOOTH_ADVERTISE),</span><br><span class="line"> <span class="number">2</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Update-DFU-lib-to-the-latest-version"><a href="#Update-DFU-lib-to-the-latest-version" class="headerlink" title="Update DFU lib to the latest version"></a>Update DFU lib to the latest version</h2><ul><li><p>DFU lib to the latest version<br>Was using the 1.11.0 and crashed when running on Android 12 phone.<br>Solution: Update the Nordic DFU lib to the latest version (current 2.3.0)<br><a href="https://github.com/NordicSemiconductor/Android-DFU-Library">https://github.com/NordicSemiconductor/Android-DFU-Library</a></p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">dependencies</span> {</span><br><span class="line"> </span><br><span class="line"> implementation <span class="string">'no.nordicsemi.android:dfu:2.3.0'</span></span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>Kotlin version to 1.7.20<br>Was using the ext.kotlin_version = “1.4.32” and got build error after updating the DFU lib to 2.3.0.<br>Solution: </p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">buildscript</span> {</span><br><span class="line"> ext.kotlin_version = <span class="string">"1.7.20"</span></span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>Add foreground service permission to the project’s AndroidManifest.xml<br>Android foreground service:<br><a href="https://developer.android.com/guide/components/foreground-services">https://developer.android.com/guide/components/foreground-services</a></p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.FOREGROUND_SERVICE"</span>/></span></span><br></pre></td></tr></table></figure></li></ul>]]></content>
<categories>
<category> APP Development </category>
</categories>
<tags>
<tag> Nordic DFU </tag>
</tags>
</entry>
<entry>
<title>工欲善其事,必先利其器 - 開發電腦及配件選擇</title>
<link href="/2023/07/27/APP%20Development/development-computer/"/>
<url>/2023/07/27/APP%20Development/development-computer/</url>
<content type="html"><![CDATA[<p>APP 開發當然選擇Mac電腦</p><h2 id="🔸-Mac電腦選擇"><a href="#🔸-Mac電腦選擇" class="headerlink" title="🔸 Mac電腦選擇"></a>🔸 Mac電腦選擇</h2><ul><li><p>Budget 允許的話直接買 16-inch MacBook Pro 標準頂規+<br><a href="https://www.apple.com/tw/shop/buy-mac/macbook-pro/16-%E5%90%8B-%E5%A4%AA%E7%A9%BA%E7%81%B0%E8%89%B2-apple-m2-max-%E9%85%8D%E5%82%99-12-%E6%A0%B8%E5%BF%83-cpu-%E8%88%87-38-%E6%A0%B8%E5%BF%83-gpu-1tb#">MacBook Pro 16-inch Apple M2 Max, 32GB Memory, 1TB SSD </a></p></li><li><p>Budget 有限的話買 14-inch MacBook Pro 低標<br><a href="https://www.apple.com/tw/shop/buy-mac/macbook-pro/14-%E5%90%8B-%E5%A4%AA%E7%A9%BA%E7%81%B0%E8%89%B2-apple-m2-pro-%E9%85%8D%E5%82%99-10-%E6%A0%B8%E5%BF%83-cpu-%E8%88%87-16-%E6%A0%B8%E5%BF%83-gpu-512gb#">MacBook Pro 14-inch Apple M2 Pro, 16GB memory, 512GB SSD</a></p></li></ul><p>不推薦memory低於16GB的Mac電腦,真的不夠用。512 SSD 勉強及格,建議 1T SSD。<br>不推薦 Mac mini,買了mini你還要買顯示器,鍵盤,巧控板來配,沒有比較省,還不方便攜帶。</p><h2 id="🔸-Mac電腦顯示器選擇"><a href="#🔸-Mac電腦顯示器選擇" class="headerlink" title="🔸 Mac電腦顯示器選擇"></a>🔸 Mac電腦顯示器選擇</h2><p>程式開發當然要有大顯示器,雙顯示器。</p><ul><li><h3 id="Apple-Studio-Display-5K-27-inch-✅"><a href="#Apple-Studio-Display-5K-27-inch-✅" class="headerlink" title="Apple Studio Display (5K 27-inch) ✅"></a>Apple Studio Display (5K 27-inch) ✅</h3></li></ul><p>Apple電腦就是要配Apple的顯示器,用過Apple Studio Display,你就<strong>回不去了</strong>。<br>Apple Studio Display 的包裝盒巨大,盒子本身就和顯示器差不多重, 到店取貨時被震撼了。</p><ul><li><h3 id="ASUS-VP28U-4K-28-inch"><a href="#ASUS-VP28U-4K-28-inch" class="headerlink" title="ASUS VP28U (4K 28-inch)"></a>ASUS VP28U (4K 28-inch)</h3></li></ul><p>和Mac電腦的顯示相比色差嚴重!美工應該不能用!<br>公司買的顯示器,搭配 Mac mini (2020) Apple M1,使用第一年還ok,第二年開始閃屏,尤其是使用 Android Studio 的時候,不能確定是不是OS升級引起的,但接我的 Macbook Pro 16 Intel 是正常的! 接我的 Macbook Pro 16 Apple M2 Max 閃屏更為嚴重, 根本無法使用!</p><ul><li><h3 id="ASUS-4K-32-inch"><a href="#ASUS-4K-32-inch" class="headerlink" title="ASUS (4K 32-inch)"></a>ASUS (4K 32-inch)</h3></li></ul><p>自己買的顯示器,一樣色差嚴重!接我 Macbook Pro 16-inch Apple M2 目前到是沒有閃屏問題。</p><ul><li><h3 id="LG-Ergo支架-4K-32-inch"><a href="#LG-Ergo支架-4K-32-inch" class="headerlink" title="LG Ergo支架 (4K 32-inch)"></a>LG Ergo支架 (4K 32-inch)</h3></li></ul><p>朋友買來配 Macbook Pro 16,具體型號不記得了,看了介紹影片覺得Ergo支架真的很酷,但朋友用了一天說看不慣,不喜歡。</p><h2 id="🔸-iOS-開發&測試裝置-iPhone-x2F-iPad-x2F-Watch"><a href="#🔸-iOS-開發&測試裝置-iPhone-x2F-iPad-x2F-Watch" class="headerlink" title="🔸 iOS 開發&測試裝置 iPhone / iPad / Watch"></a>🔸 iOS 開發&測試裝置 iPhone / iPad / Watch</h2><ul><li>開發:至少一隻iPhone,一隻iPad,都要可以追到最新版。</li><li>測試:如果你的 APP Supports iOS13+,比較完整的是測試從 iOS 13 起的所有版本。 至少要測最新的三個版本: iOS14, iOS15, iOS16(目前最新)。</li><li>我目前使用: iPhone12 + iPad Air Apple M1 + Apple Watch S8</li></ul><h2 id="🔸-Android-開發&測試裝置"><a href="#🔸-Android-開發&測試裝置" class="headerlink" title="🔸 Android 開發&測試裝置"></a>🔸 Android 開發&測試裝置</h2><ul><li>開發:推薦Google Pixel,要可以追到最新版。</li><li>測試: 如果你的 APP Supports Android 8+,比較完整的是測試從 Android 8 起的所有版本。 至少要測最新的三個版本:Android11, Android12, Android13(目前最新)。 一定要測不同廠牌的手機,越多越好,市占率高的手機一定要測,比如:Samsung, Oppo …</li><li>我目前使用: Samsung Note10, Oppo Reno5, Mi5</li></ul><h2 id="🔸-配件"><a href="#🔸-配件" class="headerlink" title="🔸 配件"></a>🔸 配件</h2><ul><li><p>鍵盤<br>有外接顯示器就要買鍵盤,巧控鍵盤就可以了。</p></li><li><p>巧控板<br>可以不買,我把電腦放在顯示器右邊,直接使用Macbook pro上的觸控式軌跡板。</p></li><li><p>巧控滑鼠<br>用不習慣。</p></li><li><p>升降桌<br>必備啊。</p></li></ul><h2 id="🔸-開發環境"><a href="#🔸-開發環境" class="headerlink" title="🔸 開發環境"></a>🔸 開發環境</h2><table><thead><tr><th align="left">個人</th><th align="left">公司</th></tr></thead><tbody><tr><td align="left">MacBook Pro (16-inch, 2023)</td><td align="left">Mac mini (2020)</td></tr><tr><td align="left">Memory 32GB</td><td align="left">Memory 16GB</td></tr><tr><td align="left">Apple M2 Max</td><td align="left">Apple M1</td></tr><tr><td align="left">巧控鍵盤</td><td align="left">巧控鍵盤</td></tr><tr><td align="left">無</td><td align="left">巧控板 - 白色</td></tr><tr><td align="left">Apple Studio Display</td><td align="left">ASUS VP28U</td></tr><tr><td align="left">Funte 升降桌</td><td align="left">一般辦公桌</td></tr></tbody></table><h2 id="🔸-Mac電腦更新"><a href="#🔸-Mac電腦更新" class="headerlink" title="🔸 Mac電腦更新"></a>🔸 Mac電腦更新</h2><p>3年+ 差不多 AppleCare+ 到期後就可以考慮買新電腦了。<br>我最近幾年用過的電腦 (買入年份:2023 / 2020 / 2017):<br><img src="/img/app_dev/computers.png"></p><div class="gallery"> <div class="fj-gallery " data-rowHeight="220" data-limit="10"> <span class="gallery-data">[{"url":"/img/app_dev/macbookpro-16-m2.png","alt":""},{"url":"/img/app_dev/macbookpro-16-intel.jpg","alt":""},{"url":"/img/app_dev/macbookpro-15.jpg","alt":""}]</span> </div> <button class="gallery-load-more"><span>Load More</span><i class="fa-solid fa-arrow-down"></i></button> </div><h2 id="🔸-Apple-故障-amp-AppleCare"><a href="#🔸-Apple-故障-amp-AppleCare" class="headerlink" title="🔸 Apple 故障 & AppleCare+"></a>🔸 Apple 故障 & AppleCare+</h2><p>我只有為電腦買AppleCare+,電腦每天隨身攜帶,修理價格昂貴,只好買。如果電腦都放家裡可以考慮不買。</p><p>以下是我遇到和聽到的Apple故障:</p><ul><li>自己買 Apple Watch SE, 用了半年,每天按 Button 的次數 < 10, 有一天 Button 就按下去再也起不來,送修 101 Apple 直營店, 幾天後拿到新的 Watch。</li><li>朋友買 iPad 用了不到半年直接黑屏,送修後拿到新 iPad。</li><li>之前 Server 工程師的 MacBook Pro,有加買保固,用了快三年,保固前電池膨出,送修 101 Apple 直營店, 當時有跟 Apple 工程師溝通,開發電腦急用不能等, 於是當天修好。</li><li>之前公司一台 MacBook Pro 13-inch Intel Chip 才過一年保固就主機板燒毀,只能花錢修理。另一台 MacBook Pro 13-inch M1 買一個多月也主機板燒毀,保固內拿到新幾。</li></ul>]]></content>
<categories>
<category> APP Development </category>
</categories>
</entry>
<entry>
<title>Highlights for developers in the iOS release</title>
<link href="/2023/05/22/APP%20Development/ios-release/"/>
<url>/2023/05/22/APP%20Development/ios-release/</url>
<content type="html"><![CDATA[<h2 id="iOS-17-x2F-iPadOS-17-2023"><a href="#iOS-17-x2F-iPadOS-17-2023" class="headerlink" title="iOS 17 / iPadOS 17 (2023)"></a>iOS 17 / iPadOS 17 (2023)</h2><h2 id="iOS-16-x2F-iPadOS-16-2022"><a href="#iOS-16-x2F-iPadOS-16-2022" class="headerlink" title="iOS 16 / iPadOS 16 (2022)"></a>iOS 16 / iPadOS 16 (2022)</h2><ul><li><p>Developer Mode<br>Need to enable the developer mode before running app from XCode.</p></li><li><p>Lockdown Mode</p></li></ul><h2 id="iOS-15-x2F-iPadOS-15-2021"><a href="#iOS-15-x2F-iPadOS-15-2021" class="headerlink" title="iOS 15 / iPadOS 15 (2021)"></a>iOS 15 / iPadOS 15 (2021)</h2><ul><li>Pre-app settings</li><li>APP Privacy Report</li><li>Passkey technology</li><li>AttributedString</li><li>Focus Mode</li></ul><h2 id="iOS-14-x2F-iPadOS-14-2020"><a href="#iOS-14-x2F-iPadOS-14-2020" class="headerlink" title="iOS 14 / iPadOS 14 (2020)"></a>iOS 14 / iPadOS 14 (2020)</h2><ul><li>Limited photos library access</li><li>APP Clips</li><li>NSObject key-value observing</li><li>Logger</li></ul><h2 id="iOS-13-x2F-iPadOS-13-2019"><a href="#iOS-13-x2F-iPadOS-13-2019" class="headerlink" title="iOS 13 / iPadOS 13 (2019)"></a>iOS 13 / iPadOS 13 (2019)</h2><ul><li>SWiftUI</li><li>SF symbol</li><li>VisionKit</li></ul>]]></content>
<categories>
<category> APP Development </category>
</categories>
</entry>
<entry>
<title>道德駭客實務入門-4/22課程筆記-3</title>
<link href="/2023/04/29/learning-notes/hacking-class-5-3/"/>
<url>/2023/04/29/learning-notes/hacking-class-5-3/</url>
<content type="html"><![CDATA[<p><a href="https://www.tibame.com/offline/ceh">TibaMe: 道德駭客實務入門及CEH認證班</a><br>Teacher: 林煌錡 (Alex Lin)<br><img src="/img/hacking-class/hacking-class-info.png"></p><blockquote><p>The class on 4/22 introduced wireless network hacking, mobile platform hacking, IOT hacking and cloud platform hacking, and cryptography. I separated the content into three parts. This note is about cryptography.</p></blockquote><p><font style="font-size: 30px;font-weight:bold;">Part3: cryptography</font></p><h1 id="🔸-Symmetric-Cryptography"><a href="#🔸-Symmetric-Cryptography" class="headerlink" title="🔸 Symmetric Cryptography"></a>🔸 Symmetric Cryptography</h1><p>From Wiki: <a href="https://en.wikipedia.org/wiki/Symmetric-key_algorithm">https://en.wikipedia.org/wiki/Symmetric-key_algorithm</a></p><blockquote><img src="/img/hacking-class/Simple_symmetric_encryption.png" height="50%" ></blockquote><h2 id="Algorithms"><a href="#Algorithms" class="headerlink" title="Algorithms"></a>Algorithms</h2><h3 id="DES-amp-3DES"><a href="#DES-amp-3DES" class="headerlink" title="DES & 3DES"></a>DES & 3DES</h3><p><a href="https://en.wikipedia.org/wiki/Data_Encryption_Standard">https://en.wikipedia.org/wiki/Data_Encryption_Standard</a><br><a href="https://en.wikipedia.org/wiki/Triple_DES">https://en.wikipedia.org/wiki/Triple_DES</a></p><h3 id="AES-128-x2F-192-x2F-256"><a href="#AES-128-x2F-192-x2F-256" class="headerlink" title="AES 128/192/256"></a>AES 128/192/256</h3><p><a href="https://en.wikipedia.org/wiki/Advanced_Encryption_Standard">https://en.wikipedia.org/wiki/Advanced_Encryption_Standard</a></p><ul><li>Initialization vector (IV)</li><li>Padding</li><li>Block cipher mode<br>ECB / CBC / CFB / CTR / GCM / OFB / CFB</li></ul><h3 id="Stream-Cipher"><a href="#Stream-Cipher" class="headerlink" title="Stream Cipher"></a>Stream Cipher</h3><p><a href="https://en.wikipedia.org/wiki/Stream_cipher">https://en.wikipedia.org/wiki/Stream_cipher</a><br>Operate on plaintext streams and generates a stream of key bits that are combined with the plaintext to produce the ciphertext. They are commonly used in applications where real-time encryption and decryption is required, such as in wireless communications and secure voice communication systems.</p><h2 id="Use-Cases"><a href="#Use-Cases" class="headerlink" title="Use Cases"></a>Use Cases</h2><ul><li>Data Encryption</li><li>File Protection</li><li>Stream Encryption</li></ul><h1 id="🔸-Asymmetric-Cryptography"><a href="#🔸-Asymmetric-Cryptography" class="headerlink" title="🔸 Asymmetric Cryptography"></a>🔸 Asymmetric Cryptography</h1><p>From Wiki: <a href="https://en.wikipedia.org/wiki/Public-key_cryptography">https://en.wikipedia.org/wiki/Public-key_cryptography</a></p><p>Asymmetric cryptography takes longer time compared to symmetric cryptography.</p><p><img src="/img/hacking-class/asymmetric-crypto-1.png"></p><h2 id="Algorithms-1"><a href="#Algorithms-1" class="headerlink" title="Algorithms"></a>Algorithms</h2><h3 id="RSA"><a href="#RSA" class="headerlink" title="RSA"></a>RSA</h3><p><a href="https://en.wikipedia.org/wiki/RSA_(cryptosystem)">https://en.wikipedia.org/wiki/RSA_(cryptosystem)</a><br>RSA Padding:</p><ul><li>NoPadding</li><li>PKCS1</li><li>OAEP</li><li>PSS</li></ul><h3 id="Elliptic-Curve-Cryptography-ECC"><a href="#Elliptic-Curve-Cryptography-ECC" class="headerlink" title="Elliptic Curve Cryptography (ECC)"></a>Elliptic Curve Cryptography (ECC)</h3><p><a href="https://en.wikipedia.org/wiki/Elliptic-curve_cryptography">https://en.wikipedia.org/wiki/Elliptic-curve_cryptography</a></p><h2 id="Use-Cases-1"><a href="#Use-Cases-1" class="headerlink" title="Use Cases"></a>Use Cases</h2><h3 id="HTTPS"><a href="#HTTPS" class="headerlink" title="HTTPS"></a>HTTPS</h3><h3 id="SSL"><a href="#SSL" class="headerlink" title="SSL"></a>SSL</h3><ul><li>SSL 1.0</li><li>SSL 2.0</li><li>SSL 3.0</li></ul><h3 id="TSL"><a href="#TSL" class="headerlink" title="TSL"></a>TSL</h3><ul><li>TSL 1.0</li><li>TSL 1.1</li><li>TSL 1.2</li><li>TSL 1.3</li></ul><h3 id="Digital-Signatures"><a href="#Digital-Signatures" class="headerlink" title="Digital Signatures"></a>Digital Signatures</h3><blockquote><p>From Wiki<br><img src="/img/hacking-class/asymmetric-crypto-3.png" width="50%" ></p></blockquote><h3 id="Key-Exchange"><a href="#Key-Exchange" class="headerlink" title="Key Exchange"></a>Key Exchange</h3><blockquote><p>From Wiki<br>Diffie-Hellman<br><img src="/img/hacking-class/asymmetric-crypto-2.png" width="50%" ></p></blockquote><h3 id="Digital-Certificates"><a href="#Digital-Certificates" class="headerlink" title="Digital Certificates"></a>Digital Certificates</h3><p>A digital certificate is issued by a trusted third-party called a Certificate Authority (CA), which verifies the identity of the certificate holder and binds their public key to a unique identifier called a “subject.” This subject can be an individual, an organization, or a device, and is included in the digital certificate along with the public key.</p><h3 id="Pretty-Good-Privacy-PGP"><a href="#Pretty-Good-Privacy-PGP" class="headerlink" title="Pretty Good Privacy (PGP)"></a>Pretty Good Privacy (PGP)</h3><p><a href="https://zh.wikipedia.org/zh-tw/PGP">https://zh.wikipedia.org/zh-tw/PGP</a></p><h3 id="GNU-Privacy-Guard-GPG"><a href="#GNU-Privacy-Guard-GPG" class="headerlink" title="GNU Privacy Guard (GPG)"></a>GNU Privacy Guard (GPG)</h3><p><a href="https://zh.wikipedia.org/zh-tw/GnuPG">https://zh.wikipedia.org/zh-tw/GnuPG</a></p><h3 id="Secure-Email"><a href="#Secure-Email" class="headerlink" title="Secure Email"></a>Secure Email</h3><p>In secure email, the sender uses the recipient’s public key to encrypt the message, which can only be decrypted using the recipient’s private key.</p><h3 id="Blockchain"><a href="#Blockchain" class="headerlink" title="Blockchain"></a>Blockchain</h3><p>In a blockchain network, each participant has a unique public-private key pair, which they use to sign and verify transactions.</p><h1 id="🔸-Hash"><a href="#🔸-Hash" class="headerlink" title="🔸 Hash"></a>🔸 Hash</h1><blockquote><p>Input Message –> Hash Function –> Hashed Message</p></blockquote><h2 id="Algorithms-2"><a href="#Algorithms-2" class="headerlink" title="Algorithms"></a>Algorithms</h2><h3 id="Message-Digest"><a href="#Message-Digest" class="headerlink" title="Message-Digest"></a>Message-Digest</h3><ul><li><p>MD5 (Has vulnerabilities)<br><a href="https://zh.wikipedia.org/zh-tw/MD5">https://zh.wikipedia.org/zh-tw/MD5</a></p></li><li><p>SHA<br><a href="https://zh.wikipedia.org/zh-hant/SHA%E5%AE%B6%E6%97%8F">https://zh.wikipedia.org/zh-hant/SHA家族</a><br>❌ SHA-0<br>❌ SHA-1<br>✅ SHA-2<br>✅ SHA-3</p></li></ul><h2 id="Use-Cases-2"><a href="#Use-Cases-2" class="headerlink" title="Use Cases"></a>Use Cases</h2><ul><li><p>Data integrity: Hash functions can be used to verify the integrity of data, ensuring that it has not been modified or corrupted in transit. This is commonly used in digital signatures, where a hash of the message is signed using a private key to generate a digital signature that can be verified using a public key.</p></li><li><p>Password storage: Hash functions are often used to store passwords securely in a database. When a user creates a password, its hash is stored in the database instead of the plaintext password. When the user logs in, the system compares the hash of the entered password with the stored hash to verify the password.</p></li><li><p>Content addressing: Hash functions can be used to generate unique identifiers for files and other content. This allows content to be identified and shared without needing to store the entire file or content. This is commonly used in peer-to-peer file sharing networks and content distribution systems.</p></li><li><p>Message authentication: Hash functions can be used to generate message authentication codes (MACs) that can be used to authenticate messages and ensure that they have not been tampered with in transit.</p></li><li><p>Blockchain: Hash functions are a fundamental component of blockchain technology. In a blockchain, each block contains a hash of the previous block, forming a chain of blocks. This allows the integrity of the blockchain to be verified by checking the hashes of each block in the chain.</p></li></ul><h1 id="🔸-HMAC"><a href="#🔸-HMAC" class="headerlink" title="🔸 HMAC"></a>🔸 HMAC</h1><p>HMAC is used for message authentication, integrity, and secrecy. HMAC includes a secret key in the computation, which allows it to provide both integrity and authenticity of a message.<br>From Alex Xu’s blog: <a href="https://blog.bytebytego.com/p/how-to-design-a-secture-web-api-access">https://blog.bytebytego.com/p/how-to-design-a-secture-web-api-access</a><br><img src="/img/hacking-class/hmac.png"></p><h1 id="🔸-Tools"><a href="#🔸-Tools" class="headerlink" title="🔸 Tools"></a>🔸 Tools</h1><h2 id="CyberChef"><a href="#CyberChef" class="headerlink" title="CyberChef"></a>CyberChef</h2><p>Can carry out all manner of “cyber” operations within a web browser.<br><a href="https://github.com/gchq/CyberChef">https://github.com/gchq/CyberChef</a><br>👉 <a href="https://gchq.github.io/CyberChef/#recipe=MD5()&input=ampqamtraw">https://gchq.github.io/CyberChef/#recipe=MD5()&input=ampqamtraw</a></p><h2 id="OpenSSL"><a href="#OpenSSL" class="headerlink" title="OpenSSL"></a>OpenSSL</h2><p><a href="https://zh.wikipedia.org/zh-tw/OpenSSL">https://zh.wikipedia.org/zh-tw/OpenSSL</a><br><a href="https://www.openssl.org/">https://www.openssl.org</a></p><h2 id="RSACTFTool"><a href="#RSACTFTool" class="headerlink" title="RSACTFTool"></a>RSACTFTool</h2><p><a href="https://github.com/RsaCtfTool/RsaCtfTool">https://github.com/RsaCtfTool/RsaCtfTool</a><br><img src="/img/hacking-class/rsactftool.png"></p><h2 id="CrypToll"><a href="#CrypToll" class="headerlink" title="CrypToll"></a>CrypToll</h2><p><a href="https://en.wikipedia.org/wiki/CrypTool">https://en.wikipedia.org/wiki/CrypTool</a><br><a href="https://www.cryptool.org/">https://www.cryptool.org</a></p><h2 id="Hard-Disk-Encryption-Tools"><a href="#Hard-Disk-Encryption-Tools" class="headerlink" title="Hard Disk Encryption Tools"></a>Hard Disk Encryption Tools</h2><h3 id="Windows-BitLocker"><a href="#Windows-BitLocker" class="headerlink" title="Windows BitLocker"></a>Windows BitLocker</h3><p>BitLocker is a built-in disk encryption tool that comes with Windows 10 Professional and Enterprise editions. It can encrypt entire drives or individual files and folders, and supports several encryption modes, including AES-CBC and XTS-AES.<br><a href="https://en.wikipedia.org/wiki/BitLocker">https://en.wikipedia.org/wiki/BitLocker</a></p><h3 id="VeraCrypt"><a href="#VeraCrypt" class="headerlink" title="VeraCrypt"></a>VeraCrypt</h3><p>VeraCrypt is a free, open-source disk encryption software that can encrypt entire partitions or drives. It supports multiple encryption algorithms, including AES, Serpent, and Twofish, and can create hidden volumes for added security.<br><a href="https://en.wikipedia.org/wiki/VeraCrypt">https://en.wikipedia.org/wiki/VeraCrypt</a></p><h3 id="DiskCryptor"><a href="#DiskCryptor" class="headerlink" title="DiskCryptor"></a>DiskCryptor</h3><p>DiskCryptor is a free, open-source disk encryption tool that supports multiple encryption algorithms, including AES, Twofish, and Serpent. It can encrypt entire disks or individual partitions and supports both BIOS and UEFI firmware.</p><h3 id="Symantec-Endpoint-Encryption"><a href="#Symantec-Endpoint-Encryption" class="headerlink" title="Symantec Endpoint Encryption"></a>Symantec Endpoint Encryption</h3><p>Symantec Endpoint Encryption is a commercial disk encryption tool that provides full-disk encryption for Windows and Mac systems. It uses strong encryption algorithms, including AES-256, and can integrate with Active Directory for easier management.</p><h3 id="FileVault"><a href="#FileVault" class="headerlink" title="FileVault"></a>FileVault</h3><p>FileVault is a built-in disk encryption tool that comes with macOS. It can encrypt the entire startup disk or just the user’s home folder and supports both AES-XTS and AES-CBC encryption modes.</p><h1 id="🔸-Public-Key-Infrastructure-PKI"><a href="#🔸-Public-Key-Infrastructure-PKI" class="headerlink" title="🔸 Public Key Infrastructure (PKI)"></a>🔸 Public Key Infrastructure (PKI)</h1><p>PKI Wiki: <a href="https://en.wikipedia.org/wiki/Public_key_infrastructure">https://en.wikipedia.org/wiki/Public_key_infrastructure</a></p><ul><li>憑證管理系統(Certificate Management System) </li><li>憑證機構(Certification Authority ,CA)</li><li>註冊中心(Register Authority ,RA) </li><li>數位憑證(Digital Certificates) </li><li>驗證中心(Validation Authority ,VA)</li></ul><p>From Teacher Alex’s PPT<br><img src="/img/hacking-class/kpi.png"></p><h1 id="🔸-Attack-Methods"><a href="#🔸-Attack-Methods" class="headerlink" title="🔸 Attack Methods"></a>🔸 Attack Methods</h1><h2 id="Hard-Code-Attack"><a href="#Hard-Code-Attack" class="headerlink" title="Hard Code Attack"></a>Hard Code Attack</h2><p>A hard-coded attack, also known as a hard-coded password attack, is a type of vulnerability exploitation where an attacker gains unauthorized access to a system or application by exploiting a hard-coded password that is stored in the code or configuration files.</p><p><strong>Prevention:</strong></p><ul><li>Avoid hard-coding passwords and instead store them in secure configuration files or databases, using strong encryption and hashing techniques.</li></ul><h2 id="Rainbow-Table-Attack"><a href="#Rainbow-Table-Attack" class="headerlink" title="Rainbow Table Attack"></a>Rainbow Table Attack</h2><p>A Rainbow Table attack is used to crack hashed passwords.</p><p>Rainbow Table attacks work by precomputing a large number of hashes and storing them in a lookup table, known as a Rainbow Table. The Rainbow Table contains a list of possible passwords and their corresponding hashes. When an attacker obtains a hashed password, they can then compare it against the entries in the Rainbow Table to find a match. If a match is found, the corresponding password is the plaintext equivalent of the hashed password.Rainbow Table attacks can be effective against weak passwords, as well as against poorly implemented hashing algorithms.</p><p><strong>Prevention:</strong></p><ul><li>Use strong and complex passwords, and to use secure hash functions that are resistant to precomputation attacks.</li></ul><h2 id="Related-key-Attack"><a href="#Related-key-Attack" class="headerlink" title="Related-key Attack"></a>Related-key Attack</h2><p>A related-key attack is a type of cryptographic attack that exploits a weakness in a cryptosystem when multiple keys are used that are related in some way, such as being generated from the same source or using the same algorithm. </p><p><strong>Prevention:</strong></p><ul><li><p>Use cryptographically secure key generation methods, such as using true random number generators or hardware security modules (HSMs), which can generate independent keys that are not related to each other.</p></li><li><p>Use multiple independent keys for different operations, such as encryption and authentication.</p></li></ul><h2 id="Padding-Oracle-Attack"><a href="#Padding-Oracle-Attack" class="headerlink" title="Padding Oracle Attack"></a>Padding Oracle Attack</h2><p>In a Padding Oracle Attack, an attacker submits an encrypted message to a server and monitors the response. The server may provide different responses depending on whether the padding in the message is valid or not. By repeatedly submitting modified versions of the message and observing the server’s response, an attacker can determine the correct padding and gradually decrypt the message.</p><p><strong>Prevention:</strong></p><ul><li>Use secure cryptographic protocols.</li><li>Avoid exposing sensitive information through error messages or other feedback mechanisms.</li></ul><h2 id="DROWN-Attack"><a href="#DROWN-Attack" class="headerlink" title="DROWN Attack"></a>DROWN Attack</h2><p>The attack exploits a weakness in the SSLv2 protocol, which is an outdated and insecure version of the SSL/TLS protocol used for secure communications over the internet. Even though SSLv2 has been deprecated for many years, some servers still support it, leaving them vulnerable to the DROWN attack.</p><p><strong>Prevent:</strong></p><ul><li>Disable SSLv2 support on the servers.</li></ul><h2 id="Brute-force-attack"><a href="#Brute-force-attack" class="headerlink" title="Brute-force attack"></a>Brute-force attack</h2><ul><li>Symmetric Encryption<br>Below is from Teacher Alex’s PPT<br><img src="/img/hacking-class/brute-force-1.png"></li><li>Password<br>Below is from: <a href="https://uwnthesis.wordpress.com/2020/07/01/brute-force-password-how-long-will-it-take-to-brute-force-a-password/">https://uwnthesis.wordpress.com/2020/07/01/brute-force-password-how-long-will-it-take-to-brute-force-a-password/</a><br><img src="/img/hacking-class/brute-force-2.png"></li></ul><h1 id="🔸-Hacking-Case-Studies"><a href="#🔸-Hacking-Case-Studies" class="headerlink" title="🔸 Hacking Case Studies"></a>🔸 Hacking Case Studies</h1><h2 id="ZeroLogon"><a href="#ZeroLogon" class="headerlink" title="ZeroLogon"></a>ZeroLogon</h2><p>From Teacher Alex’s PPT<br><img src="/img/hacking-class/crypt-attack-1.png"></p><h2 id="Dual-EC-DRBG"><a href="#Dual-EC-DRBG" class="headerlink" title="Dual_EC_DRBG"></a>Dual_EC_DRBG</h2><p>From Teacher Alex’s PPT<br><img src="/img/hacking-class/crypt-attack-2.png"></p>]]></content>
<categories>
<category> Learning notes </category>
</categories>
<tags>
<tag> Hacking </tag>
</tags>
</entry>
<entry>
<title>道德駭客實務入門-4/22課程筆記-2</title>
<link href="/2023/04/25/learning-notes/hacking-class-5-2/"/>
<url>/2023/04/25/learning-notes/hacking-class-5-2/</url>
<content type="html"><![CDATA[<p><a href="https://www.tibame.com/offline/ceh">TibaMe: 道德駭客實務入門及CEH認證班</a><br>Teacher: 林煌錡 (Alex Lin)<br><img src="/img/hacking-class/hacking-class-info.png"></p><blockquote><p>The class on 4/22 introduced wireless network hacking, mobile platform hacking, IOT hacking and cloud platform hacking, and cryptography. I separated the content into three parts. This note is about mobile platform hacking.</p></blockquote><p><font style="font-size: 30px;font-weight:bold;">Part2: Mobile Platform Hacking</font></p><h1 id="🔸-OWASP-Top-10-Mobile-Threats"><a href="#🔸-OWASP-Top-10-Mobile-Threats" class="headerlink" title="🔸 OWASP Top 10 Mobile Threats"></a>🔸 OWASP Top 10 Mobile Threats</h1><p>From OWASP: <a href="https://owasp.org/www-project-mobile-top-10/">https://owasp.org/www-project-mobile-top-10/</a><br><img src="/img/hacking-class/mobile-top10.png"></p><blockquote><p><span style="color:red;font-size: 18px"><strong>I recommend that every iOS/Android app developer reads both the OWASP Mobile Top10 and the OWASP Mobile Application Security Testing Guide (MASTG).</strong> </span></p></blockquote><h1 id="🔸-iOS-APP"><a href="#🔸-iOS-APP" class="headerlink" title="🔸 iOS APP"></a>🔸 iOS APP</h1><p>From OWASP MASTG: <a href="https://mas.owasp.org/MASTG/iOS/0x06a-Platform-Overview/#ios-application-attack-surface">https://mas.owasp.org/MASTG/iOS/0x06a-Platform-Overview/#ios-application-attack-surface</a><br><img src="/img/hacking-class/ios-attack.png"></p><h1 id="🔸-Android-APP"><a href="#🔸-Android-APP" class="headerlink" title="🔸 Android APP"></a>🔸 Android APP</h1><p>From OWASP MASTG: <a href="https://mas.owasp.org/MASTG/Android/0x05a-Platform-Overview/#android-application-attack-surface">https://mas.owasp.org/MASTG/Android/0x05a-Platform-Overview/#android-application-attack-surface</a><br><img src="/img/hacking-class/android-attack.png"></p><h1 id="🔸-Tools"><a href="#🔸-Tools" class="headerlink" title="🔸 Tools"></a>🔸 Tools</h1><p>From OWASP MASTG: <a href="https://mas.owasp.org/MASTG/Tools/0x08a-Testing-Tools/">https://mas.owasp.org/MASTG/Tools/0x08a-Testing-Tools/</a></p><ul><li><p>iOS<br>From Teacher Alex’s PPT:<br><img src="/img/hacking-class/ios-tools.png"></p></li><li><p>Android<br>From Teacher Alex’s PPT:<br><img src="/img/hacking-class/android-tools.png"></p></li></ul><h1 id="🔸-Recommend-Reading"><a href="#🔸-Recommend-Reading" class="headerlink" title="🔸 Recommend Reading"></a>🔸 Recommend Reading</h1><p>👉 <strong><a href="https://developer.android.com/topic/security/best-practices">https://developer.android.com/topic/security/best-practices</a></strong><br>👉 <strong><a href="https://developer.apple.com/documentation/security">https://developer.apple.com/documentation/security</a></strong> </p><p><a href="https://ithelp.ithome.com.tw/articles/10237144">https://ithelp.ithome.com.tw/articles/10237144</a><br><a href="https://en.wikipedia.org/wiki/Mobile_security">https://en.wikipedia.org/wiki/Mobile_security</a></p>]]></content>
<categories>
<category> Learning notes </category>
</categories>
<tags>
<tag> Hacking </tag>
</tags>
</entry>
<entry>
<title>道德駭客實務入門-4/22課程筆記-1</title>
<link href="/2023/04/25/learning-notes/hacking-class-5/"/>
<url>/2023/04/25/learning-notes/hacking-class-5/</url>
<content type="html"><![CDATA[<p><a href="https://www.tibame.com/offline/ceh">TibaMe: 道德駭客實務入門及CEH認證班</a><br>Teacher: 林煌錡 (Alex Lin)<br><img src="/img/hacking-class/hacking-class-info.png"></p><blockquote><p>The class on 4/22 introduced wireless network hacking, mobile platform hacking, IOT hacking and cloud platform hacking, and cryptography. I separated the content into three parts. This note is about wireless network, IOT and cloud platform hacking.</p></blockquote><p><font style="font-size: 30px;font-weight:bold;">Part1: Hacking wireless network, IOT and cloud platform</font></p><h1 id="🔸-Wireless-Network-Hacking"><a href="#🔸-Wireless-Network-Hacking" class="headerlink" title="🔸 Wireless Network Hacking"></a>🔸 Wireless Network Hacking</h1><h2 id="WEP-WPA-WPA2-amp-WPA3"><a href="#WEP-WPA-WPA2-amp-WPA3" class="headerlink" title="WEP, WPA, WPA2 & WPA3"></a>WEP, WPA, WPA2 & WPA3</h2><ul><li><p>❌ <strong>WEP</strong> was the first security protocol developed for wireless networks. It uses a shared key to encrypt data transmitted over the wireless network. However, it has several vulnerabilities and is no longer considered secure. </p></li><li><p>❌ <strong>WPA</strong> was developed to address the vulnerabilities of WEP. It uses a stronger encryption algorithm called Temporal Key Integrity Protocol (TKIP) to encrypt data transmitted over the wireless network. It also provides a mechanism for authentication and key management. WPA is considered a more secure protocol than WEP, but it is still vulnerable to certain attacks.</p></li><li><p>✅ <strong>WPA2</strong> is an improvement over WPA and is currently the most widely used security protocol for wireless networks. It uses Advanced Encryption Standard (AES) to encrypt data transmitted over the wireless network. It also provides a more secure mechanism for authentication and key management. WPA2 is considered a strong security protocol and is recommended for securing wireless networks.</p></li><li><p>✅ <strong>WPA3</strong> is the latest security protocol for wireless networks. It was developed to address the vulnerabilities of WPA2 and provide even stronger security. It uses Simultaneous Authentication of Equals (SAE), a stronger key exchange protocol, to provide better protection against password-guessing attacks. It also provides improved encryption and protects against certain attacks like packet sniffing. WPA3 is considered the most secure protocol for wireless networks, but it is not yet widely adopted.</p></li></ul><h2 id="Attacks"><a href="#Attacks" class="headerlink" title="Attacks"></a>Attacks</h2><ul><li><strong>Rogue Access Point (RAP)</strong></li></ul><p>A Rogue Access Point (RAP) is an unauthorized wireless access point that is installed on a network without the knowledge or approval of the network administrator. RAPs are often installed by attackers or rogue employees to gain unauthorized access to a network or to eavesdrop on network traffic.</p><p>A RAP can be set up in a few different ways. For example, an attacker might bring their own wireless access point and connect it to the network, or they might use a wireless-enabled device such as a laptop or smartphone to create an ad-hoc network that other devices can connect to.</p><p>Once a RAP is set up, it can be used to intercept network traffic, steal sensitive information, or launch attacks against other devices on the network. RAPs can also be used to create “man-in-the-middle” attacks, where the attacker intercepts and modifies network traffic to steal data or launch additional attacks.</p><p>Teacher Alex mentioned <strong>Strip SSL</strong> here.<br>SSL stripping is a type of attack where an attacker intercepts communication between a web server and a client, and downgrades the secure HTTPS connection to an insecure HTTP connection.</p><ul><li><strong>aLTEr(Long Term Evolution) Attack</strong></li></ul><p>In an aLTEr attack, the attacker first sets up a rogue base station, also known as an IMSI catcher or fake cell tower, to mimic a legitimate LTE network. The attacker can then use this fake network to intercept and modify the communication between the user’s device and the legitimate LTE network.</p><p>By intercepting the user’s traffic, the attacker can steal sensitive information such as login credentials, personal data, and financial information. The attacker can also modify the user’s traffic to inject malware or perform phishing attacks.</p><p>From Teacher Alex’s PPT: aLTEr attack 至少需在受害者附近架設一台將近2000美元的基地台,才有可能誘使受害者使用惡意的基地台。</p><ul><li><strong>Key Reinstallation Attack (KRACK)</strong></li></ul><p>The attack takes advantage of a weakness in the WPA2 protocol that allows an attacker to reuse a cryptographic key that has already been used before, allowing the attacker to decrypt and manipulate the traffic between the client and the network. This can allow the attacker to inject malware, steal data, or modify data.</p><p>To prevent KRACK attacks, it is important to update all Wi-Fi devices to the latest firmware or software updates that fix the vulnerability. Additionally, using a Virtual Private Network (VPN) or other secure communication channel can help to protect against KRACK attacks by encrypting all network traffic. It is also important to avoid using public Wi-Fi networks for sensitive transactions, such as banking or shopping, as they are more vulnerable to KRACK attacks.</p><ul><li><p><strong>Jamming Signal Attacks</strong></p></li><li><p><strong>Denial-of-Service Attack</strong></p></li><li><p><strong>Wormhole Attack</strong></p></li><li><p><strong>Sinkhole Attack</strong></p></li></ul><h2 id="Tools"><a href="#Tools" class="headerlink" title="Tools"></a>Tools</h2><h3 id="WiGLE"><a href="#WiGLE" class="headerlink" title="WiGLE"></a>WiGLE</h3><p>WiGLE (or Wireless Geographic Logging Engine) is a website for collecting information about the different wireless hotspots around the world.<br>Wiki: <a href="https://en.wikipedia.org/wiki/WiGLE">https://en.wikipedia.org/wiki/WiGLE</a><br><a href="https://wigle.net/">https://wigle.net</a></p><h3 id="Tools-provided-in-Kali-Linux"><a href="#Tools-provided-in-Kali-Linux" class="headerlink" title="Tools provided in Kali Linux."></a>Tools provided in Kali Linux.</h3><ul><li>Fern Wifi Cracker</li><li>Kismet</li><li>GISKismet</li><li>Ghost Phisher</li><li>Wifite</li></ul><h1 id="🔸-IOT-Hacking"><a href="#🔸-IOT-Hacking" class="headerlink" title="🔸 IOT Hacking"></a>🔸 IOT Hacking</h1><h2 id="IoT-OWASP-Top10"><a href="#IoT-OWASP-Top10" class="headerlink" title="IoT OWASP Top10"></a>IoT OWASP Top10</h2><p><a href="https://wiki.owasp.org/index.php/OWASP_Internet_of_Things_Project#tab=IoT_Top_10">https://wiki.owasp.org/index.php/OWASP_Internet_of_Things_Project#tab=IoT_Top_10</a><br><img src="/img/hacking-class/iot-owasp10.png"></p><h2 id="Tools-1"><a href="#Tools-1" class="headerlink" title="Tools"></a>Tools</h2><ul><li><strong>fireware-mod-kit</strong><br>From Teacher Alex’s PPT:<br><img src="/img/hacking-class/fw-mod-kit.png"><br>Kali provides buildin fireware-mod-kit.</li></ul><h2 id="Prevention"><a href="#Prevention" class="headerlink" title="Prevention"></a>Prevention</h2><ul><li>Keep FW up-to-date</li><li>Close necessary ports</li><li>DO NOT use telnet</li><li>Use encryption protocols such as SSL/TLS</li><li>Enhance password strength</li><li>Encrypt FW</li><li>DO NOT use UPnP</li></ul><h1 id="🔸-Cloud-Platform-Hacking"><a href="#🔸-Cloud-Platform-Hacking" class="headerlink" title="🔸 Cloud Platform Hacking"></a>🔸 Cloud Platform Hacking</h1><h2 id="OWASP-Cloud-Risk-Top-10"><a href="#OWASP-Cloud-Risk-Top-10" class="headerlink" title="OWASP Cloud Risk Top 10"></a>OWASP Cloud Risk Top 10</h2><p>From OWASP: <a href="https://owasp.org/www-pdf-archive/Cloud-Top10-Security-Risks.pdf">https://owasp.org/www-pdf-archive/Cloud-Top10-Security-Risks.pdf</a><br><img src="/img/hacking-class/cloud-risk-top10.png"></p><h2 id="OWASP-Cloud-Native-Application-Security-Top-10"><a href="#OWASP-Cloud-Native-Application-Security-Top-10" class="headerlink" title="OWASP Cloud-Native Application Security Top 10"></a>OWASP Cloud-Native Application Security Top 10</h2><p>From OWASP: <a href="https://owasp.org/www-project-cloud-native-application-security-top-10/">https://owasp.org/www-project-cloud-native-application-security-top-10/</a></p><h2 id="Container-Vulnerabilities"><a href="#Container-Vulnerabilities" class="headerlink" title="Container Vulnerabilities"></a>Container Vulnerabilities</h2><p>From Teacher Alex’s PPT:</p><ul><li>Impetuous Image Creation(未審視的映像檔)</li><li>Unreliable Third-Party Resources(不可靠的第三方資源)</li><li>Unauthorized Access(未經授權的存取)</li><li>Insecure Container Runtime Configurations (不安全的容器設定檔)</li><li>Data Exposure in Docker Files(Docker中的檔案遭暴露)</li><li>Embedded Malware(嵌入式的惡意程式)</li><li>Non-Updated Images(未更新的映像檔)</li><li>Hijacked Repository and Infected Resoreces (受劫持的映象檔儲存庫和資源)</li><li>Hijacked Image Registry (受劫持的映像註冊表)</li><li>Exposed Service due to open port (開放的服務Port)</li><li>Exploited Applications (利用應用程式的弱點)</li><li>Mixing of Workload(混合再一起的應用容器)</li><li>Mixing of Workload Sensitivity Levels (敏感級別的容器和一般容器混合)</li></ul><h2 id="Security-Settings"><a href="#Security-Settings" class="headerlink" title="Security Settings"></a>Security Settings</h2><h3 id="Container"><a href="#Container" class="headerlink" title="Container"></a>Container</h3><p>From Teacher Alex’s PPT:</p><ul><li>設定分配給容器的資源</li><li>Container 不應該以root執行</li><li>挑選驗證過的Repo</li><li>映像檔原始碼審查</li><li>關閉不必要的容器的網路服務</li></ul><h3 id="Bucket"><a href="#Bucket" class="headerlink" title="Bucket"></a>Bucket</h3><p>Teacher Alex mentioned that we must be careful with the S3 Identity and Access Management (IAM).<br>From Teacher Alex’s PPT:<br>若和 Bucket 連動 APP 具有 SSRF 的弱點,則可以嘗試枚舉執行個體中繼資料和使用者資料.</p><p>Can use duckduckgo.com to search s3 buckets via <code>site:s3.amazonaws.com</code></p><p>Case study: Unrestricted File Upload at Apple.com<br><a href="https://medium.com/@jonathanbouman/how-i-hacked-apple-com-unrestricted-file-upload-bcda047e27e3">https://medium.com/@jonathanbouman/how-i-hacked-apple-com-unrestricted-file-upload-bcda047e27e3</a></p><h3 id="Kubernetes"><a href="#Kubernetes" class="headerlink" title="Kubernetes"></a>Kubernetes</h3><p>From Teacher Alex’s PPT:</p><ul><li>關閉公開存取</li><li>實施存取權控管</li><li>加密 Kubernetes 金鑰</li><li>設定 Kubernetes 的存取控制器</li><li>設定 Kubernetes 的網路政策</li><li>設定容器的資安規則</li><li>分離敏感的工作負載</li><li>掃描容器映像</li><li>稽核日誌</li><li>更新最新的 Kubernetes版本</li></ul><h3 id="Kubernetes-master"><a href="#Kubernetes-master" class="headerlink" title="Kubernetes master"></a>Kubernetes master</h3><p>The Kubernetes master node is a critical component of a Kubernetes cluster, and it contains sensitive information about the configuration and operation of the cluster. Therefore, if a hacker gains access to the Kubernetes master node, they could potentially take control of the entire cluster and access or manipulate data and resources.</p><p>Can use Shodan to find Kubernetes masters.</p><h2 id="Tools-2"><a href="#Tools-2" class="headerlink" title="Tools"></a>Tools</h2><ul><li><p><strong>Trivy (Open source)</strong><br><a href="https://github.com/aquasecurity/trivy">https://github.com/aquasecurity/trivy</a><br>From trivy github:<br><img src="/img/hacking-class/trivy.png"></p></li><li><p><strong>Sysdig (Paid tool)</strong><br><a href="https://sysdig.com/">https://sysdig.com</a><br>From Teacher Alex’s PPT: 針對大量部屬的容器、Image、Kubernets Contoller 等安全性掃描</p></li><li><p><strong>Nimbostratus (Open source)</strong><br><a href="https://github.com/andresriancho/nimbostratus">https://github.com/andresriancho/nimbostratus</a><br>Same author who also created w3af: <a href="https://github.com/andresriancho/w3af">https://github.com/andresriancho/w3af</a></p></li><li><p><strong>s3-inspector (Open source)</strong><br>Check AWS S3 bucket permissions.<br><a href="https://github.com/clario-tech/s3-inspector">https://github.com/clario-tech/s3-inspector</a></p></li><li><p><strong>Kube-hunter (Open source)</strong><br>Check security weaknesses in Kubernetes clusters.<br><a href="https://github.com/aquasecurity/kube-hunter">https://github.com/aquasecurity/kube-hunter</a></p></li></ul>]]></content>
<categories>
<category> Learning notes </category>
</categories>
<tags>
<tag> Hacking </tag>
</tags>
</entry>
<entry>
<title>道德駭客實務入門-4/15課程筆記</title>
<link href="/2023/04/17/learning-notes/hacking-class-4/"/>
<url>/2023/04/17/learning-notes/hacking-class-4/</url>
<content type="html"><![CDATA[<p><a href="https://www.tibame.com/offline/ceh">TibaMe: 道德駭客實務入門及CEH認證班</a><br>Teacher: 林煌錡 (Alex Lin)<br><img src="/img/hacking-class/hacking-class-info.png"></p><blockquote><p>The class on 4/15 introduced session hijacking, malware, denial of service attack, and defense related items. </p></blockquote><h1 id="🔸-Session-hijacking"><a href="#🔸-Session-hijacking" class="headerlink" title="🔸 Session hijacking"></a>🔸 Session hijacking</h1><p>Wiki: <a href="https://en.wikipedia.org/wiki/Session_hijacking">https://en.wikipedia.org/wiki/Session_hijacking</a><br>OWASP: <a href="https://owasp.org/www-community/attacks/Session_hijacking_attack">https://owasp.org/www-community/attacks/Session_hijacking_attack</a></p><p>Session hijacking is a type of cyber attack where an attacker gains control of a user’s session in order to perform unauthorized actions. This can be done at both the network layer and the application layer.</p><h2 id="Application-layer"><a href="#Application-layer" class="headerlink" title="Application layer"></a>Application layer</h2><p>At the application layer, session hijacking involves stealing the user’s session cookie, which is used by the application to identify the user. Once the attacker has the session cookie, they can use it to impersonate the user and perform actions on their behalf.</p><h2 id="Network-layer"><a href="#Network-layer" class="headerlink" title="Network layer"></a>Network layer</h2><p>At the network layer, session hijacking involves intercepting packets between the client and server, and then impersonating the client to the server. The attacker can then inject their own packets into the session, and use them to perform actions as if they were the legitimate user.</p><h2 id="Tools"><a href="#Tools" class="headerlink" title="Tools"></a>Tools</h2><ul><li>Burp Suite</li><li>Owasp ZAP</li><li>Wireshark</li></ul><h2 id="Prevention"><a href="#Prevention" class="headerlink" title="Prevention"></a>Prevention</h2><p><a href="https://stackoverflow.com/questions/22880/what-is-the-best-way-to-prevent-session-hijacking">https://stackoverflow.com/questions/22880/what-is-the-best-way-to-prevent-session-hijacking</a></p><ul><li>DO NOT pass session ID as URL parameter</li><li>Set HttpOnly to true to prevent javascript to access the session cookie</li><li>Use encryption protocols such as SSL/TLS</li><li>Set session timeout</li><li>Ask for the password for some important operations</li></ul><h1 id="🔸-Denial-of-Service-attack-DoS"><a href="#🔸-Denial-of-Service-attack-DoS" class="headerlink" title="🔸 Denial of Service attack (DoS)"></a>🔸 Denial of Service attack (DoS)</h1><p>A DoS attack is carried out by a single attacker or machine, with the goal of overwhelming the target server or network with traffic or requests, making it unavailable to legitimate users.</p><p>Wiki: <a href="https://en.wikipedia.org/wiki/Denial-of-service_attack">https://en.wikipedia.org/wiki/Denial-of-service_attack</a></p><h2 id="Distributed-denial-of-service-attacks-DDoS"><a href="#Distributed-denial-of-service-attacks-DDoS" class="headerlink" title="Distributed denial of service attacks (DDoS)"></a>Distributed denial of service attacks (DDoS)</h2><p>A DDoS attack is carried out by a network of compromised computers, known as a botnet, which are controlled by the attacker. The botnet is used to flood the target server or network with traffic or requests, making it impossible for legitimate users to access it.</p><h3 id="Volumetric-attacks"><a href="#Volumetric-attacks" class="headerlink" title="Volumetric attacks"></a>Volumetric attacks</h3><ul><li>Flood</li><li>Amplification</li><li>ICMP Flood</li><li>UDP Flood</li></ul><h3 id="Protocol-Attacks"><a href="#Protocol-Attacks" class="headerlink" title="Protocol Attacks"></a>Protocol Attacks</h3><ul><li>SYNC flood</li><li>Fragmentation Attacks</li><li>TCP-State-Exhaustion Attacks</li><li>ACK flood</li></ul><h3 id="Application-Layer-Attacks"><a href="#Application-Layer-Attacks" class="headerlink" title="Application Layer Attacks"></a>Application Layer Attacks</h3><ul><li>Great Cannon<br>Wiki: <a href="https://en.wikipedia.org/wiki/Great_Cannon">https://en.wikipedia.org/wiki/Great_Cannon</a><br>Details: <a href="https://citizenlab.ca/2015/04/chinas-great-cannon/">https://citizenlab.ca/2015/04/chinas-great-cannon/</a><br>Very powerful DDoS attacking tool owned by China government, which makes me really worried about the servers in Taiwan.</li></ul><h3 id="Tools-1"><a href="#Tools-1" class="headerlink" title="Tools"></a>Tools</h3><ul><li><p>Slowloris<br>Script on github: <a href="https://github.com/gkbrk/slowloris">https://github.com/gkbrk/slowloris</a><br>Haven’t tried the script. If it’s working, i think i can use this to perform a DoS test attack on the server.</p></li><li><p>Botnet<br>A botnet is a network of compromised computers that are controlled by a cybercriminal or a group of cybercriminals for malicious purposes. These computers, also known as “bots” or “zombies,” are typically infected with malware that enables the attacker to remotely control them without the knowledge or consent of their owners.<br>From Teacher Alex’s PPT:<br><img src="/img/hacking-class/botnet.png"></p></li></ul><h3 id="Others"><a href="#Others" class="headerlink" title="Others"></a>Others</h3><p>DDoS is a powerful attack, and it can be carried out by individuals with minimal technical knowledge. While some of the most high-profile DDoS attacks have been carried out by hacktivist groups.</p><p>Recommend reading the news : <a href="https://technews.tw/2022/03/06/hackers-begin-weaponizing-tcp-middlebox-reflection-for-amplified-ddos-attacks/">https://technews.tw/2022/03/06/hackers-begin-weaponizing-tcp-middlebox-reflection-for-amplified-ddos-attacks/</a><br>More details regarding the cyber-attacks on Ukraine : <a href="https://www.europarl.europa.eu/RegData/etudes/BRIE/2022/733549/EPRS_BRI(2022)733549_EN.pdf">https://www.europarl.europa.eu/RegData/etudes/BRIE/2022/733549/EPRS_BRI(2022)733549_EN.pdf</a></p><h1 id="🔸-Malware"><a href="#🔸-Malware" class="headerlink" title="🔸 Malware"></a>🔸 Malware</h1><h2 id="Advanced-Persistent-Threat-APT"><a href="#Advanced-Persistent-Threat-APT" class="headerlink" title="Advanced Persistent Threat (APT)"></a>Advanced Persistent Threat (APT)</h2><p>An Advanced Persistent Threat (APT) is a type of cyberattack in which an unauthorized user gains access to a system or network and <strong>remains undetected for an extended period of time</strong>. APT attacks are typically carried out by skilled and patient attackers who are motivated by political or financial gain. The goal of an APT attack is to gain access to sensitive data, and to maintain access to the system or network for as long as possible in order to achieve their objectives.</p><p>Wiki: <a href="https://en.wikipedia.org/wiki/Advanced_persistent_threat">https://en.wikipedia.org/wiki/Advanced_persistent_threat</a><br><a href="https://ithelp.ithome.com.tw/articles/10188821">https://ithelp.ithome.com.tw/articles/10188821</a></p><h2 id="Trojan"><a href="#Trojan" class="headerlink" title="Trojan"></a>Trojan</h2><ul><li>njRAT</li><li>Poison ivy</li><li>Necurs</li><li>Rootkit Sirefef</li><li>Emotet</li><li>Glitch</li><li>Zeus</li><li>Mirai</li></ul><h2 id="Viruses-amp-Worms"><a href="#Viruses-amp-Worms" class="headerlink" title="Viruses & Worms"></a>Viruses & Worms</h2><ul><li>EternalBlue</li></ul><h2 id="Fileless-attack"><a href="#Fileless-attack" class="headerlink" title="Fileless attack"></a>Fileless attack</h2><p>A fileless attack is a type of cyber attack that does not rely on malware being downloaded and stored on a victim’s computer. Instead, it exploits vulnerabilities in the operating system or software applications to <strong>run malicious code in memory</strong> or abuse legitimate tools to carry out attacks. Because there is no file to detect and delete, fileless attacks can be <strong>harder to detect</strong> and mitigate than traditional malware-based attacks.</p><p>Wiki: <a href="https://en.wikipedia.org/wiki/Fileless_malware">https://en.wikipedia.org/wiki/Fileless_malware</a></p><p>From Microsoft:<br><a href="https://learn.microsoft.com/zh-tw/microsoft-365/security/intelligence/fileless-threats?view=o365-worldwide">https://learn.microsoft.com/zh-tw/microsoft-365/security/intelligence/fileless-threats?view=o365-worldwide</a></p><p>From Teacher Alex’s PPT:<br><img src="/img/hacking-class/fileless-attack.png"></p><h1 id="🔸-Defense"><a href="#🔸-Defense" class="headerlink" title="🔸 Defense"></a>🔸 Defense</h1><h2 id="Intrusion-Detection-System-IDS"><a href="#Intrusion-Detection-System-IDS" class="headerlink" title="Intrusion Detection System (IDS)"></a>Intrusion Detection System (IDS)</h2><p>IDS Wiki: <a href="https://en.wikipedia.org/wiki/Intrusion_detection_system">https://en.wikipedia.org/wiki/Intrusion_detection_system</a></p><h3 id="Tool-SNORT"><a href="#Tool-SNORT" class="headerlink" title="Tool: SNORT"></a>Tool: SNORT</h3><p><a href="https://zh.wikipedia.org/zh-tw/Snort">https://zh.wikipedia.org/zh-tw/Snort</a><br><a href="https://www.snort.org/">https://www.snort.org</a></p><h2 id="Firewall"><a href="#Firewall" class="headerlink" title="Firewall"></a>Firewall</h2><p>Firewall Wiki: <a href="https://en.wikipedia.org/wiki/Firewall_(computing)">https://en.wikipedia.org/wiki/Firewall_(computing)</a></p><h2 id="Honeypot"><a href="#Honeypot" class="headerlink" title="Honeypot"></a>Honeypot</h2><p>Honeypot Wiki: <a href="https://en.wikipedia.org/wiki/Honeypot_(computing)">https://en.wikipedia.org/wiki/Honeypot_(computing)</a><br>Can use Shodan to find Honeypot system, but some of them might be fake honeypots.<br>From Teacher Alex’s PPT<br><img src="/img/hacking-class/honeypot.png"></p><h2 id="Evasion"><a href="#Evasion" class="headerlink" title="Evasion"></a>Evasion</h2><p>NMAP: <a href="https://nmap.org/book/man-bypass-firewalls-ids.html">https://nmap.org/book/man-bypass-firewalls-ids.html</a></p><h3 id="Intrusion-Detection-System-Evasion"><a href="#Intrusion-Detection-System-Evasion" class="headerlink" title="Intrusion Detection System Evasion"></a>Intrusion Detection System Evasion</h3><p>Wiki: <a href="https://en.wikipedia.org/wiki/Intrusion_detection_system_evasion_techniques">https://en.wikipedia.org/wiki/Intrusion_detection_system_evasion_techniques</a><br><a href="https://medium.com/@IamLucif3r/top-10-firewall-ids-evasion-techniques-cb1e1cc06f24">https://medium.com/@IamLucif3r/top-10-firewall-ids-evasion-techniques-cb1e1cc06f24</a></p><ul><li>Obfuscation. Can use Base64 encoding.</li><li>False Negative</li><li>Session splicing</li><li>Unicode Bypass</li><li>Packet Splitting</li><li>Time To Live (TTL)</li><li>Polymorphic Shell Code</li><li>ASCII Shell Code</li></ul><h3 id="Firewall-Evasion"><a href="#Firewall-Evasion" class="headerlink" title="Firewall Evasion"></a>Firewall Evasion</h3><ul><li>Firewalking</li><li>Source Routing</li><li>Tiny Fragments</li><li>ICMP Tunneling</li><li>Ack Tunneling</li><li>HTTP / HTTPS Tunneling</li></ul><h1 id="🔸-Recommend-reading-articles-from-Alex-Xu’s-https-blog-bytebytego-com"><a href="#🔸-Recommend-reading-articles-from-Alex-Xu’s-https-blog-bytebytego-com" class="headerlink" title="🔸 Recommend reading articles from Alex Xu’s https://blog.bytebytego.com"></a>🔸 Recommend reading articles from Alex Xu’s <a href="https://blog.bytebytego.com/">https://blog.bytebytego.com</a></h1><h2 id="Password-Session-Cookie-Token-JWT-SSO-OAuth"><a href="#Password-Session-Cookie-Token-JWT-SSO-OAuth" class="headerlink" title="Password, Session, Cookie, Token, JWT, SSO, OAuth"></a>Password, Session, Cookie, Token, JWT, SSO, OAuth</h2><p><a href="https://blog.bytebytego.com/p/password-session-cookie-token-jwt">https://blog.bytebytego.com/p/password-session-cookie-token-jwt</a><br><a href="https://blog.bytebytego.com/p/ep34-session-cookie-jwt-token-sso">https://blog.bytebytego.com/p/ep34-session-cookie-jwt-token-sso</a></p><h2 id="HTTPS-amp-MitM-attack"><a href="#HTTPS-amp-MitM-attack" class="headerlink" title="HTTPS & MitM attack"></a>HTTPS & MitM attack</h2><p><a href="https://blog.bytebytego.com/p/how-does-https-work-episode-6">https://blog.bytebytego.com/p/how-does-https-work-episode-6</a><br><img src="/img/hacking-class/https-1.png"><br><a href="https://blog.bytebytego.com/p/ep21-is-https-safe-also">https://blog.bytebytego.com/p/ep21-is-https-safe-also</a><br><img src="/img/hacking-class/https-2.png"></p>]]></content>
<categories>
<category> Learning notes </category>
</categories>
<tags>
<tag> Hacking </tag>
</tags>
</entry>
<entry>
<title>道德駭客實務入門-4/8課程筆記-3</title>
<link href="/2023/04/10/learning-notes/hacking-class-3-3/"/>
<url>/2023/04/10/learning-notes/hacking-class-3-3/</url>
<content type="html"><![CDATA[<p><a href="https://www.tibame.com/offline/ceh">TibaMe: 道德駭客實務入門及CEH認證班</a><br>Teacher: 林煌錡 (Alex Lin)<br><img src="/img/hacking-class/hacking-class-info.png"></p><blockquote><p>The class on 4/8 introduced web server hacking, web application hacking, and database injection. I separated the content into three parts. This note is about database injection, web backdoor, and security testing.</p></blockquote><p><font style="font-size: 30px;font-weight:bold;">Part3: DB Injection, Web Backdoor, and Testing</font></p><h1 id="🔸-Database-Injection"><a href="#🔸-Database-Injection" class="headerlink" title="🔸 Database Injection"></a>🔸 Database Injection</h1><p>DB injection 與 CMD injection 有諸多相似之處,都是用injection這個方法,讓server執行了不該執行的指令。<br>DB injection 是注入SQL指令,目的在於從DB中偷取資料。</p><p>推薦閱讀: <a href="https://tech-blog.cymetrics.io/posts/nick/sqli/">https://tech-blog.cymetrics.io/posts/nick/sqli/</a><br><a href="https://en.wikipedia.org/wiki/SQL_injection">SQL Injection Wiki</a><br><a href="https://owasp.org/www-community/attacks/SQL_Injection">SQL Injection OWASP</a></p><h2 id="In-band-SQL-Injection-直接注入"><a href="#In-band-SQL-Injection-直接注入" class="headerlink" title="In-band SQL Injection (直接注入)"></a>In-band SQL Injection (直接注入)</h2><p>要可以看見注入的結果才能使用。</p><h3 id="Union-Base-聯合注入"><a href="#Union-Base-聯合注入" class="headerlink" title="Union Base (聯合注入)"></a>Union Base (聯合注入)</h3><p>Use the <code>union</code> command in SQL.</p><h3 id="Stacked-Base-堆疊注入"><a href="#Stacked-Base-堆疊注入" class="headerlink" title="Stacked Base (堆疊注入)"></a>Stacked Base (堆疊注入)</h3><p>Use the <code>;</code> to concatenate SQL statements.</p><h3 id="Error-Base-錯誤注入"><a href="#Error-Base-錯誤注入" class="headerlink" title="Error Base (錯誤注入)"></a>Error Base (錯誤注入)</h3><p>Use the <code>`</code> to generate DB errors to get useful information.</p><h2 id="Inferential-Injection-推測注入"><a href="#Inferential-Injection-推測注入" class="headerlink" title="Inferential Injection (推測注入)"></a>Inferential Injection (推測注入)</h2><p>可在無法看到攻擊的結果時使用,需要比較長的時間。</p><h3 id="Boolean-Base"><a href="#Boolean-Base" class="headerlink" title="Boolean Base"></a>Boolean Base</h3><p>前提是:SQL查詢結果會讓網頁的回應不同。<br>Sample1: Get the length of the DB name.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://127.0.0.1/LAB/index.php?id=1 ' and 1 = ((select length(database()))>10) #</span><br></pre></td></tr></table></figure><p>Sample2: Get the name of the DB character by character.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://127.0.0.1/LAB/index.php?id=1 ' and 1 = (select ascii(substr(database(),1,1) > 100)) #</span><br></pre></td></tr></table></figure><h3 id="Time-Base"><a href="#Time-Base" class="headerlink" title="Time Base"></a>Time Base</h3><p>用回應時間來確認查詢結果。<br>Sample1: Get the length of the DB name.<br>If the DB name length is larger than 3, sleep for 5s. Otherwise, sleep for 1s.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://127.0.0.1/LAB/index.php?id=1 ' and 1 = (select if(length(database()) > 3,SLEEP(5),SLEEP(1))) #</span><br></pre></td></tr></table></figure><p>Sample2: Get the name of the DB character by character.<br>If the ASCII code of the first character of the DB name is larger than 100, sleep for 5s. Otherwise, sleep for 1s.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://127.0.0.1/LAB/index.php?id=1 ' and 1 = (select if(ascii(substr(database(),1,1) > 100,SLEEP(5),SLEEP(1)))) #</span><br></pre></td></tr></table></figure><h2 id="Out-of-Band-SQL-Injection-帶出注入"><a href="#Out-of-Band-SQL-Injection-帶出注入" class="headerlink" title="Out-of-Band SQL Injection (帶出注入)"></a>Out-of-Band SQL Injection (帶出注入)</h2><p>這是一個比較複雜的方法。The information below comes from Teacher Alex’s PPT:</p><blockquote><p>查詢完的資料分批加入攻擊者的網址中,並向攻擊者的伺服器做請求, 攻擊者的網站會將請求記錄下來,再將這些紀錄做解析,就可以重新拼湊查詢結果。</p></blockquote><h2 id="Hacking-tool-sqlmap"><a href="#Hacking-tool-sqlmap" class="headerlink" title="Hacking tool sqlmap"></a>Hacking tool sqlmap</h2><p>sqlmap: <a href="https://sqlmap.org/">https://sqlmap.org</a><br>這個工具可以用來做注入攻擊,也可以用來做漏洞掃瞄。</p><h2 id="Prevention"><a href="#Prevention" class="headerlink" title="Prevention"></a>Prevention</h2><p>一定要過濾input data。使用<strong>ORM</strong>可以有一定的防護效果。<br>Prevention from OWASP: <a href="https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html">https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html</a><br>推薦閱讀: <a href="https://www.globaldots.com/resources/blog/8-best-practices-to-prevent-sql-injection-attacks/">https://www.globaldots.com/resources/blog/8-best-practices-to-prevent-sql-injection-attacks/</a></p><h1 id="🔸-Web-Backdoor-Web-shell"><a href="#🔸-Web-Backdoor-Web-shell" class="headerlink" title="🔸 Web Backdoor (Web shell)"></a>🔸 Web Backdoor (Web shell)</h1><p>Explanation below is from <a href="https://www.readfog.com/a/1657962631160500224">https://www.readfog.com/a/1657962631160500224</a><br><img src="/img/hacking-class/web-shell-1.png"></p><p><a href="https://en.wikipedia.org/wiki/Web_shell">Web Shell Wiki</a></p><p><strong>老師上課時有給大家看Demo影片,幾句甚至是一句 Script 就可以遠端控制整台機器,讓我印象深刻。</strong><br><strong>防禦方法: 監控網頁完整性</strong></p><h2 id="針對不同語言開發的-server-有不同的-Web-shell"><a href="#針對不同語言開發的-server-有不同的-Web-shell" class="headerlink" title="針對不同語言開發的 server 有不同的 Web shell"></a>針對不同語言開發的 server 有不同的 Web shell</h2><p><img src="/img/hacking-class/web-shell-2.png"></p><h2 id="Famous-Web-Shells"><a href="#Famous-Web-Shells" class="headerlink" title="Famous Web Shells"></a>Famous Web Shells</h2><p>老師上課時強調,以下工具威害極大,輕易不要亂試,要試也最好在VM上試。</p><h3 id="🚫-CKnife-中國菜刀"><a href="#🚫-CKnife-中國菜刀" class="headerlink" title="🚫 CKnife (中國菜刀)"></a>🚫 CKnife (中國菜刀)</h3><p><a href="https://github.com/chora10/cknife">https://github.com/chora10/cknife</a></p><h3 id="🚫-Behinder-冰蝎"><a href="#🚫-Behinder-冰蝎" class="headerlink" title="🚫 Behinder (冰蝎)"></a>🚫 Behinder (冰蝎)</h3><p><a href="https://github.com/rebeyond/Behinder">https://github.com/rebeyond/Behinder</a></p><h3 id="🚫-B374K"><a href="#🚫-B374K" class="headerlink" title="🚫 B374K"></a>🚫 B374K</h3><p><a href="https://github.com/b374k/b374k">https://github.com/b374k/b374k</a></p><h3 id="🚫-C99"><a href="#🚫-C99" class="headerlink" title="🚫 C99"></a>🚫 C99</h3><h2 id="Demo-Video"><a href="#Demo-Video" class="headerlink" title="Demo Video"></a>Demo Video</h2><p>Webshell原理讲解+剖析中国菜刀所有功能及后门<br><a href="https://www.bilibili.com/video/BV1c7411v7cS/">https://www.bilibili.com/video/BV1c7411v7cS/</a></p><h1 id="🔸-File-Upload-Vulnerability"><a href="#🔸-File-Upload-Vulnerability" class="headerlink" title="🔸 File Upload Vulnerability"></a>🔸 File Upload Vulnerability</h1><p>文件上傳漏洞就可以用來上傳 Web Shell。<br>Explanation from OWASP: <a href="https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload">https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload</a></p><h2 id="課堂練習1"><a href="#課堂練習1" class="headerlink" title="課堂練習1"></a>課堂練習1</h2><p>完全沒有限定上傳的文件。</p><ul><li>Save script <code><?php system($_GET['cmd']); ?></code> into a file.</li><li>Upload the file to the test server and retrieve the path.<br><img src="/img/hacking-class/upload-lab-1.png"><br><img src="/img/hacking-class/upload-lab-2.png"></li><li>Execute the script file with the command: <code>ls -al</code>.<br><img src="/img/hacking-class/upload-lab-3.png"></li></ul><h2 id="課堂練習2"><a href="#課堂練習2" class="headerlink" title="課堂練習2"></a>課堂練習2</h2><p>只限定了上傳文件的類型。</p><ul><li>Test sever code<br><img src="/img/hacking-class/upload-file-ext.png"></li><li>Uploading the script file failed because the sever limits the file type.</li><li>Using <strong>Burp Suite</strong>, grab the packet, change the file type in the packet and send it to server. The upload is successful. </li><li><strong>Burp Suite</strong> is really a powerful tool for capturing browser traffic and sending modified packets.<br><img src="/img/hacking-class/burp-suite-1.png"><br><img src="/img/hacking-class/burp-suite-2.png"></li></ul><h2 id="課堂練習3"><a href="#課堂練習3" class="headerlink" title="課堂練習3"></a>課堂練習3</h2><p>有檢查上傳文件內容。</p><ul><li><p>Test sever code<br><img src="/img/hacking-class/upload-file-type.png"></p></li><li><p>Edit the script file to make it looks like required file type.<br>In this exercise, the required file type is png.<br>Use hex editor to add the png header <code>89 50 4E 47 0D 0A 1A 0A</code> to the script file<br>Save it as a .png file.<br>Upload the fake png file successfully.</p></li><li><p>Use ** Burp Suite**.<br>Since the file type is png, you can not execute it as the previous exercises.<br>Use Burp Suite to add header parameters to achieve the goal.<br><img src="/img/hacking-class/burp-suite-3.png"><br><img src="/img/hacking-class/burp-suite-4.png"></p></li><li><p>Use the url below to run the script</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://172.16.25.25:8096/include.php?page=uploads/c8969a17ee5cf29d745fcdb1c9bc94497da0fba3/maochunshell2.png</span><br></pre></td></tr></table></figure></li></ul><h2 id="Burp-suite-setting"><a href="#Burp-suite-setting" class="headerlink" title="Burp suite setting"></a>Burp suite setting</h2><p>To use the burp suite, you need to set up the browser’s proxy.<br><img src="/img/hacking-class/browser-proxy-config.png"></p><h1 id="🔸-Testing"><a href="#🔸-Testing" class="headerlink" title="🔸 Testing"></a>🔸 Testing</h1><h2 id="Static-Application-Security-Testing-SAST-靜態測試"><a href="#Static-Application-Security-Testing-SAST-靜態測試" class="headerlink" title="Static Application Security Testing (SAST) (靜態測試)"></a>Static Application Security Testing (SAST) (靜態測試)</h2><p>不執行程式,只掃瞄源碼,可以精準定位有問題的原始碼,但是可能掃不出有混淆過的惡意腳本。<br><a href="https://en.wikipedia.org/wiki/Static_application_security_testing">SAST Wiki</a></p><ul><li><p>老師推薦工具:<a href="https://codeball.ai/">https://codeball.ai</a></p></li><li><p>Github上有提供多種工具</p></li></ul><p><img src="/img/hacking-class/sast-github-1.png"></p><ul><li><p>Code scan document on Github: <a href="https://docs.github.com/en/code-security/code-scanning">https://docs.github.com/en/code-security/code-scanning</a></p></li><li><p>試用 CodeQL</p></li></ul><p><img src="/img/hacking-class/sast-github-2.png"><br><img src="/img/hacking-class/sast-github-3.png"></p><h2 id="Dynamic-Application-Security-Testing-DAST-動態測試"><a href="#Dynamic-Application-Security-Testing-DAST-動態測試" class="headerlink" title="Dynamic Application Security Testing (DAST) (動態測試)"></a>Dynamic Application Security Testing (DAST) (動態測試)</h2><p>執行程式,進行測試,不需要源碼,會有誤報。<br><a href="https://en.wikipedia.org/wiki/Dynamic_application_security_testing">DAST Wiki</a></p><h2 id="Interactive-Application-Security-Testing-IAST-交互式測試"><a href="#Interactive-Application-Security-Testing-IAST-交互式測試" class="headerlink" title="Interactive Application Security Testing (IAST) (交互式測試)"></a>Interactive Application Security Testing (IAST) (交互式測試)</h2><p>結合 SAST 和 IAST,適合開發者使用。<br><a href="https://en.wikipedia.org/wiki/International_Alphabet_of_Sanskrit_Transliteration">IAST Wiki</a></p><h1 id="🔸-Bug-Bounty-Program-漏洞回報獎勵計畫"><a href="#🔸-Bug-Bounty-Program-漏洞回報獎勵計畫" class="headerlink" title="🔸 Bug Bounty Program (漏洞回報獎勵計畫)"></a>🔸 Bug Bounty Program (漏洞回報獎勵計畫)</h1><p>集眾人之力來找漏洞,是行之有效的方法。<br><a href="https://en.wikipedia.org/wiki/Bug_bounty_program">Bug Bounty Wiki</a></p><ul><li><p>大公司都有提供自己的Bug Bounty.<br><img src="/img/hacking-class/bug-bounty-1.png"></p></li><li><p>老師推薦台灣駭客協會的Bug Bounty:<a href="https://zeroday.hitcon.org/vulnerability">https://zeroday.hitcon.org/vulnerability</a><br>有漏洞破解的詳細說明。</p></li><li><p>很完整的各種漏洞reports: <a href="https://github.com/reddelexc/hackerone-reports">https://github.com/reddelexc/hackerone-reports</a><br><img src="/img/hacking-class/bug-bounty-2.png"></p></li></ul>]]></content>
<categories>
<category> Learning notes </category>
</categories>
<tags>
<tag> Hacking </tag>
</tags>
</entry>
<entry>
<title>道德駭客實務入門-4/8課程筆記-2</title>
<link href="/2023/04/10/learning-notes/hacking-class-3-2/"/>
<url>/2023/04/10/learning-notes/hacking-class-3-2/</url>
<content type="html"><![CDATA[<p><a href="https://www.tibame.com/offline/ceh">TibaMe: 道德駭客實務入門及CEH認證班</a><br>Teacher: 林煌錡 (Alex Lin)<br><img src="/img/hacking-class/hacking-class-info.png"></p><blockquote><p>The class on 4/8 introduced web server hacking, web application hacking and database injection. I separated the contents into three parts. This note is about web application hacking.</p></blockquote><p><font style="font-size: 30px;font-weight:bold;">Part2: Web application hacking</font></p><h1 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h1><h2 id="OWASP-“Web-Security”-vs-“API-security”"><a href="#OWASP-“Web-Security”-vs-“API-security”" class="headerlink" title="OWASP “Web Security” vs “API security”"></a>OWASP “Web Security” vs “API security”</h2><p>The answers below is from ChatGPT and it’s very accurate.<br>OWASP (Open Web Application Security Project) is a nonprofit organization that provides resources and guidance on best practices for securing web applications and APIs. While web applications and APIs share many common security principles, there are some differences between the two.</p><p>OWASP Web Security focuses on securing traditional web applications, such as those built with HTML, CSS, and JavaScript. This includes securing the web server, web application framework, user authentication and authorization, and preventing common web application attacks such as cross-site scripting (XSS) and SQL injection.</p><p>OWASP API Security, on the other hand, focuses on securing APIs (Application Programming Interfaces), which are used to allow different software applications to communicate with each other. This includes securing the API server, handling authentication and authorization, and preventing common API vulnerabilities such as injection attacks, broken access control, and insufficient logging and monitoring.</p><p><strong>In general, web applications are designed to be accessed through a browser and provide a user interface for interacting with the application, while APIs are designed for programmatic access and allow different software applications to communicate with each other.</strong> This means that while web applications and APIs share many common security principles, there are some differences in the specific security considerations that need to be taken into account when securing each type of application.</p><blockquote><p><span style="color:red;font-size: 18px"><strong>真心建議每一位Server開發者都仔細閱讀 OWASP</strong> <br /> <strong>“Web Security Top10” and “API Security Top10”。</strong> <br />漏洞可以用工具掃瞄,但設計上的缺陷是掃瞄不出來的。 </span></p></blockquote><h1 id="OWASP-Web-Security-Top-10"><a href="#OWASP-Web-Security-Top-10" class="headerlink" title="OWASP Web Security Top 10"></a>OWASP Web Security Top 10</h1><table><thead><tr><th align="left">2017</th><th align="left"></th></tr></thead><tbody><tr><td align="left">A1:2017 Injection (注入攻擊)</td><td align="left"></td></tr><tr><td align="left">A2:2017 Broken Authentication (無效的身分驗證)</td><td align="left"></td></tr><tr><td align="left">A3:2017 Sensitive Data Exposure (敏感資料外洩)</td><td align="left"></td></tr><tr><td align="left">A4:2017 XML External Entity (XXE XML 外部處理器弱點)</td><td align="left"></td></tr><tr><td align="left">A5:2017 Broken Access Control (無效的存取控管)</td><td align="left"></td></tr><tr><td align="left">A6:2017 Security Misconfiguration (不安全的組態設定)</td><td align="left"></td></tr><tr><td align="left">A7:2017 Cross-Site Scripting (XSS) (跨站腳本攻擊)</td><td align="left">前端攻擊</td></tr><tr><td align="left">A8:2017 Insecure Deserialization (不安全的反序列化弱點)</td><td align="left"></td></tr><tr><td align="left">A9:2017 Using Components With Known Vulnerabilities (使用已知漏洞元件)</td><td align="left"></td></tr><tr><td align="left">A10:2017 Insufficient Logging and Monitoring (記錄與監控不足)</td><td align="left"></td></tr></tbody></table><p><strong>2017 => 2021 from OWASP official site</strong><br><img src="/img/hacking-class/owasp.png"></p><table><thead><tr><th align="left">2022 new items</th></tr></thead><tbody><tr><td align="left">A04:2021 Insecure Design (不安全設計)</td></tr><tr><td align="left">A08:2021 Software and Data Integrity Failures (軟體及資料完整性失效)</td></tr><tr><td align="left">A10:2021 Server-Side Request Forgery (SSRF) (伺服端請求偽造)</td></tr></tbody></table><h2 id="🔸-A1-2017-Injection-注入攻擊"><a href="#🔸-A1-2017-Injection-注入攻擊" class="headerlink" title="🔸 A1:2017 Injection (注入攻擊)"></a>🔸 A1:2017 Injection (注入攻擊)</h2><h3 id="Command-Injection"><a href="#Command-Injection" class="headerlink" title="Command Injection"></a>Command Injection</h3><ul><li><p>Server端處理 user input 時如果使用System call,又沒有過濾 input data,就有可能執行了不該執行的command.<br>Hacker can use the linux operators to execute multiple commands, e.g. |, ; , ||, && .</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">ls</span> -l `|` <span class="built_in">wc</span> -lwc | </span><br><span class="line"><span class="built_in">cat</span> test.txt ; <span class="built_in">mkdir</span> mydir ; <span class="built_in">cd</span> Download |</span><br><span class="line"><span class="built_in">mkdir</span> mydir && <span class="built_in">cd</span> mydir</span><br><span class="line"><span class="built_in">cat</span> test1.txt || <span class="built_in">cat</span> test2.txt</span><br></pre></td></tr></table></figure><table><thead><tr><th align="left">System calls in Django</th></tr></thead><tbody><tr><td align="left">subprocess.call</td></tr><tr><td align="left">subprocess.run</td></tr><tr><td align="left">subprocess.popen</td></tr><tr><td align="left">subprocess.check_output</td></tr><tr><td align="left">os.system</td></tr></tbody></table></li><li><p>防禦方法<br>我覺得最直接的方法就是Server端處理user input時不要使用System call.<br>Prevention from <a href="https://www.imperva.com/learn/application-security/command-injection/">Imperva.com</a><br><img src="/img/hacking-class/prevent-cmd-inj.png"></p></li><li><p>課堂練習1</p></li></ul><p><img src="/img/hacking-class/cmd-inj-low.png"></p><h3 id="SQL-Injection"><a href="#SQL-Injection" class="headerlink" title="SQL Injection"></a>SQL Injection</h3><p>In part 3.</p><h3 id="LDAP-Injection"><a href="#LDAP-Injection" class="headerlink" title="LDAP Injection"></a>LDAP Injection</h3><p>…</p><h3 id="Useful-Hacking-Technology-🔥"><a href="#Useful-Hacking-Technology-🔥" class="headerlink" title="Useful Hacking Technology 🔥"></a>Useful Hacking Technology 🔥</h3><p>各種hacking和injection大全: <a href="https://github.com/swisskyrepo/PayloadsAllTheThings">https://github.com/swisskyrepo/PayloadsAllTheThings</a> 非常詳細,有提供各種hacking腳本。</p><h2 id="🔸-A2-2017-Broken-Authentication-無效的身分驗證"><a href="#🔸-A2-2017-Broken-Authentication-無效的身分驗證" class="headerlink" title="🔸 A2:2017 Broken Authentication (無效的身分驗證)"></a>🔸 A2:2017 Broken Authentication (無效的身分驗證)</h2><p>我覺得對於駭客而言,找漏洞繞過驗證,遠比暴力破解密碼有效率的多。<br>漏洞介紹:<a href="https://ithelp.ithome.com.tw/articles/10216516">https://ithelp.ithome.com.tw/articles/10216516</a><br>弱點詳細和防護:<a href="https://apisecurity.io/encyclopedia/content/owasp/api2-broken-authentication.htm">https://apisecurity.io/encyclopedia/content/owasp/api2-broken-authentication.htm</a></p><h2 id="🔸-A3-2017-Sensitive-Data-Exposure-敏感資料外洩"><a href="#🔸-A3-2017-Sensitive-Data-Exposure-敏感資料外洩" class="headerlink" title="🔸 A3:2017 Sensitive Data Exposure (敏感資料外洩)"></a>🔸 A3:2017 Sensitive Data Exposure (敏感資料外洩)</h2><p>下面兩個漏洞都是利用記憶體洩漏來竊取有用資訊。</p><h3 id="Heart-Bleed-CVE-2014-0160"><a href="#Heart-Bleed-CVE-2014-0160" class="headerlink" title="Heart Bleed CVE-2014-0160"></a>Heart Bleed CVE-2014-0160</h3><p>算是比較舊的漏洞。<br><a href="https://en.wikipedia.org/wiki/Heartbleed">Heart Bleed Wiki</a><br>這裡有檢測方法:<a href="https://github.com/0x90/CVE-2014-0160">https://github.com/0x90/CVE-2014-0160</a></p><h3 id="NginX-CVE-2017-7529"><a href="#NginX-CVE-2017-7529" class="headerlink" title="NginX CVE-2017-7529"></a>NginX CVE-2017-7529</h3><p>漏洞利用方法:<a href="https://github.com/liusec/CVE-2017-7529">https://github.com/liusec/CVE-2017-7529</a><br>影响版本:Nginx version 0.5.6 - 1.13.2<br>Check NginX command:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nginx -v</span><br></pre></td></tr></table></figure><p>No need to worry about the issue if you use the latest NginX version.</p><h2 id="🔸-A4-2017-XML-External-Entity-XXE-XML-外部處理器弱點"><a href="#🔸-A4-2017-XML-External-Entity-XXE-XML-外部處理器弱點" class="headerlink" title="🔸 A4:2017 XML External Entity (XXE XML 外部處理器弱點)"></a>🔸 A4:2017 XML External Entity (XXE XML 外部處理器弱點)</h2><p>Server使用xml格式傳送資料,如果沒有禁用ENTITY的話,駭客就有可能將ENTITY注入封包回傳server,得到有用資訊。<br>I think most of the servers use JSON format now. If the server uses JSON, no need to worry about this.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><!ENTITY xxe SYSTEM <span class="string">"file:///etc/passwd"</span>></span><br></pre></td></tr></table></figure><p>具體攻擊方法可以參考果核數位這篇:<a href="https://www.digicentre.com.tw/industry_detail?id=38">https://www.digicentre.com.tw/industry_detail?id=38</a></p><h2 id="🔸-A5-2017-Broken-Access-Control-無效的存取控管"><a href="#🔸-A5-2017-Broken-Access-Control-無效的存取控管" class="headerlink" title="🔸 A5:2017 Broken Access Control (無效的存取控管)"></a>🔸 A5:2017 Broken Access Control (無效的存取控管)</h2><p>沒有控制好server上的檔案存取權限的話,駭客就有可能用 LFI(Local File Inclusion)取的檔案內容,比如Config檔案竊取帳密,或是上傳惡意腳本並執行已取得server控制權。<br>You can find more details regarding the “CVE-2022-29597: Local File Inclusion” at <a href="https://github.com/TheGetch/CVE-2022-29597">https://github.com/TheGetch/CVE-2022-29597</a><br>我覺得使用Docker可以起到一定的防護。</p><h2 id="🔸-A6-2017-Security-Misconfiguration-不安全的組態設定"><a href="#🔸-A6-2017-Security-Misconfiguration-不安全的組態設定" class="headerlink" title="🔸 A6:2017 Security Misconfiguration (不安全的組態設定)"></a>🔸 A6:2017 Security Misconfiguration (不安全的組態設定)</h2><p>只要server上的應用程式、伺服器或平台本身設定有問題,就有可能被攻擊或被竊取資訊。<br>案例:IBM 沒有管控好網頁服務上的log檔案,攻擊者找到有用的雲端服務認證資訊。<br>推薦閱讀 <a href="https://3bodymo.medium.com/how-i-hacked-ibm-and-got-full-access-on-many-services-ecf1dab4a054">https://3bodymo.medium.com/how-i-hacked-ibm-and-got-full-access-on-many-services-ecf1dab4a054</a> ,該文章作者成功從IBM的server log中取得IBM的一些認證資訊, e.g. AWS, gitlab, Jenkins…</p><h2 id="🔸-A7-2017-Cross-Site-Scripting-XSS-跨站腳本攻擊-🔥"><a href="#🔸-A7-2017-Cross-Site-Scripting-XSS-跨站腳本攻擊-🔥" class="headerlink" title="🔸 A7:2017 Cross-Site Scripting (XSS) (跨站腳本攻擊) 🔥"></a>🔸 A7:2017 Cross-Site Scripting (XSS) (跨站腳本攻擊) 🔥</h2><p><span style="color:red;font-size: 18px"><strong>這是十種攻擊中唯一的前端攻擊,主要攻擊前端瀏覽網頁的用戶。</strong></span><br>在網頁中注入腳本,這個腳本就會被所有瀏覽網頁的瀏覽器執行。感覺這是一個門檻很低又行之有效的攻擊手法。這類攻擊不太好防。</p><ul><li><p>被動注入<br>在別人網頁的留言板中注入惡意腳本,所有瀏覽網頁的用戶都中招。</p></li><li><p>主動注入<br>在自己的網頁中注入惡意腳本,這就是惡意網站了,把網站link寄給受害者,受害者點開就中招。<br>Demo web page i created: <a href="http://maochun.rojak.fun/2023/04/13/learning-notes/xss-test-page/">http://maochun.rojak.fun/2023/04/13/learning-notes/xss-test-page/</a> 打開就會執行腳本<code><script>alert(document.cookie)</script></code>(沒有惡意腳本)</p></li><li><p>課堂練習<br>靶機網頁上有一個輸入框,輸入 <code><script>alert(document.cookie)</script></code> , 按下submit輸入的腳本被執行。<br><img src="/img/hacking-class/xss-attack-1.png"><br><img src="/img/hacking-class/xss-attack-2.png"></p></li><li><p>XSS Wiki<br><a href="https://en.wikipedia.org/wiki/Cross-site_scripting">https://en.wikipedia.org/wiki/Cross-site_scripting</a></p></li></ul><h2 id="🔸-A8-2017-Insecure-Deserialization-不安全的反序列化弱點-•-🔥"><a href="#🔸-A8-2017-Insecure-Deserialization-不安全的反序列化弱點-•-🔥" class="headerlink" title="🔸 A8:2017 Insecure Deserialization (不安全的反序列化弱點) • 🔥"></a>🔸 A8:2017 Insecure Deserialization (不安全的反序列化弱點) • 🔥</h2><p>序列化和反序列化在現在server的開發中非常常用。Django架構中REST Framework就是使用Serialization & deserialization.<br>看了這篇 <a href="https://medium.com/gdg-vit/deserialization-attacks-d312fbe58e7d">https://medium.com/gdg-vit/deserialization-attacks-d312fbe58e7d</a><br>提到 insecure deserialization statement like</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Insecure deserialization</span></span><br><span class="line">user = pickle.loads(serialized_obj)</span><br></pre></td></tr></table></figure><p>⭐ 之後打算去自己的server中找找看有沒有不安全的反序列化。<br>我覺得這個弱點很容易被忽略。</p><p>推薦閱讀<br><a href="https://portswigger.net/web-security/deserialization">https://portswigger.net/web-security/deserialization</a><br><a href="https://ithelp.ithome.com.tw/articles/10250483">https://ithelp.ithome.com.tw/articles/10250483</a></p><p>案例:Microsoft Exchange Proxylogon<br><a href="https://www.cc.ntu.edu.tw/chinese/epaper/0057/20210620_5706.html">https://www.cc.ntu.edu.tw/chinese/epaper/0057/20210620_5706.html</a> (漏洞基本介紹)<br><a href="https://hackmd.io/@1chig0/rkzZhA5Hd">https://hackmd.io/@1chig0/rkzZhA5Hd</a> (有攻擊做法詳解)<br>我真的覺得微軟的東西漏洞太多。</p><h2 id="🔸-A9-2017-Using-Components-With-Known-Vulnerabilities-使用已知漏洞元件"><a href="#🔸-A9-2017-Using-Components-With-Known-Vulnerabilities-使用已知漏洞元件" class="headerlink" title="🔸 A9:2017 Using Components With Known Vulnerabilities (使用已知漏洞元件)"></a>🔸 A9:2017 Using Components With Known Vulnerabilities (使用已知漏洞元件)</h2><p>現在Server開發多少都會用到第三方元件,如果元件本身含有漏洞,Server就可能因此被攻擊。<br>不使用第三方元件的話,一是開發進度緩慢,二是你也不能保證自己寫的沒漏洞。<br>我的想法是盡量使用用戶數多名氣大的第三方元件,有漏洞的話會比較快被發現和修補。當然要避免使用漏洞太多,或含有惡意腳本的第三方元件。</p><p>案例:FCKeditor 使用量很大的文字編輯器<br><a href="https://www.acunetix.com/vulnerabilities/web/fckeditor-arbitrary-file-upload/">https://www.acunetix.com/vulnerabilities/web/fckeditor-arbitrary-file-upload/</a><br><a href="https://brucebin.pixnet.net/blog/post/56799578">https://brucebin.pixnet.net/blog/post/56799578</a><br><a href="http://www.netqna.com/2014/03/fckeditor.html">http://www.netqna.com/2014/03/fckeditor.html</a></p><h2 id="🔸-A10-2017-Insufficient-Logging-and-Monitoring-記錄與監控不足"><a href="#🔸-A10-2017-Insufficient-Logging-and-Monitoring-記錄與監控不足" class="headerlink" title="🔸 A10:2017 Insufficient Logging and Monitoring (記錄與監控不足)"></a>🔸 A10:2017 Insufficient Logging and Monitoring (記錄與監控不足)</h2><p>這個部分非常重要,這個大家都懂。<br>可以參考:<a href="https://owasp.org/www-project-top-ten/2017/A10_2017-Insufficient_Logging%2526Monitoring">https://owasp.org/www-project-top-ten/2017/A10_2017-Insufficient_Logging%2526Monitoring</a> 做到更加完備。</p><h2 id="🔸-References"><a href="#🔸-References" class="headerlink" title="🔸 References:"></a>🔸 References:</h2><p><a href="https://owasp.org/www-project-top-ten/2017/">https://owasp.org/www-project-top-ten/2017/</a><br><a href="https://owasp.org/Top10/zh_TW/">https://owasp.org/Top10/zh_TW/</a></p><h1 id="OWASP-API-Security-Top-10"><a href="#OWASP-API-Security-Top-10" class="headerlink" title="OWASP API Security Top 10"></a>OWASP API Security Top 10</h1><table><thead><tr><th align="left">2019</th></tr></thead><tbody><tr><td align="left">API1:2019 Broken Object Level Authorization (不安全的物件授權)</td></tr><tr><td align="left">API2:2019 Broken User Authentication (無效的身分驗證)</td></tr><tr><td align="left">API3:2019 Excessive Data Exposure (資料暴露不當)</td></tr><tr><td align="left">API4:2019 Lack of Resources & Rate Limiting (缺乏資源與速率存取的限制)</td></tr><tr><td align="left">API5:2019 Broken Function Level Authorization (無效功能權限控管)</td></tr><tr><td align="left">API6:2019 Mass Assignment (批量配置不當)</td></tr><tr><td align="left">API7:2019 Security Misconfiguration (安全配置錯誤)</td></tr><tr><td align="left">API8:2019 Injection (注入攻擊)</td></tr><tr><td align="left">API9:2019 Improper Assets Management(版本控管不當版本控管不當)</td></tr><tr><td align="left">API10:2019 Insufficient Logging & Monitoring (記錄與監控不足)</td></tr></tbody></table><p>常用見的有 SOAP & REST</p><h2 id="🔸-API1-2019-Broken-Object-Level-Authorization-不安全的物件授權-🔥"><a href="#🔸-API1-2019-Broken-Object-Level-Authorization-不安全的物件授權-🔥" class="headerlink" title="🔸 API1:2019 Broken Object Level Authorization (不安全的物件授權) 🔥"></a>🔸 API1:2019 Broken Object Level Authorization (不安全的物件授權) 🔥</h2><p>以下內容引用自:<a href="https://apisecurity.io/encyclopedia/content/owasp/api1-broken-object-level-authorization.htm">https://apisecurity.io/encyclopedia/content/owasp/api1-broken-object-level-authorization.htm</a></p><blockquote><p><img src="/img/hacking-class/owasp-api1.png"></p></blockquote><p>這個issue位列第一,可見有多常見和嚴重了。<br>這是一個讓我覺得我的server最急需改進的地方。</p><h2 id="🔸-API2-2019-Broken-User-Authentication-無效的身分驗證"><a href="#🔸-API2-2019-Broken-User-Authentication-無效的身分驗證" class="headerlink" title="🔸 API2:2019 Broken User Authentication (無效的身分驗證)"></a>🔸 API2:2019 Broken User Authentication (無效的身分驗證)</h2><p>身分驗證現在最常用也相對來說比較安全的是使用<strong>JWT</strong>.<br>可以參考:<a href="https://portswigger.net/web-security/jwt">https://portswigger.net/web-security/jwt</a> 讓JWT做到真正的安全。</p><p>我覺得使用JWT要比較注意 refresh token 這個部分。<br>以下引用自:<a href="https://pjchender.dev/webdev/note-jwt/">https://pjchender.dev/webdev/note-jwt/</a></p><blockquote><p><img src="/img/hacking-class/jwt-refresh.png"></p></blockquote><h2 id="🔸-API3-2019-Excessive-Data-Exposure-資料暴露不當"><a href="#🔸-API3-2019-Excessive-Data-Exposure-資料暴露不當" class="headerlink" title="🔸 API3:2019 Excessive Data Exposure (資料暴露不當)"></a>🔸 API3:2019 Excessive Data Exposure (資料暴露不當)</h2><p>這個簡單來講就是server端要過濾掉多餘又敏感的內容。有時候為了效能,Server一次把所有data丟到client app,讓client端過濾選取需要的內容,這樣的作法就讓駭客有機會拿到資料。不要覺得這很容易避免,Facebook就踩過這個坑,外洩了百萬user的個資。<br>弱點詳細和防護:<a href="https://apisecurity.io/encyclopedia/content/owasp/api3-excessive-data-exposure.htm">https://apisecurity.io/encyclopedia/content/owasp/api3-excessive-data-exposure.htm</a></p><p>案例:Facebook user data leak<br>News: <a href="https://ctee.com.tw/news/global/440241.html">https://ctee.com.tw/news/global/440241.html</a></p><h2 id="🔸-API4-2019-Lack-of-Resources-amp-Rate-Limiting-缺乏資源與速率存取的限制"><a href="#🔸-API4-2019-Lack-of-Resources-amp-Rate-Limiting-缺乏資源與速率存取的限制" class="headerlink" title="🔸 API4:2019 Lack of Resources & Rate Limiting (缺乏資源與速率存取的限制)"></a>🔸 API4:2019 Lack of Resources & Rate Limiting (缺乏資源與速率存取的限制)</h2><p>server端要限制API存取速率,限制一定時間內的錯誤數量或是IP的位置…<br>不然會導致以下問題:</p><ul><li>駭客大量發送請求會導致API無法正常運作</li><li>駭客破解帳密</li></ul><p>弱點詳細和防護:<a href="https://apisecurity.io/encyclopedia/content/owasp/api4-lack-of-resources-and-rate-limiting.htm">https://apisecurity.io/encyclopedia/content/owasp/api4-lack-of-resources-and-rate-limiting.htm</a></p><p>案例:Instagram 密碼重設漏洞<br>以下引用自:<a href="https://www.ithome.com.tw/news/131888">https://www.ithome.com.tw/news/131888</a></p><blockquote><p><img src="/img/hacking-class/ig-bug.png"></p></blockquote><p>利用了race condition 這個概念。<br>現在使用 TOTP codes(6個數字)做驗證很多,但我覺得不加限制的話,破解6個數字比破解密碼容易多了。<br>Instagram獎勵漏洞通報者30000美金 <a href="https://www.acunetix.com/blog/web-security-zone/instagram-usd-30000-bounty/">https://www.acunetix.com/blog/web-security-zone/instagram-usd-30000-bounty/</a> 有使用漏洞影片</p><h2 id="🔸-API5-2019-Broken-Function-Level-Authorization-無效功能權限控管"><a href="#🔸-API5-2019-Broken-Function-Level-Authorization-無效功能權限控管" class="headerlink" title="🔸 API5:2019 Broken Function Level Authorization (無效功能權限控管)"></a>🔸 API5:2019 Broken Function Level Authorization (無效功能權限控管)</h2><p>API的權限控管有缺陷,比如一般使用者能夠使用管理員權限的功能的話就很危險。<br>弱點詳細和防護:<a href="https://apisecurity.io/encyclopedia/content/owasp/api5-broken-function-level-authorization">https://apisecurity.io/encyclopedia/content/owasp/api5-broken-function-level-authorization</a></p><h2 id="🔸-API6-2019-Mass-Assignment-批量配置不當"><a href="#🔸-API6-2019-Mass-Assignment-批量配置不當" class="headerlink" title="🔸 API6:2019 Mass Assignment (批量配置不當)"></a>🔸 API6:2019 Mass Assignment (批量配置不當)</h2><p>From Teacher Alex Lin’s PPT</p><blockquote><p>如果API自動將用戶端輸入的內容轉換為內部物件屬性,卻不考慮這些屬性的敏感度和可暴露度等級,攻擊者則可以非法更新不應該存取的內容。</p></blockquote><p>弱點詳細和防護:<a href="https://apisecurity.io/encyclopedia/content/owasp/api6-mass-assignment">https://apisecurity.io/encyclopedia/content/owasp/api6-mass-assignment</a></p><h2 id="🔸-API7-2019-Security-Misconfiguration-安全配置錯誤"><a href="#🔸-API7-2019-Security-Misconfiguration-安全配置錯誤" class="headerlink" title="🔸 API7:2019 Security Misconfiguration (安全配置錯誤)"></a>🔸 API7:2019 Security Misconfiguration (安全配置錯誤)</h2><p>內部使用的服務不要暴露在外網環境,e.g. DB、Redis、AWS s3、bucket.<br>Server內的DB, Redis之類不要有對外port.<br>弱點詳細和防護:<a href="https://apisecurity.io/encyclopedia/content/owasp/api7-security-misconfiguration">https://apisecurity.io/encyclopedia/content/owasp/api7-security-misconfiguration</a></p><h2 id="🔸-API8-2019-Injection-注入攻擊"><a href="#🔸-API8-2019-Injection-注入攻擊" class="headerlink" title="🔸 API8:2019 Injection (注入攻擊)"></a>🔸 API8:2019 Injection (注入攻擊)</h2><p>API和WEB一樣都要面對注入攻擊。<br>弱點詳細和防護:<a href="https://apisecurity.io/encyclopedia/content/owasp/api8-injection">https://apisecurity.io/encyclopedia/content/owasp/api8-injection</a></p><p>案例:CVE-2016-5641 Swagger Injection<br>Will study the case later. </p><h2 id="🔸-API9-2019-Improper-Assets-Management-版本控管不當版本控管不當"><a href="#🔸-API9-2019-Improper-Assets-Management-版本控管不當版本控管不當" class="headerlink" title="🔸 API9:2019 Improper Assets Management(版本控管不當版本控管不當)"></a>🔸 API9:2019 Improper Assets Management(版本控管不當版本控管不當)</h2><p>留下舊的有問題的API,就了留下隱患。<br>弱點詳細和防護:<a href="https://apisecurity.io/encyclopedia/content/owasp/api9-improper-assets-management">https://apisecurity.io/encyclopedia/content/owasp/api9-improper-assets-management</a></p><h2 id="🔸-API10-2019-Insufficient-Logging-amp-Monitoring-記錄與監控不足"><a href="#🔸-API10-2019-Insufficient-Logging-amp-Monitoring-記錄與監控不足" class="headerlink" title="🔸 API10:2019 Insufficient Logging & Monitoring (記錄與監控不足)"></a>🔸 API10:2019 Insufficient Logging & Monitoring (記錄與監控不足)</h2><p>弱點詳細和防護:<a href="https://apisecurity.io/encyclopedia/content/owasp/api10-insufficient-logging-and-monitoring">https://apisecurity.io/encyclopedia/content/owasp/api10-insufficient-logging-and-monitoring</a></p><h2 id="🔸-References-1"><a href="#🔸-References-1" class="headerlink" title="🔸 References:"></a>🔸 References:</h2><p><a href="https://owasp.org/www-project-api-security/">https://owasp.org/www-project-api-security/</a><br>推薦 <a href="https://apisecurity.io/encyclopedia/content/owasp/api1-broken-object-level-authorization">https://apisecurity.io/encyclopedia/content/owasp/api1-broken-object-level-authorization</a> 有針對每一條提出防護方法。</p>]]></content>
<categories>
<category> Learning notes </category>
</categories>
<tags>
<tag> Hacking </tag>
</tags>
</entry>
<entry>
<title>道德駭客實務入門-4/8課程筆記-1</title>
<link href="/2023/04/10/learning-notes/hacking-class-3/"/>
<url>/2023/04/10/learning-notes/hacking-class-3/</url>
<content type="html"><![CDATA[<p><a href="https://www.tibame.com/offline/ceh">TibaMe: 道德駭客實務入門及CEH認證班</a><br>Teacher: 林煌錡 (Alex Lin)<br><img src="/img/hacking-class/hacking-class-info.png"></p><blockquote><p>The class on 4/8 introduced web server hacking, web application hacking and database injection. I separated the content into three parts. This note is about web server hacking.</p></blockquote><p><font style="font-size: 28px;font-weight:bold;">Part1: Web server hacking</font></p><h2 id="Web-server"><a href="#Web-server" class="headerlink" title="Web server"></a>Web server</h2><p>常用的Web server有:IIS, Apache, Apache Tomcat, NginX<br>駭客要駭入Web server前要先收集Server的資訊,如果知道Web server是用哪種,就可以針對這種Web Server找尋相應的漏洞,有的放矢。<br>所以作為開發者,應該盡量隱藏Server資訊。</p><h2 id="Invalid-Web-Server-Settings"><a href="#Invalid-Web-Server-Settings" class="headerlink" title="Invalid Web Server Settings"></a>Invalid Web Server Settings</h2><p>設定不當就有可能引狼入室。</p><h3 id="🔸-Unlimited-uri-access-control"><a href="#🔸-Unlimited-uri-access-control" class="headerlink" title="🔸 Unlimited uri access control"></a>🔸 Unlimited uri access control</h3><p>資源uri列表:<a href="https://en.wikipedia.org/wiki/List_of_URI_schemes">https://en.wikipedia.org/wiki/List_of_URI_schemes</a><br>因爲資源uri是公開定義的,如果Server對資源存取不加以限制的話,就很容易被竊取資料。</p><h3 id="🔸-Unnecessary-services"><a href="#🔸-Unnecessary-services" class="headerlink" title="🔸 Unnecessary services"></a>🔸 Unnecessary services</h3><p>不必要的service不要開,開了多餘的service就是找自己麻煩,給駭客可乘之機。<br>For example, service like remote desktop connection should not be enabled in a production server.</p><h3 id="🔸-Use-default-account-amp-password"><a href="#🔸-Use-default-account-amp-password" class="headerlink" title="🔸 Use default account & password"></a>🔸 Use default account & password</h3><p>Never ever use the default username & password in a production server.<br>For example, google “tomcat default username and password” and you will get the info. below.<br><img src="/img/hacking-class/google-default-acc.png"></p><!-- <p style="align: left"><img src="/img/hacking-class/google-default-acc.png" width="50%"></p> --><p>And you can find more in github.<br><a href="https://github.com/netbiosX/Default-Credentials/blob/master/Apache-Tomcat-Default-Passwords.mdown">https://github.com/netbiosX/Default-Credentials/blob/master/Apache-Tomcat-Default-Passwords.mdown</a></p><h3 id="🔸-Redundant-files"><a href="#🔸-Redundant-files" class="headerlink" title="🔸 Redundant files"></a>🔸 Redundant files</h3><p>多餘的檔案不要放,不然 your trash is someone’s treasure.</p><h3 id="🔸-Debug-mode"><a href="#🔸-Debug-mode" class="headerlink" title="🔸 Debug mode"></a>🔸 Debug mode</h3><p>Production server should disable the debug mode.</p><h3 id="🔸-Unlimited-file-access-rights"><a href="#🔸-Unlimited-file-access-rights" class="headerlink" title="🔸 Unlimited file access rights"></a>🔸 Unlimited file access rights</h3><p>Web server要限定可以看到的副檔名。</p><h3 id="🔸-Web-server-vulnerabilities"><a href="#🔸-Web-server-vulnerabilities" class="headerlink" title="🔸 Web server vulnerabilities"></a>🔸 Web server vulnerabilities</h3><p>架構本身存在漏洞,比如老師提到 <a href="https://zh.wikipedia.org/zh-tw/Struts">STRUTS</a>是一款Apache中常用的CMS,但 STRUTS 漏洞實在太多,很容易被攻擊。 </p><h2 id="駭客常用手法"><a href="#駭客常用手法" class="headerlink" title="駭客常用手法"></a>駭客常用手法</h2><h3 id="🔸-CRLF"><a href="#🔸-CRLF" class="headerlink" title="🔸 CRLF"></a>🔸 CRLF</h3><ul><li>CR: Carriage-Return, URL hex: %0d</li><li>LF: Line-Feed, URL hex: %0a</li><li>HTTP header injection: 利用這兩個特殊符號注入url實現跳轉。 這就可以用正常網域做一個惡意鏈接。</li><li>還有 HTTP Response CRLF injection 和 Mail Header CRLF injection。</li><li>防禦方法 from <a href="https://www.acunetix.com/websitesecurity/crlf-injection/">acunetix.com</a>。這個攻擊相對來說比較好防。<br><img src="/img/hacking-class/prevent-crlf.png"></li><li>References<br><a href="https://owasp.org/www-community/vulnerabilities/CRLF_Injection">https://owasp.org/www-community/vulnerabilities/CRLF_Injection</a><br><a href="https://www.acunetix.com/websitesecurity/crlf-injection/">https://www.acunetix.com/websitesecurity/crlf-injection/</a><br><a href="https://book.hacktricks.xyz/pentesting-web/crlf-0d-0a">https://book.hacktricks.xyz/pentesting-web/crlf-0d-0a</a><br><a href="https://cloud.tencent.com/developer/article/1728657">https://cloud.tencent.com/developer/article/1728657</a></li></ul><h3 id="🔸-Web-Cache-Deception-網頁快取攻擊"><a href="#🔸-Web-Cache-Deception-網頁快取攻擊" class="headerlink" title="🔸 Web Cache Deception (網頁快取攻擊)"></a>🔸 Web Cache Deception (網頁快取攻擊)</h3><p>網頁快取是ㄧ種用cache來減少伺服器延遲的常用作法。 網頁快取常常存放在靜態網頁中,e.g. html, png, css… Server對這些靜態網檔案管控通常不嚴,如果快取檔案中有帳密訊息就很容易被竊取。</p><p>案例:ChatGPT的WCD漏洞。<br>駭客發現ChatGPT會用網頁快取來存放使用者的authentication data而且沒有讀取限制,於是製造一個link: <a href="https://chat.openai.com/api/auth/session/demo.css">https://chat.openai.com/api/auth/session/demo.css</a> 寄給受害者,這裡demo.css可以是任意名稱,受害者看到ChatGPT的域名通常不疑有他,點開後會看到自己的authentication data可能也沒什麼感覺, 但殊不知駭客也可以用同一個link得到受害者所有authentication data,並且可以用這些authentication data來登入帳戶。這個漏洞已被ChatGPT修補完成,大家可以放心點開demo link.</p><p>Find more details regarding the ChatGPT’s WCD in the link below. And you can google “ChatGPT account takeover” for more information.<br><a href="https://medium.com/@diegotellaroli05/account-takeover-in-chatgpt-e134ed11654d">https://medium.com/@diegotellaroli05/account-takeover-in-chatgpt-e134ed11654d</a><br><a href="https://www.ithome.com.tw/news/156205">https://www.ithome.com.tw/news/156205</a></p><h3 id="🔸-Server-Side-Request-Forgery-SSRF-伺服器端請求偽造"><a href="#🔸-Server-Side-Request-Forgery-SSRF-伺服器端請求偽造" class="headerlink" title="🔸 Server-Side Request Forgery (SSRF) (伺服器端請求偽造)"></a>🔸 Server-Side Request Forgery (SSRF) (伺服器端請求偽造)</h3><p>SSRF大全: <a href="https://github.com/jdonsec/AllThingsSSRF">https://github.com/jdonsec/AllThingsSSRF</a><br>要練習SSRF攻擊的話,可以參考 <a href="https://github.com/m6a-UdS/dvca">https://github.com/m6a-UdS/dvca</a> 架一個環境。</p><p>SSRF攻擊種類繁多</p><ul><li><p>課堂練習1<br>利用 file://[host]/path 非法存取server端的檔案</p></li><li><p>課堂練習2<br>更改 url path 繞過驗證<br>靶機 url: 172.16.25.25:8088, 改成 172.16.25.25:8088/jkstatus; 直接繞過驗證。<br><a href="https://www.immunit.ch/blog/2018/11/01/cve-2018-11759-apache-mod_jk-access-bypass/">https://www.immunit.ch/blog/2018/11/01/cve-2018-11759-apache-mod_jk-access-bypass/</a><br><a href="https://ithelp.ithome.com.tw/articles/10242449">https://ithelp.ithome.com.tw/articles/10242449</a></p></li><li><p>我找到的 SSRF sample<br>看完Jayden這篇”項莊舞劍-意在沛公”,讓我覺的如果Server RESTful APIs沒有JWT token的保護實在太危險,我對自己的Server好焦慮。<br>推薦Jayden的blog程式猿吃香蕉 <a href="https://medium.com/@jaydenlin">https://medium.com/@jaydenlin</a> ,有很多資安相關文章。<br><a href="https://medium.com/%E7%A8%8B%E5%BC%8F%E7%8C%BF%E5%90%83%E9%A6%99%E8%95%89/%E7%B6%B2%E7%AB%99%E5%AE%89%E5%85%A8-%E4%BC%BA%E6%9C%8D%E5%99%A8%E8%AB%8B%E6%B1%82%E5%81%BD%E9%80%A0-ssrf-%E6%94%BB%E6%93%8A-%E9%A0%85%E8%8E%8A%E8%88%9E%E5%8A%8D-%E6%84%8F%E5%9C%A8%E6%B2%9B%E5%85%AC-7a5524926362">https://medium.com/程式猿吃香蕉/網站安全-伺服器請求偽造-ssrf-攻擊-項莊舞劍-意在沛公-7a5524926362</a></p></li></ul><h3 id="🔸-Cross-Site-Request-Forgery-CSRF"><a href="#🔸-Cross-Site-Request-Forgery-CSRF" class="headerlink" title="🔸 Cross-Site Request Forgery (CSRF)"></a>🔸 Cross-Site Request Forgery (CSRF)</h3><p>Difference between SSRF & CSRF from ChatGPT:</p><blockquote><p>In summary, while SSRF involves exploiting a server-side vulnerability to make unauthorized requests, CSRF involves exploiting a client-side vulnerability to trick the user into making unauthorized requests.</p></blockquote><p>推薦閱讀:<a href="https://blog.techbridge.cc/2017/02/25/csrf-introduction/">https://blog.techbridge.cc/2017/02/25/csrf-introduction/</a></p><h2 id="常用工具"><a href="#常用工具" class="headerlink" title="常用工具"></a>常用工具</h2><p>工具本身沒有善惡之分,駭客手裡就是尋找漏洞駭入系統的利劍,資安手裡就是修補防護的盾牌。<br>之後有時間試用的話再補充心得。</p><h3 id="🔸-owasp-zap"><a href="#🔸-owasp-zap" class="headerlink" title="🔸 owasp-zap"></a>🔸 owasp-zap</h3><p>網頁漏洞掃瞄工具。<br><a href="https://www.zaproxy.org/">https://www.zaproxy.org</a></p><h3 id="🔸-Openvas"><a href="#🔸-Openvas" class="headerlink" title="🔸 Openvas"></a>🔸 Openvas</h3><p>主機漏洞掃瞄工具。<br><a href="https://www.openvas.org/">https://www.openvas.org</a></p><h3 id="🔸-Burpsuite-Pro"><a href="#🔸-Burpsuite-Pro" class="headerlink" title="🔸 Burpsuite Pro"></a>🔸 Burpsuite Pro</h3><p>老師提到這個工具可以用來http封包攔截重送。<br><a href="https://zh.wikipedia.org/zh-tw/Burp_suite">https://zh.wikipedia.org/zh-tw/Burp_suite</a><br><a href="https://portswigger.net/burp/pro">https://portswigger.net/burp/pro</a></p><h3 id="🔸-Acunetix-付費工具"><a href="#🔸-Acunetix-付費工具" class="headerlink" title="🔸 Acunetix (付費工具)"></a>🔸 Acunetix (付費工具)</h3><p>網頁漏洞掃瞄工具。<br><a href="https://www.acunetix.com/">https://www.acunetix.com</a></p><h3 id="🔸-Nessus"><a href="#🔸-Nessus" class="headerlink" title="🔸 Nessus"></a>🔸 Nessus</h3><p>非常有名的漏洞掃瞄工具。<br><a href="https://www.tenable.com/products/nessus">https://www.tenable.com/products/nessus</a><br>價格有點高。<br><img src="/img/hacking-class/nessus-price.png"></p><h3 id="🔸-Nikto"><a href="#🔸-Nikto" class="headerlink" title="🔸 Nikto"></a>🔸 Nikto</h3><p>Kali內建掃瞄工具。<br><a href="https://www.kali.org/tools/nikto/">https://www.kali.org/tools/nikto/</a></p><h2 id="防禦方法"><a href="#防禦方法" class="headerlink" title="防禦方法"></a>防禦方法</h2><p>駭客手法千變萬化,駭客工具層出不窮。對於防守方而言,當然要不斷加固自己的堤防。道高一尺,魔高ㄧ丈,相信這是一場永無止境的攻防之戰。</p><p>The image below is from Teacher <strong>Alex Lin</strong>‘s class PPT<br><img src="/img/hacking-class/countermeasure.png"></p>]]></content>
<categories>
<category> Learning notes </category>
</categories>
<tags>
<tag> Hacking </tag>
</tags>
</entry>
<entry>
<title>Fix build & archive errors in XCode 14.3</title>
<link href="/2023/04/09/APP%20Development/fix-errors-in-xcode-14/"/>
<url>/2023/04/09/APP%20Development/fix-errors-in-xcode-14/</url>
<content type="html"><![CDATA[<blockquote><p>The build and archive errors only occur when your Xcode project uses CocoaPods.</p></blockquote><h2 id="Build-error-in-XCode-14-3"><a href="#Build-error-in-XCode-14-3" class="headerlink" title="Build error in XCode 14.3"></a>Build error in XCode 14.3</h2><p>Building project in XCode 14.3 gets “File not found” error.<br><img src="/img/app_dev/xcode-error-1.png"></p><h2 id="Solution"><a href="#Solution" class="headerlink" title="Solution"></a>Solution</h2><ul><li>Add the script below to your project’s Podfile<br>Have to increase the minimum supported iOS version from 12.2 to 13.0.</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">post_install <span class="keyword">do</span> |installer|</span><br><span class="line"> installer.generated_projects.each <span class="keyword">do</span> |project|</span><br><span class="line"> project.targets.each <span class="keyword">do</span> |target|</span><br><span class="line"> target.build_configurations.each <span class="keyword">do</span> |config|</span><br><span class="line"> config.build_settings[<span class="string">'IPHONEOS_DEPLOYMENT_TARGET'</span>] = <span class="string">'13.0'</span></span><br><span class="line"> end</span><br><span class="line"> end</span><br><span class="line"> end</span><br><span class="line">end</span><br></pre></td></tr></table></figure><ul><li>Reinstall all the pods</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pod deintegrate; pod install</span><br></pre></td></tr></table></figure><ul><li>Clean and rebuild the project</li></ul><h2 id="Archive-error-in-XCode-14-3"><a href="#Archive-error-in-XCode-14-3" class="headerlink" title="Archive error in XCode 14.3"></a>Archive error in XCode 14.3</h2><p>Archiving project in XCode 14.3 gets “rsync error”. </p><p><img src="/img/app_dev/xcode-error-2.png"><br>It’s clear the error is from executing the pod script. </p><p><img src="/img/app_dev/xcode-error-3.png"></p><h2 id="Solution-1"><a href="#Solution-1" class="headerlink" title="Solution"></a>Solution</h2><p>Find the solution at: <a href="https://developer.apple.com/forums/thread/725230?login=true">https://developer.apple.com/forums/thread/725230?login=true</a></p><p>In /Pods/Target Support Files/Pods-AppName/Pods-AppName-frameworks.sh<br>Replace </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">source</span>=<span class="string">"<span class="subst">$(readlink <span class="string">"<span class="variable">${source}</span>"</span>)</span>"</span></span><br></pre></td></tr></table></figure><p>with</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">source</span>=<span class="string">"<span class="subst">$(readlink -f <span class="string">"<span class="variable">${source}</span>"</span>)</span>"</span></span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> APP Development </category>
</categories>
<tags>
<tag> XCode </tag>
</tags>
</entry>
<entry>
<title>Swift & Kotlin operators</title>
<link href="/2023/04/08/APP%20Development/swift-kotlin-operators/"/>
<url>/2023/04/08/APP%20Development/swift-kotlin-operators/</url>
<content type="html"><![CDATA[<table><thead><tr><th align="left">operators</th><th align="left">SWift</th><th align="left">Kotlin</th></tr></thead><tbody><tr><td align="left">==, !=</td><td align="left">value equality</td><td align="left">value equality</td></tr><tr><td align="left">===, !==</td><td align="left">referential equality</td><td align="left">referential equality</td></tr><tr><td align="left">..</td><td align="left">-</td><td align="left">[1..3] //range result: 1,2,3</td></tr><tr><td align="left">…</td><td align="left">[1…3] //range result: 1,2,3</td><td align="left">-</td></tr><tr><td align="left">?.</td><td align="left">Perform a safe all</td><td align="left">Perform a safe all</td></tr><tr><td align="left">? :</td><td align="left">let r = (10 > 1) ? “yes” : “no” //r=”yes”</td><td align="left">-</td></tr><tr><td align="left">?:</td><td align="left">-</td><td align="left">val c = a?:b if a is null, c = b</td></tr><tr><td align="left">??</td><td align="left">var c = a ?? b if a is null, c=b</td><td align="left">-</td></tr><tr><td align="left">?</td><td align="left">var a? // a can be null</td><td align="left">val a? // a can be null</td></tr><tr><td align="left">!</td><td align="left">var a! // assert a is not null</td><td align="left">-</td></tr><tr><td align="left">!!</td><td align="left">-</td><td align="left">val a!! // assert a is not null</td></tr></tbody></table><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://kotlinlang.org/docs/keyword-reference.html#operators-and-special-symbols">https://kotlinlang.org/docs/keyword-reference.html#operators-and-special-symbols</a></p><p><a href="https://developer.apple.com/documentation/swift/operator-declarations">https://developer.apple.com/documentation/swift/operator-declarations</a></p>]]></content>
<categories>
<category> APP Development </category>
</categories>
<tags>
<tag> Lang basic </tag>
</tags>
</entry>
<entry>
<title>Keywords in comment</title>
<link href="/2023/04/08/APP%20Development/lang-comments/"/>
<url>/2023/04/08/APP%20Development/lang-comments/</url>
<content type="html"><![CDATA[<table><thead><tr><th align="left">keyword</th><th align="left">Swift</th><th align="left">Kotlin</th><th align="left">Python</th></tr></thead><tbody><tr><td align="left">MARK</td><td align="left">//MARK: this is a mark</td><td align="left"></td><td align="left"></td></tr><tr><td align="left">TODO</td><td align="left">//TODO: this is a todo</td><td align="left">//TODO to do items</td><td align="left"># TODO to do items</td></tr><tr><td align="left">FIXME</td><td align="left">//FIXME: this is something to fix</td><td align="left">//FIXME items to fix</td><td align="left"># FIXME items to fix</td></tr></tbody></table>]]></content>
<categories>
<category> APP Development </category>
</categories>
<tags>
<tag> Lang basic </tag>
</tags>
</entry>
<entry>
<title>iOS observer design pattern (Swift)</title>
<link href="/2023/04/01/APP%20Development/ios-observer-design-pattern/"/>
<url>/2023/04/01/APP%20Development/ios-observer-design-pattern/</url>
<content type="html"><![CDATA[<h2 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h2><p><a href="https://en.wikipedia.org/wiki/Observer_patterns">Observe design pattern Wiki</a><br>I use the Observer design pattern when I need to inform the user about changes made to an object in the background thread. </p><h2 id="Observable-variable-class-definition"><a href="#Observable-variable-class-definition" class="headerlink" title="Observable variable class definition"></a>Observable variable class definition</h2><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Observable</span><<span class="title class_">T</span>> {</span><br><span class="line"> <span class="keyword">init</span>(<span class="params">value</span>: <span class="type">T</span>) {</span><br><span class="line"> <span class="keyword">self</span>.value <span class="operator">=</span> value</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">var</span> value: <span class="type">T</span> {</span><br><span class="line"> <span class="keyword">didSet</span> {</span><br><span class="line"> <span class="type">DispatchQueue</span>.main.async {</span><br><span class="line"> <span class="keyword">self</span>.valueChanged<span class="operator">?</span>(<span class="keyword">self</span>.value)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">var</span> valueChanged: ((<span class="type">T</span>) -> <span class="type">Void</span>)<span class="operator">?</span></span><br><span class="line"> <span class="keyword">func</span> <span class="title function_">addObserver</span>(<span class="keyword">_</span> <span class="params">onChange</span>: ((<span class="type">T</span>) -> <span class="type">Void</span>)<span class="operator">?</span>) {</span><br><span class="line"> valueChanged <span class="operator">=</span> onChange</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">func</span> <span class="title function_">removeObserver</span>() {</span><br><span class="line"> valueChanged <span class="operator">=</span> <span class="literal">nil</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Create-observable-variable"><a href="#Create-observable-variable" class="headerlink" title="Create observable variable"></a>Create observable variable</h2><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> hasInternet <span class="operator">=</span> <span class="type">Observable</span><<span class="type">Bool</span>>(value: <span class="literal">true</span>)</span><br></pre></td></tr></table></figure><h2 id="Set-observable-variable-value"><a href="#Set-observable-variable-value" class="headerlink" title="Set observable variable value"></a>Set observable variable value</h2><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hasInternet.value <span class="operator">=</span> <span class="literal">false</span></span><br></pre></td></tr></table></figure><h2 id="Observe-the-variable-for-value-change"><a href="#Observe-the-variable-for-value-change" class="headerlink" title="Observe the variable for value change"></a>Observe the variable for value change</h2><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">hasInternet.addObserver( {value <span class="keyword">in</span></span><br><span class="line"> <span class="keyword">if</span> <span class="operator">!</span>value{</span><br><span class="line"> <span class="comment">//handle observable variable value changed to false</span></span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="comment">//handle observable variable value changed to true</span></span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h2 id="Sample"><a href="#Sample" class="headerlink" title="Sample"></a>Sample</h2><p>For example, I want to monitor the internet connection. If internet is disconnected, pop up a notification message.</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">application</span>(<span class="keyword">_</span> <span class="params">application</span>: <span class="type">UIApplication</span>, <span class="params">didFinishLaunchingWithOptions</span> <span class="params">launchOptions</span>: [<span class="type">UIApplication</span>.<span class="params">LaunchOptionsKey</span>: <span class="keyword">Any</span>]<span class="operator">?</span>) -> <span class="type">Bool</span> {</span><br><span class="line"></span><br><span class="line"> <span class="operator">...</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">self</span>.monitorNetworkConnection()</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">CommonModel</span>{</span><br><span class="line"> <span class="operator">...</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">var</span> hasInternet <span class="operator">=</span> <span class="type">Observable</span><<span class="type">Bool</span>>(value: <span class="literal">true</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Network</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> internetMonitor <span class="operator">=</span> <span class="type">NWPathMonitor</span>()</span><br><span class="line"><span class="keyword">func</span> <span class="title function_">monitorNetworkConnection</span>(){</span><br><span class="line"> internetMonitor.pathUpdateHandler <span class="operator">=</span> { pathUpdateHandler <span class="keyword">in</span></span><br><span class="line"> <span class="keyword">if</span> pathUpdateHandler.status <span class="operator">==</span> .satisfied {</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Internet connection is on."</span>)</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> <span class="operator">!</span><span class="type">CommonModel</span>.hasInternet.value{</span><br><span class="line"> <span class="type">CommonModel</span>.hasInternet.value <span class="operator">=</span> <span class="literal">true</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"There's no internet connection."</span>)</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> <span class="type">CommonModel</span>.hasInternet.value{</span><br><span class="line"> <span class="type">CommonModel</span>.hasInternet.value <span class="operator">=</span> <span class="literal">false</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> internetMonitor.start(queue: <span class="type">DispatchQueue</span>.global())</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">TestViewController</span>: <span class="title class_">UIViewController</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="keyword">func</span> <span class="title function_">viewWillAppear</span>(<span class="keyword">_</span> <span class="params">animated</span>: <span class="type">Bool</span>) {</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> <span class="operator">!</span><span class="type">CommonModel</span>.hasInternet.value{</span><br><span class="line"> <span class="comment">//Pop-up no internet message here</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="type">CommonModel</span>.hasInternet.addObserver( {value <span class="keyword">in</span></span><br><span class="line"> <span class="keyword">if</span> <span class="operator">!</span>value{</span><br><span class="line"> <span class="comment">//Pop-up no internet message here</span></span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="keyword">func</span> <span class="title function_">viewWillDisappear</span>(<span class="keyword">_</span> <span class="params">animated</span>: <span class="type">Bool</span>) {</span><br><span class="line"> <span class="operator">...</span></span><br><span class="line"> <span class="type">CommonModel</span>.hasInternet.removeObserver()</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> APP Development </category>
</categories>
<tags>
<tag> Design pattern </tag>
</tags>
</entry>
<entry>
<title>道德駭客實務入門-3/26課程筆記</title>
<link href="/2023/03/27/learning-notes/hacking-class-2/"/>
<url>/2023/03/27/learning-notes/hacking-class-2/</url>
<content type="html"><![CDATA[<p><a href="https://www.tibame.com/offline/ceh">TibaMe: 道德駭客實務入門及CEH認證班</a><br>Teacher: 林煌錡 (Alex Lin)<br><img src="/img/hacking-class/hacking-class-info.png"></p><blockquote><p>The class on 3/26 introduced NetBIOS, SNMP, LDAP, NTP, NFS, SMTP, DNS enumeration, vulnerability assessment and system hacking.</p></blockquote><h1 id="Enumerate-ports-amp-services"><a href="#Enumerate-ports-amp-services" class="headerlink" title="Enumerate ports & services"></a>Enumerate ports & services</h1><h2 id="Common-communication-ports"><a href="#Common-communication-ports" class="headerlink" title="Common communication ports"></a>Common communication ports</h2><p><a href="https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers">Wiki List of TCP and UDP port numbers</a><br><img src="/img/hacking-class/common-port.png" width="60%"></p><h2 id="NetBIOS-enumeration"><a href="#NetBIOS-enumeration" class="headerlink" title="NetBIOS enumeration"></a>NetBIOS enumeration</h2><ul><li><a href="https://en.wikipedia.org/wiki/NetBIOS">NetBIOS Wiki</a></li><li>Commands<br>nmap uses –script to add plugin script<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo nmap -sU --script nbstat.nse -p 137 192.168.1.0/24</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nbtscan -v 192.168.1.0/24</span><br></pre></td></tr></table></figure>Command result<br><img src="/img/hacking-class/netbios.png"><br>Attack usually focuses on the target device with 1c & 1b service.<p style="align: left"><img src="/img/hacking-class/netbios-2.png" width="50%"></p></li></ul><h2 id="SNMP-enumeration"><a href="#SNMP-enumeration" class="headerlink" title="SNMP enumeration"></a>SNMP enumeration</h2><ul><li><a href="https://en.wikipedia.org/wiki/Simple_Network_Management_Protocol">SNMP Wiki</a></li><li>SNMP Manager Tools: <a href="https://www.whatsupgold.com/">WhatsUp Gold</a>, <a href="https://www.solarwinds.com/">Solar winds</a></li><li>Commands<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo snmp-check 192.168.1.2 -c public</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo snmpwalk -v 2c -c lab 192.168.1.1</span><br></pre></td></tr></table></figure></li></ul><h2 id="LDAP-enumeration"><a href="#LDAP-enumeration" class="headerlink" title="LDAP enumeration"></a>LDAP enumeration</h2><ul><li><a href="https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol">LDAP Wiki</a></li><li>Tools: <a href="https://github.com/NetSPI/goddi">Goddi</a></li><li>Commands<br>Need user name & password<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ldapdomaindump -u username -p password</span><br></pre></td></tr></table></figure></li></ul><h2 id="NTP-enumeration"><a href="#NTP-enumeration" class="headerlink" title="NTP enumeration"></a>NTP enumeration</h2><ul><li><a href="https://en.wikipedia.org/wiki/Network_Time_Protocol">NTP Wiki</a></li><li>Commands<br>Use rpcinfo to list all the services. Most devices do not response this command.<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rpcinfo -p 192.168.1.1</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ntpd -d 172.17.0.3</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo nmap -Pn --script default -sV -vvv -sU - p123 172.17.0.3</span><br></pre></td></tr></table></figure></li></ul><h2 id="NFS-enumeration"><a href="#NFS-enumeration" class="headerlink" title="NFS enumeration"></a>NFS enumeration</h2><ul><li><a href="https://en.wikipedia.org/wiki/Network_File_System">NFS Wiki</a></li><li>Commands<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo nmap -Pn --script default -sV -vvv -p111 172.17.0.4</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo nmap -vv -Pn --script default -sV 172.17.0.4</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">showmount -e 192.168.1.1</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo mount -t nfs -o nolock -o tcp xxx:/dir /mnt/dir</span><br></pre></td></tr></table></figure></li></ul><h2 id="SMTP-enumeration"><a href="#SMTP-enumeration" class="headerlink" title="SMTP enumeration"></a>SMTP enumeration</h2><ul><li><a href="https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol">SMTP Wiki</a></li><li>Commands<br>Search methods: VRFY, EXPN, RCPT. <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">smtp-user-enum -M VRFY -U /usr/share/wordlists/fern-wifi/common.txt -t 192.168.1.1</span><br></pre></td></tr></table></figure></li></ul><h2 id="DNS-enumeration"><a href="#DNS-enumeration" class="headerlink" title="DNS enumeration"></a>DNS enumeration</h2><ul><li><a href="https://en.wikipedia.org/wiki/Domain_Name_System">DNS Wiki</a></li><li>Commands<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dnsrecon -t axfr -d victim.com -n victim.com</span><br></pre></td></tr></table></figure></li></ul><h1 id="Vulnerability-analysis"><a href="#Vulnerability-analysis" class="headerlink" title="Vulnerability analysis"></a>Vulnerability analysis</h1><h2 id="CTF"><a href="#CTF" class="headerlink" title="CTF"></a>CTF</h2><p>拔旗遊戲。網絡安全的攻防競賽,看起來實在太好玩。</p><ul><li><a href="https://en.wikipedia.org/wiki/Capture_the_flag_(cybersecurity)">Capture the flag (cybersecurity) Wiki</a></li><li><a href="https://picoctf.org/">pico CTF</a></li><li><a href="https://ctftime.org/">CTF time</a></li></ul><h2 id="Vulnerability-category"><a href="#Vulnerability-category" class="headerlink" title="Vulnerability category"></a>Vulnerability category</h2><p>所有漏洞分門別類,標上號碼,本是方便釐清問題,結果更方便駭客找到對應漏洞的破解腳本。</p><ul><li><a href="https://cve.mitre.org/">CVE</a></li><li><a href="https://cwe.mitre.org/">CWE</a></li></ul><h2 id="Tools"><a href="#Tools" class="headerlink" title="Tools"></a>Tools</h2><p>網頁和伺服器漏洞掃瞄工具,屬於付費工具。</p><ul><li>Web -> <a href="https://www.acunetix.com/">acunetix</a></li><li>Server -> <a href="https://www.openvas.org/">open vas</a></li></ul><h2 id="Practice"><a href="#Practice" class="headerlink" title="Practice"></a>Practice</h2><p>駭客技術不是光上幾堂課,知道些工具,暸解些原理就可以了。技術是練出來的,但是駭客技術不能隨便亂玩,下面的網站提供了練習駭客技術的靶機。</p><ul><li><a href="https://www.hackthebox.com/">Hackthebox</a></li><li><a href="https://www.vulnhub.com/">vulnhub</a></li></ul><p>推薦Nikhil的blog,有很多Hackthebox的題目解法。<br><a href="https://0xw0lf.github.io/category/#/HackTheBox">https://0xw0lf.github.io/category/#/HackTheBox</a></p><h1 id="System-hacking"><a href="#System-hacking" class="headerlink" title="System hacking"></a>System hacking</h1><h2 id="Gain-access"><a href="#Gain-access" class="headerlink" title="Gain access"></a>Gain access</h2><p>首先要駭入一個系統,就要想辦法拿到登入帳號密碼,並且可以遠端登錄。<br>我覺的暴力破解是一個很花時間的方法,駭客常用手法應該是釣魚/詐騙,直接用騙的,用偷的比較快。</p><ul><li><p>Find system bugs: <a href="https://www.exploit-db.com/">exploit-db</a></p></li><li><p>Understand Microsoft Authentication</p></li><li><p>Password cracking<br>pwdump7<br>RainbowCrach<br>Cain and Abel<br>John The Ripper<br>Hashcat</p></li><li><p>Stack Overflow detect<br>Windows -> <a href="https://zh.wikipedia.org/zh-tw/OllyDbg">Ollydbg</a><br>Linux -> <a href="https://en.wikipedia.org/wiki/GNU_Debugger">GDB</a></p></li></ul><h2 id="Privilege-escalation"><a href="#Privilege-escalation" class="headerlink" title="Privilege escalation"></a>Privilege escalation</h2><p>一旦駭入了系統就要想辦法把自己權限提升到最高,這樣才可以為所欲為。</p><ul><li>DLL Hijacking</li><li>Exploit DB (Search privilege keyword)</li><li>Pipe</li><li>SUID & SGID</li><li>PEAS<br>Windows -> <a href="https://github.com/carlospolop/PEASS-ng/tree/master/winPEAS">winPEAS</a><br>Linux -> <a href="https://github.com/carlospolop/PEASS-ng/tree/master/linPEA">linPEAS</a></li></ul><h2 id="Maintaining-Access"><a href="#Maintaining-Access" class="headerlink" title="Maintaining Access"></a>Maintaining Access</h2><p>終於打下來的機器要想辦法維持聯繫。</p><ul><li><p><strong>Remote Control Tools</strong><br>Windows: WMI, WinRM<br>Linux: SSH<br>我在Kali上裝 <a href="https://en.wikipedia.org/wiki/Remmina">Remmina</a>,感覺不錯用。 </p></li><li><p><strong>KeyLogger</strong><br> 這是駭客常用工具,他可以在背景記錄你所有的鍵盤使用,強大一點的還可以抓取螢幕截圖,這些訊息呢都會通過email送給駭入電腦的人,以便他隨時監控。 也就是說你就算改了電腦的密碼,他也可以從key logger工具傳回的訊息中找到新的密碼繼續使用。<br> 多年前我在做<a href="http://www.urti.com.tw/OTi%20-%20SmartKM.html">Oti SmartKMLink</a>開發的時候就有寫過Windows和MAC這部分的程式, 背景監聽電腦的鍵盤和滑鼠Event實作不難,但是唯一的問題是這種程式在安裝的時候需要很高的權限。 類似的軟體比如說遠端電腦控制軟體, 全部都有在監聽鍵盤和滑鼠事件,這類軟體我真不想用,總覺得給了這類軟體太多的權限也不知他們在背後做什麽,但是有時候還是不得不用,我有用Google remote desktop, no machine… 相信自己只是一個nobody就算了。但如果你是somebody或者是很重要的電腦,真心建議不要裝這類軟體。<br> Google “Key Logger github”你就可以找到很多工具,像是下面這篇提到的<a href="https://github.com/z00z/ZLogger">Zlogger</a>,號稱一般權限就可以安裝,我比較好奇他是怎麼做到的。<br> <a href="https://medium.com/purple-team/make-a-keylogger-using-the-zlogger-tool-9945bc87922">https://medium.com/purple-team/make-a-keylogger-using-the-zlogger-tool-9945bc87922</a></p></li></ul><h2 id="Track-Covering"><a href="#Track-Covering" class="headerlink" title="Track Covering"></a>Track Covering</h2><p>清理作案現場盡量不讓人發現被駭。基本上就是去清log。</p><ul><li>Windows: SECEVENT.EVT, SYSEVENT.EVT, APPECENT.EVT</li><li>Linux Logs</li></ul><table><thead><tr><th align="left">log file</th><th align="left">content</th><th align="left">command</th></tr></thead><tbody><tr><td align="left">/var/log/btmp</td><td align="left">Failed login records</td><td align="left">lasttb</td></tr><tr><td align="left">/var/log/lastlog</td><td align="left">All login users’ last login time</td><td align="left">lastlog</td></tr><tr><td align="left">/var/log/wtmp</td><td align="left">All login user’s login & logout records</td><td align="left">last</td></tr><tr><td align="left">/var/log/utmp</td><td align="left">Current login user info.</td><td align="left">w, who, users</td></tr><tr><td align="left">/var/log/secure</td><td align="left">Security related info.</td><td align="left"></td></tr><tr><td align="left">/var/log/messsage</td><td align="left">General system logs and errors</td><td align="left"></td></tr></tbody></table><p><a href="https://help.ubuntu.com/community/LinuxLogFiles">Ubuntu logs doc</a></p><p>這裡老師有說,如果log檔案突然被大量修改,或變小很多,就有可能是駭客清除的結果。我想應該有監視log檔案的工具吧。</p><p>Clean logs commands</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">""</span> > /var/log/btmp</span><br><span class="line"><span class="built_in">echo</span> <span class="string">""</span> > /var/log/wtmp</span><br><span class="line"><span class="built_in">echo</span> <span class="string">""</span> > /var/log/lastlog</span><br><span class="line"><span class="built_in">echo</span> <span class="string">""</span> > /var/log/utmp</span><br><span class="line"><span class="built_in">cat</span> /dev/null > /var/log/secure</span><br><span class="line"><span class="built_in">cat</span> /dev/null > /var/log/message</span><br></pre></td></tr></table></figure><h1 id="課堂練習"><a href="#課堂練習" class="headerlink" title="課堂練習"></a>課堂練習</h1><h2 id="列舉伺服器上可能的檔案"><a href="#列舉伺服器上可能的檔案" class="headerlink" title="列舉伺服器上可能的檔案"></a>列舉伺服器上可能的檔案</h2><ul><li>dirsearch<br>Install dirsearch<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get update</span><br><span class="line">sudo apt install dirsearch</span><br></pre></td></tr></table></figure>Install dictionary<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install wordlists</span><br></pre></td></tr></table></figure>搜尋靶機找到檔案<br><img src="/img/hacking-class/dirsearch.png"><br>這個工具我覺的可以用來測自己的Server看是不是有不小心多放了什麼不該放的檔案。</li></ul><h2 id="駭入靶機練習"><a href="#駭入靶機練習" class="headerlink" title="駭入靶機練習"></a>駭入靶機練習</h2><ul><li><p><strong>Scan target device ports</strong><br>用nmap掃瞄靶機找到 2222 ssh port<br><img src="/img/hacking-class/hack_sys_lab1.png"></p></li><li><p><strong>Use hydra to crack the password</strong><br>暴力破解找密碼。<br>-l user name. 此案例中已知user name是user。如果user未知的話,也可以加user查詢字典。<br>-P 從字典找密碼。<br>-t 加delay避免被查覺。<br><img src="/img/hacking-class/hack_sys_lab3.png"><br><img src="/img/hacking-class/hack_sys_lab2.png"><br>因為密碼老師已經放在字典中,所以很快找到。<br>Google “password dictionary github” 可以找到很多密碼字典。<br>比如:<a href="https://github.com/duyet/bruteforce-database">https://github.com/duyet/bruteforce-database</a><br>密碼字典當然要夠大才有機會找到密碼,可想而知用暴力破解跑完整本字典真的是天長地久,我想一般駭客大概把常用的試過就算了,所以設定時應該盡量避免常用的user name & password。</p></li><li><p><strong>Enter the target device and get more info.</strong><br>找到 user name & password 你就拿到了入場券。<br>ㄧ入場當然是查看環境先。<br>Commands for checking the environment.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">whoami</span></span><br><span class="line"><span class="built_in">id</span></span><br><span class="line"><span class="built_in">ls</span> -al</span><br><span class="line"><span class="built_in">cat</span> .bash_history</span><br><span class="line"><span class="built_in">cat</span> /etc/passwd</span><br></pre></td></tr></table></figure><p><img src="/img/hacking-class/hack_sys_lab4.png"></p></li><li><p><strong>Find the target device vulnerability</strong><br>可以掃瞄漏洞的工具非常多,課上好像是用下面這個。<br><a href="https://github.com/The-Z-Labs/linux-exploit-suggester">https://github.com/The-Z-Labs/linux-exploit-suggester</a><br>在靶機上安裝然後掃瞄,老師刻意安排的靶機當然有很多漏洞。<br><img src="/img/hacking-class/hack_sys_lab5.png"></p></li><li><p>Get root privilege<br>選定利用sudo漏洞CVE-2021-3156。選取哪個漏洞來用,這個就是經驗了。<br><a href="https://nvd.nist.gov/vuln/detail/CVE-2021-3156">漏洞 CVE-2021-3156 detail</a><br>Check sudo verion<br><img src="/img/hacking-class/hack_sys_lab6.png"><br>Google “sudo 1.8.31” or “CVE-2021-3156 github” 就找到下面這個工具。<br><a href="https://github.com/teamtopkarl/CVE-2021-3156">https://github.com/teamtopkarl/CVE-2021-3156</a><br>安裝工具執行後,權限直接提升為root。</p></li></ul><h1 id="Others"><a href="#Others" class="headerlink" title="Others"></a>Others</h1><h2 id="Metasploit"><a href="#Metasploit" class="headerlink" title="Metasploit"></a>Metasploit</h2><p>在Kali上找到的工具,感覺非常強大。<br><a href="https://www.metasploit.com/">https://www.metasploit.com</a><br><a href="https://zh.wikipedia.org/zh-tw/Metasploit">https://zh.wikipedia.org/zh-tw/Metasploit</a></p><p><img src="/img/hacking-class/metasploit-1.png"></p><p>這個工具我覺的可以用來遠端掃瞄Server是否有漏洞。</p>]]></content>
<categories>
<category> Learning notes </category>
</categories>
<tags>
<tag> Hacking </tag>
</tags>
</entry>
<entry>
<title>道德駭客實務入門-3/18課程筆記</title>
<link href="/2023/03/20/learning-notes/hacking-class-1/"/>
<url>/2023/03/20/learning-notes/hacking-class-1/</url>
<content type="html"><![CDATA[<p><a href="https://www.tibame.com/offline/ceh">TibaMe: 道德駭客實務入門及CEH認證班</a><br>Teacher: 林煌錡 (Alex Lin)<br><img src="/img/hacking-class/hacking-class-info.png"></p><blockquote><p>The class on 3/18 introduced general hacking concepts and focused on the reconnaissance (偵察) stage of hacking.<br>I am really interested in the reconnaissance (偵察) tools, most of which I have never heard of before.<br>Below are some tools introduced in the class.</p></blockquote><h1 id="Framework"><a href="#Framework" class="headerlink" title="Framework"></a>Framework</h1><h2 id="Cyber-Kill-Chain"><a href="#Cyber-Kill-Chain" class="headerlink" title="Cyber Kill Chain"></a>Cyber Kill Chain</h2><ul><li>The image below is from Teacher <strong>Alex Lin</strong>‘s class PPT<br><img src="/img/hacking-class/cyber-kill-chain.png"></li></ul><h2 id="ATT-amp-CK"><a href="#ATT-amp-CK" class="headerlink" title="ATT&CK"></a>ATT&CK</h2><ul><li><a href="https://zh.wikipedia.org/zh-tw/ATT%26CK">ATT&CK Wiki</a></li><li>Link: <a href="https://mitre-attack.github.io/attack-navigator/">https://mitre-attack.github.io/attack-navigator/</a><br><img src="/img/hacking-class/mitre-1.png"><br><img src="/img/hacking-class/mitre-2.png"></li><li>There is an ID for each hacking technique. You can obtain details of each technique from <a href="https://attack.mitre.org/techniques/enterprise/">https://attack.mitre.org/techniques/enterprise/</a><div class="gallery"> <div class="fj-gallery " data-rowHeight="220" data-limit="10"> <span class="gallery-data">[{"url":"/img/hacking-class/mitre-3.png","alt":""},{"url":"/img/hacking-class/mitre-4.png","alt":""}]</span> </div> <button class="gallery-load-more"><span>Load More</span><i class="fa-solid fa-arrow-down"></i></button> </div></li></ul><h1 id="Reconnaissance-Tools"><a href="#Reconnaissance-Tools" class="headerlink" title="Reconnaissance Tools"></a>Reconnaissance Tools</h1><blockquote><p>偵察的目地主要是尋找目標。</p></blockquote><h2 id="Google-Hacking"><a href="#Google-Hacking" class="headerlink" title="Google Hacking"></a>Google Hacking</h2><ul><li><a href="https://zh.wikipedia.org/zh-tw/%E8%B0%B7%E6%AD%8C%E9%A7%AD%E4%BE%B5%E6%B3%95">Google Hacking Wiki</a><br>Google Hacking 意思是:熟悉<a href="https://www.googleguide.com/advanced_operators_reference.html">Google search operators</a>,你就有可能用Google找到適合攻擊的目標。</li><li><a href="https://www.exploit-db.com/google-hacking-database">Google Hacking Database</a><br>Google Hacking 指令集。 看了指令集才感嘆自己對Google大神了解太少。</li><li>可以使用<strong>robots.txt</strong>來阻止搜尋引擎來抓取內容。也看到有人說加了也不一定能擋。<br><a href="https://developers.google.com/search/docs/crawling-indexing/robots/create-robots-txt?hl=zh-tw">https://developers.google.com/search/docs/crawling-indexing/robots/create-robots-txt?hl=zh-tw</a></li></ul><h2 id="DNS-related-tools"><a href="#DNS-related-tools" class="headerlink" title="DNS related tools"></a>DNS related tools</h2><ul><li><p><strong>DNSdumpsters</strong><br><a href="https://dnsdumpster.com/">https://dnsdumpster.com</a><br>輸入域名可以看見域名下所有的Server,和一些訊息, e.g. Web server used, on cloud or not.<br>輸入公司域名果然列出公司所有server。<br><img src="/img/hacking-class/dnsdumpster-1.png"><br>有完整的 Domain map.<br><img src="/img/hacking-class/dnsdumpster-2.png"><br>還可以對Server做port掃瞄<br><img src="/img/hacking-class/dnsdumpster-3.png"></p></li><li><p><strong>DNSrecon (Kali內建)</strong><br><a href="https://www.kali.org/tools/dnsrecon/">https://www.kali.org/tools/dnsrecon/</a></p></li><li><p><strong>Sbulist3r (Kali內建)</strong><br><a href="https://www.kali.org/tools/sublist3r/">https://www.kali.org/tools/sublist3r/</a></p></li></ul><h2 id="Server-analysis-tools"><a href="#Server-analysis-tools" class="headerlink" title="Server analysis tools"></a>Server analysis tools</h2><ul><li><strong>Netcraft</strong><br><a href="https://sitereport.netcraft.com/">https://sitereport.netcraft.com</a><br>掃瞄Server找問題。<br>首先掃了自己的server看到一個risk,非常焦慮。<br><img src="/img/hacking-class/netcraft-1.png"><br>然後掃了公司的另幾台server看到一樣有一個risk,就不焦慮了。<br><img src="/img/hacking-class/netcraft-2.png"></li></ul><p>這個risk問題跟朋友討論後,覺的不是嚴重問題,之後會往討論的方向去嘗試改動。</p><p>又掃了公司的官網,看到Web Trackers,不能確定這是什麼。<br>後來查了一下,應該是網頁有加Google Analytics,是正常Tracker。<br><img src="/img/hacking-class/netcraft-3.png"></p><ul><li><p><strong>virtustotal</strong><br>一樣掃瞄Server找問題。<br><a href="https://www.virustotal.com/gui/home/url">https://www.virustotal.com/gui/home/url</a><br>用這個掃了自己的server是clean的。</p></li><li><p><strong>Whois</strong><br><a href="https://whois.domaintools.com/">https://whois.domaintools.com</a><br>輸入域名可以看見完整的Domain profile。也可以從ip反查域名。</p><div class="gallery"> <div class="fj-gallery " data-rowHeight="220" data-limit="10"> <span class="gallery-data">[{"url":"/img/hacking-class/whois-1.png","alt":""},{"url":"/img/hacking-class/whois-2.png","alt":""}]</span> </div> <button class="gallery-load-more"><span>Load More</span><i class="fa-solid fa-arrow-down"></i></button> </div></li><li><p><strong>Traceroute</strong><br>我架的Server access不到,看到MIS用這個Command來抓問題。<br><img src="/img/hacking-class/traceroute.png"></p></li></ul><h2 id="Service-Search-engine"><a href="#Service-Search-engine" class="headerlink" title="Service Search engine"></a>Service Search engine</h2><ul><li><p><strong>Censys</strong><br><a href="https://search.censys.io/">https://search.censys.io</a><br>非常強大的服務搜尋引擎。我輸入公司名稱果然可以找到很多公司的router。<br><img src="/img/hacking-class/censys.png"></p></li><li><p><strong>Shodan</strong><br>比Censys更強大,可以分區域查找。可看訊息也更多。<br><a href="https://www.shodan.io/">https://www.shodan.io</a><br><img src="/img/hacking-class/shodan.png"><br>點進裝置還有所有Open Ports 和 Vulnerabilities!<br><img src="/img/hacking-class/shodan-2.png"></p></li><li><p><strong>Fofa</strong><br><a href="https://en.fofa.info/">https://en.fofa.info</a><br>可以針對某個漏洞去找有問題的Server.<br>比如,我查找台灣Server有log4j問題的,就列了一堆。<br><img src="/img/hacking-class/fofa.png"></p></li></ul><p>Shodan & Fofa更多精準搜尋推薦<br><a href="https://tech-blog.cymetrics.io/posts/nick/shodan-fofa/">https://tech-blog.cymetrics.io/posts/nick/shodan-fofa/</a></p><ul><li><strong>ZoomEy</strong><br>For China area</li></ul><h2 id="Web-crawling-tools"><a href="#Web-crawling-tools" class="headerlink" title="Web crawling tools"></a>Web crawling tools</h2><ul><li><p>BurpSuite<br><a href="https://portswigger.net/burp">https://portswigger.net/burp</a></p></li><li><p>HTTrack<br><a href="https://www.httrack.com/page/2/en/index.html">https://www.httrack.com/page/2/en/index.html</a><br>沒有實測,看了下面的文章,用HTTrack可以完整抓下整個網站內容。<br><a href="https://www.minwt.com/webdesign-dev/html/15898.html">https://www.minwt.com/webdesign-dev/html/15898.html</a></p></li><li><p>Web archive<br><a href="http://web.archive.org/">http://web.archive.org</a><br>真是一個神奇的時光機器。完整紀錄了網站的歷史,有Snapshot。<br><img src="/img/hacking-class/web-archive.png"></p></li><li><p>Octoparse<br><a href="https://www.octoparse.com/">https://www.octoparse.com</a><br>抓取整個網站內容工具,要付費。</p></li><li><p>ceWL (Kali內建)<br><a href="https://www.kali.org/tools/cewl/">https://www.kali.org/tools/cewl/</a></p></li><li><p>metagoofil (Kali內建)<br><a href="https://www.kali.org/tools/metagoofil/">https://www.kali.org/tools/metagoofil/</a></p></li></ul><h2 id="Email-analysis-tools"><a href="#Email-analysis-tools" class="headerlink" title="Email analysis tools"></a>Email analysis tools</h2><p>從email中獲取各種訊息,比如說email伺服器地址。</p><ul><li><p><a href="https://mha.azurewebsites.net/">Email header analysis tool</a></p></li><li><p>theHarvester (Kali內建)<br><a href="https://www.kali.org/tools/theharvester/">https://www.kali.org/tools/theharvester/</a></p></li><li><p>infoga</p></li></ul><h2 id="Collect-info-from-social-media-tools"><a href="#Collect-info-from-social-media-tools" class="headerlink" title="Collect info. from social media tools"></a>Collect info. from social media tools</h2><p>這個部分感覺主要用於尋找目標,蒐集訊息,先跳過好了。</p><ul><li><p>Buzzsumo</p></li><li><p>Followerwonk</p></li><li><p>Sherlock</p></li><li><p>Social searcher</p></li><li><p>DarkWeb</p></li></ul><h2 id="Open-source-intelligence-OSINT"><a href="#Open-source-intelligence-OSINT" class="headerlink" title="Open-source intelligence (OSINT)"></a>Open-source intelligence (OSINT)</h2><ul><li><p>OSINT framework<br>這個網頁可以引導你去各種搜尋工具。<br><a href="https://osintframework.com/">https://osintframework.com</a><br><img src="/img/hacking-class/osint.png"></p></li><li><p>Maltego<br><a href="https://www.maltego.com/">https://www.maltego.com</a><br>屬於付費工具。</p></li><li><p>Recon-ng (Kali內建)<br><a href="https://www.kali.org/tools/recon-ng/">https://www.kali.org/tools/recon-ng/</a></p></li><li><p>Osrframework (Kali內建)<br><a href="https://www.kali.org/tools/osrframework/">https://www.kali.org/tools/osrframework/</a></p></li></ul><h1 id="Network-scan-command-line-tools"><a href="#Network-scan-command-line-tools" class="headerlink" title="Network scan command line tools"></a>Network scan command line tools</h1><blockquote><p>以下command line tools都內建在Kali中。</p></blockquote><h2 id="NMAP"><a href="#NMAP" class="headerlink" title="NMAP"></a><a href="https://nmap.org/book/man.html">NMAP</a></h2><p>指令集:<a href="http://www.osslab.tw/books/linux-administration/page/nmap-%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4%E9%9B%86">http://www.osslab.tw/books/linux-administration/page/nmap-常用指令集</a><br>NMAP也有提供圖形化介面工具。</p><ul><li><p>Scan a host</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo nmap 192.168.1.1</span><br><span class="line">sudo nmap -v 192.168.1.1</span><br></pre></td></tr></table></figure><p><img src="/img/hacking-class/nmap-1.png"></p></li><li><p>No response from these commands </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo nmap 192.168.105.0/24</span><br><span class="line">sudo nmap -A 192.168.105.2</span><br></pre></td></tr></table></figure><p><img src="/img/hacking-class/nmap-2.png"></p></li><li><p>Scan host in a range</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo nmap -sP 192.168.105.0/24</span><br></pre></td></tr></table></figure><p><img src="/img/hacking-class/nmap-3.png"></p></li><li><p>Scan host ports</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sudo nmap --top-ports 10 192.168.105.2</span><br><span class="line">sudo nmap --top-ports 10 192.168.105.0/24</span><br><span class="line">sudo nmap -PN 192.168.105.2</span><br></pre></td></tr></table></figure><div class="gallery"> <div class="fj-gallery " data-rowHeight="220" data-limit="10"> <span class="gallery-data">[{"url":"/img/hacking-class/nmap-4.png","alt":""},{"url":"/img/hacking-class/nmap-5.png","alt":""},{"url":"/img/hacking-class/nmap-6.png","alt":""}]</span> </div> <button class="gallery-load-more"><span>Load More</span><i class="fa-solid fa-arrow-down"></i></button> </div></li><li><p>nmap -v -sV –script=nfs-showmount 192.168.105.2 </p><div class="gallery"> <div class="fj-gallery " data-rowHeight="220" data-limit="10"> <span class="gallery-data">[{"url":"/img/hacking-class/nmap-7.png","alt":""},{"url":"/img/hacking-class/nmap-8.png","alt":""},{"url":"/img/hacking-class/nmap-9.png","alt":""}]</span> </div> <button class="gallery-load-more"><span>Load More</span><i class="fa-solid fa-arrow-down"></i></button> </div></li></ul><h2 id="HPING3"><a href="#HPING3" class="headerlink" title="HPING3"></a>HPING3</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sudo hping3 -A 192.168.105.2 -p 443</span><br><span class="line">sudo hping3 -2 192.168.105.2 -p 443</span><br><span class="line">sudo hping3 -1 192.168.105.x --rand-dest -I eth0</span><br></pre></td></tr></table></figure><div class="gallery"> <div class="fj-gallery " data-rowHeight="220" data-limit="10"> <span class="gallery-data">[{"url":"/img/hacking-class/hping-1.png","alt":""},{"url":"/img/hacking-class/hping-2.png","alt":""},{"url":"/img/hacking-class/hping-3.png","alt":""}]</span> </div> <button class="gallery-load-more"><span>Load More</span><i class="fa-solid fa-arrow-down"></i></button> </div><h2 id="massscan"><a href="#massscan" class="headerlink" title="massscan"></a>massscan</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo masscan -p80,443 192.168.105.0/24</span><br></pre></td></tr></table></figure><p><img src="/img/hacking-class/massscan.png"></p><h1 id="Host-scan"><a href="#Host-scan" class="headerlink" title="Host scan"></a>Host scan</h1><blockquote><p>主機掃描目地主要是針對選定目標收集更多資訊。</p></blockquote><h2 id="ARP-scan"><a href="#ARP-scan" class="headerlink" title="ARP scan"></a>ARP scan</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo nmap -sn -PR 192.168.11.0/24</span><br></pre></td></tr></table></figure><h2 id="UDP-scan"><a href="#UDP-scan" class="headerlink" title="UDP scan"></a>UDP scan</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo nmap -sn -PU -Pn 192.168.105.193</span><br></pre></td></tr></table></figure><h2 id="ICMP-scan"><a href="#ICMP-scan" class="headerlink" title="ICMP scan"></a>ICMP scan</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sudo nmap -sn --disable-arp-ping -PE 192.168.1.0/24</span><br><span class="line">sudo nmap -sn --disable-arp-ping -PP 192.168.1.0/24</span><br><span class="line">sudo nmap -sn --disable-arp-ping -PM 192.168.1.0/24</span><br></pre></td></tr></table></figure><h2 id="TCP-scan"><a href="#TCP-scan" class="headerlink" title="TCP scan"></a>TCP scan</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">sudo nmap --disable-arp-ping -sT 192.168.105.193 -p 445</span><br><span class="line">sudo nmap --disable-arp-ping -sS 192.168.105.193 -p 445</span><br><span class="line">sudo nmap --disable-arp-ping -sX 192.168.105.193 -p 445</span><br><span class="line">sudo nmap --disable-arp-ping -sU 192.168.105.193 -p 5900</span><br></pre></td></tr></table></figure><h2 id="SCTP-scan"><a href="#SCTP-scan" class="headerlink" title="SCTP scan"></a>SCTP scan</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo nmap --disable-arp- ping -sY 192.168.105.193 -p 10000</span><br><span class="line">sudo nmap --disable-arp- ping -sY 192.168.105.* -p 10000</span><br></pre></td></tr></table></figure><h1 id="OS-fingerprint"><a href="#OS-fingerprint" class="headerlink" title="OS fingerprint"></a>OS fingerprint</h1><h2 id="TTL長度辯識OS"><a href="#TTL長度辯識OS" class="headerlink" title="TTL長度辯識OS"></a>TTL長度辯識OS</h2><p>Linux -> 64<br>Windows -> 128<br>Solaris/AIX -> 254</p><h2 id="nmap-commands"><a href="#nmap-commands" class="headerlink" title="nmap commands"></a>nmap commands</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo nmap -Pn --disable-arp-ping -O 192.168.105.2</span><br><span class="line">sudo nmap -Pn --disable-arp-ping -A 192.168.105.2</span><br></pre></td></tr></table></figure><h1 id="Scan-bypass-IDS-amp-firewall"><a href="#Scan-bypass-IDS-amp-firewall" class="headerlink" title="Scan bypass IDS & firewall"></a>Scan bypass IDS & firewall</h1><h2 id="封包分段-Packet-Fragmentation"><a href="#封包分段-Packet-Fragmentation" class="headerlink" title="封包分段(Packet Fragmentation)"></a>封包分段(Packet Fragmentation)</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ping -l 1473 192.168.1.x</span><br><span class="line">sudo nmap -Pn -sS -sV --send-eth --mtu 8 -p 443 nmap.scame.org</span><br><span class="line">sudo nmap -Pn -sS -sV --send-eth -f -p 443 nmap.scame.org</span><br></pre></td></tr></table></figure><h2 id="通訊埠來源修改-Source-Port-Manipulation"><a href="#通訊埠來源修改-Source-Port-Manipulation" class="headerlink" title="通訊埠來源修改(Source Port Manipulation)"></a>通訊埠來源修改(Source Port Manipulation)</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo nmap -g 80 -p 445 192.168.1.250</span><br></pre></td></tr></table></figure><h2 id="IP-位址誘餌-IP-Decoy"><a href="#IP-位址誘餌-IP-Decoy" class="headerlink" title="IP 位址誘餌(IP Decoy)"></a>IP 位址誘餌(IP Decoy)</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo nmap -D RND:10 192.168.1.250</span><br></pre></td></tr></table></figure><h2 id="IP-位址欺騙-IP-Spoofing"><a href="#IP-位址欺騙-IP-Spoofing" class="headerlink" title="IP 位址欺騙(IP Spoofing)"></a>IP 位址欺騙(IP Spoofing)</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo nmap -e eth1 --disable-arp -Pn -S 192.168.40.5 -p 445 192.168.1.250</span><br></pre></td></tr></table></figure><h2 id="代理伺服器-Proxy"><a href="#代理伺服器-Proxy" class="headerlink" title="代理伺服器(Proxy)"></a>代理伺服器(Proxy)</h2><ul><li><p>TOR<br><a href="https://www.torproject.org/">https://www.torproject.org</a><br>Wiki: <a href="https://zh.wikipedia.org/zh-tw/Tor">https://zh.wikipedia.org/zh-tw/Tor</a></p></li><li><p>VPN<br><a href="https://www.vpngate.net/cn/">https://www.vpngate.net/cn/</a></p></li></ul>]]></content>
<categories>
<category> Learning notes </category>
</categories>
<tags>
<tag> Hacking </tag>
</tags>
</entry>
<entry>
<title>道德駭客實務入門-課前準備</title>
<link href="/2023/03/20/learning-notes/hacking-class-0/"/>
<url>/2023/03/20/learning-notes/hacking-class-0/</url>
<content type="html"><![CDATA[<p><a href="https://www.tibame.com/offline/ceh">TibaMe: 道德駭客實務入門及CEH認證班</a><br>Teacher: 林煌錡 (Alex Lin)<br><img src="/img/hacking-class/hacking-class-info.png"></p><h2 id="Install-VMWare"><a href="#Install-VMWare" class="headerlink" title="Install VMWare"></a>Install VMWare</h2><ul><li>For Mac : using <a href="https://www.vmware.com/tw/products/fusion.html">VMware Fusion</a>. Note: registration is required.</li><li>Fow Windows : using <a href="https://www.vmware.com/go/getplayer-win">VMware player</a>.</li></ul><h2 id="Download-Kali-image"><a href="#Download-Kali-image" class="headerlink" title="Download Kali image"></a>Download Kali image</h2><ul><li><a href="https://www.kali.org/get-kali/">Kali Installer Images link</a></li></ul><h2 id="Setup-virtual-machine"><a href="#Setup-virtual-machine" class="headerlink" title="Setup virtual machine"></a>Setup virtual machine</h2><ul><li>Create a New Virtual machine</li><li>Select OS Linux, version: Debian 10.x 64-bit</li><li>Max disk size: 60GB. Select “split virtual disk into multiple files”</li><li>Memory: 4096MB</li><li>Processors: 2</li><li>Install Kali from the Kali iso image</li></ul><div class="gallery"> <div class="fj-gallery " data-rowHeight="220" data-limit="10"> <span class="gallery-data">[{"url":"/img/hacking-class/vm1.png","alt":""},{"url":"/img/hacking-class/vm2.png","alt":""}]</span> </div> <button class="gallery-load-more"><span>Load More</span><i class="fa-solid fa-arrow-down"></i></button> </div><h2 id="Kali"><a href="#Kali" class="headerlink" title="Kali"></a>Kali</h2><ul><li><p><a href="https://en.wikipedia.org/wiki/Kali_Linux">Kali Wiki</a><br>After reading the introduction to Kali on Wiki, I completely understand that this is the OS designed for hackers.<br>Kali has all kinds of build-in hacking tools.<br><img src="/img/hacking-class/kali.png"></p></li><li><p><a href="https://www.kali.org/tools/">Kali Tools</a><br>為駭客而生的Kali果然武器配備完整精良!</p></li></ul><p>Kali tools introduction: <a href="https://zhuanlan.zhihu.com/p/76950214">https://zhuanlan.zhihu.com/p/76950214</a></p>]]></content>
<categories>
<category> Learning notes </category>
</categories>
<tags>
<tag> Hacking </tag>
</tags>
</entry>
<entry>
<title>APP工程師不務正業之Server開發2</title>
<link href="/2023/03/05/Server/server-django/"/>
<url>/2023/03/05/Server/server-django/</url>
<content type="html"><![CDATA[<h2 id="Server-Architecture"><a href="#Server-Architecture" class="headerlink" title="Server Architecture"></a>Server Architecture</h2><p><img src="/img/others/server-architecture.png"></p><h2 id="Development-IDE-Language-amp-Framework"><a href="#Development-IDE-Language-amp-Framework" class="headerlink" title="Development IDE, Language & Framework"></a>Development IDE, Language & Framework</h2><blockquote><p>PyCharm<br>Python<br>Django</p></blockquote><h2 id="Development-Environment-Setup"><a href="#Development-Environment-Setup" class="headerlink" title="Development Environment Setup"></a>Development Environment Setup</h2><ul><li><p><strong>Install Docker</strong><br><a href="https://docs.docker.com/desktop/mac/install/">https://docs.docker.com/desktop/mac/install/</a></p></li><li><p><strong>Install Python</strong><br><a href="https://www.python.org/downloads/">https://www.python.org/downloads/</a><br>Currently I am using Python 3.9</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python3 --version</span><br></pre></td></tr></table></figure></li><li><p><strong>Install pip</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py</span><br><span class="line">python3 get-pip.py</span><br></pre></td></tr></table></figure></li><li><p><strong>Install Django</strong><br>Currently I am using Django 3.2.4</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install Django==3.2.4</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">django-admin --version</span><br></pre></td></tr></table></figure></li><li><p><strong>Install Virtual Env</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install virtualenv</span><br></pre></td></tr></table></figure></li></ul><h2 id="Create-Virtual-Env"><a href="#Create-Virtual-Env" class="headerlink" title="Create Virtual Env"></a>Create Virtual Env</h2><ul><li><strong>Create virtual environment</strong><br>Create a project folder. Create virtual environment in the project folder.<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">virtualenv venv --python=python3.9</span><br></pre></td></tr></table></figure></li><li><strong>Enter virtual environment</strong><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">source</span> ./venv/bin/activate</span><br></pre></td></tr></table></figure></li><li><strong>Exit virtual environment</strong><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">deactivate</span><br></pre></td></tr></table></figure></li></ul><h2 id="Create-Django-Project-amp-App"><a href="#Create-Django-Project-amp-App" class="headerlink" title="Create Django Project & App"></a>Create Django Project & App</h2><ul><li><strong>Create Django project</strong><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">django-admin startproject maochun_test</span><br></pre></td></tr></table></figure></li><li><strong>Create Django App</strong><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">django-admin startapp user</span><br></pre></td></tr></table></figure></li><li><strong>Django project structure</strong><br><img src="/img/others/server-proj-struct.png"></li></ul><h2 id="Install-Python-Packages"><a href="#Install-Python-Packages" class="headerlink" title="Install Python Packages"></a>Install Python Packages</h2><ul><li>Django</li><li>djangorestframework</li><li>drf-yasg</li><li>psycopg2-binary</li></ul><p><img src="/img/others/server-install-pkg.png"></p><h2 id="Run-server-in-local"><a href="#Run-server-in-local" class="headerlink" title="Run server in local"></a>Run server in local</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">python manage.py makemigrations</span><br><span class="line">python manage.py migrate</span><br><span class="line">python manage.py runserver</span><br></pre></td></tr></table></figure><h2 id="Others"><a href="#Others" class="headerlink" title="Others"></a>Others</h2><ul><li><p>Generate installed package list</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip freeze > requirements.txt</span><br></pre></td></tr></table></figure></li><li><p>Install package from list</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install -r requirements.txt</span><br></pre></td></tr></table></figure></li></ul><h2 id="Django-Settings"><a href="#Django-Settings" class="headerlink" title="Django Settings"></a>Django Settings</h2><p><a href="https://docs.djangoproject.com/en/4.1/ref/settings/">https://docs.djangoproject.com/en/4.1/ref/settings/</a></p><ul><li><p><strong>Common Settings</strong></p></li><li><p><strong>DB Settings</strong></p></li><li><p><strong>Installed APP Settings</strong></p></li><li><p><strong>Middleware Settings</strong></p></li><li><p><strong>Static Settings</strong></p></li><li><p><strong>Template Settings</strong></p></li><li><p><strong>REST Framework Settings</strong><br><a href="https://www.django-rest-framework.org/api-guide/settings/">https://www.django-rest-framework.org/api-guide/settings/</a></p></li></ul><h2 id="Django-AdminSite"><a href="#Django-AdminSite" class="headerlink" title="Django AdminSite"></a>Django AdminSite</h2><p><a href="https://docs.djangoproject.com/en/4.1/ref/contrib/admin/">https://docs.djangoproject.com/en/4.1/ref/contrib/admin/</a></p><h2 id="Django-Management-Command"><a href="#Django-Management-Command" class="headerlink" title="Django Management Command"></a>Django Management Command</h2><p><a href="https://docs.djangoproject.com/en/4.1/howto/custom-management-commands/">https://docs.djangoproject.com/en/4.1/howto/custom-management-commands/</a></p><h2 id="Database-amp-Caching"><a href="#Database-amp-Caching" class="headerlink" title="Database & Caching"></a>Database & Caching</h2><ul><li><strong>PostgreSQL</strong></li><li><strong>Redis</strong></li></ul><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><ul><li><p><a href="https://docs.docker.com/desktop/install/mac-install/">Docker document</a></p></li><li><p><a href="https://pip.pypa.io/en/stable/getting-started/">Pip document</a></p></li><li><p><a href="https://docs.djangoproject.com/en/4.1/contents/">Django document</a></p></li><li><p><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/development_environment">Django development tutorial from mozilla org.</a></p></li><li><p><a href="https://www.django-rest-framework.org/">Django rest framework document</a></p></li><li><p><a href="https://www.jetbrains.com/pycharm/">PyCharm IDE</a></p></li></ul>]]></content>
<categories>
<category> Server </category>
</categories>
</entry>
<entry>
<title>APP工程師不務正業之Server開發1</title>
<link href="/2023/03/05/Server/Server-Dev-Start/"/>
<url>/2023/03/05/Server/Server-Dev-Start/</url>
<content type="html"><![CDATA[<p>作為一個APP工程師,常常與Server工程師緊密合作,久而久之產生了學習Server的想法。</p><blockquote><p><font style="font-size: 20px;">在此感謝好友<a href="https://blog.langgo.app/about/">小K</a>帶我跨越Server的重重門檻,引領我一路向前。</font></p></blockquote><h2 id="Server之門ㄧ入深似海"><a href="#Server之門ㄧ入深似海" class="headerlink" title="Server之門ㄧ入深似海"></a>Server之門ㄧ入深似海</h2><p>ㄧ入Server之門,各種語言,框架,工具,理論,直看的眼花撩亂,目不暇給。</p><h2 id="十八般兵器怎麼選擇?"><a href="#十八般兵器怎麼選擇?" class="headerlink" title="十八般兵器怎麼選擇?"></a>十八般兵器怎麼選擇?</h2><p>十八般兵器不需要樣樣精通,每個兵器都有所長,選適合自己的就好。</p><ul><li><strong>Language:</strong> Python</li><li><strong>Framework:</strong> Django</li><li><strong>Server APIs:</strong> RESTful APIs</li><li><strong>API tool:</strong> Swagger</li><li><strong>Database:</strong> PostgreSQL</li><li><strong>Caching:</strong> Redis</li><li><strong>Containerization:</strong> Docker</li><li><strong>CI/CD:</strong> Gitlab CI/CD</li></ul><h2 id="十八般武藝先練哪般?"><a href="#十八般武藝先練哪般?" class="headerlink" title="十八般武藝先練哪般?"></a>十八般武藝先練哪般?</h2><ol><li>學習Server, 語言先行,沒有語言其他都是浮雲。所以先學了Python,驚豔於Python精簡的語法,感動於Python之禪。</li><li>學習Django框架,這部分耗時比較多。</li><li>學習PostgreSQL DB。</li><li>學習RESTFul API & Swagger。</li><li>學習Redis。</li><li>學習Docker。</li><li>學習Server容器化部署。</li><li>學習Gitlab CI/CD。</li></ol><h2 id="結尾"><a href="#結尾" class="headerlink" title="結尾"></a>結尾</h2><p>身為APP工程師,心中最愛始終是APP開發,然而我不會因此限縮了自己學習的範圍,唯有持續不斷的學習與嘗試,才有面對各種改變與挑戰的勇氣。長風破浪會有時,直挂雲帆濟滄海。 與所有認真Coding,銳意進取的工程師共勉之。</p><hr><h2 id="Brij-Kishore-Pandey的Backend-Burger"><a href="#Brij-Kishore-Pandey的Backend-Burger" class="headerlink" title="Brij Kishore Pandey的Backend Burger"></a><a href="https://www.linkedin.com/in/brijpandeyji/">Brij Kishore Pandey</a>的Backend Burger</h2><p>你也可以把Server看成是一個大Burger,盡情選擇自己喜歡的搭配!</p><p><img src="/img/server/server-burger.png"> </p>]]></content>
<categories>
<category> Server </category>
</categories>
</entry>
<entry>
<title>Android UDP Broadcast (Kotlin)</title>
<link href="/2023/03/04/APP%20Development/Android-UDPBroadcast/"/>
<url>/2023/03/04/APP%20Development/Android-UDPBroadcast/</url>
<content type="html"><![CDATA[<h1 id="My-sample-project-source-code-in-GitHub"><a href="#My-sample-project-source-code-in-GitHub" class="headerlink" title="My sample project source code in GitHub"></a><a href="https://github.com/MaochunS/Android-TestUDPBroadcast">My sample project source code in GitHub</a></h1><h2 id="Permissions"><a href="#Permissions" class="headerlink" title="Permissions"></a>Permissions</h2><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.INTERNET"</span>/></span></span><br><span class="line"><span class="tag"><<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.ACCESS_WIFI_STATE"</span>/></span></span><br></pre></td></tr></table></figure><h2 id="Open-socket"><a href="#Open-socket" class="headerlink" title="Open socket"></a>Open socket</h2><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">open</span><span class="params">(port: <span class="type">Int</span>)</span></span>: <span class="built_in">Boolean</span> {</span><br><span class="line"> mPort = port</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> mSocket = DatagramSocket(port)</span><br><span class="line"> mSocket?.broadcast = <span class="literal">true</span></span><br><span class="line"> mSocket?.reuseAddress = <span class="literal">true</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line"> } <span class="keyword">catch</span> (e: Exception) {</span><br><span class="line"> e.printStackTrace()</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Close-socket"><a href="#Close-socket" class="headerlink" title="Close socket"></a>Close socket</h2><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">close</span><span class="params">()</span></span>: <span class="built_in">Boolean</span> {</span><br><span class="line"> <span class="keyword">if</span> (mSocket != <span class="literal">null</span> && mSocket?.isClosed?.not() <span class="keyword">as</span> <span class="built_in">Boolean</span>) {</span><br><span class="line"> mSocket?.close()</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Send"><a href="#Send" class="headerlink" title="Send"></a>Send</h2><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">sendPacket</span><span class="params">(buffer: <span class="type">ByteArray</span>)</span></span>: <span class="built_in">Boolean</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">val</span> packet = DatagramPacket(buffer, buffer.size)</span><br><span class="line"> packet.address = InetAddress.getByName(<span class="string">"255.255.255.255"</span>)</span><br><span class="line"> packet.port = mPort</span><br><span class="line"> mSocket?.send(packet)</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line"> } <span class="keyword">catch</span> (e: Exception) {</span><br><span class="line"> e.printStackTrace()</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Receive"><a href="#Receive" class="headerlink" title="Receive"></a>Receive</h2><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">recvPacket</span><span class="params">(buffer: <span class="type">ByteArray</span>)</span></span>: <span class="built_in">Boolean</span> {</span><br><span class="line"> <span class="keyword">val</span> packet = DatagramPacket(buffer, buffer.size)</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> mSocket?.receive(packet)</span><br><span class="line"> println(<span class="string">"recv packet <span class="subst">${packet.address}</span> <span class="subst">${packet.data}</span>"</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line"> } <span class="keyword">catch</span> (e: Exception) {</span><br><span class="line"> e.printStackTrace()</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><blockquote><p><a href="https://developer.android.com/reference/kotlin/java/net/DatagramSocket">Android DatagramSocket Doc</a></p></blockquote>]]></content>
<categories>
<category> APP Development </category>
</categories>
<tags>
<tag> UDP Broadcast </tag>
</tags>
</entry>
<entry>
<title>iOS UDP Broadcast (Swift)</title>
<link href="/2023/03/04/APP%20Development/iOS-UDPBroadcast/"/>
<url>/2023/03/04/APP%20Development/iOS-UDPBroadcast/</url>
<content type="html"><![CDATA[<h1 id="Apply-for-the-Multicast-Networking-capability"><a href="#Apply-for-the-Multicast-Networking-capability" class="headerlink" title="Apply for the Multicast Networking capability"></a>Apply for the Multicast Networking capability</h1><p>To use UDP broadcast in iOS, you must submit a Multicast Networking Entitlement Request application to Apple using the link below:<br><a href="https://developer.apple.com/contact/request/networking-multicast">https://developer.apple.com/contact/request/networking-multicast</a></p><p>Sample information you need to provide</p><blockquote><p><strong>APP Name</strong> : XXXXX<br><strong>App Store URL</strong> : <a href="https://apps.apple.com/us/app/xxxxxxxxx">https://apps.apple.com/us/app/xxxxxxxxx</a><br><strong>Apple ID</strong> : 1234567890<br><strong>App Category</strong> : the APP’s category</p><p><strong>Describe the main purpose of your app:</strong><br>The app helps users to setup our company’s router products.</p><p><strong>Explain why your app needs to send multicast:</strong><br>Our APP uses the UDP broadcast to discover our company’s devices.</p><p><strong>UDP IP</strong> : 255.255.255.255<br><strong>Port</strong> : 12345</p></blockquote><p>Waiting for serval days, if your application is approved by Apple, you will see the Multicast Networking capability in your APP identifier’s Additional Capabilities.<br><img src="/img/others/apple-multicast-capability.png"></p><h1 id="UDP-broadcast-via-CocoaAsyncSocket"><a href="#UDP-broadcast-via-CocoaAsyncSocket" class="headerlink" title="UDP broadcast via CocoaAsyncSocket"></a>UDP broadcast via <a href="https://github.com/robbiehanson/CocoaAsyncSocket">CocoaAsyncSocket</a></h1><h2 id="My-sample-project-source-code-in-GitHub"><a href="#My-sample-project-source-code-in-GitHub" class="headerlink" title="My sample project source code in GitHub"></a><a href="https://github.com/MaochunS/iOS-TestUDPBroadcast">My sample project source code in GitHub</a></h2><p><strong>Please note: In order to run the sample project, you need to use the iPhone simulator. If you try to run the sample project on a real phone, you will need to have “Multicast Networking capability”.</strong></p><h2 id="Pod-install"><a href="#Pod-install" class="headerlink" title="Pod install"></a>Pod install</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pod <span class="string">'CocoaAsyncSocket'</span></span><br></pre></td></tr></table></figure><p>If you use XCode 13+, set the project format to avoid pod init error<br><img src="/img/others/pod-init.png"></p><h2 id="Start-broadcast"><a href="#Start-broadcast" class="headerlink" title="Start broadcast"></a>Start broadcast</h2><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">startBroadcast</span>(<span class="params">port</span>:<span class="type">Int</span>, <span class="params">data</span>:<span class="type">Data</span>) -> <span class="type">Bool</span>{</span><br><span class="line"> socket <span class="operator">=</span> <span class="type">GCDAsyncUdpSocket</span>(delegate: <span class="keyword">self</span>, delegateQueue: <span class="type">DispatchQueue</span>.main)</span><br><span class="line"> <span class="keyword">guard</span> <span class="keyword">let</span> s <span class="operator">=</span> socket <span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line"> }</span><br><span class="line"> s.setIPv6Enabled(<span class="literal">false</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">do</span> {</span><br><span class="line"> <span class="keyword">try</span> s.bind(toPort: <span class="type">UInt16</span>(port), interface: <span class="string">"en0"</span>)</span><br><span class="line"> <span class="keyword">try</span> s.enableBroadcast(<span class="literal">true</span>)</span><br><span class="line"> <span class="keyword">try</span> s.beginReceiving()</span><br><span class="line"> } <span class="keyword">catch</span> {</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Error: <span class="subst">\(error)</span>"</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> s.send(data, toHost: <span class="string">"255.255.255.255"</span>, port: <span class="type">UInt16</span>(port), withTimeout: <span class="operator">-</span><span class="number">1</span>, tag: <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Stop-broadcast"><a href="#Stop-broadcast" class="headerlink" title="Stop broadcast"></a>Stop broadcast</h2><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">stopBroadcast</span>(){</span><br><span class="line"> socket<span class="operator">?</span>.close()</span><br><span class="line"> socket <span class="operator">=</span> <span class="literal">nil</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Handle-delegate"><a href="#Handle-delegate" class="headerlink" title="Handle delegate"></a>Handle delegate</h2><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">extension</span> <span class="title class_">UDPBroadcast</span>: <span class="title class_">GCDAsyncUdpSocketDelegate</span>{</span><br><span class="line"> <span class="keyword">func</span> <span class="title function_">udpSocket</span>(<span class="keyword">_</span> <span class="params">sock</span>: <span class="type">GCDAsyncUdpSocket</span>, <span class="params">didReceive</span> <span class="params">data</span>: <span class="type">Data</span>, <span class="params">fromAddress</span> <span class="params">address</span>: <span class="type">Data</span>, <span class="params">withFilterContext</span> <span class="params">filterContext</span>: <span class="keyword">Any</span><span class="operator">?</span>) {</span><br><span class="line"> <span class="keyword">var</span> host: <span class="type">NSString</span>?</span><br><span class="line"> <span class="keyword">var</span> port: <span class="type">UInt16</span> <span class="operator">=</span> <span class="number">0</span></span><br><span class="line"> <span class="type">GCDAsyncUdpSocket</span>.getHost(<span class="operator">&</span>host, port: <span class="operator">&</span>port, fromAddress: (address <span class="keyword">as</span> <span class="type">Data</span>?)<span class="operator">!</span>)</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">let</span> dataStr <span class="operator">=</span> data.reduce(<span class="string">""</span>) {<span class="variable">$0</span> <span class="operator">+</span> <span class="type">String</span>(format: <span class="string">"0x%02x "</span>, <span class="variable">$1</span>)}</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Receive data from <span class="subst">\(host <span class="operator">??</span> <span class="string">"N/A"</span>)</span> <span class="subst">\(port)</span>"</span>)</span><br><span class="line"> <span class="built_in">print</span>(dataStr)</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">func</span> <span class="title function_">udpSocketDidClose</span>(<span class="keyword">_</span> <span class="params">sock</span>: <span class="type">GCDAsyncUdpSocket</span>, <span class="params">withError</span> <span class="params">error</span>: <span class="type">Error</span>?) {</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"socket close with error <span class="subst">\(String(describing: error))</span>"</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="UDP-broadcast-via-NWConnection"><a href="#UDP-broadcast-via-NWConnection" class="headerlink" title="UDP broadcast via NWConnection"></a>UDP broadcast via NWConnection</h1><blockquote><p>Technically, NWConnection can be used to perform UDP broadcast. However, in my experience, I have not been able to get it to work.</p></blockquote>]]></content>
<categories>
<category> APP Development </category>
</categories>
<tags>
<tag> UDP Broadcast </tag>
</tags>
</entry>
<entry>
<title>Publish APP to Google Play Store</title>
<link href="/2023/02/26/APP%20Development/Android-Publish-APP/"/>
<url>/2023/02/26/APP%20Development/Android-Publish-APP/</url>
<content type="html"><![CDATA[<h1 id="Items-you-need-to-prepare-for-the-Google-Play-Store"><a href="#Items-you-need-to-prepare-for-the-Google-Play-Store" class="headerlink" title="Items you need to prepare for the Google Play Store"></a>Items you need to prepare for the Google Play Store</h1><ol><li>Icon 1024 x 1024</li><li>Android store required image 1024 x 500, JPG 或 24 位元 PNG (無 alpha 透明層)</li><li>APP name: XXX</li><li>APP descriptions & Categories</li><li>Copyright Link</li><li>APP screenshots<blockquote><p>I prefer for the UI designer to create these screenshots for a more professional look.</p></blockquote></li><li>Google play account for <a href="https://play.google.com/console/about/">Google Play Console</a></li></ol><h1 id="If-your-app-requires-login-account"><a href="#If-your-app-requires-login-account" class="headerlink" title="If your app requires login account"></a>If your app requires login account</h1><blockquote><ul><li>APP Content -> APP access, click “Manage”</li></ul></blockquote><p><img src="/img/others/playstore1.png"></p><blockquote><ul><li>Select “All or some functionality in my app is restricted” and click “Add instructions”</li></ul></blockquote><p><img src="/img/others/playstore2.png"></p><blockquote><ul><li>Fill in the forms with test account & password.</li></ul></blockquote><p><img src="/img/others/playstore3.png"></p><blockquote><ul><li>You can put extra info. in the other information session.</li></ul></blockquote><p><img src="/img/others/playstore4.png"></p><h1 id="If-your-app-uses-background-location"><a href="#If-your-app-uses-background-location" class="headerlink" title="If your app uses background location"></a>If your app uses background location</h1><p><strong>You need to provide demo video (Provide YouTube link to the demo video)</strong></p><blockquote><ul><li>APP Content -> Sensitive permissions and APIs, click “Manage”</li></ul></blockquote><p><img src="/img/others/android-bkloc-permission1.png"></p><blockquote><ul><li>Input your app purpose and describe the reason why you need to use the background location.</li></ul></blockquote><p><img src="/img/others/android-bkloc-permission2.png"></p><blockquote><ul><li>Provide demo video</li></ul></blockquote><p><strong>Do fulfill the requirements in the orange box.</strong><br><img src="/img/others/android-bkloc-permission3.png"></p>]]></content>
<categories>
<category> APP Development </category>
</categories>
<tags>
<tag> Publish APP </tag>
</tags>
</entry>
<entry>
<title>Android QRCode Scan (Kotlin)</title>
<link href="/2023/02/25/APP%20Development/Android-QRCode-Scan/"/>
<url>/2023/02/25/APP%20Development/Android-QRCode-Scan/</url>
<content type="html"><![CDATA[<h1 id="My-sample-project-source-code-in-GitHub"><a href="#My-sample-project-source-code-in-GitHub" class="headerlink" title="My sample project source code in GitHub"></a><a href="https://github.com/MaochunS/Android-QRCodeScanner-kotlin">My sample project source code in GitHub</a></h1><h1 id="Using-Google-MLKit"><a href="#Using-Google-MLKit" class="headerlink" title="Using Google MLKit"></a>Using Google MLKit</h1><h2 id="Add-dependencies"><a href="#Add-dependencies" class="headerlink" title="Add dependencies"></a>Add dependencies</h2><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">dependencies</span> {</span><br><span class="line"> ...</span><br><span class="line"> implementation <span class="string">'androidx.camera:camera-lifecycle:1.2.1'</span></span><br><span class="line"> implementation <span class="string">'com.google.mlkit:barcode-scanning:17.0.3'</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> camerax_version = <span class="string">"1.2.1"</span></span><br><span class="line"> implementation <span class="string">"androidx.camera:camera-core:${camerax_version}"</span></span><br><span class="line"> implementation <span class="string">"androidx.camera:camera-camera2:${camerax_version}"</span></span><br><span class="line"> implementation <span class="string">"androidx.camera:camera-view:${camerax_version}"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Request-camera-permission"><a href="#Request-camera-permission" class="headerlink" title="Request camera permission"></a>Request camera permission</h2><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.CAMERA"</span> /></span></span><br></pre></td></tr></table></figure><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">val</span> REQUEST_CODE_PERMISSIONS = <span class="number">10</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">val</span> REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)</span><br></pre></td></tr></table></figure><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onCreate</span><span class="params">(savedInstanceState: <span class="type">Bundle</span>?)</span></span> {</span><br><span class="line"> <span class="keyword">super</span>.onCreate(savedInstanceState)</span><br><span class="line"> ...</span><br><span class="line"> <span class="comment">// Request camera permissions</span></span><br><span class="line"> <span class="keyword">if</span> (allPermissionsGranted()) {</span><br><span class="line"> startPreview()</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> ActivityCompat.requestPermissions(</span><br><span class="line"> <span class="keyword">this</span>, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onRequestPermissionsResult</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function"> requestCode: <span class="type">Int</span>, permissions: <span class="type">Array</span><<span class="type">String</span>>, grantResults: <span class="type">IntArray</span>)</span></span> {</span><br><span class="line"> <span class="keyword">if</span> (requestCode == REQUEST_CODE_PERMISSIONS) {</span><br><span class="line"> <span class="keyword">if</span> (allPermissionsGranted()) {</span><br><span class="line"> startPreview()</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> Toast.makeText(<span class="keyword">this</span>,</span><br><span class="line"> <span class="string">"Permissions not granted by the user."</span>,</span><br><span class="line"> Toast.LENGTH_SHORT).show()</span><br><span class="line"> finish()</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">allPermissionsGranted</span><span class="params">()</span></span> = REQUIRED_PERMISSIONS.all {</span><br><span class="line"> ContextCompat.checkSelfPermission(</span><br><span class="line"> baseContext, it) == PackageManager.PERMISSION_GRANTED</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Bind-preview-and-image-analyzer"><a href="#Bind-preview-and-image-analyzer" class="headerlink" title="Bind preview and image analyzer"></a>Bind preview and image analyzer</h2><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">startPreview</span><span class="params">()</span></span>{</span><br><span class="line"> cameraProviderFuture = ProcessCameraProvider.getInstance(<span class="keyword">this</span>)</span><br><span class="line"> cameraProviderFuture.addListener(Runnable {</span><br><span class="line"> bindPreview()</span><br><span class="line"> }, ContextCompat.getMainExecutor(<span class="keyword">this</span>))</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">bindPreview</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">var</span> preview = Preview.Builder()</span><br><span class="line"> .build()</span><br><span class="line"></span><br><span class="line"> <span class="keyword">var</span> cameraSelector = CameraSelector.Builder()</span><br><span class="line"> .requireLensFacing(CameraSelector.LENS_FACING_BACK)</span><br><span class="line"> .build()</span><br><span class="line"></span><br><span class="line"> preview.setSurfaceProvider(previewView.surfaceProvider)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> analyzer = ImageAnalysis.Builder()</span><br><span class="line"> .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)</span><br><span class="line"> .build()</span><br><span class="line"></span><br><span class="line"> cameraExecutor = Executors.newSingleThreadExecutor()</span><br><span class="line"> analyzer?.let {</span><br><span class="line"> it.setAnalyzer(cameraExecutor) { imageProxy -></span><br><span class="line"> handleImage(imageProxy)</span><br><span class="line"> imageProxy.close()</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">val</span> cameraProvider = cameraProviderFuture.<span class="keyword">get</span>()</span><br><span class="line"> cameraProvider.unbindAll()</span><br><span class="line"> cameraProvider.bindToLifecycle(<span class="keyword">this</span>, cameraSelector, preview, analyzer)</span><br><span class="line"> } <span class="keyword">catch</span> (e: Exception) {</span><br><span class="line"> Log.e(<span class="string">"ScanQRCode Test APP"</span>, <span class="string">"cameraProvider binding failed!"</span>, e)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Handle-QRCode-image"><a href="#Handle-QRCode-image" class="headerlink" title="Handle QRCode image"></a>Handle QRCode image</h2><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">handleImage</span><span class="params">(imageProxy: <span class="type">ImageProxy</span>)</span></span>{</span><br><span class="line"> <span class="keyword">val</span> mediaImage = imageProxy.image</span><br><span class="line"> <span class="keyword">if</span> (mediaImage != <span class="literal">null</span>) {</span><br><span class="line"> <span class="keyword">val</span> image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Pass image to an ML Kit Vision API</span></span><br><span class="line"> <span class="keyword">val</span> options = BarcodeScannerOptions.Builder()</span><br><span class="line"> .setBarcodeFormats(</span><br><span class="line"> Barcode.FORMAT_QR_CODE,</span><br><span class="line"> Barcode.FORMAT_AZTEC)</span><br><span class="line"> .build()</span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> scanner = BarcodeScanning.getClient(options)</span><br><span class="line"> scanner.process(image).addOnSuccessListener { barcodes -></span><br><span class="line"> <span class="keyword">for</span> (barcode <span class="keyword">in</span> barcodes) {</span><br><span class="line"> <span class="comment">// Handle received barcodes...</span></span><br><span class="line"> println(<span class="string">"Scan result: <span class="subst">${barcode.rawValue}</span>"</span>)</span><br><span class="line"></span><br><span class="line"> Toast.makeText(</span><br><span class="line"> <span class="keyword">this</span>,</span><br><span class="line"> <span class="string">"Scan result: <span class="subst">${barcode.rawValue}</span>"</span>,</span><br><span class="line"> Toast.LENGTH_SHORT</span><br><span class="line"> ).show()</span><br><span class="line"></span><br><span class="line"> cameraExecutor.shutdown()</span><br><span class="line"> finish()</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><ul><li><p><a href="https://developers.google.com/codelabs/camerax-getting-started?hl=en#1">CameraX</a></p></li><li><p><a href="https://developer.android.com/training/camerax/preview">CameraX Preview</a></p></li><li><p><a href="https://developer.android.com/training/camerax/analyze?hl=en">ImageAnalysis</a></p></li><li><p><a href="https://developers.google.com/ml-kit/vision/barcode-scanning/android">Scan Barcode with ML Kit on Android</a></p></li></ul><h1 id="Other-methods"><a href="#Other-methods" class="headerlink" title="Other methods"></a>Other methods</h1><h2 id="Zxing"><a href="#Zxing" class="headerlink" title="Zxing"></a>Zxing</h2><ul><li><p>You can use <a href="https://github.com/journeyapps/zxing-android-embedded">Zxing lib</a> to scan QRCode.</p></li><li><p><a href="https://github.com/MaochunS/Android-QRCodeScanner">My sample project source code in GitHub (in Java)</a></p></li></ul><h2 id="com-google-android-gms-vision-barcode"><a href="#com-google-android-gms-vision-barcode" class="headerlink" title="com.google.android.gms.vision.barcode"></a>com.google.android.gms.vision.barcode</h2><ul><li>The API is deprecated. Don’t use it.<br><a href="https://developers.google.com/android/reference/com/google/android/gms/vision/barcode/package-summary">Google Vision API deprecated info.</a></li></ul>]]></content>
<categories>
<category> APP Development </category>
</categories>
<tags>
<tag> QRCode Scan </tag>
</tags>
</entry>
<entry>
<title>PostgreSQL Commands</title>
<link href="/2023/02/25/Server/PostgreSQL-Command/"/>
<url>/2023/02/25/Server/PostgreSQL-Command/</url>
<content type="html"><![CDATA[<h1 id="Connect-to-DB"><a href="#Connect-to-DB" class="headerlink" title="Connect to DB"></a>Connect to DB</h1><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">psql -U userXXX -d dbXXX</span><br></pre></td></tr></table></figure><h1 id="DB-operations"><a href="#DB-operations" class="headerlink" title="DB operations"></a>DB operations</h1><ul><li><p>List all DBs</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">\l</span><br></pre></td></tr></table></figure></li><li><p>List all tables</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">\dt</span><br></pre></td></tr></table></figure></li><li><p>List all users</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">\<span class="built_in">du</span></span><br></pre></td></tr></table></figure></li><li><p>List table info.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">\d table_name</span><br></pre></td></tr></table></figure></li><li><p>Quit</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">\q</span><br></pre></td></tr></table></figure></li></ul><h1 id="Backup-amp-restore"><a href="#Backup-amp-restore" class="headerlink" title="Backup & restore"></a>Backup & restore</h1><ul><li><p>Backup</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pg_dump -U userXXX dbXXX | gzip > backup.gz</span><br></pre></td></tr></table></figure></li><li><p>Restore</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pg_restore --clean -U userXXX -d dbXXX backup</span><br></pre></td></tr></table></figure></li></ul><h1 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h1><p><a href="https://www.postgresqltutorial.com/postgresql-administration/psql-commands/">PostgreSQL tutorial</a></p>]]></content>
<categories>
<category> Server </category>
</categories>
<tags>
<tag> PostgreSQL </tag>
</tags>
</entry>
<entry>
<title>Google Consoles</title>
<link href="/2023/02/25/Server/Google-Consoles/"/>
<url>/2023/02/25/Server/Google-Consoles/</url>
<content type="html"><![CDATA[<p>Below are Google Consoles and related functions I used.</p><h2 id="Google-Play-Console"><a href="#Google-Play-Console" class="headerlink" title="Google Play Console"></a><a href="https://play.google.com/console">Google Play Console</a></h2><ul><li>Create APP for publishing in Google Play Store</li></ul><h2 id="Google-Cloud-Console"><a href="#Google-Cloud-Console" class="headerlink" title="Google Cloud Console"></a><a href="https://console.cloud.google.com/">Google Cloud Console</a></h2><ul><li>Create VM</li><li>Create Storage Bucket</li><li>Use Google APIs, e.g. map apis.</li></ul><h2 id="Google-Firebase-Console"><a href="#Google-Firebase-Console" class="headerlink" title="Google Firebase Console"></a><a href="https://console.firebase.google.com/">Google Firebase Console</a></h2><ul><li>Create cloud messaging project</li></ul><h2 id="Google-Search-Console"><a href="#Google-Search-Console" class="headerlink" title="Google Search Console"></a><a href="https://search.google.com/search-console/about">Google Search Console</a></h2><ul><li>Monitoring my blog</li></ul><h2 id="Google-Analytics"><a href="#Google-Analytics" class="headerlink" title="Google Analytics"></a><a href="https://analytics.google.com/">Google Analytics</a></h2><ul><li>Analysis my blog traffic</li></ul>]]></content>
<categories>
<category> Google </category>
</categories>
<tags>
<tag> Google </tag>
</tags>
</entry>
<entry>
<title>Docker Commands</title>
<link href="/2023/02/19/Server/Docker-Commands/"/>
<url>/2023/02/19/Server/Docker-Commands/</url>
<content type="html"><![CDATA[<h1 id="Get-container-info"><a href="#Get-container-info" class="headerlink" title="Get container info."></a>Get container info.</h1><h2 id="List-all-containers"><a href="#List-all-containers" class="headerlink" title="List all containers"></a>List all containers</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo docker ps</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo docker ps -a</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo docker ps -q</span><br></pre></td></tr></table></figure><h2 id="Check-docker-container-size"><a href="#Check-docker-container-size" class="headerlink" title="Check docker container size:"></a>Check docker container size:</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo docker ps -s</span><br></pre></td></tr></table></figure><h2 id="Check-docker-container-resource-usage"><a href="#Check-docker-container-resource-usage" class="headerlink" title="Check docker container resource usage:"></a>Check docker container resource usage:</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo docker stats</span><br></pre></td></tr></table></figure><h1 id="Container-operations"><a href="#Container-operations" class="headerlink" title="Container operations"></a>Container operations</h1><h2 id="Start"><a href="#Start" class="headerlink" title="Start"></a>Start</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo docker start Nginx</span><br></pre></td></tr></table></figure><h2 id="Restart"><a href="#Restart" class="headerlink" title="Restart"></a>Restart</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo docker restart Nginx</span><br></pre></td></tr></table></figure><h2 id="Stop"><a href="#Stop" class="headerlink" title="Stop"></a>Stop</h2><ul><li>Stop a container<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo docker stop Nginx</span><br></pre></td></tr></table></figure></li><li>Stop all container<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo docker stop $(sudo docker ps -a -q)</span><br></pre></td></tr></table></figure></li></ul><h2 id="Kill"><a href="#Kill" class="headerlink" title="Kill"></a>Kill</h2><ul><li>Kill a container<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo docker <span class="built_in">kill</span> Nginx</span><br></pre></td></tr></table></figure></li><li>Kill all container<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo docker <span class="built_in">kill</span> $(sudo docker ps -a -q)</span><br></pre></td></tr></table></figure></li></ul><h2 id="Remove"><a href="#Remove" class="headerlink" title="Remove"></a>Remove</h2><ul><li>Remove a container<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo docker <span class="built_in">rm</span> Nginx</span><br></pre></td></tr></table></figure></li><li>Remove all container<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo docker <span class="built_in">rm</span> $(sudo docker ps -a -q)</span><br></pre></td></tr></table></figure></li></ul><h1 id="File-operations"><a href="#File-operations" class="headerlink" title="File operations"></a>File operations</h1><h2 id="Copy-file-x2F-directory-to-container"><a href="#Copy-file-x2F-directory-to-container" class="headerlink" title="Copy file/directory to container"></a>Copy file/directory to container</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">cp</span> <src-path> <container>:<dest-path> </span><br></pre></td></tr></table></figure><p>e.g. <em>docker cp db:/backup ./</em></p><h2 id="Copy-file-x2F-directory-from-container"><a href="#Copy-file-x2F-directory-from-container" class="headerlink" title="Copy file/directory from container"></a>Copy file/directory from container</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">cp</span> <container>:<src-path> <local-dest-path> </span><br></pre></td></tr></table></figure><p>e.g. <em>docker cp db:/backup ./</em></p><h1 id="Execute-command-in-a-running-container"><a href="#Execute-command-in-a-running-container" class="headerlink" title="Execute command in a running container"></a>Execute command in a running container</h1><ul><li>Execute shell<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">exec</span> -it Nginx sh</span><br></pre></td></tr></table></figure></li><li>Dump PostgreSQL db<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo docker <span class="built_in">exec</span> db_container_name_xxx /bin/bash -c <span class="string">"pg_dump -U postgres db_name_xxx | gzip -9 > postgres-backup.sql.gz"</span></span><br></pre></td></tr></table></figure></li></ul><h1 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h1><p><a href="https://docs.docker.com/engine/reference/commandline/docker/">Docker Commands</a></p>]]></content>
<categories>
<category> Server </category>
</categories>
<tags>
<tag> Docker </tag>
</tags>
</entry>
<entry>
<title>Android Normal Push Notification (Kotlin)</title>
<link href="/2023/02/18/APP%20Development/Android-Normal-Push-Notification/"/>
<url>/2023/02/18/APP%20Development/Android-Normal-Push-Notification/</url>
<content type="html"><![CDATA[<p>Implement FCM push notification for Android APP in Kotlin.</p><h2 id="Add-Firebase-GoogleService-Info-plist-to-the-project-app-folder"><a href="#Add-Firebase-GoogleService-Info-plist-to-the-project-app-folder" class="headerlink" title="Add Firebase GoogleService-Info.plist to the project app folder"></a>Add Firebase GoogleService-Info.plist to the project app folder</h2><p><img src="/img/app_dev/android-fcm-info.png"></p><h2 id="Add-FCM-to-dependencies"><a href="#Add-FCM-to-dependencies" class="headerlink" title="Add FCM to dependencies"></a>Add FCM to dependencies</h2><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">dependencies {</span><br><span class="line"> ...</span><br><span class="line"> implementation <span class="string">'com.google.firebase:firebase-messaging-ktx:23.0.8'</span></span><br><span class="line"> implementation platform(<span class="string">'com.google.firebase:firebase-bom:30.4.1'</span>)</span><br><span class="line"> implementation <span class="string">'com.google.firebase:firebase-analytics-ktx'</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Add-permission-to-Manifest"><a href="#Add-permission-to-Manifest" class="headerlink" title="Add permission to Manifest"></a>Add permission to Manifest</h2><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.POST_NOTIFICATIONS"</span>/></span></span><br></pre></td></tr></table></figure><h2 id="Initialize-FCM"><a href="#Initialize-FCM" class="headerlink" title="Initialize FCM"></a>Initialize FCM</h2><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MainActivity</span> : <span class="type">BaseActivity</span>() {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onCreate</span><span class="params">(savedInstanceState: <span class="type">Bundle</span>?)</span></span> {</span><br><span class="line"> <span class="keyword">super</span>.onCreate(savedInstanceState)</span><br><span class="line"> setContentView(R.layout.activity_main)</span><br><span class="line"></span><br><span class="line"> ...</span><br><span class="line"> Firebase.messaging.isAutoInitEnabled = <span class="literal">true</span></span><br><span class="line"> FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task -></span><br><span class="line"> <span class="keyword">if</span> (!task.isSuccessful) {</span><br><span class="line"> <span class="keyword">return</span><span class="symbol">@OnCompleteListener</span></span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//Handle FCM token</span></span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Implement-FCMService"><a href="#Implement-FCMService" class="headerlink" title="Implement FCMService"></a>Implement FCMService</h2><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">FCMService</span> : <span class="type">FirebaseMessagingService</span>() {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onMessageReceived</span><span class="params">(remoteMessage: <span class="type">RemoteMessage</span>)</span></span> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// TODO(developer): Handle FCM messages here.</span></span><br><span class="line"> remoteMessage.notification?.let { noti -></span><br><span class="line"> noti.title?.let { title -></span><br><span class="line"> sendNotification(title, noti.body ?: <span class="string">""</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Log.d(<span class="string">"TAG"</span>, <span class="string">"Message Notification Body: "</span> + noti.title?:<span class="string">"title empty"</span>)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onNewToken</span><span class="params">(token: <span class="type">String</span>)</span></span> {</span><br><span class="line"> <span class="keyword">super</span>.onNewToken(token)</span><br><span class="line"> println(token)</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//Handle FCM token</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">sendNotification</span><span class="params">(title: <span class="type">String</span>, body: <span class="type">String</span>)</span></span> {</span><br><span class="line"> <span class="keyword">val</span> notifID = System.currentTimeMillis().hashCode()</span><br><span class="line"> <span class="keyword">val</span> intent = Intent(<span class="keyword">this</span>, NotificationListActivity::<span class="keyword">class</span>.java)</span><br><span class="line"> intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)</span><br><span class="line"> intent.putExtra(<span class="string">"notificationId"</span>, notifID)</span><br><span class="line"> <span class="keyword">val</span> contentIntent = PendingIntent.getActivity(<span class="keyword">this</span>, <span class="number">0</span>, intent, PendingIntent.FLAG_ONE_SHOT)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)</span><br><span class="line"> <span class="keyword">val</span> notificationBuilder = NotificationCompat.Builder(<span class="keyword">this</span>, <span class="string">"default"</span>)</span><br><span class="line"> .setSmallIcon(R.drawable.ic_launcher)</span><br><span class="line"> .setContentTitle(title)</span><br><span class="line"> .setContentText(body)</span><br><span class="line"><span class="comment">// .setDefaults(Notification.DEFAULT_ALL)</span></span><br><span class="line"> .setAutoCancel(<span class="literal">true</span>)</span><br><span class="line"> .setContentIntent(contentIntent)</span><br><span class="line"> .setTicker(<span class="string">"PushNotificationTest APP"</span>)</span><br><span class="line"> .setSound(defaultSoundUri)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) <span class="keyword">as</span> NotificationManager</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {</span><br><span class="line"> <span class="keyword">val</span> channelID = <span class="string">"pushnotificationtest"</span>;<span class="comment">//applicationInfo.loadLabel(packageManager).toString()</span></span><br><span class="line"> <span class="keyword">val</span> channel = <span class="keyword">this</span>.createNotificationChannel(</span><br><span class="line"> notificationManager,</span><br><span class="line"> channelID,</span><br><span class="line"> channelID,</span><br><span class="line"> NotificationManager.IMPORTANCE_HIGH</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> channel.setShowBadge(<span class="literal">true</span>)</span><br><span class="line"> notificationManager?.createNotificationChannel(channel)</span><br><span class="line"> notificationBuilder.setChannelId(channelID)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> notificationManager.notify(notifID <span class="comment">/* ID of notification */</span>, notificationBuilder.build())</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@TargetApi(26)</span></span><br><span class="line"> <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">createNotificationChannel</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function"> notificationManager: <span class="type">NotificationManager</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> id: <span class="type">String</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> name: <span class="type">String</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> importance: <span class="type">Int</span></span></span></span><br><span class="line"><span class="params"><span class="function"> )</span></span>: NotificationChannel {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">if</span> (notificationManager.getNotificationChannel(id) != <span class="literal">null</span>) {</span><br><span class="line"> notificationManager.getNotificationChannel(id)</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">val</span> notificationChannel = NotificationChannel(id, name, importance)</span><br><span class="line"> notificationChannel.enableVibration(<span class="literal">true</span>)</span><br><span class="line"> notificationChannel.enableLights(<span class="literal">true</span>)</span><br><span class="line"> notificationChannel</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Add-FCMService-to-Manifest"><a href="#Add-FCMService-to-Manifest" class="headerlink" title="Add FCMService to Manifest"></a>Add FCMService to Manifest</h2><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"utf-8"</span>?></span></span><br><span class="line"><span class="tag"><<span class="name">manifest</span> <span class="attr">xmlns:android</span>=<span class="string">"http://schemas.android.com/apk/res/android"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">package</span>=<span class="string">"com.maochun.pushnotificationtest"</span>></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="tag"><<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.POST_NOTIFICATIONS"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">application</span></span></span><br><span class="line"><span class="tag"> <span class="attr">...</span>></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="tag"><<span class="name">service</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:name</span>=<span class="string">".FCMService"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:exported</span>=<span class="string">"false"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">intent-filter</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">action</span> <span class="attr">android:name</span>=<span class="string">"com.google.firebase.MESSAGING_EVENT"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">intent-filter</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">service</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">application</span>></span></span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> APP Development </category>
</categories>
<tags>
<tag> Push notification </tag>
</tags>
</entry>
<entry>
<title>iOS Normal Push Notification (Swift)</title>
<link href="/2023/02/18/APP%20Development/iOS-Normal-Push-Notification/"/>
<url>/2023/02/18/APP%20Development/iOS-Normal-Push-Notification/</url>
<content type="html"><![CDATA[<p>Implement FCM push notification for iOS APP in Swift.</p><h2 id="Add-Firebase-GoogleService-Info-plist-to-the-project-root"><a href="#Add-Firebase-GoogleService-Info-plist-to-the-project-root" class="headerlink" title="Add Firebase GoogleService-Info.plist to the project root"></a>Add Firebase GoogleService-Info.plist to the project root</h2><p><img src="/img/app_dev/ios-fcm-info.png"></p><h2 id="Add-Firebase-SDK"><a href="#Add-Firebase-SDK" class="headerlink" title="Add Firebase SDK"></a>Add Firebase SDK</h2><blockquote><p> <a href="https://github.com/firebase/firebase-ios-sdk">https://github.com/firebase/firebase-ios-sdk</a></p></blockquote><p><img src="/img/app_dev/add-firebase-sdk.png"></p><h2 id="Initialize-FCM-amp-ask-for-notification-permission"><a href="#Initialize-FCM-amp-ask-for-notification-permission" class="headerlink" title="Initialize FCM & ask for notification permission"></a>Initialize FCM & ask for notification permission</h2><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">application</span>(<span class="keyword">_</span> <span class="params">application</span>: <span class="type">UIApplication</span>, <span class="params">didFinishLaunchingWithOptions</span> <span class="params">launchOptions</span>: [<span class="type">UIApplication</span>.<span class="params">LaunchOptionsKey</span>: <span class="keyword">Any</span>]<span class="operator">?</span>) -> <span class="type">Bool</span> {</span><br><span class="line"> <span class="comment">// Override point for customization after application launch.</span></span><br><span class="line"></span><br><span class="line"> <span class="type">FirebaseApp</span>.configure()</span><br><span class="line"> </span><br><span class="line"> <span class="type">UNUserNotificationCenter</span>.current().getNotificationSettings(completionHandler: { settings <span class="keyword">in</span></span><br><span class="line"> <span class="keyword">switch</span> settings.authorizationStatus {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">case</span> .denied:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"denied"</span>)</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">case</span> .notDetermined:</span><br><span class="line"> <span class="type">UNUserNotificationCenter</span>.current().requestAuthorization(options: [.alert, .sound, .badge], completionHandler: { granted, error <span class="keyword">in</span></span><br><span class="line"> <span class="keyword">if</span> granted {</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Request notification permission is allowed!"</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Request notification permission is rejected!"</span>)</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line"> default:</span><br><span class="line"> <span class="keyword">break</span></span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"> </span><br><span class="line"> application.registerForRemoteNotifications()</span><br><span class="line"> <span class="type">UNUserNotificationCenter</span>.current().delegate <span class="operator">=</span> <span class="keyword">self</span></span><br><span class="line"> <span class="type">Messaging</span>.messaging().delegate <span class="operator">=</span> <span class="keyword">self</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Implement-UNUserNotificationCenterDelegate"><a href="#Implement-UNUserNotificationCenterDelegate" class="headerlink" title="Implement UNUserNotificationCenterDelegate"></a>Implement UNUserNotificationCenterDelegate</h2><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">extension</span> <span class="title class_">AppDelegate</span>: <span class="title class_">UNUserNotificationCenterDelegate</span> {</span><br><span class="line"> <span class="keyword">func</span> <span class="title function_">userNotificationCenter</span>(<span class="keyword">_</span> <span class="params">center</span>: <span class="type">UNUserNotificationCenter</span>, <span class="params">willPresent</span> <span class="params">notification</span>: <span class="type">UNNotification</span>, <span class="params">withCompletionHandler</span> <span class="params">completionHandler</span>: <span class="keyword">@escaping</span> (<span class="type">UNNotificationPresentationOptions</span>) -> <span class="type">Void</span>) {</span><br><span class="line"> completionHandler([.sound, .badge, .alert])</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">func</span> <span class="title function_">userNotificationCenter</span>(<span class="keyword">_</span> <span class="params">center</span>: <span class="type">UNUserNotificationCenter</span>, <span class="params">didReceive</span> <span class="params">response</span>: <span class="type">UNNotificationResponse</span>, <span class="params">withCompletionHandler</span> <span class="params">completionHandler</span>: <span class="keyword">@escaping</span> () -> <span class="type">Void</span>) {</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"didReceive notification"</span>)</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">let</span> userInfo <span class="operator">=</span> response.notification.request.content.userInfo</span><br><span class="line"> <span class="keyword">let</span> id <span class="operator">=</span> response.notification.request.content.categoryIdentifier</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"info <span class="subst">\(userInfo)</span> <span class="subst">\(id)</span>"</span>)</span><br><span class="line"> </span><br><span class="line"> <span class="operator">...</span></span><br><span class="line"> </span><br><span class="line"> completionHandler()</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">func</span> <span class="title function_">application</span>(</span><br><span class="line"> <span class="keyword">_</span> <span class="params">application</span>: <span class="type">UIApplication</span>,</span><br><span class="line"> <span class="params">didRegisterForRemoteNotificationsWithDeviceToken</span> <span class="params">deviceToken</span>: <span class="type">Data</span>) {</span><br><span class="line"> <span class="type">Messaging</span>.messaging().apnsToken <span class="operator">=</span> deviceToken</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Handle-FCM-token-in-MessagingDelegate"><a href="#Handle-FCM-token-in-MessagingDelegate" class="headerlink" title="Handle FCM token in MessagingDelegate"></a>Handle FCM token in MessagingDelegate</h2><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">extension</span> <span class="title class_">AppDelegate</span>: <span class="title class_">MessagingDelegate</span> {</span><br><span class="line"> <span class="keyword">func</span> <span class="title function_">messaging</span>(<span class="keyword">_</span> <span class="params">messaging</span>: <span class="type">Messaging</span>, <span class="params">didReceiveRegistrationToken</span> <span class="params">fcmToken</span>: <span class="type">String</span>?) { </span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">let</span> token <span class="operator">=</span> fcmToken{</span><br><span class="line"> <span class="built_in">print</span>(token)</span><br><span class="line"> <span class="comment">//Handle fcm token</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> APP Development </category>
</categories>
<tags>
<tag> Push notification </tag>
</tags>
</entry>
<entry>
<title>Android Actionable Push Notification (Kotlin)</title>
<link href="/2023/02/18/APP%20Development/Android-Actionable-Push-Notification/"/>
<url>/2023/02/18/APP%20Development/Android-Actionable-Push-Notification/</url>
<content type="html"><![CDATA[<p>Implement actionable push notification for Android APP in Kotlin. Use Google Firebase for pushing notifications.</p><blockquote><p>To create actionable notification in Android, server must send firebase notifications using <strong>Data messages</strong> type.</p></blockquote><h2 id="Create-a-receiver-class"><a href="#Create-a-receiver-class" class="headerlink" title="Create a receiver class"></a>Create a receiver class</h2><blockquote><p>Handle actions in a <a href="https://developer.android.com/reference/android/content/BroadcastReceiver">BroadcastReceiver</a> class</p></blockquote><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">NotificationActionReceiver</span> : <span class="type">BroadcastReceiver</span>() {</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onReceive</span><span class="params">(context: <span class="type">Context</span>, intent: <span class="type">Intent</span>)</span></span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> notificationId = intent.getIntExtra(<span class="string">"notificationId"</span>, <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">val</span> action = intent.getStringExtra(<span class="string">"action"</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (action == <span class="string">"Action1"</span>){</span><br><span class="line"> <span class="comment">// Handle action1</span></span><br><span class="line"></span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span> (action == <span class="string">"Action2"</span>){</span><br><span class="line"> <span class="comment">// Handle action2</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">val</span> manager = context.getSystemService(Context.NOTIFICATION_SERVICE) <span class="keyword">as</span> NotificationManager</span><br><span class="line"> manager.cancel(notificationId)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Add-the-receiver-class-to-the-manifest"><a href="#Add-the-receiver-class-to-the-manifest" class="headerlink" title="Add the receiver class to the manifest"></a>Add the receiver class to the manifest</h2><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"utf-8"</span>?></span></span><br><span class="line"><span class="tag"><<span class="name">manifest</span> <span class="attr">xmlns:android</span>=<span class="string">"http://schemas.android.com/apk/res/android"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">package</span>=<span class="string">"com.maochun.pushnotificationtest"</span>></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="tag"><<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.POST_NOTIFICATIONS"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">application</span></span></span><br><span class="line"><span class="tag"> <span class="attr">...</span>></span></span><br><span class="line"> ...</span><br><span class="line"> <span class="tag"><<span class="name">receiver</span> <span class="attr">android:name</span>=<span class="string">".NotificationActionReceiver"</span>></span><span class="tag"></<span class="name">receiver</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">application</span>></span></span><br><span class="line"><span class="tag"></<span class="name">manifest</span>></span></span><br><span class="line"> </span><br></pre></td></tr></table></figure><h2 id="Define-actionable-notification-in-FCM-service-class"><a href="#Define-actionable-notification-in-FCM-service-class" class="headerlink" title="Define actionable notification in FCM service class"></a>Define actionable notification in FCM service class</h2><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">FCMService</span> : <span class="type">FirebaseMessagingService</span>() {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onMessageReceived</span><span class="params">(remoteMessage: <span class="type">RemoteMessage</span>)</span></span> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// TODO(developer): Handle FCM messages here.</span></span><br><span class="line"> <span class="keyword">var</span> type = <span class="number">0</span></span><br><span class="line"> <span class="keyword">if</span> (remoteMessage.<span class="keyword">data</span>.isNotEmpty()) {</span><br><span class="line"> Log.d(<span class="string">"TAG"</span>, <span class="string">"Message data payload: "</span> + remoteMessage.<span class="keyword">data</span>)</span><br><span class="line"> <span class="keyword">if</span> (remoteMessage.<span class="keyword">data</span>[<span class="string">"type"</span>] != <span class="literal">null</span>){</span><br><span class="line"> type = remoteMessage.<span class="keyword">data</span>[<span class="string">"type"</span>]?.toInt() ?: <span class="number">0</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (type == <span class="number">3</span>) {</span><br><span class="line"> <span class="keyword">val</span> title = remoteMessage.<span class="keyword">data</span>[<span class="string">"title"</span>] ?: <span class="string">"Test Action Notification"</span></span><br><span class="line"> <span class="keyword">val</span> body = remoteMessage.<span class="keyword">data</span>[<span class="string">"body"</span>] ?: <span class="string">""</span></span><br><span class="line"> </span><br><span class="line"> sendActionNotification(title, body)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">sendActionNotification</span><span class="params">(title: <span class="type">String</span>, body: <span class="type">String</span>)</span></span> {</span><br><span class="line"> <span class="keyword">val</span> notifID = System.currentTimeMillis().hashCode()</span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> button1Intent = Intent(<span class="keyword">this</span>, NotificationActionReceiver::<span class="keyword">class</span>.java)</span><br><span class="line"> button1Intent.putExtra(<span class="string">"notificationId"</span>, notifID)</span><br><span class="line"> button1Intent.putExtra(<span class="string">"action"</span>, <span class="string">"Action1"</span>)</span><br><span class="line"> <span class="keyword">val</span> btn1PendingIntent = PendingIntent.getBroadcast(</span><br><span class="line"> <span class="keyword">this</span>,</span><br><span class="line"> notifID,</span><br><span class="line"> button1Intent,</span><br><span class="line"> PendingIntent.FLAG_UPDATE_CURRENT</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> button2Intent = Intent(<span class="keyword">this</span>, NotificationActionReceiver::<span class="keyword">class</span>.java)</span><br><span class="line"> button2Intent.putExtra(<span class="string">"notificationId"</span>, notifID)</span><br><span class="line"> button2Intent.putExtra(<span class="string">"action"</span>, <span class="string">"Action2"</span>)</span><br><span class="line"> <span class="keyword">val</span> btn2PendingIntent = PendingIntent.getBroadcast(</span><br><span class="line"> <span class="keyword">this</span>,</span><br><span class="line"> notifID,</span><br><span class="line"> button2Intent,</span><br><span class="line"> PendingIntent.FLAG_UPDATE_CURRENT</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)</span><br><span class="line"> <span class="keyword">val</span> notificationBuilder = NotificationCompat.Builder(<span class="keyword">this</span>, <span class="string">"default"</span>)</span><br><span class="line"> .setSmallIcon(R.drawable.ic_launcher)</span><br><span class="line"> .setContentTitle(title)</span><br><span class="line"> .setContentText(body)</span><br><span class="line"> .setSound(defaultSoundUri)</span><br><span class="line"> .addAction(R.drawable.common_alert_button, <span class="string">"Action1"</span>, btn1PendingIntent)</span><br><span class="line"> .addAction(R.drawable.common_alert_button, <span class="string">"Action2"</span>, btn2PendingIntent)</span><br><span class="line"> .setOngoing(<span class="literal">true</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) <span class="keyword">as</span> NotificationManager</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {</span><br><span class="line"> <span class="keyword">val</span> channelID = <span class="string">"pushnotificationtest"</span>;</span><br><span class="line"> <span class="keyword">val</span> channel = <span class="keyword">this</span>.createNotificationChannel(</span><br><span class="line"> notificationManager,</span><br><span class="line"> channelID,</span><br><span class="line"> channelID,</span><br><span class="line"> NotificationManager.IMPORTANCE_HIGH</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> channel.setShowBadge(<span class="literal">false</span>)</span><br><span class="line"> notificationManager?.createNotificationChannel(channel)</span><br><span class="line"> notificationBuilder.setChannelId(channelID)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> notificationManager.notify(notifID, notificationBuilder.build())</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@TargetApi(26)</span></span><br><span class="line"> <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">createNotificationChannel</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function"> notificationManager: <span class="type">NotificationManager</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> id: <span class="type">String</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> name: <span class="type">String</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> importance: <span class="type">Int</span></span></span></span><br><span class="line"><span class="params"><span class="function"> )</span></span>: NotificationChannel {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">if</span> (notificationManager.getNotificationChannel(id) != <span class="literal">null</span>) {</span><br><span class="line"> notificationManager.getNotificationChannel(id)</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">val</span> notificationChannel = NotificationChannel(id, name, importance)</span><br><span class="line"> notificationChannel.enableVibration(<span class="literal">true</span>)</span><br><span class="line"> notificationChannel.enableLights(<span class="literal">true</span>)</span><br><span class="line"> notificationChannel</span><br><span class="line"> }</span><br><span class="line"> } </span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> APP Development </category>
</categories>
<tags>
<tag> Push notification </tag>
</tags>
</entry>
<entry>
<title>iOS Actionable Push Notification (Swift)</title>
<link href="/2023/02/18/APP%20Development/iOS-Actionable-Push-Notification/"/>
<url>/2023/02/18/APP%20Development/iOS-Actionable-Push-Notification/</url>
<content type="html"><![CDATA[<p>Implement actionable push notification for iOS APP in Swift. Use Google Firebase for pushing notifications.</p><h2 id="Note"><a href="#Note" class="headerlink" title="Note"></a>Note</h2><blockquote><p>Server send notification using FCM <strong>Notification Message</strong> type with an identifier. The sample program below use “NOTIFICATION_CUSTOM” as the actionable notification id. </p></blockquote><h2 id="Define-actionable-notification"><a href="#Define-actionable-notification" class="headerlink" title="Define actionable notification"></a>Define actionable notification</h2><blockquote><p>Define the actionable notification via <a href="https://developer.apple.com/documentation/usernotifications/unnotificationcategory">UNNotificationCategory</a> & <a href="https://developer.apple.com/documentation/usernotifications/unusernotificationcenter">UNUserNotificationCenter</a></p></blockquote><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">application</span>(<span class="keyword">_</span> <span class="params">application</span>: <span class="type">UIApplication</span>, <span class="params">didFinishLaunchingWithOptions</span> <span class="params">launchOptions</span>: [<span class="type">UIApplication</span>.<span class="params">LaunchOptionsKey</span>: <span class="keyword">Any</span>]<span class="operator">?</span>) -> <span class="type">Bool</span> {</span><br><span class="line"> <span class="operator">...</span></span><br><span class="line"> application.registerForRemoteNotifications()</span><br><span class="line"> <span class="type">UNUserNotificationCenter</span>.current().delegate <span class="operator">=</span> <span class="keyword">self</span></span><br><span class="line"> <span class="type">Messaging</span>.messaging().delegate <span class="operator">=</span> <span class="keyword">self</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">self</span>.registerCustomActions()</span><br><span class="line"> <span class="operator">...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">func</span> <span class="title function_">registerCustomActions</span>() {</span><br><span class="line"> <span class="comment">// Define the custom actions.</span></span><br><span class="line"> <span class="keyword">let</span> action1 <span class="operator">=</span> <span class="type">UNNotificationAction</span>(identifier: <span class="string">"CUS_ACTION1"</span>,</span><br><span class="line"> title: <span class="string">"Action1"</span>,</span><br><span class="line"> options: [.foreground])</span><br><span class="line"> <span class="keyword">let</span> action2 <span class="operator">=</span> <span class="type">UNNotificationAction</span>(identifier: <span class="string">"CUS_ACTION2"</span>,</span><br><span class="line"> title: <span class="string">"Action2"</span>,</span><br><span class="line"> options: [.foreground])</span><br><span class="line"> <span class="comment">// Define the notification type</span></span><br><span class="line"> <span class="keyword">let</span> actionCategory <span class="operator">=</span> <span class="type">UNNotificationCategory</span>(identifier: <span class="string">"NOTIFICATION_CUSTOM"</span>,</span><br><span class="line"> actions: [action1, action2],</span><br><span class="line"> intentIdentifiers: [],</span><br><span class="line"> hiddenPreviewsBodyPlaceholder: <span class="string">""</span>,</span><br><span class="line"> options: .customDismissAction)</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// Register the notification type.</span></span><br><span class="line"> <span class="keyword">let</span> notificationCenter <span class="operator">=</span> <span class="type">UNUserNotificationCenter</span>.current()</span><br><span class="line"> notificationCenter.setNotificationCategories([actionCategory])</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Handle-notification-actions"><a href="#Handle-notification-actions" class="headerlink" title="Handle notification actions"></a>Handle notification actions</h2><blockquote><p>Handle the notification actions in userNotificationCenter didReceive function</p></blockquote><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">userNotificationCenter</span>(<span class="keyword">_</span> <span class="params">center</span>: <span class="type">UNUserNotificationCenter</span>, <span class="params">didReceive</span> <span class="params">response</span>: <span class="type">UNNotificationResponse</span>, <span class="params">withCompletionHandler</span> <span class="params">completionHandler</span>: <span class="keyword">@escaping</span> () -> <span class="type">Void</span>) {</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"didReceive notification"</span>)</span><br><span class="line"> <span class="keyword">let</span> userInfo <span class="operator">=</span> response.notification.request.content.userInfo</span><br><span class="line"> <span class="keyword">let</span> id <span class="operator">=</span> response.notification.request.content.categoryIdentifier</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"info <span class="subst">\(userInfo)</span> <span class="subst">\(id)</span>"</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> id <span class="operator">==</span> <span class="string">"NOTIFICATION_CUSTOM"</span>{</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">switch</span> response.actionIdentifier {</span><br><span class="line"> <span class="keyword">case</span> <span class="string">"CUS_ACTION1"</span>:</span><br><span class="line"> <span class="comment">// Handle action1</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">case</span> <span class="string">"CUS_ACTION2"</span>:</span><br><span class="line"> <span class="comment">// Handle action2</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">break</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> APP Development </category>
</categories>
<tags>
<tag> Push notification </tag>
</tags>
</entry>
<entry>
<title>APP Push notification</title>
<link href="/2023/02/18/APP%20Development/Push-notification/"/>
<url>/2023/02/18/APP%20Development/Push-notification/</url>
<content type="html"><![CDATA[<h1 id="Push-notification-vs-Email-vs-SMS"><a href="#Push-notification-vs-Email-vs-SMS" class="headerlink" title="Push notification vs Email vs SMS"></a>Push notification vs Email vs SMS</h1><p><img src="/img/app_dev/push-email-sms.png"></p><h1 id="Push-notification-use-scenario"><a href="#Push-notification-use-scenario" class="headerlink" title="Push notification use scenario"></a>Push notification use scenario</h1><ul><li>Use SMS for critical information</li><li>Use Email for non-critical, long-form content, such as advertisements.</li><li>Use push notification for non-critical, short text information</li></ul><h1 id="Push-notification-via-Firebase"><a href="#Push-notification-via-Firebase" class="headerlink" title="Push notification via Firebase"></a>Push notification via Firebase</h1><p>Suggest using <a href="https://firebase.google.com/docs/cloud-messaging/concept-options">Firebase Cloud Messaging (FCM)</a> to push notifications to both iOS and Android, as well as web apps.</p><h1 id="Push-notification-limitations"><a href="#Push-notification-limitations" class="headerlink" title="Push notification limitations"></a>Push notification limitations</h1><ul><li>FCM Maximum payload for both Notification messages and Data message is 4000 bytes.</li><li>Suggest notification title <= 40 characters.</li><li>Suggest notification body <= 150 characters.</li></ul><h1 id="Create-Firebase-projects"><a href="#Create-Firebase-projects" class="headerlink" title="Create Firebase projects"></a>Create Firebase projects</h1><p><a href="https://console.firebase.google.com/">Firebase Console</a></p><h2 id="Create-iOS-x2F-Android-project"><a href="#Create-iOS-x2F-Android-project" class="headerlink" title="Create iOS / Android project"></a>Create iOS / Android project</h2><blockquote><p>iOS: APP bundle ID, APP Store ID & APP Store team ID<br>Android: APP package name</p></blockquote><p><img src="/img/app_dev/create-fcm-project.png"></p><blockquote><p><strong>Note: for iOS project, you must upload the APP Store’s APN key.</strong></p></blockquote><p><img src="/img/app_dev/upload-apn-key.png"></p><blockquote><p>Create APN key in your apple development account</p></blockquote><p><img src="/img/app_dev/apn-key.png"></p><h2 id="Push-notification-via-FCM-diagram"><a href="#Push-notification-via-FCM-diagram" class="headerlink" title="Push notification via FCM diagram"></a>Push notification via FCM diagram</h2><blockquote><p>Check out the great diagram made by <a href="https://blog.bytebytego.com/archive">Alex Xu @ bytebytego.com</a></p></blockquote><p><img src="/img/app_dev/alex-fcm-struct.png"></p>]]></content>
<categories>
<category> APP Development </category>
</categories>
<tags>
<tag> Push notification </tag>
</tags>
</entry>
<entry>
<title>My Blog</title>
<link href="/2023/02/18/Projects/About-My-Blog/"/>
<url>/2023/02/18/Projects/About-My-Blog/</url>
<content type="html"><![CDATA[<h1 id="Create-blog-via-Hexo-amp-Butterfly-theme"><a href="#Create-blog-via-Hexo-amp-Butterfly-theme" class="headerlink" title="Create blog via Hexo & Butterfly theme"></a>Create blog via Hexo & Butterfly theme</h1><h2 id="Initialization"><a href="#Initialization" class="headerlink" title="Initialization"></a>Initialization</h2><ul><li><p>Install <a href="https://hexo.io/">Hexo</a> </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo-cli -g</span><br></pre></td></tr></table></figure></li><li><p>Create Hexo project</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo init my-blog</span><br></pre></td></tr></table></figure></li><li><p>Install plugins</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> my-blog</span><br><span class="line">npm install</span><br></pre></td></tr></table></figure></li></ul><h2 id="Select-Hexo-themes"><a href="#Select-Hexo-themes" class="headerlink" title="Select Hexo themes"></a>Select <a href="https://hexo.io/themes/">Hexo themes</a></h2><ul><li>My blog uses the <a href="https://butterfly.js.org/">Butterfly theme</a></li></ul><h2 id="Write-blog-articles"><a href="#Write-blog-articles" class="headerlink" title="Write blog articles"></a>Write blog articles</h2><ul><li><p>Open my-blog in VSCode</p></li><li><p>Create new article</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo new my-test-article</span><br></pre></td></tr></table></figure><p>You will find the my-test-article.md in the /my-blog/source/_posts folder.<br>You can create subfolder in the _posts.</p></li><li><p>Write article using <a href="https://en.wikipedia.org/wiki/Markdown#GitHub_Flavored_Markdown">Markdown</a><br>Each article must start with a title session.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">title: xxxxx</span><br><span class="line">date: 2023-01-27 15:26:36</span><br><span class="line">tags:</span><br><span class="line">categories: ... //Optional</span><br><span class="line">description: ... //Optional</span><br><span class="line">---</span><br><span class="line">...</span><br></pre></td></tr></table></figure></li></ul><h2 id="Start-server-in-local"><a href="#Start-server-in-local" class="headerlink" title="Start server in local"></a>Start server in local</h2><ul><li>Use default port 4000<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo server</span><br></pre></td></tr></table></figure></li><li>Use a specific port, e.g. 4500<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo server -p 4500</span><br></pre></td></tr></table></figure></li></ul><h2 id="Others"><a href="#Others" class="headerlink" title="Others"></a>Others</h2><ul><li><p>Install plugin for blog file encryption</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install –save hexo-blog-encrypt</span><br></pre></td></tr></table></figure><p>Add the password to the blog file’s title session</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">title: xxxxx</span><br><span class="line">...</span><br><span class="line">password: test12345</span><br><span class="line">...</span><br><span class="line">---</span><br></pre></td></tr></table></figure></li><li><p>Install plugin for hide blog file<br><a href="https://github.com/prinsss/hexo-hide-posts">https://github.com/prinsss/hexo-hide-posts</a></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo-hide-posts --save</span><br></pre></td></tr></table></figure><p>Add <code>filter: hidden</code> to _config.yml<br>Add the hidden to the blog file’s title session</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">title: xxxxx</span><br><span class="line">...</span><br><span class="line">hidden: true</span><br><span class="line">...</span><br><span class="line">---</span><br></pre></td></tr></table></figure></li><li><p>Install search function </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo-generator-search --save</span><br></pre></td></tr></table></figure><p>Enable the local_search in the butterfly _config.yml</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">local_search:</span><br><span class="line"> <span class="built_in">enable</span>: <span class="literal">true</span></span><br><span class="line"> preload: <span class="literal">false</span></span><br><span class="line"> CDN:</span><br></pre></td></tr></table></figure></li><li><p>Generate sitemap </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo-generator-sitemap --save</span><br></pre></td></tr></table></figure></li><li><p>Add Disqus<br>Create Disqus account & add the domain name<br>Setup the _config.yml in the /my-blog/themes/butterfly/</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">disqus:</span><br><span class="line"> shortname: xxxxx-xxx-xxx</span><br></pre></td></tr></table></figure></li></ul><h1 id="Deploy-blog-to-my-github"><a href="#Deploy-blog-to-my-github" class="headerlink" title="Deploy blog to my github"></a>Deploy blog to my github</h1><ul><li><p>Install Hexo deployer</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo-deployer-git --save</span><br></pre></td></tr></table></figure><p>Edit _config.yml in /my-blog folder</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">url:</span> <span class="string">https://XXXX.github.io/</span></span><br><span class="line"><span class="attr">deploy:</span></span><br><span class="line"> <span class="attr">type:</span> <span class="string">git</span></span><br><span class="line"> <span class="attr">repo:</span> <span class="string">git@github.com:XXXX/XXXX.github.io.git</span> </span><br><span class="line"> <span class="attr">branch:</span> <span class="string">master</span> </span><br><span class="line"> <span class="attr">message:</span></span><br><span class="line"></span><br></pre></td></tr></table></figure></li><li><p>Deploy</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">hexo clean</span><br><span class="line">hexo generate</span><br><span class="line">hexo deploy</span><br></pre></td></tr></table></figure></li></ul><h1 id="Set-domain-name"><a href="#Set-domain-name" class="headerlink" title="Set domain name"></a>Set domain name</h1><ul><li>Buy domain name from GoDaddy</li><li>Set domain DNS in GoDaddy<blockquote><p>Mapping github IP address below in GoDaddy to your domain<br>185.199.108.153<br>185.199.109.153<br>185.199.110.153<br>185.199.111.153</p></blockquote></li></ul><p><img src="/img/others/godaddy-git-dns.png"></p><ul><li>Set domain in Github<br><img src="/img/others/github-io-setting.png"></li></ul><p><strong>Reference: <a href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site">Github instructions</a></strong></p><h1 id="Add-to-Google-Search-Console"><a href="#Add-to-Google-Search-Console" class="headerlink" title="Add to Google Search Console"></a>Add to <a href="https://search.google.com/search-console/about">Google Search Console</a></h1><p><a href="https://support.google.com/webmasters/answer/6258314?utm_source=wnc_376106&utm_medium=panel&utm_campaign=wnc_376106&utm_content=msg_376106&hl=en">Google Search Console Usage</a></p><ul><li>Set URL prefix in Google Search Console<br><img src="/img/gsc/gsc-1.png"></li><li>Download the google html file and save it to /my-blog/source/<br><img src="/img/gsc/gsc-2.png"><blockquote><p>Note: need add a title session below to the html</p></blockquote><figure class="highlight md"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line"><span class="section">layout: false</span></span><br><span class="line"><span class="section">---</span></span><br><span class="line"></span><br><span class="line">google-site-verification: googlexxxxxxxxxx.html</span><br></pre></td></tr></table></figure></li><li>Complete the verify<br><img src="/img/gsc/gsc-3.png"></li><li>Setup sitemap<br><img src="/img/gsc/gsc-4.png"></li><li>Waiting</li></ul><h1 id="Use-Google-Analytics-to-analyze-web-traffic"><a href="#Use-Google-Analytics-to-analyze-web-traffic" class="headerlink" title="Use Google Analytics to analyze web traffic"></a>Use <a href="https://analytics.google.com/">Google Analytics</a> to analyze web traffic</h1><ul><li>Add google analytics “MEASUREMENT ID” to /butterfly/_config.yml<br><img src="/img/gsc/google-analytics-2.png"></li><li>Add google analytics script section to the /butterfly/layout/includes/head/analytics.pug<br><img src="/img/gsc/google-analytics-1.png"></li></ul>]]></content>
<categories>
<category> Projects </category>
</categories>
<tags>
<tag> Blog </tag>
</tags>
</entry>
<entry>
<title>台南萬福庵</title>
<link href="/2023/02/16/Others/Wan-fu-an/"/>
<url>/2023/02/16/Others/Wan-fu-an/</url>
<content type="html"><![CDATA[<h1 id="萬福庵-Wiki"><a href="#萬福庵-Wiki" class="headerlink" title="萬福庵 Wiki"></a><a href="https://zh.wikipedia.org/zh-hant/%E8%90%AC%E7%A6%8F%E5%BA%B5">萬福庵 Wiki</a></h1><blockquote><p>首貮境萬福庵,是位於臺灣臺南市中西區的齊天大聖廟。</p></blockquote><p>去台南萬福庵拜拜已十數載,從喧鬧的民族路轉進紅磚鋪就的小巷,左手邊便是萬福庵,時光似乎在此凝結。</p><p>每每站在萬福庵院中,台南溫暖的陽光下,看見天上悠悠飄過的白雲,看見猴靈樹王公繁茂的枝葉,看見香爐裡裊裊升起的青煙,總會找回內心久違的平靜與釋然。</p><p>感謝<font style="font-size: 20px;"><strong>齊天大聖</strong></font>多年的庇佑與指引。</p><p><img src="/img/others/wan-fu-an.png"></p>]]></content>
<categories>
<category> Others </category>
</categories>
</entry>
<entry>
<title>Publish APP to Apple Store</title>
<link href="/2023/02/12/APP%20Development/iOS-Publish-APP/"/>
<url>/2023/02/12/APP%20Development/iOS-Publish-APP/</url>
<content type="html"><![CDATA[<h1 id="APP-Production-Page-Spec"><a href="#APP-Production-Page-Spec" class="headerlink" title="APP Production Page Spec."></a><a href="https://developer.apple.com/app-store/product-page/">APP Production Page Spec.</a></h1><h1 id="Items-you-need-to-prepare-for-the-Apple-Store"><a href="#Items-you-need-to-prepare-for-the-Apple-Store" class="headerlink" title="Items you need to prepare for the Apple Store"></a>Items you need to prepare for the Apple Store</h1><ol><li>Icon 1024x1024</li><li>APP name: XXX<blockquote><p>Max length: 30 characters</p></blockquote></li><li>APP descriptions & Categories</li><li></li><li>Screenshots<br><img src="/img/app_dev/apple-screenshot.png"><blockquote><p>You can take screenshots of the required sizes from either a real phone or a simulator, but I prefer for the UI designer to create these screenshots for a more professional look.</p></blockquote></li><li>APP demo video (Provide YouTube link to the demo video)<blockquote><p>If your app is related to a device, please provide a demo video showcasing the device with app operations. Note that it is important to use a production device, not a demo set, and to use a real phone to demonstrate the app operations, otherwise your submission may be rejected like this:<br><img src="/img/app_dev/apple-reject1.png"></p></blockquote></li><li>If your app requires login, provide test account and password in the review information.<br><img src="/img/app_dev/apple-review-info.png"></li></ol><h1 id="Questions-you-may-encounter-and-sample-answers"><a href="#Questions-you-may-encounter-and-sample-answers" class="headerlink" title="Questions you may encounter and sample answers"></a>Questions you may encounter and sample answers</h1><ol><li><p>Is your app restricted to users who are part of a single company? This may include users of the company’s partners, employees, and contractors.<br><strong>No. The app is not intended for users of a specific company.</strong></p></li><li><p>Is your app designed for use by a limited or specific group of companies?<br><strong>No. Any company can be a client and utilize this app.</strong></p></li><li><p>What features in the app, if any, are intended for use by the general public?<br><strong>No, the app is not for general public.</strong></p></li><li><p>Identify the specific countries or regions where you plan to distribute your app.<br><strong>The app is for users in Taiwan.</strong></p></li><li><p>How do users obtain an account?<br><strong>They can apply for an account from XXX Corp.</strong></p></li></ol><h1 id="Last-but-not-the-least"><a href="#Last-but-not-the-least" class="headerlink" title="Last but not the least"></a>Last but not the least</h1><p>Don’t be upset if your app is rejected. I have submitted apps hundreds of times and have been rejected many times. I understand the feeling of facing rejection. But trust me, Apple rejects apps for specific reasons, so find the cause and resolve it. There’s no need to be angry, upset, or anxious. You will get used to it, and as you gain more experience, you will find solutions faster and faster.</p>]]></content>
<categories>
<category> APP Development </category>
</categories>
<tags>
<tag> Publish APP </tag>
</tags>
</entry>
<entry>
<title>iOS LaunchScreen (Swift)</title>
<link href="/2023/02/11/APP%20Development/iOS-LaunchScreen/"/>
<url>/2023/02/11/APP%20Development/iOS-LaunchScreen/</url>
<content type="html"><![CDATA[<p>Creating LaunchScreen without Storyboard.</p><h2 id="Create-a-LaunchScreenView-class"><a href="#Create-a-LaunchScreenView-class" class="headerlink" title="Create a LaunchScreenView class"></a>Create a LaunchScreenView class</h2><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> UIKit</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LaunchScreenView</span>: <span class="title class_">UIView</span> {</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">lazy</span> <span class="keyword">var</span> nameLabel : <span class="type">UILabel</span> <span class="operator">=</span> {</span><br><span class="line"> <span class="keyword">let</span> label <span class="operator">=</span> <span class="type">UILabel</span>()</span><br><span class="line"> label.translatesAutoresizingMaskIntoConstraints <span class="operator">=</span> <span class="literal">false</span></span><br><span class="line"> label.font <span class="operator">=</span> <span class="type">UIFont</span>.systemFont(ofSize: <span class="number">28</span>, weight: .semibold)</span><br><span class="line"> label.text <span class="operator">=</span> <span class="string">"My Test APP"</span></span><br><span class="line"> label.textColor <span class="operator">=</span> .black</span><br><span class="line"> label.textAlignment <span class="operator">=</span> <span class="type">NSTextAlignment</span>.center</span><br><span class="line"> label.numberOfLines <span class="operator">=</span> <span class="number">0</span></span><br><span class="line"> label.lineBreakMode <span class="operator">=</span> <span class="type">NSLineBreakMode</span>.byWordWrapping</span><br><span class="line"> label.sizeToFit()</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">self</span>.addSubview(label)</span><br><span class="line"> <span class="type">NSLayoutConstraint</span>.activate([</span><br><span class="line"> label.centerXAnchor.constraint(equalTo: <span class="keyword">self</span>.centerXAnchor),</span><br><span class="line"> label.topAnchor.constraint(equalTo: <span class="keyword">self</span>.logoImageView.bottomAnchor, constant: <span class="number">25</span>)</span><br><span class="line"> ])</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> label</span><br><span class="line"> }()</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">lazy</span> <span class="keyword">var</span> logoImageView : <span class="type">UIImageView</span> <span class="operator">=</span> {</span><br><span class="line"> <span class="keyword">let</span> imgView <span class="operator">=</span> <span class="type">UIImageView</span>()</span><br><span class="line"> imgView.translatesAutoresizingMaskIntoConstraints <span class="operator">=</span> <span class="literal">false</span></span><br><span class="line"> imgView.image <span class="operator">=</span> <span class="type">UIImage</span>(named: <span class="string">"launch_icon"</span>)</span><br><span class="line"> <span class="keyword">self</span>.addSubview(imgView)</span><br><span class="line"> </span><br><span class="line"> <span class="type">NSLayoutConstraint</span>.activate([</span><br><span class="line"> imgView.centerYAnchor.constraint(equalTo: <span class="keyword">self</span>.centerYAnchor, constant: <span class="operator">-</span><span class="number">25</span>),</span><br><span class="line"> imgView.centerXAnchor.constraint(equalTo: <span class="keyword">self</span>.centerXAnchor, constant: <span class="number">0</span>)</span><br><span class="line"> ])</span><br><span class="line"> <span class="keyword">return</span> imgView</span><br><span class="line"> }()</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">override</span> <span class="keyword">init</span>(<span class="params">frame</span>: <span class="type">CGRect</span>) {</span><br><span class="line"> <span class="keyword">super</span>.<span class="keyword">init</span>(frame: frame)</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">_</span> <span class="operator">=</span> <span class="keyword">self</span>.nameLabel</span><br><span class="line"> <span class="keyword">self</span>.backgroundColor <span class="operator">=</span> .orange</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">required</span> <span class="keyword">init?</span>(<span class="params">coder</span> <span class="params">aDecoder</span>: <span class="type">NSCoder</span>) {</span><br><span class="line"> <span class="keyword">super</span>.<span class="keyword">init</span>(coder: aDecoder)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Add-the-LauchScreenView-in-SceneDelegate"><a href="#Add-the-LauchScreenView-in-SceneDelegate" class="headerlink" title="Add the LauchScreenView in SceneDelegate"></a>Add the LauchScreenView in SceneDelegate</h2><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">scene</span>(<span class="keyword">_</span> <span class="params">scene</span>: <span class="type">UIScene</span>, <span class="params">willConnectTo</span> <span class="params">session</span>: <span class="type">UISceneSession</span>, <span class="params">options</span> <span class="params">connectionOptions</span>: <span class="type">UIScene</span>.<span class="type">ConnectionOptions</span>) {</span><br><span class="line"> <span class="operator">...</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">let</span> launchView <span class="operator">=</span> <span class="type">LaunchScreenView</span>(frame: <span class="type">UIScreen</span>.main.bounds)</span><br><span class="line"> window<span class="operator">?</span>.rootViewController <span class="operator">=</span> <span class="type">UIStoryboard</span>(name: <span class="string">"LaunchScreen"</span>, bundle: <span class="literal">nil</span>).instantiateInitialViewController()</span><br><span class="line"> window<span class="operator">?</span>.rootViewController<span class="operator">?</span>.view.addSubview(launchView)</span><br><span class="line"> window<span class="operator">?</span>.rootViewController<span class="operator">?</span>.view.bringSubviewToFront(launchView)</span><br><span class="line"> window<span class="operator">?</span>.makeKeyAndVisible()</span><br><span class="line"></span><br><span class="line"> <span class="type">DispatchQueue</span>.main.asyncAfter(deadline: <span class="type">DispatchTime</span>.now() <span class="operator">+</span> <span class="number">1</span>) {</span><br><span class="line"> <span class="keyword">self</span>.window<span class="operator">?</span>.rootViewController <span class="operator">=</span> <span class="type">ViewController</span>()</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> APP Development </category>
</categories>
<tags>
<tag> LaunchScreen </tag>
</tags>
</entry>
<entry>
<title>iOS QRCode Scan (Swift)</title>
<link href="/2023/02/11/APP%20Development/iOS-QRCode-Scan/"/>
<url>/2023/02/11/APP%20Development/iOS-QRCode-Scan/</url>
<content type="html"><![CDATA[<p>Implement scanning QRCode ViewController via <a href="https://developer.apple.com/documentation/avfoundation/avcapturesession">AVCaptureSession</a>, <a href="https://developer.apple.com/documentation/avfoundation/avcapturemetadataoutput">AVCaptureMetadataOutput</a> and <a href="https://developer.apple.com/documentation/avfoundation/avcapturevideopreviewlayer">AVCaptureVideoPreviewLayer</a></p><h1 id="My-sample-project-source-code-in-GitHub"><a href="#My-sample-project-source-code-in-GitHub" class="headerlink" title="My sample project source code in GitHub"></a><a href="https://github.com/MaochunS/iOS-QRCodeScanner">My sample project source code in GitHub</a></h1><h1 id="Prerequisite"><a href="#Prerequisite" class="headerlink" title="Prerequisite"></a>Prerequisite</h1><p><img src="/img/others/ios-camera-permisssion.png"></p><h1 id="Initialization"><a href="#Initialization" class="headerlink" title="Initialization"></a>Initialization</h1><blockquote><p>Carry out all the initializations in viewDidLoad</p></blockquote><h2 id="Initialize-AVCaptureSession"><a href="#Initialize-AVCaptureSession" class="headerlink" title="Initialize AVCaptureSession"></a>Initialize AVCaptureSession</h2><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">captureSession <span class="operator">=</span> <span class="type">AVCaptureSession</span>()</span><br></pre></td></tr></table></figure><h2 id="Add-video-input-to-the-capture-session"><a href="#Add-video-input-to-the-capture-session" class="headerlink" title="Add video input to the capture session"></a>Add video input to the capture session</h2><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">guard</span> <span class="keyword">let</span> videoCaptureDevice <span class="operator">=</span> <span class="type">AVCaptureDevice</span>.default(for: .video) <span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line">}</span><br><span class="line"><span class="keyword">guard</span> <span class="keyword">let</span> videoInput <span class="operator">=</span> <span class="keyword">try?</span> <span class="type">AVCaptureDeviceInput</span>(device: videoCaptureDevice) <span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (captureSession.canAddInput(videoInput)) {</span><br><span class="line"> captureSession.addInput(videoInput)</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Add-metadata-output-to-the-capture-session"><a href="#Add-metadata-output-to-the-capture-session" class="headerlink" title="Add metadata output to the capture session"></a>Add metadata output to the capture session</h2><blockquote><p>Use AVCaptureMetadataOutput for capturing QRCode or others<br>Metadata object type:<br>.qr -> QRCode<br>.code128 -> Bar code</p></blockquote><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> metadataOutput <span class="operator">=</span> <span class="type">AVCaptureMetadataOutput</span>()</span><br><span class="line"><span class="keyword">if</span> (captureSession.canAddOutput(metadataOutput)) {</span><br><span class="line"> captureSession.addOutput(metadataOutput)</span><br><span class="line"></span><br><span class="line"> metadataOutput.setMetadataObjectsDelegate(<span class="keyword">self</span>, queue: <span class="type">DispatchQueue</span>.main)</span><br><span class="line"> metadataOutput.metadataObjectTypes <span class="operator">=</span> [.qr, .code128]</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="Add-preview-layer-to-the-ViewController"><a href="#Add-preview-layer-to-the-ViewController" class="headerlink" title="Add preview layer to the ViewController"></a>Add preview layer to the ViewController</h2><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">previewLayer <span class="operator">=</span> <span class="type">AVCaptureVideoPreviewLayer</span>(session: captureSession)</span><br><span class="line">previewLayer.frame <span class="operator">=</span> view.layer.bounds</span><br><span class="line">previewLayer.videoGravity <span class="operator">=</span> .resizeAspectFill</span><br><span class="line">view.layer.addSublayer(previewLayer)</span><br></pre></td></tr></table></figure><h2 id="Start-capture-session"><a href="#Start-capture-session" class="headerlink" title="Start capture session"></a>Start capture session</h2><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">DispatchQueue</span>.global().async {</span><br><span class="line"> <span class="keyword">self</span>.captureSession.startRunning()</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="Start-amp-Stop-capture-session"><a href="#Start-amp-Stop-capture-session" class="headerlink" title="Start & Stop capture session"></a>Start & Stop capture session</h1><blockquote><p>Start the capture session when view appears and stop the capture session when view disappears</p></blockquote><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">override</span> <span class="keyword">func</span> <span class="title function_">viewWillAppear</span>(<span class="keyword">_</span> <span class="params">animated</span>: <span class="type">Bool</span>) {</span><br><span class="line"> <span class="keyword">super</span>.viewWillAppear(animated)</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (captureSession<span class="operator">?</span>.isRunning <span class="operator">==</span> <span class="literal">false</span>) {</span><br><span class="line"> <span class="type">DispatchQueue</span>.global().async {</span><br><span class="line"> <span class="keyword">self</span>.captureSession.startRunning()</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">override</span> <span class="keyword">func</span> <span class="title function_">viewWillDisappear</span>(<span class="keyword">_</span> <span class="params">animated</span>: <span class="type">Bool</span>) {</span><br><span class="line"> <span class="keyword">super</span>.viewWillDisappear(animated)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (captureSession<span class="operator">?</span>.isRunning <span class="operator">==</span> <span class="literal">true</span>) {</span><br><span class="line"> captureSession.stopRunning()</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="Implement-the-delegate"><a href="#Implement-the-delegate" class="headerlink" title="Implement the delegate"></a>Implement the delegate</h1><blockquote><p>Implement the AVCaptureMetadataOutputObjectsDelegate to get the scan result</p></blockquote><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">extension</span> <span class="title class_">ScannerViewController</span>: <span class="title class_">AVCaptureMetadataOutputObjectsDelegate</span>{</span><br><span class="line"> <span class="keyword">func</span> <span class="title function_">metadataOutput</span>(<span class="keyword">_</span> <span class="params">output</span>: <span class="type">AVCaptureMetadataOutput</span>, <span class="params">didOutput</span> <span class="params">metadataObjects</span>: [<span class="type">AVMetadataObject</span>], <span class="params">from</span> <span class="params">connection</span>: <span class="type">AVCaptureConnection</span>) {</span><br><span class="line"> captureSession.stopRunning()</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">let</span> metadataObject <span class="operator">=</span> metadataObjects.first {</span><br><span class="line"> <span class="keyword">guard</span> <span class="keyword">let</span> readableObject <span class="operator">=</span> metadataObject <span class="keyword">as?</span> <span class="type">AVMetadataMachineReadableCodeObject</span> <span class="keyword">else</span> { <span class="keyword">return</span> }</span><br><span class="line"> <span class="keyword">guard</span> <span class="keyword">let</span> value <span class="operator">=</span> readableObject.stringValue <span class="keyword">else</span> { <span class="keyword">return</span> }</span><br><span class="line"> <span class="type">AudioServicesPlaySystemSound</span>(<span class="type">SystemSoundID</span>(kSystemSoundID_Vibrate))</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// value here is the scanning result string</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> APP Development </category>
</categories>
<tags>
<tag> QRCode Scan </tag>
</tags>
</entry>
<entry>
<title>Android Splash Activity (Kotlin)</title>
<link href="/2023/02/11/APP%20Development/Android-Splash-Activity/"/>
<url>/2023/02/11/APP%20Development/Android-Splash-Activity/</url>
<content type="html"><![CDATA[<h2 id="To-avoid-the-appearance-of-an-empty-window-before-the-splash-screen"><a href="#To-avoid-the-appearance-of-an-empty-window-before-the-splash-screen" class="headerlink" title="To avoid the appearance of an empty window before the splash screen"></a>To avoid the appearance of an empty window before the splash screen</h2><blockquote><p>Add the style to the styles.xml</p></blockquote><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">style</span> <span class="attr">name</span>=<span class="string">"SplashTheme"</span> <span class="attr">parent</span>=<span class="string">"Theme.AppCompat"</span>></span><span class="language-xml"></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">item</span> <span class="attr">name</span>=<span class="string">"windowNoTitle"</span>></span>true<span class="tag"></<span class="name">item</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">item</span> <span class="attr">name</span>=<span class="string">"android:windowDisablePreview"</span>></span>true<span class="tag"></<span class="name">item</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">item</span> <span class="attr">name</span>=<span class="string">"android:windowBackground"</span>></span>@drawable/splash_screen<span class="tag"></<span class="name">item</span>></span></span></span><br><span class="line"><span class="language-xml"></span><span class="tag"></<span class="name">style</span>></span></span><br></pre></td></tr></table></figure><blockquote><p>Add the style to the SplashActivity in AndroidManifest.xml</p></blockquote><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">activity</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:name</span>=<span class="string">".activity.SplashActivity"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">android:theme</span>=<span class="string">"@style/SplashTheme"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">intent-filter</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">action</span> <span class="attr">android:name</span>=<span class="string">"android.intent.action.MAIN"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">category</span> <span class="attr">android:name</span>=<span class="string">"android.intent.category.LAUNCHER"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">intent-filter</span>></span></span><br><span class="line"><span class="tag"></<span class="name">activity</span>></span></span><br></pre></td></tr></table></figure><h2 id="Sample-SplashActivity"><a href="#Sample-SplashActivity" class="headerlink" title="Sample SplashActivity"></a>Sample SplashActivity</h2><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">SplashActivity</span> : <span class="type">AppCompatActivity</span>() {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">var</span> handler: Handler?=<span class="literal">null</span></span><br><span class="line"> <span class="keyword">lateinit</span> <span class="keyword">var</span> runnable: Runnable</span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onCreate</span><span class="params">(savedInstanceState: <span class="type">Bundle</span>?)</span></span> {</span><br><span class="line"> <span class="keyword">super</span>.onCreate(savedInstanceState)</span><br><span class="line"> setContentView(R.layout.activity_splash)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">val</span> intent = getIntent()</span><br><span class="line"> <span class="keyword">if</span> (!isTaskRoot()</span><br><span class="line"> && intent != <span class="literal">null</span></span><br><span class="line"> && intent.hasCategory(Intent.CATEGORY_LAUNCHER)</span><br><span class="line"> && intent.getAction() != <span class="literal">null</span></span><br><span class="line"> && intent.getAction().equals(Intent.ACTION_MAIN)</span><br><span class="line"> ) {</span><br><span class="line"> finish()</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> window.navigationBarColor = ContextCompat.getColor(<span class="keyword">this</span>, R.color.splash_bottom)</span><br><span class="line"> window.statusBarColor = ContextCompat.getColor(<span class="keyword">this</span>, R.color.main_red)</span><br><span class="line"></span><br><span class="line"> handler = Handler(mainLooper)</span><br><span class="line"> runnable = Runnable{</span><br><span class="line"> <span class="keyword">val</span> mIntent = Intent(<span class="keyword">this</span><span class="symbol">@SplashActivity</span>, MainActivity::<span class="keyword">class</span>.java)</span><br><span class="line"> startActivity(mIntent)</span><br><span class="line"> finish()</span><br><span class="line"> handler = <span class="literal">null</span></span><br><span class="line"> }</span><br><span class="line"> handler?.postDelayed(runnable, <span class="number">1000</span>) <span class="comment">//millis</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> APP Development </category>
</categories>
<tags>
<tag> Splash </tag>
</tags>
</entry>
<entry>
<title>DrayTek Push Notification Server</title>
<link href="/2023/01/27/Projects/Push-Notification-Server/"/>
<url>/2023/01/27/Projects/Push-Notification-Server/</url>
<content type="html"><![CDATA[<h1 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h1><h2 id="Requirement"><a href="#Requirement" class="headerlink" title="Requirement:"></a>Requirement:</h2><ul><li>The company requires a mechanism to push notifications to its mobile applications.</li></ul><h2 id="Solution"><a href="#Solution" class="headerlink" title="Solution:"></a>Solution:</h2><ul><li>Build a standalong server that provides RESTful APIs for other servers or devices to initiate push requests</li><li>Facilitate iOS and Android apps in submitting Firebase tokens and retrieving notification lists and details.</li><li>Integrate with Google firebase to push the notifications to the users’ iPhons and Android phones. </li><li>Implement iOS & Android test APPs to send firebase token and receive notifications.</li><li>Provide a server backend control center in Web.</li></ul><h1 id="Development-IDE-amp-languages"><a href="#Development-IDE-amp-languages" class="headerlink" title="Development IDE & languages"></a>Development IDE & languages</h1><ul><li>Server: PyCharm, Python & Django</li><li>Backend control center: Vue, Javascript</li><li>iOS test APP: XCode, Swift</li><li>Android test APP: Android Studio, Kotlin</li></ul><h1 id="Server-Architecture"><a href="#Server-Architecture" class="headerlink" title="Server Architecture"></a>Server Architecture</h1><p><img src="/img/draytek/ServerArchitecture.png"></p><ul><li>Server docker containers<br><img src="/img/draytek/DockerContainers.png"></li><li>Containers status<br><img src="/img/draytek/DockerContainersStatus.png"></li></ul><h1 id="Server-related-items"><a href="#Server-related-items" class="headerlink" title="Server related items"></a>Server related items</h1><ul><li>Use swagger for all RESTful APIs<br><img src="/img/draytek/swagger.png"></li><li>Provide doc link for all the RESTFul APIs definition<br><img src="/img/draytek/doc.png"></li><li>Use GoAccess as web log analyzer<br><img src="/img/draytek/goaccess.png"></li><li>Backend control center<br><img src="/img/draytek/backend-control-center1.png"><br><img src="/img/draytek/backend-control-center2.png"></li></ul><h1 id="Others"><a href="#Others" class="headerlink" title="Others"></a>Others</h1><ul><li>Deploy the server to GCP</li><li>Setup the iOS & Android projects in Google firebase console</li><li>Daily backup DB to Google Bucket</li></ul><h1 id="Usage"><a href="#Usage" class="headerlink" title="Usage"></a>Usage</h1><ul><li><p>Normal (has sound) / Alarm (no sound) notification<br><img src="/img/draytek/notification.png"></p></li><li><p>two-factor authentication notification<br>Use firebase data message when pushing to Android. Use notification message when pushing to iOS.<br><img src="/img/draytek/authentication.png"></p></li><li><p>Normal/alarm notification & authentication notification on iWatch<br><img src="/img/draytek/iwatch.png"></p></li></ul><h1 id="Challenges-and-what-I-learned"><a href="#Challenges-and-what-I-learned" class="headerlink" title="Challenges and what I learned"></a>Challenges and what I learned</h1><ul><li>Have done so many push notifications on APP side, this is my first time to implement it in both server and apps. </li><li>Learned Python Django, PostgreSQL, Django REST framework (DRF) and swagger, CronTab</li><li>Learned Docker, NginX, PostgreSQL</li><li>Learned Google VM & Bucket</li><li>Learned Difference between Firebase data message and notification message.</li></ul>]]></content>
<categories>
<category> Projects </category>
</categories>
</entry>
<entry>
<title>DrayTek Router APP (iOS & Android)</title>
<link href="/2023/01/27/Projects/DrayTek-Router/"/>
<url>/2023/01/27/Projects/DrayTek-Router/</url>
<content type="html"><![CDATA[<h1 id="Published-on-both-APP-Store-amp-Google-play-store"><a href="#Published-on-both-APP-Store-amp-Google-play-store" class="headerlink" title="Published on both APP Store & Google play store."></a>Published on both APP Store & Google play store.</h1><div style="float: left;" class="store-badge" data-name="Cheerswipe" data-app-store-url="https://apps.apple.com/us/app/draytek-router/id1559664462" ></div><div style="float: left;" class="store-badge" data-name="Cheerswipe" data-google-play-url="https://play.google.com/store/apps/details?id=com.draytek.router"></div><script async src="https://cdn.jsdelivr.net/npm/store-badge@1/build/bundle.js"></script><br><br><h1 id="Introduction-from-DrayTek"><a href="#Introduction-from-DrayTek" class="headerlink" title="Introduction from DrayTek"></a>Introduction from DrayTek</h1><p><a href="https://www.draytek.com/products/draytek-network-management-app/">https://www.draytek.com/products/draytek-network-management-app/</a></p><h1 id="UX-Designer-Victor-UI-Designer-Sonia"><a href="#UX-Designer-Victor-UI-Designer-Sonia" class="headerlink" title="UX Designer: Victor; UI Designer: Sonia"></a>UX Designer: Victor; UI Designer: <a href="https://sonia-fan.com/about.html">Sonia</a></h1><blockquote><p>Thanks Victor & Sonia for the great work.<br>Tool: <a href="https://www.sketch.com/">Sketch</a>, <a href="https://www.figma.com/">Figma</a></p></blockquote><h1 id="Development-IDE-amp-languages"><a href="#Development-IDE-amp-languages" class="headerlink" title="Development IDE & languages"></a>Development IDE & languages</h1><blockquote><p>XCode, Swift & ObjectC, UIkit, Core Data, Network, Core Location …<br>Android Studio, Kotlin & Java</p></blockquote><h1 id="Communications-between-APP-and-router"><a href="#Communications-between-APP-and-router" class="headerlink" title="Communications between APP and router"></a>Communications between APP and router</h1><ul><li>Scan routers via UDP broadcasting</li><li>APP and router use TR069 protocol to communicate</li><li>Use framework for the communication lib in iOS</li><li>Use AAR for the communication lib in Android</li></ul><!-- # Architecture --><!-- ![](/img/draytek/ProsumerArchitecture.png) --><h1 id="Challenges-and-what-I-learned"><a href="#Challenges-and-what-I-learned" class="headerlink" title="Challenges and what I learned"></a>Challenges and what I learned</h1><ul><li>While building the apps, I gained experience in setting up routers, discovering that the configuration of a commercial router is far more complicated than any other device I’ve encountered. </li><li>The app specification document runs over 300 pages and outlines around 100 app UIs, making it a challenging endeavor to implement, pass quality control testing, and publish the two apps within a mere one and a half years.</li><li>The communication (TR069) between app and router takes really long time, which requires longer debugging time.</li><li>Learned SOAP, TR069, UDB broadcasting and all kinds of router settings.</li></ul><h1 id="Some-Third-party-libs-integrated"><a href="#Some-Third-party-libs-integrated" class="headerlink" title="Some Third-party libs integrated"></a>Some Third-party libs integrated</h1><h2 id="iOS"><a href="#iOS" class="headerlink" title="iOS"></a>iOS</h2><ul><li><a href="https://github.com/danielgindi/Charts">Charts</a></li><li><a href="https://github.com/robbiehanson/CocoaAsyncSocket">CocoaAsyncSocket</a></li><li><a href="https://github.com/robbiehanson/CocoaHTTPServer">CocoaHTTPServer</a></li><li><a href="https://firebase.google.com/">Google Firebase</a></li></ul><h2 id="Android"><a href="#Android" class="headerlink" title="Android"></a>Android</h2><ul><li><a href="https://sentry.io/">Sentry (For capturing crash logs)</a></li><li><a href="https://github.com/PhilJay/MPAndroidChart">MPAndroidChart</a></li><li><a href="https://github.com/simpligility/ksoap2-android">ksoap2</a></li><li><a href="https://firebase.google.com/">Google Firebase</a></li></ul><h1 id="Some-UIs"><a href="#Some-UIs" class="headerlink" title="Some UIs"></a>Some UIs</h1><p><img src="/img/draytek/dt-screen1.png"><br><img src="/img/draytek/dt-screen2.png"><br><img src="/img/draytek/dt-screen3.png"></p><h1 id="Demo-Video"><a href="#Demo-Video" class="headerlink" title="Demo Video"></a>Demo Video</h1><div class="video-container"><iframe src="https://www.youtube.com/embed/USAK48lMMQQ" frameborder="0" loading="lazy" allowfullscreen></iframe></div><h1 id="References"><a href="#References" class="headerlink" title="References"></a>References</h1><p>Check out the <a href="https://blog.bytebytego.com/p/soap-vs-rest-vs-graphql-vs-rpc">Alex Xu’s article</a> for SOAP, REST, GraphQL and RPC comparisons. </p>]]></content>
<categories>
<category> Projects </category>