From 3c3e2d1289d2a22cded3f0b7186b56b1e4db64eb Mon Sep 17 00:00:00 2001 From: ianchen0119 Date: Thu, 4 Jan 2024 03:36:23 +0000 Subject: [PATCH] Deployed 93d8243 with MkDocs version: 1.5.3 --- .nojekyll | 0 404.html | 756 ++ CNAME | 1 + assets/golden.png | Bin 0 -> 21172 bytes assets/icon.png | Bin 0 -> 6844 bytes assets/images/favicon.png | Bin 0 -> 1870 bytes assets/javascripts/bundle.6eac0284.min.js | 29 + assets/javascripts/bundle.6eac0284.min.js.map | 8 + assets/javascripts/lunr/min/lunr.ar.min.js | 1 + assets/javascripts/lunr/min/lunr.da.min.js | 18 + assets/javascripts/lunr/min/lunr.de.min.js | 18 + assets/javascripts/lunr/min/lunr.du.min.js | 18 + assets/javascripts/lunr/min/lunr.es.min.js | 18 + assets/javascripts/lunr/min/lunr.fi.min.js | 18 + assets/javascripts/lunr/min/lunr.fr.min.js | 18 + assets/javascripts/lunr/min/lunr.he.min.js | 1 + assets/javascripts/lunr/min/lunr.hi.min.js | 1 + assets/javascripts/lunr/min/lunr.hu.min.js | 18 + assets/javascripts/lunr/min/lunr.hy.min.js | 1 + assets/javascripts/lunr/min/lunr.it.min.js | 18 + assets/javascripts/lunr/min/lunr.ja.min.js | 1 + assets/javascripts/lunr/min/lunr.jp.min.js | 1 + assets/javascripts/lunr/min/lunr.kn.min.js | 1 + assets/javascripts/lunr/min/lunr.ko.min.js | 1 + assets/javascripts/lunr/min/lunr.multi.min.js | 1 + assets/javascripts/lunr/min/lunr.nl.min.js | 18 + assets/javascripts/lunr/min/lunr.no.min.js | 18 + assets/javascripts/lunr/min/lunr.pt.min.js | 18 + assets/javascripts/lunr/min/lunr.ro.min.js | 18 + assets/javascripts/lunr/min/lunr.ru.min.js | 18 + assets/javascripts/lunr/min/lunr.sa.min.js | 1 + .../lunr/min/lunr.stemmer.support.min.js | 1 + assets/javascripts/lunr/min/lunr.sv.min.js | 18 + assets/javascripts/lunr/min/lunr.ta.min.js | 1 + assets/javascripts/lunr/min/lunr.te.min.js | 1 + assets/javascripts/lunr/min/lunr.th.min.js | 1 + assets/javascripts/lunr/min/lunr.tr.min.js | 18 + assets/javascripts/lunr/min/lunr.vi.min.js | 1 + assets/javascripts/lunr/min/lunr.zh.min.js | 1 + assets/javascripts/lunr/tinyseg.js | 206 + assets/javascripts/lunr/wordcut.js | 6708 +++++++++++++++++ .../workers/search.a264c092.min.js | 42 + .../workers/search.a264c092.min.js.map | 8 + assets/logo.png | Bin 0 -> 39105 bytes assets/members/Accton.png | Bin 0 -> 11921 bytes assets/members/Advantech.png | Bin 0 -> 16234 bytes assets/members/Intel.png | Bin 0 -> 29615 bytes assets/members/Moxa.png | Bin 0 -> 189357 bytes assets/members/Transnet.png | Bin 0 -> 2403 bytes assets/members/alpha.png | Bin 0 -> 5073 bytes assets/members/cht.jpeg | Bin 0 -> 30363 bytes assets/members/edge-core.png | Bin 0 -> 4624 bytes assets/members/estinet.png | Bin 0 -> 2105 bytes assets/members/fujitsu.png | Bin 0 -> 26448 bytes assets/members/onf.png | Bin 0 -> 31218 bytes assets/members/wnc.png | Bin 0 -> 7468 bytes assets/membership-info.jpeg | Bin 0 -> 1029668 bytes assets/platinum.png | Bin 0 -> 45116 bytes assets/silver.png | Bin 0 -> 16637 bytes assets/stylesheets/main.79e020e9.min.css | 1 + assets/stylesheets/main.79e020e9.min.css.map | 1 + assets/stylesheets/palette.a5377069.min.css | 1 + .../stylesheets/palette.a5377069.min.css.map | 1 + blog/20230705/1-1.png | Bin 0 -> 104844 bytes blog/20230705/1-2.png | Bin 0 -> 115113 bytes .../1-free5gc-with-namespace/index.html | 1624 ++++ blog/20230712/TSN/index.html | 995 +++ blog/20230712/TSN_OSI_layer.png | Bin 0 -> 91691 bytes blog/20230712/gPTP_flow.png | Bin 0 -> 216382 bytes blog/20230712/time_aware_system.png | Bin 0 -> 1752197 bytes blog/20230719/1.png | Bin 0 -> 104145 bytes blog/20230719/2.png | Bin 0 -> 37525 bytes blog/20230719/3.png | Bin 0 -> 36957 bytes blog/20230719/4.png | Bin 0 -> 100147 bytes blog/20230719/5.png | Bin 0 -> 19397 bytes blog/20230719/6.png | Bin 0 -> 46517 bytes blog/20230719/7.png | Bin 0 -> 65484 bytes blog/20230719/UDM_introduce/index.html | 1259 ++++ blog/20230726/network_slice/index.html | 1092 +++ blog/20230726/slice-1.png | Bin 0 -> 120376 bytes blog/20230726/slice-2.png | Bin 0 -> 232977 bytes blog/20230726/slice-3.png | Bin 0 -> 168864 bytes blog/20230726/slice-4.png | Bin 0 -> 98930 bytes blog/20230726/slice-5.png | Bin 0 -> 109728 bytes blog/20230802/20230802/index.html | 1112 +++ blog/20230802/20230802figure1.png | Bin 0 -> 41074 bytes blog/20230802/20230802figure2.png | Bin 0 -> 69029 bytes blog/20230802/20230802figure3.png | Bin 0 -> 63887 bytes blog/20230802/20230802figure4.png | Bin 0 -> 49855 bytes blog/20230802/20230802table1.png | Bin 0 -> 88097 bytes blog/20230802/20230802table2.png | Bin 0 -> 120565 bytes blog/20230802/20230802table3.png | Bin 0 -> 60788 bytes blog/20230802/20230802table4.png | Bin 0 -> 9059 bytes blog/20230809/cve-1.png | Bin 0 -> 78904 bytes blog/20230809/cve-2.png | Bin 0 -> 14477 bytes blog/20230809/cve-3.png | Bin 0 -> 4632 bytes blog/20230809/go-fuzzing-1.png | Bin 0 -> 53157 bytes blog/20230809/go-fuzzing-2.png | Bin 0 -> 47623 bytes blog/20230809/main/index.html | 1310 ++++ blog/20230816/Service_Monitoring.jpg | Bin 0 -> 449214 bytes blog/20230816/check_free5gc_pod.jpg | Bin 0 -> 274263 bytes blog/20230816/check_helm.jpg | Bin 0 -> 27609 bytes blog/20230816/check_prometheus_pod.jpg | Bin 0 -> 142621 bytes blog/20230816/grafana_login.jpg | Bin 0 -> 406040 bytes blog/20230816/image.png | Bin 0 -> 848494 bytes blog/20230816/kubernetes_architecture.jpg | Bin 0 -> 72689 bytes blog/20230816/main/index.html | 1261 ++++ blog/20230816/minikube_status.jpg | Bin 0 -> 41994 bytes blog/20230816/webconsole_login.jpg | Bin 0 -> 227510 bytes blog/20230816/webconsole_subscribe.jpg | Bin 0 -> 181471 bytes blog/20230823/20230823/index.html | 1300 ++++ blog/20230823/img/1.png | Bin 0 -> 78151 bytes blog/20230823/img/10.png | Bin 0 -> 96699 bytes blog/20230823/img/11.png | Bin 0 -> 30976 bytes blog/20230823/img/2.png | Bin 0 -> 134190 bytes blog/20230823/img/3.png | Bin 0 -> 56970 bytes blog/20230823/img/4.png | Bin 0 -> 77153 bytes blog/20230823/img/5.png | Bin 0 -> 25941 bytes blog/20230823/img/6.png | Bin 0 -> 97653 bytes blog/20230823/img/7.png | Bin 0 -> 147526 bytes blog/20230823/img/8.png | Bin 0 -> 366957 bytes blog/20230823/img/9.png | Bin 0 -> 36678 bytes blog/20230830/1-1.png | Bin 0 -> 148779 bytes blog/20230830/2-1.png | Bin 0 -> 130619 bytes blog/20230830/2-2.png | Bin 0 -> 192298 bytes blog/20230830/2-3.png | Bin 0 -> 84060 bytes blog/20230830/20230830/index.html | 1058 +++ blog/20230830/3-1.png | Bin 0 -> 193220 bytes blog/20230830/3-2.png | Bin 0 -> 196645 bytes blog/20230830/3-3.png | Bin 0 -> 136371 bytes blog/20230830/3-4.png | Bin 0 -> 125472 bytes blog/20230830/3-5.png | Bin 0 -> 220672 bytes blog/20230830/4-1.png | Bin 0 -> 76447 bytes blog/20230830/4-2.png | Bin 0 -> 145405 bytes blog/20230830/4-3.png | Bin 0 -> 142717 bytes blog/20230906/0905-2023blog/index.html | 925 +++ blog/20230906/0905blog/NSD.jfif | Bin 0 -> 64353 bytes blog/20230906/0905blog/NSD2.png | Bin 0 -> 132420 bytes blog/20230906/0905blog/VDU.png | Bin 0 -> 92448 bytes blog/20230906/0905blog/VNF.jfif | Bin 0 -> 86249 bytes blog/20230906/0905blog/VNFD.jfif | Bin 0 -> 46291 bytes blog/20230906/0905blog/VNFD2.jfif | Bin 0 -> 156670 bytes blog/20230906/0905blog/VNFD3.jfif | Bin 0 -> 80421 bytes blog/20230913/20230913/index.html | 1003 +++ blog/20230913/CHF_preview.png | Bin 0 -> 45006 bytes .../High_Level_Common_Architecture.png | Bin 0 -> 43250 bytes blog/20230913/converged_charging_func.png | Bin 0 -> 96872 bytes blog/20230913/diff_4G_5G_charging.png | Bin 0 -> 81452 bytes blog/20230913/free5GC_icon.png | Bin 0 -> 6867 bytes blog/20230913/offline_charging_func.png | Bin 0 -> 84209 bytes blog/20230913/online_charging_func.png | Bin 0 -> 96453 bytes .../index.html | 1778 +++++ .../index.html | 1056 +++ blog/20230927/clocks.png | Bin 0 -> 124342 bytes blog/20230927/e2e_vs_p2p.png | Bin 0 -> 52117 bytes blog/20230927/tsctsf_spec.png | Bin 0 -> 126118 bytes blog/20230927/tsn_architecture.png | Bin 0 -> 80030 bytes blog/20231004/20231004/index.html | 961 +++ blog/20231004/algo1.png | Bin 0 -> 107520 bytes blog/20231004/algo2.png | Bin 0 -> 104670 bytes blog/20231004/algo3.png | Bin 0 -> 32262 bytes blog/20231004/figure1.png | Bin 0 -> 86405 bytes blog/20231004/figure2.png | Bin 0 -> 53314 bytes blog/20231004/figure3.png | Bin 0 -> 57072 bytes blog/20231004/figure4.png | Bin 0 -> 101898 bytes blog/20231004/figure5.png | Bin 0 -> 88782 bytes blog/20231004/figure6.png | Bin 0 -> 43742 bytes blog/20231018/20231018/index.html | 1039 +++ blog/20231018/figure1.png | Bin 0 -> 46953 bytes blog/20231018/figure2.png | Bin 0 -> 46964 bytes blog/20231018/figure3.png | Bin 0 -> 135951 bytes blog/20231108/20231108/index.html | 1795 +++++ blog/20231108/IMS_Architecture.jpg | Bin 0 -> 40174 bytes blog/20231115/OAuth2_dark.png | Bin 0 -> 2055294 bytes blog/20231115/OAuth2_light.png | Bin 0 -> 1953174 bytes .../free5GC_OAuth2_Procedure/index.html | 911 +++ blog/20231122/001.png | Bin 0 -> 97378 bytes blog/20231122/002.png | Bin 0 -> 461213 bytes blog/20231122/003.png | Bin 0 -> 79577 bytes blog/20231122/004.png | Bin 0 -> 65645 bytes blog/20231122/005.png | Bin 0 -> 118031 bytes blog/20231122/006.png | Bin 0 -> 74882 bytes blog/20231122/20231122/index.html | 1064 +++ blog/20231129/001.png | Bin 0 -> 46018 bytes blog/20231129/002.png | Bin 0 -> 57087 bytes blog/20231129/003.png | Bin 0 -> 138398 bytes blog/20231129/004.png | Bin 0 -> 874362 bytes blog/20231129/20231129/index.html | 957 +++ blog/20231206/20231206/index.html | 1014 +++ blog/20231206/pcscf_discovery.jpg | Bin 0 -> 71708 bytes blog/20231206/signalling_flows.jpg | Bin 0 -> 28483 bytes blog/20231213/20231213/index.html | 981 +++ blog/20231213/c1.JPG | Bin 0 -> 22700 bytes blog/20231213/c2.JPG | Bin 0 -> 23758 bytes blog/20231213/c3.JPG | Bin 0 -> 24202 bytes blog/20231213/c4.JPG | Bin 0 -> 23664 bytes blog/20231213/c5.JPG | Bin 0 -> 12713 bytes blog/20231213/g4.JPG | Bin 0 -> 30584 bytes blog/20231213/g5.JPG | Bin 0 -> 29536 bytes blog/20231213/g6.JPG | Bin 0 -> 22545 bytes blog/20231213/p1.png | Bin 0 -> 89397 bytes blog/20231213/p10.JPG | Bin 0 -> 19157 bytes blog/20231213/p11.JPG | Bin 0 -> 24815 bytes blog/20231213/p12.JPG | Bin 0 -> 24602 bytes blog/20231213/p13.JPG | Bin 0 -> 21276 bytes blog/20231213/p14.JPG | Bin 0 -> 20283 bytes blog/20231213/p2.JPG | Bin 0 -> 38109 bytes blog/20231213/p7.JPG | Bin 0 -> 26849 bytes blog/20231213/p8.JPG | Bin 0 -> 26073 bytes blog/20231213/p9.JPG | Bin 0 -> 20497 bytes blog/20231213/slice_graph3.JPG | Bin 0 -> 91055 bytes blog/20240103/20240103/index.html | 941 +++ blog/20240103/PMFP_PLR(UE).png | Bin 0 -> 1036373 bytes blog/20240103/PMFP_PLR(UPF).png | Bin 0 -> 1020531 bytes blog/index.html | 851 +++ guide/0-compose/index.html | 944 +++ guide/1-1.png | Bin 0 -> 209201 bytes guide/1-10.png | Bin 0 -> 161625 bytes guide/1-11.png | Bin 0 -> 202810 bytes guide/1-12.png | Bin 0 -> 194174 bytes guide/1-2.png | Bin 0 -> 113205 bytes guide/1-3.png | Bin 0 -> 143247 bytes guide/1-4.png | Bin 0 -> 79867 bytes guide/1-5.png | Bin 0 -> 46092 bytes guide/1-6.png | Bin 0 -> 45657 bytes guide/1-7.png | Bin 0 -> 47535 bytes guide/1-8.png | Bin 0 -> 57869 bytes guide/1-9.png | Bin 0 -> 78406 bytes guide/1-vm-en/index.html | 936 +++ guide/2-config-vm-en/index.html | 959 +++ guide/3-install-free5gc/index.html | 1000 +++ guide/4-test-free5gc/index.html | 831 ++ guide/5-install-ueransim/index.html | 1105 +++ guide/6-simple-app/index.html | 975 +++ guide/A-reload-config-script-example.png | Bin 0 -> 81510 bytes guide/Appendix/index.html | 985 +++ guide/Configuration/index.html | 868 +++ guide/Environment/index.html | 823 ++ guide/SMF-Config/index.html | 1037 +++ guide/Trouble_Shooting/index.html | 961 +++ .../index.html | 886 +++ guide/Webconsole/images/create_subscriber.png | Bin 0 -> 23429 bytes .../Webconsole/images/created_subscriber.png | Bin 0 -> 27126 bytes guide/Webconsole/images/login.png | Bin 0 -> 89591 bytes guide/Webconsole/images/subscriberData1.png | Bin 0 -> 46354 bytes guide/Webconsole/images/subscriberData2.png | Bin 0 -> 36652 bytes guide/Webconsole/images/subscriberData3.png | Bin 0 -> 17435 bytes guide/features/index.html | 904 +++ guide/hardware/index.html | 822 ++ guide/index.html | 922 +++ index.html | 998 +++ mathjaxhelper.js | 5 + membership/index.html | 826 ++ publication/index.html | 981 +++ search/search_index.json | 1 + sitemap.xml | 3 + sitemap.xml.gz | Bin 0 -> 127 bytes stylesheets/extra.css | 18 + support/index.html | 798 ++ videos/index.html | 859 +++ 260 files changed, 53782 insertions(+) create mode 100644 .nojekyll create mode 100644 404.html create mode 100644 CNAME create mode 100644 assets/golden.png create mode 100644 assets/icon.png create mode 100644 assets/images/favicon.png create mode 100644 assets/javascripts/bundle.6eac0284.min.js create mode 100644 assets/javascripts/bundle.6eac0284.min.js.map create mode 100644 assets/javascripts/lunr/min/lunr.ar.min.js create mode 100644 assets/javascripts/lunr/min/lunr.da.min.js create mode 100644 assets/javascripts/lunr/min/lunr.de.min.js create mode 100644 assets/javascripts/lunr/min/lunr.du.min.js create mode 100644 assets/javascripts/lunr/min/lunr.es.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.he.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hu.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hy.min.js create mode 100644 assets/javascripts/lunr/min/lunr.it.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ja.min.js create mode 100644 assets/javascripts/lunr/min/lunr.jp.min.js create mode 100644 assets/javascripts/lunr/min/lunr.kn.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ko.min.js create mode 100644 assets/javascripts/lunr/min/lunr.multi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.nl.min.js create mode 100644 assets/javascripts/lunr/min/lunr.no.min.js create mode 100644 assets/javascripts/lunr/min/lunr.pt.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ro.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ru.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sa.min.js create mode 100644 assets/javascripts/lunr/min/lunr.stemmer.support.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sv.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ta.min.js create mode 100644 assets/javascripts/lunr/min/lunr.te.min.js create mode 100644 assets/javascripts/lunr/min/lunr.th.min.js create mode 100644 assets/javascripts/lunr/min/lunr.tr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.vi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.zh.min.js create mode 100644 assets/javascripts/lunr/tinyseg.js create mode 100644 assets/javascripts/lunr/wordcut.js create mode 100644 assets/javascripts/workers/search.a264c092.min.js create mode 100644 assets/javascripts/workers/search.a264c092.min.js.map create mode 100644 assets/logo.png create mode 100644 assets/members/Accton.png create mode 100644 assets/members/Advantech.png create mode 100644 assets/members/Intel.png create mode 100644 assets/members/Moxa.png create mode 100644 assets/members/Transnet.png create mode 100644 assets/members/alpha.png create mode 100644 assets/members/cht.jpeg create mode 100644 assets/members/edge-core.png create mode 100644 assets/members/estinet.png create mode 100644 assets/members/fujitsu.png create mode 100644 assets/members/onf.png create mode 100644 assets/members/wnc.png create mode 100644 assets/membership-info.jpeg create mode 100644 assets/platinum.png create mode 100644 assets/silver.png create mode 100644 assets/stylesheets/main.79e020e9.min.css create mode 100644 assets/stylesheets/main.79e020e9.min.css.map create mode 100644 assets/stylesheets/palette.a5377069.min.css create mode 100644 assets/stylesheets/palette.a5377069.min.css.map create mode 100644 blog/20230705/1-1.png create mode 100644 blog/20230705/1-2.png create mode 100644 blog/20230705/1-free5gc-with-namespace/index.html create mode 100644 blog/20230712/TSN/index.html create mode 100644 blog/20230712/TSN_OSI_layer.png create mode 100644 blog/20230712/gPTP_flow.png create mode 100644 blog/20230712/time_aware_system.png create mode 100644 blog/20230719/1.png create mode 100644 blog/20230719/2.png create mode 100644 blog/20230719/3.png create mode 100644 blog/20230719/4.png create mode 100644 blog/20230719/5.png create mode 100644 blog/20230719/6.png create mode 100644 blog/20230719/7.png create mode 100644 blog/20230719/UDM_introduce/index.html create mode 100644 blog/20230726/network_slice/index.html create mode 100644 blog/20230726/slice-1.png create mode 100644 blog/20230726/slice-2.png create mode 100644 blog/20230726/slice-3.png create mode 100644 blog/20230726/slice-4.png create mode 100644 blog/20230726/slice-5.png create mode 100644 blog/20230802/20230802/index.html create mode 100644 blog/20230802/20230802figure1.png create mode 100644 blog/20230802/20230802figure2.png create mode 100644 blog/20230802/20230802figure3.png create mode 100644 blog/20230802/20230802figure4.png create mode 100644 blog/20230802/20230802table1.png create mode 100644 blog/20230802/20230802table2.png create mode 100644 blog/20230802/20230802table3.png create mode 100644 blog/20230802/20230802table4.png create mode 100644 blog/20230809/cve-1.png create mode 100644 blog/20230809/cve-2.png create mode 100644 blog/20230809/cve-3.png create mode 100644 blog/20230809/go-fuzzing-1.png create mode 100644 blog/20230809/go-fuzzing-2.png create mode 100644 blog/20230809/main/index.html create mode 100644 blog/20230816/Service_Monitoring.jpg create mode 100644 blog/20230816/check_free5gc_pod.jpg create mode 100644 blog/20230816/check_helm.jpg create mode 100644 blog/20230816/check_prometheus_pod.jpg create mode 100644 blog/20230816/grafana_login.jpg create mode 100644 blog/20230816/image.png create mode 100644 blog/20230816/kubernetes_architecture.jpg create mode 100644 blog/20230816/main/index.html create mode 100644 blog/20230816/minikube_status.jpg create mode 100644 blog/20230816/webconsole_login.jpg create mode 100644 blog/20230816/webconsole_subscribe.jpg create mode 100644 blog/20230823/20230823/index.html create mode 100644 blog/20230823/img/1.png create mode 100644 blog/20230823/img/10.png create mode 100644 blog/20230823/img/11.png create mode 100644 blog/20230823/img/2.png create mode 100644 blog/20230823/img/3.png create mode 100644 blog/20230823/img/4.png create mode 100644 blog/20230823/img/5.png create mode 100644 blog/20230823/img/6.png create mode 100644 blog/20230823/img/7.png create mode 100644 blog/20230823/img/8.png create mode 100644 blog/20230823/img/9.png create mode 100644 blog/20230830/1-1.png create mode 100644 blog/20230830/2-1.png create mode 100644 blog/20230830/2-2.png create mode 100644 blog/20230830/2-3.png create mode 100644 blog/20230830/20230830/index.html create mode 100644 blog/20230830/3-1.png create mode 100644 blog/20230830/3-2.png create mode 100644 blog/20230830/3-3.png create mode 100644 blog/20230830/3-4.png create mode 100644 blog/20230830/3-5.png create mode 100644 blog/20230830/4-1.png create mode 100644 blog/20230830/4-2.png create mode 100644 blog/20230830/4-3.png create mode 100644 blog/20230906/0905-2023blog/index.html create mode 100644 blog/20230906/0905blog/NSD.jfif create mode 100644 blog/20230906/0905blog/NSD2.png create mode 100644 blog/20230906/0905blog/VDU.png create mode 100644 blog/20230906/0905blog/VNF.jfif create mode 100644 blog/20230906/0905blog/VNFD.jfif create mode 100644 blog/20230906/0905blog/VNFD2.jfif create mode 100644 blog/20230906/0905blog/VNFD3.jfif create mode 100644 blog/20230913/20230913/index.html create mode 100644 blog/20230913/CHF_preview.png create mode 100644 blog/20230913/High_Level_Common_Architecture.png create mode 100644 blog/20230913/converged_charging_func.png create mode 100644 blog/20230913/diff_4G_5G_charging.png create mode 100644 blog/20230913/free5GC_icon.png create mode 100644 blog/20230913/offline_charging_func.png create mode 100644 blog/20230913/online_charging_func.png create mode 100644 blog/20230920/Introduction_of_gtp5g_and_some_kernel_concepts/index.html create mode 100644 blog/20230927/Support_of_Time_Sensitive_Communication_and_Time_Synchronization_in_5G_system_-_Introduction_(Rel-17)/index.html create mode 100644 blog/20230927/clocks.png create mode 100644 blog/20230927/e2e_vs_p2p.png create mode 100644 blog/20230927/tsctsf_spec.png create mode 100644 blog/20230927/tsn_architecture.png create mode 100644 blog/20231004/20231004/index.html create mode 100644 blog/20231004/algo1.png create mode 100644 blog/20231004/algo2.png create mode 100644 blog/20231004/algo3.png create mode 100644 blog/20231004/figure1.png create mode 100644 blog/20231004/figure2.png create mode 100644 blog/20231004/figure3.png create mode 100644 blog/20231004/figure4.png create mode 100644 blog/20231004/figure5.png create mode 100644 blog/20231004/figure6.png create mode 100644 blog/20231018/20231018/index.html create mode 100644 blog/20231018/figure1.png create mode 100644 blog/20231018/figure2.png create mode 100644 blog/20231018/figure3.png create mode 100644 blog/20231108/20231108/index.html create mode 100644 blog/20231108/IMS_Architecture.jpg create mode 100644 blog/20231115/OAuth2_dark.png create mode 100644 blog/20231115/OAuth2_light.png create mode 100644 blog/20231115/free5GC_OAuth2_Procedure/index.html create mode 100644 blog/20231122/001.png create mode 100644 blog/20231122/002.png create mode 100644 blog/20231122/003.png create mode 100644 blog/20231122/004.png create mode 100644 blog/20231122/005.png create mode 100644 blog/20231122/006.png create mode 100644 blog/20231122/20231122/index.html create mode 100644 blog/20231129/001.png create mode 100644 blog/20231129/002.png create mode 100644 blog/20231129/003.png create mode 100644 blog/20231129/004.png create mode 100644 blog/20231129/20231129/index.html create mode 100644 blog/20231206/20231206/index.html create mode 100644 blog/20231206/pcscf_discovery.jpg create mode 100644 blog/20231206/signalling_flows.jpg create mode 100644 blog/20231213/20231213/index.html create mode 100644 blog/20231213/c1.JPG create mode 100644 blog/20231213/c2.JPG create mode 100644 blog/20231213/c3.JPG create mode 100644 blog/20231213/c4.JPG create mode 100644 blog/20231213/c5.JPG create mode 100644 blog/20231213/g4.JPG create mode 100644 blog/20231213/g5.JPG create mode 100644 blog/20231213/g6.JPG create mode 100644 blog/20231213/p1.png create mode 100644 blog/20231213/p10.JPG create mode 100644 blog/20231213/p11.JPG create mode 100644 blog/20231213/p12.JPG create mode 100644 blog/20231213/p13.JPG create mode 100644 blog/20231213/p14.JPG create mode 100644 blog/20231213/p2.JPG create mode 100644 blog/20231213/p7.JPG create mode 100644 blog/20231213/p8.JPG create mode 100644 blog/20231213/p9.JPG create mode 100644 blog/20231213/slice_graph3.JPG create mode 100644 blog/20240103/20240103/index.html create mode 100644 blog/20240103/PMFP_PLR(UE).png create mode 100644 blog/20240103/PMFP_PLR(UPF).png create mode 100644 blog/index.html create mode 100644 guide/0-compose/index.html create mode 100644 guide/1-1.png create mode 100644 guide/1-10.png create mode 100644 guide/1-11.png create mode 100644 guide/1-12.png create mode 100644 guide/1-2.png create mode 100644 guide/1-3.png create mode 100644 guide/1-4.png create mode 100644 guide/1-5.png create mode 100644 guide/1-6.png create mode 100644 guide/1-7.png create mode 100644 guide/1-8.png create mode 100644 guide/1-9.png create mode 100644 guide/1-vm-en/index.html create mode 100644 guide/2-config-vm-en/index.html create mode 100644 guide/3-install-free5gc/index.html create mode 100644 guide/4-test-free5gc/index.html create mode 100644 guide/5-install-ueransim/index.html create mode 100644 guide/6-simple-app/index.html create mode 100644 guide/A-reload-config-script-example.png create mode 100644 guide/Appendix/index.html create mode 100644 guide/Configuration/index.html create mode 100644 guide/Environment/index.html create mode 100644 guide/SMF-Config/index.html create mode 100644 guide/Trouble_Shooting/index.html create mode 100644 guide/Webconsole/Create-Subscriber-via-webconsole/index.html create mode 100644 guide/Webconsole/images/create_subscriber.png create mode 100644 guide/Webconsole/images/created_subscriber.png create mode 100644 guide/Webconsole/images/login.png create mode 100644 guide/Webconsole/images/subscriberData1.png create mode 100644 guide/Webconsole/images/subscriberData2.png create mode 100644 guide/Webconsole/images/subscriberData3.png create mode 100644 guide/features/index.html create mode 100644 guide/hardware/index.html create mode 100644 guide/index.html create mode 100644 index.html create mode 100644 mathjaxhelper.js create mode 100644 membership/index.html create mode 100644 publication/index.html create mode 100644 search/search_index.json create mode 100644 sitemap.xml create mode 100644 sitemap.xml.gz create mode 100644 stylesheets/extra.css create mode 100644 support/index.html create mode 100644 videos/index.html diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/404.html b/404.html new file mode 100644 index 00000000..73a214ab --- /dev/null +++ b/404.html @@ -0,0 +1,756 @@ + + + + + + + + + + + + + + + + + + + free5GC + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 00000000..934a4e59 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +free5gc.org diff --git a/assets/golden.png b/assets/golden.png new file mode 100644 index 0000000000000000000000000000000000000000..21c8eb9a79abd600b72cbd82f43af9e63ca3ee40 GIT binary patch literal 21172 zcmc$`g;$i{*FJm?-Q67uh@_H($Pf}rmq>?7N)H_ah#*Q>h=DW`QqtWa-9t!sl^c-Y0gSefGW%&-HaRD9PB!APAz=(o{2qAUyC-Jcxt{{MZi|KLtNX zJvATrKoC{?^vWdd3Za1`ge;B zf_NY;HD%*~&wu9vKXXn53Z5_c6#Uz~Ggm~onNC^6!%s?6|6@_n`WIuAnOpaR$FF~A z?`m-*I==Q22<+DC>ig93MtfP?+VQ1q>BUTkfL|oDn?ur5h?hns*mc38alWWv@Md^L zi>BtE&H3O0-HRri?Y%$2;gP|GU*LT{5a0jre=_%I(6HD%ul)}uZ}fHGg>;qph7!+x zfqdCXhI7iO5Ru0@N*)Gdk(@5i$r=$h58Q8?`7wQoWs4((47%1&@09aVGJk9lDP{6e z^UW1nEwCj*P2*{s*b1Y>pjNd7Dntp?NX9|zslp&=vR_jm@AgvBmY0}JW=350pQ6l2 zGdry`H93^YFxqXZ`$ESzhs}~Vj#1wd)~W)L!zyZZ{FND@GoCumIA(^&F|lsszZMB= z16>m_o@%}_OpqE?zDj|fSnVzCNLS+nwpeS*9yX}l(y_2S3X)NMRNmsApgQ-n&JXR5 z&WPQ8323I@$gSP<8b>PPH8K{+;@6P2JVe)W$iqguGgc)L+~jXY1&ZZ)T)4p6l-Ww= zQ*3&|^LU-?&`+X8Hi)n~-|%{*JwHVlv`*co)p_vU+jRgb#Aj0m@f4K*>AHG7zSjcU z3qp9@30nI*=1$V0$9R07eJXtUEzBSk4Eo3u3Mt0jW3hj4u+7TYL~sZT0;No==w@3lv_La_NGSlW>F|wrK#@pBDh%` z4+Ez$m@I{O*shwS1pNALq2?7sHcxJwi_s2zXSVo(lMzD2a@>scuy`kK9qw_I$f?2K zO943JAJ%w36i;5Gk=|U;Vvi0aVG*`)TR7@Hh~WD!D{<_tA$elc=b;qPqb!x#Zwo{C za@8RpNwUT+WMpy^Z4+rXWN}Rx=!HlAYai*vYKlZ^5S&s6TTq8=9?rg!y3TH33TO&q zwQ6=U73riF-j^jzS%XujE-bx~O!&ev$A*yfZeUq#QV~is4-7sUA^Jb6piIhp?uID~ zbc>2mT=Cv;Cz3m1cuc7)Cg`0D%X*aZz3mO@>aDq{l!t25t#~N!dyKDdm90E+8PMRZ z=|pm|H8R(^3Yl~@Z!g5&nlLZL{VGtP{%O-~ruLx?jMk8~ve0}Tp}=??3SGBa%P0$e ztikwvg0wjSVJ$>(>~=%yVhfEueZy>Jj96i3g5I*%ZOo`JP=}E4;qIjGff+l`?K{is z3y`5Y4iEK6&Nl{=L(|qGm3zsVpcO@vl!w1|kDyah!sMDkRr;G|Ra0JHL{SH}YJSWw zb~j9N&wSccP`A4APz^a`S~6mdsu4J%9z%3s-fi7OfvNC{vaAEX@0>15VHdZtQh zFwi(SWP_4#*IDe}-cSa;WJ0RTpd=LW>HxM~e}AETt1P#H*MJ^aRmu8Lu^CPDk2Dfb z+2fPL(JxW7LByZn@DgRA1j4lS3B9?H_R2Be1pg>9W%NN31znORFKOS zP=N5^@xJYU1Les zbiO?JJw`(BgOIQfWE8I-O)oO;iE zd2IEUeh2|#9Zp?awx#*hDD+zBFjGF`%djr0f#VXl>)H=m@KA;&bzwi=srSE!zImla z|D2h(Y9A(`g1J0HJiM2|GGBR`FgH{?sNyTxg@iK64Wh><#m}VqTj>`E(j*#4BsS?L zOeS0!Ha?VyoDLO)^knWY$GkDr; z7MJVGP`nlK`=>X?3LO}ctPrOPH3V6nCtbhgRGauIr9rHI}YXz1bA zV(CJy($v|jLw%N1&<})cfO(8vng=7>nGotauu`{c!Ilt&utx^sQ0`NwEtwdw+Bpbg zI%Rc5?JlHQJF z>J_j7%2P9`7p^?EuxR>jn~D)xQDTC|^(*81!c>j1uv=~KyROACdc95riHUKgS@BZS z*QSbXrJ6yNFb5cdlKzPq*1+Ig`dB`TbUPG_J0DjoyLbVHtW#SF@`mrUX(PXqJqM=6 zkRHAYCx2vfn;hs@&1<)NXkH0NUmTT`26&tUo`_{7XvV;aPk4b*8cY&Tb;Y7LZiCF++-w`t_>QV7+kUQd<3>au_&Ikn~S14@n_zWNOs;>-U@x zf4<4)md9gS1hNAU^@q>A`LABaaSDcG<-Lo}D>EiYyufsGODP!FmUy`UkRLr$kPg?7(1T+5H!w|pdfWXWd8@c~( zYfyI3qF#)s)P6g95@_41Q~X9>2DrFn;q9h>^ms_nt%Mr(_*Hx?9C9bm#}`x3tNx{KbpZLOI8zn6^~Fl&|yfb~KokN2BC# z1~@oCk`Uqv<1mY+FiKSQ31R6~9@eK__^2Y8&5|3&RZ~qRlNCpOH~7ETXYn^J#rbo? ze*;|+lS6FM7b9(xHI2VxP~JTwS0$4?+4Midt_`9Hj3U||YRa)n6vc;nSUl^xK#UXX z=&HJQN6ctBg^&%O-Y!a$OI>U!%PTI~C@Gdv^d^qWRsfgoO2Z@tJn zgs7#a7Fvkyozt&frKf+m>jz&E)9FT3SW!7!LC&eVo$?{2uza=*BL(1xDyWM@J897#^Ih@12T!^8t(UM69 ziz83r;0Co7W5=)G8keo+=0NZW0=7Uts`1{xi_nUC@h`Qi6%Zo8q2-!(okA zuTEt!r$-GTvfe$|qX%*wxa)mvxZF+6W? zi$Lp)1?=y?NwK*Rymc7=#nbsgBGr}mO)>OzK&JcG^phC<7xoCziXaGHbC9zEV`Hb? z5NC?QT%PAKAoPlX1w`tc-Ge{-x)GF)B9p(pT6RmhR*jeu^XI%e_PWD`h^sBZp#ta)~$H1|u5;mNt{ zWcI-gP0XX=KeX#DMj`}VJY{utQ^;L<==L&DA#sW6{%fa;*Rm@8M=e90>jYLLx%2Q_ z<3r((kUoFal&H8^^IxQ^+zYlNPQ!a`XyhHp<4pRS()HiW5kFd4h%;b<4{DD&?;x#$ zQhvej{n3Ku?zyD=kj2kkgva|I{!@DXW!^ySe2m41>npk*~2Gf%@wB0``DA#otXv+m2;X~FyvPfncF4sME9L~chcL+Q_wP5wPD zFiLCp!FB_LrvZ^58tgXU(IG#O3C83KG}@tx*`fKEE7tYHcr7ENy~VoYmk*NYfVt>7C7W% zYo~IHpn5{sQcf@^NW<~bE$+TpWAOcEaBxt`UR$%wJ``{Hggc40B6&PVdUG?r;Y9XF z$}^m}no3HstF2fdKaH>U`N6HXLSmd`%X)Q#%I;nSJl%tzcaId&@}rtF@A+$1bp9@) zA3B&ra%@#Zv(2>T^G40+{by5Ekqv$mb^o~QbyeI;p`L4@k0-LT4FgRh?i812$2luY zY*IQ_9U7EBnG0ii=W-D|;p9y1hEqobiVgNfM>M3Xj@>U)*1zpV#`AUA<#+S;YPig` zL4?Gb_53+!nW;gM^yBMk6*g2iFGQ2@^eV$@tfG0*cQPi4_pw$PG zYYKg)U1uNAV^dSI@UAsOxN7V6%#`d1L6rx7MMGiP)*DgP_u68XWh<%m-OU$~H=grD z@(cv6Hj%P8JA2#QMyA&N>YddsGb<)2K`nBrY9Q4WwzN0I`sTE6$EJo#d7vAQ$8ov) zeKUTGh2@$ip%Fr2Z%!f=U8k$!WC-(owoya(_zwoQ^p6UPbvZ2-J+i?Dq2=>x&7hT7 zcdG|aM}}$!Uy<1nM?S#t6j>6zVp7dRkHnGlfhcsa&{B zgXJ?miKTG}F1bUsO`}?Uw+L=740PSTHujbf#hfMSHbk8v9m8sfpr9IZF1xon7gh>V zGifdj$eS;r9rv_h;e3{fH{}4x-YD z=>B?)<(lW68797z9n*g&x@A|&D4lT%?tBtP?$H&NtMKPHE6s3odkba~-C&vsn09Y@ zn;Kg29dDB#`d!fP?A^ZM<^`12QBW9)-W%7-m{ zZFlso$GSmGkTuFVLOYa`r>OXil$B^z^Y<-5krgJ7}4OU*Xjl>F-7?stC(!*vgqtF6}3yKG^LyrI5IPZ!e7IXDUjrx;Fh>BUx#Im6*6^T9-V|X^NTXv^Q-aRygd6|YH9{*_b-GEnU`WRgk~ zQ}=zBLs=`w^iv1e;)m?b9)-(GxW?FJoNi8y54)jhV$eIx&~i`BHg}$k;N*c5n_%7Y zbctwC7KX#v21qnZK;vB<3$TmiLh>@b-ghLX6Udi4q%*d;Kgl%1#4;)ie%m94R@TNo z;ZGdgD}LJB?3J4Jpnrv(SM+@yAm;7qW;lP+vPe(%yJ80^ozIIp7TlH%9Z0GIZYGF} z)HaM>UVh0SuH3F}x0-KOFiLg$6He*?3tuS>K9~2=<@UlAPJ$9@? zCNZ{7(TIC-iLGgoahzT&XV)4^E%uR$`VhpP-Lm@d zflLUV?O(XMmF5)bhUG7~x{+C7dDVKPKTzudP%FLgsWO`Nqq4bJ^oHdooJdDM-!kek z61EtaqII1o`U9Z-j_7L}uhfV?QG_Vs-oz3!l6M?#PpK~hwfFb_R@dA8xHKIeBX@PK zgfCUkoBlaLGo0#TWp=Gxdp6FZY^CS#| zl-K-%)Gh*5Z{_GDbxk|^Lr5cr#aQscUl7`Je+g@@;|w`mH_t2`)1N!QlDxpBqu9u{ z48sxsIJb!?ki~i+%KNH{eG!I*(w+x|KdKQ=ugt=6kMk zW*SPoyd6CAmfT}*Ph;$i9Btq~@auzO~)xC>-0lOJbh{)ZTwG?!^0&*^r zI$-f|w*J;J+`TrfB)*y4e7k3XQT;PiO(I`rTVT%%o+WDFUFVK8NA4(cO;2Fr|7($dI=`8OP?3eUH{hP*G^S|K4 z@zms~{@s9M8-m*};+diaZm z$>PuEm=FcXsp86K0_Pq7@&p~vUz5b9Ou*~QfQFQ{iq?@9x5kk17?N?QNfxSO*)ZFH ztMKc@<%ASsXr%l^pzoU7a5OF39rHEiI3K_?QP12tu1|A`PSY+KFq37l`xwpY^w$h} zm?Uhy1y{{V87#EYNGJL+ONH|Jk^Q@vUrIZ(%`cU;1CPhS_u_RQbR2k9@=20k_{?lW+Eu+aQuTNU3qmn$&aGgnY+vNXYeX^1THj8P4SJ?#@@ATY$Vwp<1jtvL=wk|O zC(r7+ANUUnQ2wHe??fS9V$fs{OX4L9n7XQAd+WBS=oTQzB_3G%jiNm(4X0Q&-*^9; ze?uiaXu=rQQ17DYMU;4Pz*3E~LJU)pey!0p@c-EJy-~@0-LaaV8t@ofV^+_`Rm}0) z)Pp^X23%@0&Bc(Z>Hiqag%0d0fz;tZ>Z<4(-C+_ge|3IOf<^o6N5vQt%2&pVzbkt+ zz*iLrC)4|hNpqz6D-Rpf9a7-5+5s^X4Sz{u zql<42_W)8*Dvj5n5Jk%PXKT27zdvGFKJYFX1Gi~uc1=zM-=kuTAQ32}a|u_rv!e~s zE-|H1*us)1!wx870S~l3m>)JzP&R% zbU^*qN~4Ek=dYT!Yb%hB8I)~hBZc3TipSzttBasCd|_|wT83enrav};gerW-76yJx z*z}e`nzcg4TLOKJKaaE_6c$E>OMKx-2tf=AZPS4MPB1{e3|k2ikwYF{*znJhTE+C^ zsSM;IkYNHLqW3xNR^3z0sY|htkfU~JsWl+z;NQ$|P zbh{C@2@dvndR(#97ZBaNGq<z)SQhhm+PoDpoOcHA#P z(8YLl;Cl8QNGm(lgCF9vzP-7UTJRl8Au-zDGP4!ZfD8vq^ghR^jhFmx9VJ_sVH@6F zQo20oDd?dL3=Q%TG=K6A7&75gwW}4|cq7L%fXkSM4Z|>rw^#4h9{X@oZ!G zG}=!eK;scM3zUUl6dMA7vHr=wPl)nhmdAVjv>P`W#j=pw^%gJ>WTU{?Oa`?le)b?D zx~@NnLr2(jmQ{*CqY3way`?v3E++e8wL-$evb-H`bzDW%5^%`8O9M1#6pKvFXqvh< zD0>Ua(;Y9_dIpmdf9nm1(Bnsw{^<=e9a^zuxSD5taT;6q>psl3XgliF<>$dX8yh~t zp|~S09M3Fn@`ZWxq(Pq31d^yujtSaWGl4;#1TBK07b4J~?h;x^7naRUj@sWVgi1MJ z+k{}ohf`CI>wp3f;l0U@4`Lq`Lg#FM`U^7*DZQ3wb^2&4=jX&tKuQh5`)J7m5_LLy z^A7ZgGZ9zBU({ONW;U_?;>#r^y1ZXNTSlP4>o`4;5HXh*bH@wX^hu2n17>llzLsQ5 z!F1BN%%_{-m}@&F5KWdK$l_jH?%M_KJWFk5-+mo7!;Pq$>4gO?HU9U-WxoPLagh5w zTv}(|^UNmH`R_tr3IXr`(JLd z^PH#76jUG{@dK_%y<3oxL1R;e%JDzf*|$9^>s;B6Y7+GQ?MsJ{L~N|&C=va?NY0G; z3XoB}I3=z*ZJrPLOh-R)@&Cd^oL~K*J}5(f*D$GtN@?wJxqXCK#fr;lNyNh4tS#+M zJNVJ?!z_-CZ`XsMg=KmZjvxOLd^F?MfHIA4F-ka0U6jDwDA~@cMP(?Wtp0Cdgk4?@ zc~dGQS+ps2J(aS<@X)_-`(BRDIlWN^+&J>7VS>e~MWWQB$^L+1k$R0ooR zEqea;KU|4Ie8#JAMM#^+0%(2&I6SQ|mJu2zF<&yan|aVThGSDM?1m07Dbx_jxI4&9 z)nmZH5IeJG>F7ity5+?gp+2mn|IB2FsUQbcVq_~E{S~B-|1D&vA4JrYN=Aa{!&b^| z>xctost+_lpM(3Yf zYirgIzs7o;v0ZfH@u;dj0>^E9dQK{^9zu$DK#;T*%X7un5Q60QdH6HHI?eTfQRFlA z;ZZ*{?K)o2$^HThqY`!B&4|E$pkGCBM(~w}F+mgUVN@|eC}8z^zao}Qr?}!GK%Eki z1#Dim%{hP!6?0a`0AHEgeb!%S%x_%q3d1r#a#yy&6-SOx(=D<>4~TpJx7~xfLb63t zkgG+EK^yI>N|ehR+s!OHyE7{{nV<*!0prN0>ccYz;%AwTtiZ9;)D^9tT>t#>|6u|A zaQG0vUlhmzE>k7-Z)qT?uP>GZ_E^2#B#jGd6P7qdH$6Es>{MQ*)eIsWz?te0DS>5O zT+_1fbGn}X*+(~O9!vLj4)^CCz0>s2QaGZ(%IB#9bOX3sSI>-MDD=fIHH+7c5x%lT zZY&Ppy5G}FZGr-<%=W4#OK10gB+23);eXkP(X@f5JP^kMBV3RHEf&tJ%& zzP4imckoqps6Js>g3tT!pZYpkP`YsdGLCw<`G%Hf-tXt=sJn+k&Lh|w4s-8Um`5U( z5QW(02kB@CF!i@&O&GZ9FCpKSzT)nDSvli7mlxoGJH%;GiK#g2hblXMI`D{JIN9VK@A zEs{EIrT`CFn#Y|EuQOCs%pqS2B*C8d9@T=qyYX7^z5D)w4(Tj1;h9mi;(;Qf2$VnN z=VVvlDu?-0vc;cHft~}){EP?4(Ff2t1a9(R(^%aZ?k)Opu})F zniubS%Vi0JgY@r@80hyKjM~n28tEv(%-NftAxx^0mgj4v^jUuSHfJJBoa|YH&rc2|xbKlK96JKinr&zz^7Fg?tcxO0p1|9%qsJ3Qr z!ysa-H75bipJWQ9)5w?oi+^xCWvb$czIsqB5O#RjJ>k!>-8^DORR(P*efw!{w0IL# zIX)>~ME$h-+?{3lT))`!IQ&~h5DR?!>1Ayx7OVH_T*R-39#o5jE2_e%P+N|#zvzg* zenVZRHB1tu>jQ>M{fSV#AW4_6r;g)D3(7wU2ARAhD8k(}^Jcx8K4^#@#DbOkDBKaOO=K5((1r4>MI*r67kAqXkZ{LnpKR%+!4HSR8&by2yu; z%4GU1T*Qs>aQ_IVunvDtE*O=4u4EOH-em_{lzdSCmHJ}|Cro^FhV)7|mo2Sl0#`AX zThStF^8_X}c<*a-c{Jgk)|b;YS-=8$`??Vn1Zt1UuH8u-I3nBHMM}u{-7HT1p2O4^ zjdX*ivzlRreZOj>Gq#kJ$I(*LX0PPBu;{Cpa@ z{UesToU4@ND2}8Fi)Rd{;JC3I{R6ww{g2pf8p$B|x+AkhDJ}fSpj>*&Xdz}Yp+((oyK}01 z61(Sx|JJdeL6wsO)S*=-Z?issmsm5n^l>tQSAO@Z)8`W8E5)yS_xE2WcU_b!E<~xH zoN9m8Gm~iM#}i}tLCOTl)6G|0r11oRnlxJKAEy{z0kNfqa~w!hBsys4_C{t%r`{|? zb-8gcvIIH}a0Uk!m?nT2#%coK6U*=zpQ^}}b#%HasM0vse3|DJiow|^Uq7=A<{F$R zYho67#gKGH+tvWGl$)LGez*7|!#z{^Iq9wlaz`p&I3O-K4_5x*eDb0>r|^PL1%Gk{ zsk1C@l{7k};U*+fqk+f?y_OsxT~LjuA_I+#A3MaTj^@DKyV5H8pSLy>9m`Cn#+05? zxf2Dn%&~fB@|+SE8|m+XaLjv&11ks{Mtt(93Ld@Ep;|lt zEBvSG;?Y>sT+)Z3Vjh?YFID}n{ zHR@1_P%K>Df~JSxb!+~J4K3e8nELjy}|KC=Putx4@Y*=|lg z;+Nlf;gV}m(`;oaLh(VidbcTNkAyt1{3NO&uVusWzHbYu>SQRqujo*G%#ShwO{O#i zyjV@*w+xSyKj$jr@|1mFRn*Bk*3q>z>QhDzr?sq40kcCteXvEHN8N>AaqJZ)X#d7# z0a?mk1_ti5SLwCM`&TS%>n0%*%ddeycHZF2c~0#`J&}swoT1_jVHz&LNIj4=qiA<6 zZgSQ0yI*B!1=_&n;{g3|gcmZ2OwcB+vlS?b-q6AX2i)n(%-*%8cB7Rx%f1%C@eRK^iFO+Hq4HHt95StxRLR;Rco$PkR{n>>)ocC4QoROg$ob`!zwnD+d zi_-t1laEyH)k7A3oZal|+l$Dff8k#N{=?c5AHjSDZz8QKMDCjD{cELDNG|b?sM63x z{Ln;IvN7JxK3>l20+tlxnQ}(5;qfu)6Bmd_+!e{y3DNO_e6ug|e*OjuVb_41)F!&$ zTid<7MP>UPBhvJGSwwU8chck?0Dy3qzvFrAB%Z*r<*Hu?*N>jmrQq&2QY47KwPm#U zmg1>s&C-m*;S-_Zg@b7mD#1UDCtW)N%Dw3%?M=2Q{A zJ2uh`Z@8Wr#F57tVVdYEX=4=og!FHnv_E&v0+Q}7K>A3zjGR8=j#{KI_E|}A22C!w zB?W_{D%s6J7v-Ny`dx#K2{9vYcDF6VeChI1S-2+GD4RFpl(&iRFEk!CgZz3LK+V*3 z80nr^V(&y00~0@r^wYE>%9g_qP9Rw46oKrBulz-{6FEyo2&f`%$^DEL-@wnbcJlp5 zyq%3A!S0*}ABFcIsd}lz@Uji)BmPWotM1rJk+)zh*M3xH6X+cBvi(utYGwO-&tjE; zWQHe~QbM#nJ~lgGH4SDXHbI$+_0>{g$JX?^-4VaBz`Nby!f^jVt0MoVuP(#1?WVhr9M*N}0=+8@f6L&r ze}Y!T|3QQ34QuSsza#<#Q3V*0z)Z5w{~TdSdWNC|yG;ODa7K^T5}E~u{FJeHb|)0} z4@%0_m9kb)Oybe4;Zq8~;v)KI4*j!ns~+Hp4nfeUBk`Ep5j8l!0o_iPY0bCBx(;S! z)BK6Kzfg# zG};6A8u5?dD?Z!m6+pUx7}M`|*GVKU6~nW_%mf+xFHz1m3kdt;*~WZ?mnR3~_U^cJ z>fa7HNS(v2!%0to2TQ93{5mXo(R&7!YK3rm?bsB*#f$Q{2}L)F*HWp30U1%!&(~Zi z$2O57;;0LTlHYti*-xXsp)d4o%Pe@PDaI1O$pBl=ge{x*1YQ2RLUOlDWX|p~cG4)| zexvrEitcu~{`NEM?q-io^UYY z+T{i5CaRXIC9Amnp@IGM$&kPLN`8Wj&z{cFzgd7*`;AWyxvg6Z;|$L(Xr^$d=NPe_-d)||e`S7eJ#JC8vNeJ)b*vG%^qqiP6jJFuK)gGEdUOz*$hfD>csddT9K48wNP6* zYGnzT?o;hJS&cDJp2<}#|_}0yYa4&5Tox{WwadEIfGg_`MG7&%JvTy4ZU46 zLUAECnjRa=;G?F*Sd&}HXeTKZ@JvA670sOa zF1U%yZikpWWkDf*J6iZ!YQsh3rd3PXVX2Z zx=$p~%~`(t&jmX8*knOa9xuG}BSyoh;LA^^k@DKOz%3FD&teRScD$TpR?-_R?d+U zg?+Z&&L@~z37^ADc|Af6JI-2dx;fGVa`629?azU;CY>8UT#CvGe5{#m4_#F&2J>*3 zEx5ia4s=1(HG@#Cg7y?xDP`Z@Cmw{fZq1?V$7El5V+W%^I2qJi6|2d$KhQ$UTVEks zH+PpftAstih*Tbi@094&7ED!Do5%R>^XU*wY?2a%f3)5H@Q|-w1Cvk`aV=hs5Rq?m zZy!Bs{IP)WwCchBlk%yH#Fm%rc>MuLlyf2F!_N!pzZBh0ji}kkU4lh>^#oa_Qg~SJ zj#%Ly6_hqpR1xLA)f#z}>~vlfZYAVPlDqIwVs8SLYkf7z*4O5%dG=IY%#-l$zOG0o zy~t4`rl?P&!DC9~7WhVF;N&v?;DoWc-=@`a6-|)hm)d$2&B6c|Kc9dH+*&08mHQj| zvsrm=S`ww#lnr}ZbZ{JQl-kbi*Py%ao>yU!SGn*KE~Hw^(gbMmwXN+WHqE2*3n}35 z&qHLD?IVmD-Od#F$c?FmglFmBeksrUSB2<8-}^2tkwXcBp)}jbsVpVf`t3VdZt;!b zXeL1wa?QiI&(FRiLMZY!@99t5N$$TKSTVwOmVr6smvfd&eXBiW!)*h67gxFn1ZAm| zPCl6`%W;#ekw^AH9|(Jl2TbIPrFm{==l!e9`&%K@m?TvspQtcnnjejU&Oyyh4FUlF zR|z}~jeUPb!WVg=0o{I!+p@Iv&EMEK;`razsBn=)coNa{GZEZmZ1s(> z#Iuc4;rMLf$@`I{!sgY!@bXw(b~hh9BX9G*G1UX1L^z_5`aQAKRv zX~*DgqO|EW3sqi1(KnUGGxcFt7;0%mBZl zdCJgu<`Szrom)LMK*k^WfzGxdg*mNCo2Hi&y1gmILJPd(nwM|usAIa>!LH4V3^_Kz z^|D$qe70_g2KIUWB@o zbqF5n3~Rna@S+WmDVnf?%ym*qpU{5(e8`ZY^^rga9=-MXDm6sJe9=!Y4KgC{yD)w} z|Cg&ner!=W(R^a$stjhtawad{P*l0Pyo@ASO`&bA890w&V@hyh zxXf^bZxm+?zh}lPk4j23)Ol-mhhxoOvFn8_HYXRgLH82J=K{L^D-3CAJs+~lO?6c6 zod}^4o;M{E4!^k~=kCzK@yR{!uBdwL>*n^Cc(mG#H_JX@r=MxNA`g4%1&J_w-f9l* zw2-&Dx$u@Ur#rS~n>a9tW-(5Md^MUk&QfGbB9I*`9&1}gS~kugQ62^k<&=koo&FPB zzhk>Vk|Up$hoi}ZQe5yv#2F-&C`P(e(u?g2HM^!|d0b~Y77gj`pI@{6n9^k_BI=Ky zq|g9=O)p-By+d!R^4`Sj#`6(Z(fh@E`uTKO=6k`%&8if+&y~A{?(gd*RhEgFS>5(6 zW%W?s_vV1{2-=tVXKBr*&!|a=i`d@Dh-GaJR45-25@M*F8u}D26uBYLGMbJ$5pbIv z;D%n3{vLeX$U4#oo>2)B1o@XQl=DQ&E5}}cA!17?s^P+tD>TOH3CfuD*JcsAy^XM8DqL`EIyDX;IP zn_2A@WT*nfsq#tN7?i(3HxYd0D)apE2ZIQ(7jLCps@eVw!577KU9(9GTpS5pGUQj9 zR9CLzKze&o!~;m*C}%Sm@x_QoP9}U>8R1wz=*`Q7NYfd16(y?mq=EE{t_qoaNA9K} zB($FC{l>VB3uEg>90ZA}tPa^)hVC%Ey$ifJ7w$E^ESr6@N=+gD@`DSWwlquxrR-`W zOtNcEF~00}>*588fb#HFBCGu7jpon&A4jCOkn2j@;^4Lv1`?Ls@Afw;x2B=>8$?!J zi-r*XWafeZi~gri()cpJi^5fDeG$W6k1^JM?x&7pOOq|&{@@H`q~jCP)k`&Nst^fB z;(Us}f!nj$i|+;1S@_Pb7uRVj;?h+KQYEk)v_(RU=GV*bf4(KZ2q*iEuK}jNo}3Bp zeDg_3;-^izD2e2C7He6FhoVHtfH}m2i)+LM*UZlEGbk%iz}0SCybU*qJgk}7b%$`> zNQPcw@I*(7FDS7jN|y-nRdW3)43FoTeUI>67lZFiIWH4vIwsp>Y7a_c($j!y&unIdP;tV~zTJ5sDqN8FFkQ&W7h@BVeg|cm zvP8>BVtz&RlUizN>}FeGZwz$}l?wJ32_${T?XMzgWfuth{!F_O5zP8q1Ovk4;9F#R zL7dRlXk|qU92o&2EY8t=Ta>2ad3vnn!r!58C#Ol+vCseAzO+`v(4?d45}vjpaU{aaIS0E4pQnBwyfQ3 z3`dhD)KL(d5z81mc)q0;LC{X9f)_opq*Uk_wsnWO?rU?1ofS`uzPvi!RT3Tv$FKzi z4GE13js0<21jWL7qh-oktn}f`L}*N!H+a|61ukcDS+DaOan8&T$sk3RUiQ=QG6A`T zdwF=28qXeL87c7T#R%s(%q zh9rq?b^P^JD+8Sb$O+UT)Ju#LB;S_3!QC<`e%2}Pss|nja6Ucn0Hme}wFS3+3STug z96o&yK~Fm6f~qkzE8;**a><3basgeD*H7DV&aKy^T(0QC^i@AQU(|Dp+_?EDog?FQ z<{MmWQAEC1fLHFbSjvBdN;V%d$&3p*uzIXn8FtJ%E+p3J`O3bj7y>C71GkKy=V@@f zMl_OWm!2FdsBhm(E+O0&2Cy}^Udmesi2^FcQ#S&!mBmI$;g^*K)-ubQx00q2v4uFJ zdvsx6^RSnd1j=e`=h-Eg& zdbp<^z+KG3k=D z4!;zvVtACcBXtgVIX?DtjD(v`Yyo_*1Ah`VXx9GsuFUV@{C5oW%@gpY&U2*fm39eK zBohht+ij>U_W7?=LrQ=9!Z87%d&IoY%li5J3G~%+Ekp9|acUNT#Ukjva}TyQi+zTt z%+@dN`EM2!cTBSJ*!ZW$bY1EXJWd^c%JC_Dd%za6&@X9k!*WyemgiUQqP{+q?*K2^@Evl<% zZMRSG`u>y4O@&r)B-^9Y;dpr@O)EF(p4eEIU-GY-S@)(Sj}`|08ppfGhR>(kZ| za`MxLe(`q0H=%TGC($cA!JrxT- z`3pEg*YEAjJLCibmvBOv%%CO0aA8Y|6vgV>Hv?Pw(7ZyQ0FN~+?DP0vuD+74>>;wL zANF9&I>OJBk)+o7oYGP3!x{PgIVg%IE{maQonA6fTluX=lgnH@m<}%`hAi+M%C|r%KvBB0(v&-VP z171VseJIDGt%*ZQ9Y}aQNUS;JJ3!i9T2T7Y50?7#{6+8>Up1b$Wp;`!r7IE;ZO#z0 zMGOc}FguHzZ05%$l}dXaRxrIj4h8UJKj2>m73*Vu4z!a`uE`6u9r2yi6a)pZm(*Y!P@ z>1r#w`>dUfz>V^=Vx-!1&Kvb;K*#ISg@7)h?addJSk4~>O&4Ebw~V_r#|ij?#4Yg0 zoB$!ToED`k0))wz*bJYMH#l`I!5h(tVv*@l{RncMx z{W`GDi;wf%Wd|Na`BQb2bJz7q`ZJw>4+;Z9?p%Icp2um=NEZg9p9M8e6-1~e;!jzZ zptDwK!u*{Hz$TH018GVo1Rmf`X_q>I4T~#g;Z>gf-#LOGmDL`YhJ89U*o{GFFBun(@@%pGrV-!&rA24=Y#M{pG$ zVGJFfvxLQLM1o}kIX_iDluVbc1%h#4tMRa@3?nDcf3TmtRxf7p2Tqs+;x1fV;e4gV z-G17zaJsF=0+%2}!gXohy7sMNUwo|r(dQlW0C*W658Bu!E7De22-YHLgKfewq$N|5zB8afiQtqv?;@3KJ zfuE=TS1Kbu06i=7fh%bVcu+g|9r?RA6IB#`Q{$lTdbN9z^y!nypVK!bNdl~QldE-w z7Bs1Z;kMt^G(d|#&oqs%rTB{XMkRju+KhN56U4$qF$T93)aoB>w%-iK<&^$>2Fa}} zL~1n2g7+*mTBfY&>4G^aJ*5q`_vM4q|i5nv~o~**%ReIDRn_JV=EI zon?D=dd25FE!akK?=77ixY!#P5!-fdBKK{9mey3HE=)GB@h#o82bE^i47L%R`EML? zyHnNz7$OkxfTBp(bze`(`LmA0qn_eOSIXn+6J72DSX$bRG3w8k8l5~*T-o%aR^4a< zy*cD{mu-%h1dcv3Gw*%VR@q&E(M~$_j&PHHv-50LepD5*Zmi1cvsmG5e$+(Nj-}B? zP`SgtmPXzE37=ly4tu3r`;#1#)ecq6OfoZ>?v#t7j2!Lksr`FlCo*^?e)2O)A*Ha8 zPP{)Ixul?xncPS`jAmfa&Ixfb7nygk>Zi)f8_^V*U5fa)iWH`%)#AG2xqwJB-u{iH ze-f|lup=)vI_0+f^jK7(B!vg>F%qb;tkbSG~j0PD2J~o^Y39%aV;cWeR3I zE0*$&IRqx5$CVZg^f6WrspLCI?c+J$Fh~P98oBJN^sg7Y`qdhou1-}Os=h<0`iI-w zCneu>i&jwNKU`0{CC=N|4zG^9=!xN}K(Hzqo_}UoxNXJLu8XU2;p`d$xQ`uQJ`g!= z31hfy2zGgZ0=qzddT6&;Q+++UZ#!}$R zDcCi3*YQbOQYCg6+Vnxw;M?WA;dWivgzhVjuBRNFd1}Kv?0jYr0YC>x=#RHz?3hGB zJ$^F2$G;T4GheX#;X!#;%eip!4ud;3|Jx4lm%i+9bln;8@IwYg5=jVXChPuqUA4SZ zpRY2yZ!2Fs-0r(BSnwicBiOAtlXn6<^I!=^%&76s``8qCFs{NU18D0GEk%9`W6@-H6mL2ngEOvTi(fP(k_NZ2J~7g)=sUIVQxi&i|Q-FwyT} z`zQ+PE_EJ=OV*I&{1(j+ZffB&HKRH+d=xiiI(GG4+pBWODzU~wSWPvOa zI0Di4Zci8Y|5JozQKO?0SWN52^#yeqEX5u|6G)75rWX>T*Drbd0rwtyDNO(bMDupu z0o}IKZ*9e0r*YpA8)2p^*g7$C6p0NrX@2;s!KqKFL{Z6lrXDM-L^>NWbokO)r1m~H zIUvF2wWqem8-|qDTS=eQlh6KFRJEf)W!Q-FcjRD09=Q7MP1v=J#op8(+PL(fMXnw8 z8*B(RYw@dYJ6d01nI2#eRufx0bP==TzZ&z!pncUj#=iQ@4{dMzg8Q_J)hiD6<^a@O zJyu-l{RFYX;xm6WpUEPxC>nfGWvWPDY?R2_`KHv?@Yb5wTru!&#FNXx_L_Tp0ygzV zP@oV~>wd)7Y#j@>ezs)~bitg}n+Eknrt>kPH5QhqOkHMlayLP-_)<12=c?6Qu)X>6 zl~@G*#bqHbiJZHMu=Ot5F+1*r&Fw5fzMlJ$5r>!#pecX-6^-)G^AW+!AZ=sDShrNzm4RKW<^#vEk#k~M$LvbDG) z0i;#1<0LrmTAnEK{!_b%J`~^BgW3R&5bcH)^Y~Cc?*n;em`NTlJDNlBIDyNU^O7`g z-Y}%N0XE<@FrJ^l>Nlln5H{@jGe8e2<#BLvg+L^%^10)7u2BV0aZgF!#7NcD4_I;G}&W0@{}&7n%LBr3U}I6n=>6u$dFB_ z*z<7?$|`MAG?@lIWy&rN^3W3eK&1V|KWKjK>FRu(O@q|gC(U0Ky(+64GlY z<2uwDtF?AVGg~%+pwy5*9uuYEcw9AK2dU~tP0Y%`2kLZM z96Fstjk%$$fP&5>p4f{g3Z9S2!w*RMqj?tqgt|F7*y-EsRO=2u=73Xvh;G@rJ-MO{W!a#tJ(bzXo61(GW+@-Jf9u4^}35R6WmEX*NBD?2@UZ5MjU zSc*x*aLB?zrgSJzI{Fd$a+2ZQcc-~AG;!m5TNXV^;Q>`C?X%R&^+U7Wa0ahOwx6lM zaD(HgHTQbooTf7`SHxp#jd6~Yo>-Ywt>VwunY`K;+}`*P$C2UCsM5@0$Q8G(uX4;$ zO>YT{_ZoFLBth`AOPxXax#o8}YW66&fntd?@aJJFsHlWO(be$b@ zGCfi8Gn(A6d}r3Ax2+#GGu7AknS7ljbZ(AvHL=ygZdz|aHUKmIT@1i{&#e1f;Zs&H zO6PM@C5^-?zdpXaVRjy^{KJYyQqVWl0pK1Rj$;M@H%q5FCiH}E;TORprK_F3=;h{u zF?WL)+nWVGqO!s1yrgDoc7^sw2~trX^b6&qXsC9>#zKYE6&to7YYf{xaAc~!(-YU% zZBOB#*lyu**B9$F)}C)gix8sVS-mVVm5lv+duK|&ZrM=7NS;04>3^mIMgzQ&(y(t- zH&-g3spxqC!@*t%1L9IlXeFw19k7^4#w^1^vcS(3#Aot@N~&j@mE%(iTKSIG!ww zKcX8&IOcjI?}O|>96EY_VdA(HhzscHUu`m)LR_bXo59dkIxW?w3$(@3@3$rfILS{H z%HcnLUhSbwcRYSiztiK(-^&OxBhE%(|AC6P*s~@#nHqwS;=?F%Kwn%q)B7Wz5_ca1 z%$48i!bZeXcw-{XgayLv8}w2#WonQNrVe4x7pR9miE|aI z%WcJVmS%Y)L66!Hml5{^#`&w+upp!vU9y<*@{OzZgrdjXN0M3&Jhl>G?H0)VBfrOF zvyJs0QD(`^meO+OS^Pm;qpf*Er+_pLvKD(c)m2}gXtOf^zvoj8^J5RUAmt1kJ-q=7RdoJDWTAzX8a za4lq6xl>qxf%S0jYo&&`Om= zoIzAsTJtrT^O0Broz19A{|g!=4k@(Ieho29QW8m7oRN z{X(f~?{zO1dv}X8Ws$b4MsFSeQha5*VN|Jf+k|xN_+z1HAV;Jd+JqFvtMpw4;D&ZM zF+U@NRXqW&x|zhe&;$Cf#opE+?Ax%E3CCHVb3kkDoT>FsN9`;2_9RVu2vGQC5*dX zGOOn}u^^%RI!elfa&ONRWf3f@C`$K8uDCjDKaK$Ep@;-fTqvqX?nJ6d&0hji=iLo>B=85PEWEk}=j_ur?Y` zk8nPn5-2do?-LLS7sS+X7;MuLd_ri+nMuZKI2>jjhhvn$h|rQ#lYBf5;!^sS-~yo} zCwS&11RS9xr<0IT3GySXGFI>RIm||s$mt9va{7R|exE=I?vP2wx)4I0@Za`m zJl*T_DRDz6gYW`8>^0GNdWiEeT_KZ<^&^Cu2|u2S#?$2?Au(qiLK#ZJfx>$|9gU}# z2tFNVFnHmO(RezLs(sdw6J5Ts8tG0EejJF{AT2qAtN~_KhA0|Ok7^I3^>vN3@UtMG z+U5bXVz1zH5sFzQIqi^1#=4^MbeG@f;RKmvYy=^+s>s_m&~}tUfs1wx>foheKV!xv zaso7U86*`5mHwoH>IBV$mPy8jOoSxCbE1~W2?!cF4S*f37gy6t{vtLw7G7SXS!HvE z4WurF0e1cv@KHQ>Z1U$>2`}(b=)V2ER`LFoBC3 z^FnCJ2@;|@WTe!?K4FmlP7o?`f|Jk{ji;A{jg)9c6NIKms2D;)PLuks2>G1NQ(=7Q zNC-SRA(ht__L~Oj)lmPARs@!uU{fZbW^*<`+A=;-2qZa!nrdVPNX=O{|0z(F@R?++ z0fN7?hM0g(zfXXG?II5zm>~RYla>ZV{NB6*@uKfXAk?}H#I%fkK`^*s2>zI%*@l`O z++dnRKLsH!sxT1LS2E?#i{LcnjC>p_IGqa-(o0S+a=3)=HII9#=20mR0}+hA^P;^y zA|Fb(($foJAuTz993x&0rZN@pdb&x&^bq8cRg;<`#^AvB4MpSWenx`s^2#}rlAI8` zZ1G~7g{eYGXc2?Mb5V_(FG^}=lCc(Zq5@;C5TTuqmnN2Uk8wpa86AFKqh$5zu=JeaSq5+S}bKaUrBd8Zz zMjCR$1+1t2rN^79teuG4D+k63Y(}`r(#_d)0+3b{EDsfYb_s72$qV%^6FI3IfbpiP zVjY8xl>=jghD{YDX-NWTmO-ZuNikK<;d}-~s(0VX2^VmPCPzNrR5j^jyx*1AfBnI{ zZ5LL!K1s;{TENzU6Kx_P(;^2^+DL}<mo@SA6U1F$c%}NF>1bb5EG~QurZ5AqOoM zw(o6G6{Qblr;(~C`X7|dB2~#Er2Rn%edqoi2x&jdPGA08$s#qY{aiVsDoV@bzPON# z7xYf;^7*=QFNXHqTX)+%INnr6eZrLkV@x;fhAjsMRYh5MnP6EUB%YjV7JiN=XWnO# zl5L-Q>sMTA27s9LMtI3jg7PlX@k4UDEviV+6yMSk^ShnH>ScmunNH3jdan8LFM5!w zlXm!v;S|%unn}j`fs{F?&+GiR1zZbeJheID$#SLIDhI}BhE_RV)3W&KcP1Al4*3!E zt(*U&$JP0_x6@9D>&MiR>JViE76Vi|ajRJJ+9Bc+taU04D+k7ks+c9u{$X_SGrv=5 zclMnBx4(~^f8~DCd9W1XcFtSf>geGxm%Z7%Z4kmrUW3~@anHyJs)kFdOOpyf&;;Qv zYl;!LdfTzVCC?pJSt`Q+^iq@Uu|FSpbn^4vtxRfl-ffSlSKsom8mrwzsvgo+d9gwe znAKV1^*U~l+FT|?dt!5fsOM7FZ?>mE#ZzC1rM>SJlTcG6N&49f>m8GB{Ef#V6%Egk zzH1nUzLJKwPQ-5BxF&scXHMG~J^D_+-&9cSwf))2a$l_QN^<2XR zIEe3sA2e{Y^^EkH3O3d%=7;PbJpO`Web5Y_D(T>9r(>%3O->&P(dADD_vwiZ*-@kypp;xYWfLV%w{i)X^j5k${j5k%OP;w{|yWAeYH90kD z24qyh)-Y6J9aO9=cDq^`%Ia8DDq6ntENS-nhS8tbTAdDT9{$|cwXSWp$$fFsF$&J@ zt3SRJ!G*!DyzxF}f?gb%ZzM2`hqMkzDnxbiXo7R)5K$!jbIa)&MLkm$`0`iJVe39W zZo8@ASc{*2hm?LhP5Zq(kUqSlJa+TOf(I-5y}wg4AHD9}DQl{-TM1*lN3Rd992n!O z_T8#>X35D~i9#zyA>B#+3*NZvJhtxh<40GJj)nD_KkdGoTh4fL|C8A7p8uoAvDY$v zbf;!!)LLJURoqp1D)l7U8M4eudRO^H#U!1rRbp*I@DVRt#KlbfcE`zNhAb@N%JN-ffTcB|ICvP0*3)-h*Q%GV}aP5Yo0jP2!Lk=mFm1|(%va}it$PaEG;p}K>3P-Krr{EMzdrt2UJCS^({Ds@-n#Xn zt{fPPI3Xx&D~m(4vwVoE@IycgD3`H=8aZ?QJJZwXgZnCNFZ9yh_sUg8nJ@}J%)LLC zdxEfqA3S@Aa)lo%oU{HORhcPDQL-p~c!%xpamAv>AvK$=%YB!k>{7-KVAFZwP|idNE>Z_~y(WYs{1w=F;8?M}w$&(1hq^5!DZO5>t!`Vb#>&Z1TJ3x4|RdP1UL&F8yYojOl@F+*EhDJtF@5kh-R|Mz53 zKLgrmd&roIjd|FfjI$<<5!pvHiM#U6=@yh#`c!2)XYU0hpJo)epqZyD4TPHx!0{#Rb}S;mCxUjPF1tSty#VG3O7|OtYuWuc-pG!^E_{-#0(+w zwAq}>zv)%Rf9rK?26r$8;lV~^YFLwD49(Ks_rkWX-1^Wm1-)ehbA|{eoKX=?24VuZ zR~zw0tag)Q$v6GkK2Lgsylyjg9fUUs2PbZI4m5RB!`YG1annAx>mY(dJ-ewy&KdB|etC-Q`TcoysgbfZ)b?Q}8LM#ny%oF(YsHT=S6MIkjxp2jJPHkqC$XS92a3OkV zY|aw$@z;wk)Mb(}v(1V9w}-nxsbk9}a)uQDb>^A`4Dbppd?zP7-MFWUYK`;p_46Gk z00_flhtG80L2Vc5RY?|nKJX}gRT2j2qdTdNGpFh<^Kk;V9oY)MyDwE6ao;CuGZ+Cu zNNE!Btp)iroz$s=C33RDDe>HnPn<=NvB202>LiCRaL z`F7{P1j>`2YztmOsafjHK#%1$WFuV!txfVrMV zIaUR2_okps$1aRxwg*?uzwq&W^+?IKnRf{>l+5i#+3DWN{ZCp=3Y>FDGwsb<=0;k$ zMFnGq04(PsJ@jiFrazgMKIHn=O#=+mzvaJL)XZpDD{>C|@q z95j;on9H)f9a4{bfn2WD zh;>F11B%+P%U(X&ixee@!v^}UTG`U4D%lNRAAc=J(km9<>s2_c+2y@q3Tq*doGims zof^WP;Q#KudUD0?i+Qizxa++7Ve^8>@||awsM#!OZFDhchJ63It!s%68BJAE`Q1-7 zKxT`@bm+VC#`|=VWk9-*x^1Y9cgj=Ksw`jiNGTyJ>dZB>M72jHBy0^2Tqbf<1UUA3|WmmJYC{pp)| zYS%QoN_8`HpF?cZb^p2^=cHMyBGIHcI}|+e%U(W7jF+B%;;*n5@rEr2xZBoccO|gS z)y4nmA=-AA-tWf}?3*)ti1z~SQyWXMHe?E2Emh^wzn;sjJ0V>_h>1RYpsF9E5?3vI z@q}GKA7|&Wdii$f#Jpx}_rvZ>D~irXjmFbmn&4ruMJI4InUru#?)<_JQ%0xVbhFyw zwGF(BKt$rh<^?#Zx5~Qv(!l9ouEzdu*Kgj>&B-Xg`zh?PF8=pctlb~Gd1DQ3_hshP z?#MT%-*7AuEvnuLm<9tI0F%Hix$#v=(RjL7d*Xnsy=cZmmu_La9Zo#B5T7N&cVEf~ z-0ULe=z_^%B09mDWNehfZYVAh5ZZdqiX!_erNfDh-^CKNdfTxAf}ZKT1N+{EkGr27 zT)sAmeIULVP8EFT)wev{7OUNad;hM?N3XkzE-*+@EHcShKM-B}Gia>l0djMRfKUbD zwI>QQ*;aadv}s8Mw{IP)lBPzt+-g?o35;D>wy=HgvPdciSV_ z_kMl+HE#C4G3XZxe#(uIPcaYOhR~Yxv(DfK6#lq=ps6e2VMVd9UF4zwp(LE+QMUM* z->HkAen)@W&nD_fnxX8v);-h`p`zhAqV09zU^15io_~A$=)Bdfq?_-`1|%UGPuCm4 zd(ep3@6pIcDQPEov~;t!X#}zz;^fbk*W0YC?pu^Nq~8G8;D&tTSm+ z)$DW`<6_o|KfTnX6Na=T>)Q3(Yg8qBm{4pytS?lxU(DK=KD@)V$4hff&`c4ydF7*9 z=W#Dr|NI}s2lLvd=WB{g3AU?Bnu5iwo)FUtC<51m z_nhu8b1>dj_hnFm@&>@*&kLiBZn@QI4%-SlRmVEMy79~3zu>7h!d(+1c+c}YSU$(B z)=ghL;x@tW1)SuH7bOnqs@Us&>$q@|7qGh~ytN{WAmgzltHy1SfJdk;_J%W^cZ?YI zhc`U&wrG=O!-a!MUrP^Vzh_+La~hckS$|oUb2C>YEqr_*FQO)<4s=S9#E|)Z8E~!E z&5%*Wi;OBP`020fL9N%D)cVk$fAf$uEi5=VA!aDlx&^&Z+Al?=JQS)3TRV9EmHQ16 zN=-&Hf*>&saGjL|q-=n|tqhn5`doCL0KpLmVj>YU;iK_%0}NQ;l3!&7RALLe5vf#n zFK(}SN5^Ib7&RxU7}t1+cqYXos+I)_P1t7doF42wLxD1-(ZGzu#KH5sb>9 zV)Ld*;B*}mtV9<+JQivPM?G|s3Th<1Mk>|qd(8nnq&)0?H&yLZZX8liCL2SXv5jLt zH5PC~gobrwAYL&*Y?q7nfU0&Tg1U__a}B~ffvBK3v|^EoKEzUbAOIj?9;O0GY?$ zeG8024+W%dMNtq^3vqB2V32;M9 z9-p^^Tj}gsMhRvV`O+N68&Z&Sys3(dAx~=(gcT63(+QeM#zstIW27rev4a$9ohPXb zFP;~8+Ke7MN4DoJafBlW-zDdGQx#Y5lK1g~2q#I7Aj}-_oty&fBW}k_rMkIR(R-0$ zq9L8^p1lm@Nd~v_F*OjK&n{xFD zzdAED>Dke-S&A8_G6w6bw;j{9i|^#D92n~wZ>r)(PVIQO*P&IRK;466Z7~;V{BPuT zgd%^->y&Uf#bco@2JHx;9s$So7OJXi7vIV0H$Zyk5qdZP)$TY%(<$e3r(GfAYfIW>ZfH&u1fdhfi5 z=|g9wD90$b&PBwrg#+q6q>-EkKs7tHn<|1RG0jM&x@C0=X(VRZeo60Ynr|#vlw!2rF<>6UnI7XCqRn zZYhTC`hn_g$7T{EmfB+JC8rmF9w97f3xuI8j#PMZknno57urEes4Avd&lOIT?F|76VCLdDR^qo9(Knm{&lD0cK4;Ysu-dN2m@+s*1>syCuKO qsNQyLM*Emjl?6}{`u_j`0RR8VoqO&CCb@S20000Gc)JR9QMau)O=X#!i9;T z37kk-upj^(fsR36MHs_+1RCI)NNu9}lD0S{B^g8PN?Ww(5|~L#Ng*g{WsqleV}|#l zz8@ri&cTzw_h33bHI+12+kK6WN$h#n5cD8OQt`5kw6p~9H3()bUQ8OS4Q4HTQ=1Ol z_JAocz`fLbT2^{`8n~UAo=#AUOf=SOq4pYkt;XbC&f#7lb$*7=$na!mWCQ`dBQsO0 zLFBSPj*N?#u5&pf2t4XjEGH|=pPQ8xh7tpx;US5Cx_Ju;!O`ya-yF`)b%TEt5>eP1ZX~}sjjA%FJF?h7cX8=b!DZl<6%Cv z*G0uvvU+vmnpLZ2paivG-(cd*y3$hCIcsZcYOGh{$&)A6*XX&kXZd3G8m)G$Zz-LV z^GF3VAW^Mdv!)4OM8EgqRiz~*Cji;uzl2uC9^=8I84vNp;ltJ|q-*uQwGp2ma6cY7 z;`%`!9UXO@fr&Ebapfs34OmS9^u6$)bJxrucutf>`dKPKT%%*d3XlFVKunp9 zasduxjrjs>f8V=D|J=XNZp;_Zy^WgQ$9WDjgY=z@stwiEBm9u5*|34&1Na8BMjjgf3+SHcr`5~>oz1Y?SW^=K z^bTyO6>Gar#P_W2gEMwq)ot3; zREHn~U&Dp0l6YT0&k-wLwYjb?5zGK`W6S2v+K>AM(95m2C20L|3m~rN8dprPr@t)5lsk9Hu*W z?pS990s;Ez=+Rj{x7p``4>+c0G5^pYnB1^!TL=(?HLHZ+HicG{~4F1d^5Awl_2!1jICM-!9eoLhbbT^;yHcefyTAaqRcY zmuctDopPT!%k+}x%lZRKnzykr2}}XfG_ne?nRQO~?%hkzo;@RN{P6o`&mMUWBYMTe z6i8ChtjX&gXl`nvrU>jah)2iNM%JdjqoaeaU%yVn!^70x-flljp6Q5tK}5}&X8&&G zX3fpb3E(!rH=zVI_9Gjl45w@{(ITqngWFe7@9{mX;tO25Z_8 zQHEpI+FkTU#4xu>RkN>b3Tnc3UpWzPXWm#o55GKF09j^Mh~)K7{QqbO_~(@CVq! zS<8954|P8mXN2MRs86xZ&Q4EfM@JB94b=(YGuk)s&^jiSF=t3*oNK3`rD{H`yQ?d; ztE=laAUoZx5?RC8*WKOj`%LXEkgDd>&^Q4M^z`%u0rg-It=hLCVsq!Z%^6eB-OvOT zFZ28TN&cRmgU}Elrnk43)!>Z1FCPL2K$7}gwzIc48NX}#!A1BpJP?#v5wkNprhV** z?Cpalt1oH&{r!o3eSKc&ap)iz2BTn_VV`4>9M^b3;(YY}4>#ML6{~(4mH+?%07*qo IM6N<$f(jP3KmY&$ literal 0 HcmV?d00001 diff --git a/assets/javascripts/bundle.6eac0284.min.js b/assets/javascripts/bundle.6eac0284.min.js new file mode 100644 index 00000000..d3a34791 --- /dev/null +++ b/assets/javascripts/bundle.6eac0284.min.js @@ -0,0 +1,29 @@ +"use strict";(()=>{var wi=Object.create;var dr=Object.defineProperty;var Si=Object.getOwnPropertyDescriptor;var Ti=Object.getOwnPropertyNames,kt=Object.getOwnPropertySymbols,Oi=Object.getPrototypeOf,hr=Object.prototype.hasOwnProperty,eo=Object.prototype.propertyIsEnumerable;var Zr=(e,t,r)=>t in e?dr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,R=(e,t)=>{for(var r in t||(t={}))hr.call(t,r)&&Zr(e,r,t[r]);if(kt)for(var r of kt(t))eo.call(t,r)&&Zr(e,r,t[r]);return e};var to=(e,t)=>{var r={};for(var o in e)hr.call(e,o)&&t.indexOf(o)<0&&(r[o]=e[o]);if(e!=null&&kt)for(var o of kt(e))t.indexOf(o)<0&&eo.call(e,o)&&(r[o]=e[o]);return r};var br=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Mi=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Ti(t))!hr.call(e,n)&&n!==r&&dr(e,n,{get:()=>t[n],enumerable:!(o=Si(t,n))||o.enumerable});return e};var Ht=(e,t,r)=>(r=e!=null?wi(Oi(e)):{},Mi(t||!e||!e.__esModule?dr(r,"default",{value:e,enumerable:!0}):r,e));var oo=br((vr,ro)=>{(function(e,t){typeof vr=="object"&&typeof ro!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(vr,function(){"use strict";function e(r){var o=!0,n=!1,i=null,s={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function a(C){return!!(C&&C!==document&&C.nodeName!=="HTML"&&C.nodeName!=="BODY"&&"classList"in C&&"contains"in C.classList)}function c(C){var it=C.type,Ne=C.tagName;return!!(Ne==="INPUT"&&s[it]&&!C.readOnly||Ne==="TEXTAREA"&&!C.readOnly||C.isContentEditable)}function p(C){C.classList.contains("focus-visible")||(C.classList.add("focus-visible"),C.setAttribute("data-focus-visible-added",""))}function l(C){C.hasAttribute("data-focus-visible-added")&&(C.classList.remove("focus-visible"),C.removeAttribute("data-focus-visible-added"))}function f(C){C.metaKey||C.altKey||C.ctrlKey||(a(r.activeElement)&&p(r.activeElement),o=!0)}function u(C){o=!1}function d(C){a(C.target)&&(o||c(C.target))&&p(C.target)}function v(C){a(C.target)&&(C.target.classList.contains("focus-visible")||C.target.hasAttribute("data-focus-visible-added"))&&(n=!0,window.clearTimeout(i),i=window.setTimeout(function(){n=!1},100),l(C.target))}function b(C){document.visibilityState==="hidden"&&(n&&(o=!0),z())}function z(){document.addEventListener("mousemove",G),document.addEventListener("mousedown",G),document.addEventListener("mouseup",G),document.addEventListener("pointermove",G),document.addEventListener("pointerdown",G),document.addEventListener("pointerup",G),document.addEventListener("touchmove",G),document.addEventListener("touchstart",G),document.addEventListener("touchend",G)}function K(){document.removeEventListener("mousemove",G),document.removeEventListener("mousedown",G),document.removeEventListener("mouseup",G),document.removeEventListener("pointermove",G),document.removeEventListener("pointerdown",G),document.removeEventListener("pointerup",G),document.removeEventListener("touchmove",G),document.removeEventListener("touchstart",G),document.removeEventListener("touchend",G)}function G(C){C.target.nodeName&&C.target.nodeName.toLowerCase()==="html"||(o=!1,K())}document.addEventListener("keydown",f,!0),document.addEventListener("mousedown",u,!0),document.addEventListener("pointerdown",u,!0),document.addEventListener("touchstart",u,!0),document.addEventListener("visibilitychange",b,!0),z(),r.addEventListener("focus",d,!0),r.addEventListener("blur",v,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var zr=br((Ot,Vr)=>{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Ot=="object"&&typeof Vr=="object"?Vr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Ot=="object"?Ot.ClipboardJS=r():t.ClipboardJS=r()})(Ot,function(){return function(){var e={686:function(o,n,i){"use strict";i.d(n,{default:function(){return Ei}});var s=i(279),a=i.n(s),c=i(370),p=i.n(c),l=i(817),f=i.n(l);function u(U){try{return document.execCommand(U)}catch(O){return!1}}var d=function(O){var S=f()(O);return u("cut"),S},v=d;function b(U){var O=document.documentElement.getAttribute("dir")==="rtl",S=document.createElement("textarea");S.style.fontSize="12pt",S.style.border="0",S.style.padding="0",S.style.margin="0",S.style.position="absolute",S.style[O?"right":"left"]="-9999px";var $=window.pageYOffset||document.documentElement.scrollTop;return S.style.top="".concat($,"px"),S.setAttribute("readonly",""),S.value=U,S}var z=function(O,S){var $=b(O);S.container.appendChild($);var F=f()($);return u("copy"),$.remove(),F},K=function(O){var S=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},$="";return typeof O=="string"?$=z(O,S):O instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(O==null?void 0:O.type)?$=z(O.value,S):($=f()(O),u("copy")),$},G=K;function C(U){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?C=function(S){return typeof S}:C=function(S){return S&&typeof Symbol=="function"&&S.constructor===Symbol&&S!==Symbol.prototype?"symbol":typeof S},C(U)}var it=function(){var O=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},S=O.action,$=S===void 0?"copy":S,F=O.container,Q=O.target,_e=O.text;if($!=="copy"&&$!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(Q!==void 0)if(Q&&C(Q)==="object"&&Q.nodeType===1){if($==="copy"&&Q.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if($==="cut"&&(Q.hasAttribute("readonly")||Q.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(_e)return G(_e,{container:F});if(Q)return $==="cut"?v(Q):G(Q,{container:F})},Ne=it;function Pe(U){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Pe=function(S){return typeof S}:Pe=function(S){return S&&typeof Symbol=="function"&&S.constructor===Symbol&&S!==Symbol.prototype?"symbol":typeof S},Pe(U)}function ui(U,O){if(!(U instanceof O))throw new TypeError("Cannot call a class as a function")}function Xr(U,O){for(var S=0;S0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof F.action=="function"?F.action:this.defaultAction,this.target=typeof F.target=="function"?F.target:this.defaultTarget,this.text=typeof F.text=="function"?F.text:this.defaultText,this.container=Pe(F.container)==="object"?F.container:document.body}},{key:"listenClick",value:function(F){var Q=this;this.listener=p()(F,"click",function(_e){return Q.onClick(_e)})}},{key:"onClick",value:function(F){var Q=F.delegateTarget||F.currentTarget,_e=this.action(Q)||"copy",Ct=Ne({action:_e,container:this.container,target:this.target(Q),text:this.text(Q)});this.emit(Ct?"success":"error",{action:_e,text:Ct,trigger:Q,clearSelection:function(){Q&&Q.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(F){return ur("action",F)}},{key:"defaultTarget",value:function(F){var Q=ur("target",F);if(Q)return document.querySelector(Q)}},{key:"defaultText",value:function(F){return ur("text",F)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(F){var Q=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return G(F,Q)}},{key:"cut",value:function(F){return v(F)}},{key:"isSupported",value:function(){var F=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],Q=typeof F=="string"?[F]:F,_e=!!document.queryCommandSupported;return Q.forEach(function(Ct){_e=_e&&!!document.queryCommandSupported(Ct)}),_e}}]),S}(a()),Ei=yi},828:function(o){var n=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function s(a,c){for(;a&&a.nodeType!==n;){if(typeof a.matches=="function"&&a.matches(c))return a;a=a.parentNode}}o.exports=s},438:function(o,n,i){var s=i(828);function a(l,f,u,d,v){var b=p.apply(this,arguments);return l.addEventListener(u,b,v),{destroy:function(){l.removeEventListener(u,b,v)}}}function c(l,f,u,d,v){return typeof l.addEventListener=="function"?a.apply(null,arguments):typeof u=="function"?a.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(b){return a(b,f,u,d,v)}))}function p(l,f,u,d){return function(v){v.delegateTarget=s(v.target,f),v.delegateTarget&&d.call(l,v)}}o.exports=c},879:function(o,n){n.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},n.nodeList=function(i){var s=Object.prototype.toString.call(i);return i!==void 0&&(s==="[object NodeList]"||s==="[object HTMLCollection]")&&"length"in i&&(i.length===0||n.node(i[0]))},n.string=function(i){return typeof i=="string"||i instanceof String},n.fn=function(i){var s=Object.prototype.toString.call(i);return s==="[object Function]"}},370:function(o,n,i){var s=i(879),a=i(438);function c(u,d,v){if(!u&&!d&&!v)throw new Error("Missing required arguments");if(!s.string(d))throw new TypeError("Second argument must be a String");if(!s.fn(v))throw new TypeError("Third argument must be a Function");if(s.node(u))return p(u,d,v);if(s.nodeList(u))return l(u,d,v);if(s.string(u))return f(u,d,v);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function p(u,d,v){return u.addEventListener(d,v),{destroy:function(){u.removeEventListener(d,v)}}}function l(u,d,v){return Array.prototype.forEach.call(u,function(b){b.addEventListener(d,v)}),{destroy:function(){Array.prototype.forEach.call(u,function(b){b.removeEventListener(d,v)})}}}function f(u,d,v){return a(document.body,u,d,v)}o.exports=c},817:function(o){function n(i){var s;if(i.nodeName==="SELECT")i.focus(),s=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var a=i.hasAttribute("readonly");a||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),a||i.removeAttribute("readonly"),s=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var c=window.getSelection(),p=document.createRange();p.selectNodeContents(i),c.removeAllRanges(),c.addRange(p),s=c.toString()}return s}o.exports=n},279:function(o){function n(){}n.prototype={on:function(i,s,a){var c=this.e||(this.e={});return(c[i]||(c[i]=[])).push({fn:s,ctx:a}),this},once:function(i,s,a){var c=this;function p(){c.off(i,p),s.apply(a,arguments)}return p._=s,this.on(i,p,a)},emit:function(i){var s=[].slice.call(arguments,1),a=((this.e||(this.e={}))[i]||[]).slice(),c=0,p=a.length;for(c;c{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var Ha=/["'&<>]/;Un.exports=$a;function $a(e){var t=""+e,r=Ha.exec(t);if(!r)return t;var o,n="",i=0,s=0;for(i=r.index;i0&&i[i.length-1])&&(p[0]===6||p[0]===2)){r=0;continue}if(p[0]===3&&(!i||p[1]>i[0]&&p[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function N(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],s;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(a){s={error:a}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(s)throw s.error}}return i}function D(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o1||a(u,d)})})}function a(u,d){try{c(o[u](d))}catch(v){f(i[0][3],v)}}function c(u){u.value instanceof Ze?Promise.resolve(u.value.v).then(p,l):f(i[0][2],u)}function p(u){a("next",u)}function l(u){a("throw",u)}function f(u,d){u(d),i.shift(),i.length&&a(i[0][0],i[0][1])}}function ao(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof we=="function"?we(e):e[Symbol.iterator](),r={},o("next"),o("throw"),o("return"),r[Symbol.asyncIterator]=function(){return this},r);function o(i){r[i]=e[i]&&function(s){return new Promise(function(a,c){s=e[i](s),n(a,c,s.done,s.value)})}}function n(i,s,a,c){Promise.resolve(c).then(function(p){i({value:p,done:a})},s)}}function k(e){return typeof e=="function"}function at(e){var t=function(o){Error.call(o),o.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var Rt=at(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(o,n){return n+1+") "+o.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function De(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Ie=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,o,n,i;if(!this.closed){this.closed=!0;var s=this._parentage;if(s)if(this._parentage=null,Array.isArray(s))try{for(var a=we(s),c=a.next();!c.done;c=a.next()){var p=c.value;p.remove(this)}}catch(b){t={error:b}}finally{try{c&&!c.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}else s.remove(this);var l=this.initialTeardown;if(k(l))try{l()}catch(b){i=b instanceof Rt?b.errors:[b]}var f=this._finalizers;if(f){this._finalizers=null;try{for(var u=we(f),d=u.next();!d.done;d=u.next()){var v=d.value;try{so(v)}catch(b){i=i!=null?i:[],b instanceof Rt?i=D(D([],N(i)),N(b.errors)):i.push(b)}}}catch(b){o={error:b}}finally{try{d&&!d.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}}if(i)throw new Rt(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)so(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&De(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&De(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var xr=Ie.EMPTY;function Pt(e){return e instanceof Ie||e&&"closed"in e&&k(e.remove)&&k(e.add)&&k(e.unsubscribe)}function so(e){k(e)?e():e.unsubscribe()}var Ae={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var st={setTimeout:function(e,t){for(var r=[],o=2;o0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var o=this,n=this,i=n.hasError,s=n.isStopped,a=n.observers;return i||s?xr:(this.currentObservers=null,a.push(r),new Ie(function(){o.currentObservers=null,De(a,r)}))},t.prototype._checkFinalizedStatuses=function(r){var o=this,n=o.hasError,i=o.thrownError,s=o.isStopped;n?r.error(i):s&&r.complete()},t.prototype.asObservable=function(){var r=new P;return r.source=this,r},t.create=function(r,o){return new bo(r,o)},t}(P);var bo=function(e){ie(t,e);function t(r,o){var n=e.call(this)||this;return n.destination=r,n.source=o,n}return t.prototype.next=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.next)===null||n===void 0||n.call(o,r)},t.prototype.error=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.error)===null||n===void 0||n.call(o,r)},t.prototype.complete=function(){var r,o;(o=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||o===void 0||o.call(r)},t.prototype._subscribe=function(r){var o,n;return(n=(o=this.source)===null||o===void 0?void 0:o.subscribe(r))!==null&&n!==void 0?n:xr},t}(x);var yt={now:function(){return(yt.delegate||Date).now()},delegate:void 0};var Et=function(e){ie(t,e);function t(r,o,n){r===void 0&&(r=1/0),o===void 0&&(o=1/0),n===void 0&&(n=yt);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=o,i._timestampProvider=n,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=o===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,o),i}return t.prototype.next=function(r){var o=this,n=o.isStopped,i=o._buffer,s=o._infiniteTimeWindow,a=o._timestampProvider,c=o._windowTime;n||(i.push(r),!s&&i.push(a.now()+c)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var o=this._innerSubscribe(r),n=this,i=n._infiniteTimeWindow,s=n._buffer,a=s.slice(),c=0;c0?e.prototype.requestAsyncId.call(this,r,o,n):(r.actions.push(this),r._scheduled||(r._scheduled=lt.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,o,n){var i;if(n===void 0&&(n=0),n!=null?n>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,o,n);var s=r.actions;o!=null&&((i=s[s.length-1])===null||i===void 0?void 0:i.id)!==o&&(lt.cancelAnimationFrame(o),r._scheduled=void 0)},t}(jt);var xo=function(e){ie(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var o=this._scheduled;this._scheduled=void 0;var n=this.actions,i;r=r||n.shift();do if(i=r.execute(r.state,r.delay))break;while((r=n[0])&&r.id===o&&n.shift());if(this._active=!1,i){for(;(r=n[0])&&r.id===o&&n.shift();)r.unsubscribe();throw i}},t}(Wt);var Oe=new xo(go);var L=new P(function(e){return e.complete()});function Ut(e){return e&&k(e.schedule)}function Mr(e){return e[e.length-1]}function Qe(e){return k(Mr(e))?e.pop():void 0}function Me(e){return Ut(Mr(e))?e.pop():void 0}function Nt(e,t){return typeof Mr(e)=="number"?e.pop():t}var mt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Dt(e){return k(e==null?void 0:e.then)}function Vt(e){return k(e[pt])}function zt(e){return Symbol.asyncIterator&&k(e==null?void 0:e[Symbol.asyncIterator])}function qt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Pi(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Kt=Pi();function Qt(e){return k(e==null?void 0:e[Kt])}function Yt(e){return io(this,arguments,function(){var r,o,n,i;return $t(this,function(s){switch(s.label){case 0:r=e.getReader(),s.label=1;case 1:s.trys.push([1,,9,10]),s.label=2;case 2:return[4,Ze(r.read())];case 3:return o=s.sent(),n=o.value,i=o.done,i?[4,Ze(void 0)]:[3,5];case 4:return[2,s.sent()];case 5:return[4,Ze(n)];case 6:return[4,s.sent()];case 7:return s.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function Bt(e){return k(e==null?void 0:e.getReader)}function I(e){if(e instanceof P)return e;if(e!=null){if(Vt(e))return Ii(e);if(mt(e))return Fi(e);if(Dt(e))return ji(e);if(zt(e))return yo(e);if(Qt(e))return Wi(e);if(Bt(e))return Ui(e)}throw qt(e)}function Ii(e){return new P(function(t){var r=e[pt]();if(k(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function Fi(e){return new P(function(t){for(var r=0;r=2;return function(o){return o.pipe(e?M(function(n,i){return e(n,i,o)}):ue,xe(1),r?He(t):Fo(function(){return new Jt}))}}function jo(){for(var e=[],t=0;t=2,!0))}function le(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new x}:t,o=e.resetOnError,n=o===void 0?!0:o,i=e.resetOnComplete,s=i===void 0?!0:i,a=e.resetOnRefCountZero,c=a===void 0?!0:a;return function(p){var l,f,u,d=0,v=!1,b=!1,z=function(){f==null||f.unsubscribe(),f=void 0},K=function(){z(),l=u=void 0,v=b=!1},G=function(){var C=l;K(),C==null||C.unsubscribe()};return g(function(C,it){d++,!b&&!v&&z();var Ne=u=u!=null?u:r();it.add(function(){d--,d===0&&!b&&!v&&(f=$r(G,c))}),Ne.subscribe(it),!l&&d>0&&(l=new tt({next:function(Pe){return Ne.next(Pe)},error:function(Pe){b=!0,z(),f=$r(K,n,Pe),Ne.error(Pe)},complete:function(){v=!0,z(),f=$r(K,s),Ne.complete()}}),I(C).subscribe(l))})(p)}}function $r(e,t){for(var r=[],o=2;oe.next(document)),e}function q(e,t=document){return Array.from(t.querySelectorAll(e))}function W(e,t=document){let r=ce(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function ce(e,t=document){return t.querySelector(e)||void 0}function Re(){return document.activeElement instanceof HTMLElement&&document.activeElement||void 0}var na=_(h(document.body,"focusin"),h(document.body,"focusout")).pipe(ke(1),V(void 0),m(()=>Re()||document.body),J(1));function Zt(e){return na.pipe(m(t=>e.contains(t)),X())}function Je(e){return{x:e.offsetLeft,y:e.offsetTop}}function Do(e){return _(h(window,"load"),h(window,"resize")).pipe(Ce(0,Oe),m(()=>Je(e)),V(Je(e)))}function er(e){return{x:e.scrollLeft,y:e.scrollTop}}function dt(e){return _(h(e,"scroll"),h(window,"resize")).pipe(Ce(0,Oe),m(()=>er(e)),V(er(e)))}function Vo(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Vo(e,r)}function T(e,t,...r){let o=document.createElement(e);if(t)for(let n of Object.keys(t))typeof t[n]!="undefined"&&(typeof t[n]!="boolean"?o.setAttribute(n,t[n]):o.setAttribute(n,""));for(let n of r)Vo(o,n);return o}function tr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function ht(e){let t=T("script",{src:e});return H(()=>(document.head.appendChild(t),_(h(t,"load"),h(t,"error").pipe(E(()=>Lr(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(m(()=>{}),A(()=>document.head.removeChild(t)),xe(1))))}var zo=new x,ia=H(()=>typeof ResizeObserver=="undefined"?ht("https://unpkg.com/resize-observer-polyfill"):j(void 0)).pipe(m(()=>new ResizeObserver(e=>{for(let t of e)zo.next(t)})),E(e=>_(Ve,j(e)).pipe(A(()=>e.disconnect()))),J(1));function he(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ye(e){return ia.pipe(w(t=>t.observe(e)),E(t=>zo.pipe(M(({target:r})=>r===e),A(()=>t.unobserve(e)),m(()=>he(e)))),V(he(e)))}function bt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function rr(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}var qo=new x,aa=H(()=>j(new IntersectionObserver(e=>{for(let t of e)qo.next(t)},{threshold:0}))).pipe(E(e=>_(Ve,j(e)).pipe(A(()=>e.disconnect()))),J(1));function or(e){return aa.pipe(w(t=>t.observe(e)),E(t=>qo.pipe(M(({target:r})=>r===e),A(()=>t.unobserve(e)),m(({isIntersecting:r})=>r))))}function Ko(e,t=16){return dt(e).pipe(m(({y:r})=>{let o=he(e),n=bt(e);return r>=n.height-o.height-t}),X())}var nr={drawer:W("[data-md-toggle=drawer]"),search:W("[data-md-toggle=search]")};function Qo(e){return nr[e].checked}function Ke(e,t){nr[e].checked!==t&&nr[e].click()}function We(e){let t=nr[e];return h(t,"change").pipe(m(()=>t.checked),V(t.checked))}function sa(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function ca(){return _(h(window,"compositionstart").pipe(m(()=>!0)),h(window,"compositionend").pipe(m(()=>!1))).pipe(V(!1))}function Yo(){let e=h(window,"keydown").pipe(M(t=>!(t.metaKey||t.ctrlKey)),m(t=>({mode:Qo("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),M(({mode:t,type:r})=>{if(t==="global"){let o=Re();if(typeof o!="undefined")return!sa(o,r)}return!0}),le());return ca().pipe(E(t=>t?L:e))}function pe(){return new URL(location.href)}function ot(e,t=!1){if(te("navigation.instant")&&!t){let r=T("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function Bo(){return new x}function Go(){return location.hash.slice(1)}function ir(e){let t=T("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function pa(e){return _(h(window,"hashchange"),e).pipe(m(Go),V(Go()),M(t=>t.length>0),J(1))}function Jo(e){return pa(e).pipe(m(t=>ce(`[id="${t}"]`)),M(t=>typeof t!="undefined"))}function jr(e){let t=matchMedia(e);return Xt(r=>t.addListener(()=>r(t.matches))).pipe(V(t.matches))}function Xo(){let e=matchMedia("print");return _(h(window,"beforeprint").pipe(m(()=>!0)),h(window,"afterprint").pipe(m(()=>!1))).pipe(V(e.matches))}function Wr(e,t){return e.pipe(E(r=>r?t():L))}function ar(e,t){return new P(r=>{let o=new XMLHttpRequest;o.open("GET",`${e}`),o.responseType="blob",o.addEventListener("load",()=>{o.status>=200&&o.status<300?(r.next(o.response),r.complete()):r.error(new Error(o.statusText))}),o.addEventListener("error",()=>{r.error(new Error("Network Error"))}),o.addEventListener("abort",()=>{r.error(new Error("Request aborted"))}),typeof(t==null?void 0:t.progress$)!="undefined"&&(o.addEventListener("progress",n=>{t.progress$.next(n.loaded/n.total*100)}),t.progress$.next(5)),o.send()})}function Ue(e,t){return ar(e,t).pipe(E(r=>r.text()),m(r=>JSON.parse(r)),J(1))}function Zo(e,t){let r=new DOMParser;return ar(e,t).pipe(E(o=>o.text()),m(o=>r.parseFromString(o,"text/xml")),J(1))}function en(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function tn(){return _(h(window,"scroll",{passive:!0}),h(window,"resize",{passive:!0})).pipe(m(en),V(en()))}function rn(){return{width:innerWidth,height:innerHeight}}function on(){return h(window,"resize",{passive:!0}).pipe(m(rn),V(rn()))}function nn(){return B([tn(),on()]).pipe(m(([e,t])=>({offset:e,size:t})),J(1))}function sr(e,{viewport$:t,header$:r}){let o=t.pipe(ee("size")),n=B([o,r]).pipe(m(()=>Je(e)));return B([r,t,n]).pipe(m(([{height:i},{offset:s,size:a},{x:c,y:p}])=>({offset:{x:s.x-c,y:s.y-p+i},size:a})))}function la(e){return h(e,"message",t=>t.data)}function ma(e){let t=new x;return t.subscribe(r=>e.postMessage(r)),t}function an(e,t=new Worker(e)){let r=la(t),o=ma(t),n=new x;n.subscribe(o);let i=o.pipe(Z(),re(!0));return n.pipe(Z(),qe(r.pipe(Y(i))),le())}var fa=W("#__config"),vt=JSON.parse(fa.textContent);vt.base=`${new URL(vt.base,pe())}`;function me(){return vt}function te(e){return vt.features.includes(e)}function be(e,t){return typeof t!="undefined"?vt.translations[e].replace("#",t.toString()):vt.translations[e]}function Ee(e,t=document){return W(`[data-md-component=${e}]`,t)}function oe(e,t=document){return q(`[data-md-component=${e}]`,t)}function ua(e){let t=W(".md-typeset > :first-child",e);return h(t,"click",{once:!0}).pipe(m(()=>W(".md-typeset",e)),m(r=>({hash:__md_hash(r.innerHTML)})))}function sn(e){if(!te("announce.dismiss")||!e.childElementCount)return L;if(!e.hidden){let t=W(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return H(()=>{let t=new x;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),ua(e).pipe(w(r=>t.next(r)),A(()=>t.complete()),m(r=>R({ref:e},r)))})}function da(e,{target$:t}){return t.pipe(m(r=>({hidden:r!==e})))}function cn(e,t){let r=new x;return r.subscribe(({hidden:o})=>{e.hidden=o}),da(e,t).pipe(w(o=>r.next(o)),A(()=>r.complete()),m(o=>R({ref:e},o)))}function ha(e,t){let r=H(()=>B([Do(e),dt(t)])).pipe(m(([{x:o,y:n},i])=>{let{width:s,height:a}=he(e);return{x:o-i.x+s/2,y:n-i.y+a/2}}));return Zt(e).pipe(E(o=>r.pipe(m(n=>({active:o,offset:n})),xe(+!o||1/0))))}function pn(e,t,{target$:r}){let[o,n]=Array.from(e.children);return H(()=>{let i=new x,s=i.pipe(Z(),re(!0));return i.subscribe({next({offset:a}){e.style.setProperty("--md-tooltip-x",`${a.x}px`),e.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),or(e).pipe(Y(s)).subscribe(a=>{e.toggleAttribute("data-md-visible",a)}),_(i.pipe(M(({active:a})=>a)),i.pipe(ke(250),M(({active:a})=>!a))).subscribe({next({active:a}){a?e.prepend(o):o.remove()},complete(){e.prepend(o)}}),i.pipe(Ce(16,Oe)).subscribe(({active:a})=>{o.classList.toggle("md-tooltip--active",a)}),i.pipe(Ir(125,Oe),M(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:a})=>a)).subscribe({next(a){a?e.style.setProperty("--md-tooltip-0",`${-a}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),h(n,"click").pipe(Y(s),M(a=>!(a.metaKey||a.ctrlKey))).subscribe(a=>{a.stopPropagation(),a.preventDefault()}),h(n,"mousedown").pipe(Y(s),ne(i)).subscribe(([a,{active:c}])=>{var p;if(a.button!==0||a.metaKey||a.ctrlKey)a.preventDefault();else if(c){a.preventDefault();let l=e.parentElement.closest(".md-annotation");l instanceof HTMLElement?l.focus():(p=Re())==null||p.blur()}}),r.pipe(Y(s),M(a=>a===o),ze(125)).subscribe(()=>e.focus()),ha(e,t).pipe(w(a=>i.next(a)),A(()=>i.complete()),m(a=>R({ref:e},a)))})}function Ur(e){return T("div",{class:"md-tooltip",id:e},T("div",{class:"md-tooltip__inner md-typeset"}))}function ln(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return T("aside",{class:"md-annotation",tabIndex:0},Ur(t),T("a",{href:r,class:"md-annotation__index",tabIndex:-1},T("span",{"data-md-annotation-id":e})))}else return T("aside",{class:"md-annotation",tabIndex:0},Ur(t),T("span",{class:"md-annotation__index",tabIndex:-1},T("span",{"data-md-annotation-id":e})))}function mn(e){return T("button",{class:"md-clipboard md-icon",title:be("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}function Nr(e,t){let r=t&2,o=t&1,n=Object.keys(e.terms).filter(c=>!e.terms[c]).reduce((c,p)=>[...c,T("del",null,p)," "],[]).slice(0,-1),i=me(),s=new URL(e.location,i.base);te("search.highlight")&&s.searchParams.set("h",Object.entries(e.terms).filter(([,c])=>c).reduce((c,[p])=>`${c} ${p}`.trim(),""));let{tags:a}=me();return T("a",{href:`${s}`,class:"md-search-result__link",tabIndex:-1},T("article",{class:"md-search-result__article md-typeset","data-md-score":e.score.toFixed(2)},r>0&&T("div",{class:"md-search-result__icon md-icon"}),r>0&&T("h1",null,e.title),r<=0&&T("h2",null,e.title),o>0&&e.text.length>0&&e.text,e.tags&&e.tags.map(c=>{let p=a?c in a?`md-tag-icon md-tag--${a[c]}`:"md-tag-icon":"";return T("span",{class:`md-tag ${p}`},c)}),o>0&&n.length>0&&T("p",{class:"md-search-result__terms"},be("search.result.term.missing"),": ",...n)))}function fn(e){let t=e[0].score,r=[...e],o=me(),n=r.findIndex(l=>!`${new URL(l.location,o.base)}`.includes("#")),[i]=r.splice(n,1),s=r.findIndex(l=>l.scoreNr(l,1)),...c.length?[T("details",{class:"md-search-result__more"},T("summary",{tabIndex:-1},T("div",null,c.length>0&&c.length===1?be("search.result.more.one"):be("search.result.more.other",c.length))),...c.map(l=>Nr(l,1)))]:[]];return T("li",{class:"md-search-result__item"},p)}function un(e){return T("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>T("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?tr(r):r)))}function Dr(e){let t=`tabbed-control tabbed-control--${e}`;return T("div",{class:t,hidden:!0},T("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function dn(e){return T("div",{class:"md-typeset__scrollwrap"},T("div",{class:"md-typeset__table"},e))}function ba(e){let t=me(),r=new URL(`../${e.version}/`,t.base);return T("li",{class:"md-version__item"},T("a",{href:`${r}`,class:"md-version__link"},e.title))}function hn(e,t){return T("div",{class:"md-version"},T("button",{class:"md-version__current","aria-label":be("select.version")},t.title),T("ul",{class:"md-version__list"},e.map(ba)))}function va(e){return e.tagName==="CODE"?q(".c, .c1, .cm",e):[e]}function ga(e){let t=[];for(let r of va(e)){let o=[],n=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=n.nextNode();i;i=n.nextNode())o.push(i);for(let i of o){let s;for(;s=/(\(\d+\))(!)?/.exec(i.textContent);){let[,a,c]=s;if(typeof c=="undefined"){let p=i.splitText(s.index);i=p.splitText(a.length),t.push(p)}else{i.textContent=a,t.push(i);break}}}}return t}function bn(e,t){t.append(...Array.from(e.childNodes))}function cr(e,t,{target$:r,print$:o}){let n=t.closest("[id]"),i=n==null?void 0:n.id,s=new Map;for(let a of ga(t)){let[,c]=a.textContent.match(/\((\d+)\)/);ce(`:scope > li:nth-child(${c})`,e)&&(s.set(c,ln(c,i)),a.replaceWith(s.get(c)))}return s.size===0?L:H(()=>{let a=new x,c=a.pipe(Z(),re(!0)),p=[];for(let[l,f]of s)p.push([W(".md-typeset",f),W(`:scope > li:nth-child(${l})`,e)]);return o.pipe(Y(c)).subscribe(l=>{e.hidden=!l,e.classList.toggle("md-annotation-list",l);for(let[f,u]of p)l?bn(f,u):bn(u,f)}),_(...[...s].map(([,l])=>pn(l,t,{target$:r}))).pipe(A(()=>a.complete()),le())})}function vn(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return vn(t)}}function gn(e,t){return H(()=>{let r=vn(e);return typeof r!="undefined"?cr(r,e,t):L})}var yn=Ht(zr());var xa=0;function En(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return En(t)}}function xn(e){return ye(e).pipe(m(({width:t})=>({scrollable:bt(e).width>t})),ee("scrollable"))}function wn(e,t){let{matches:r}=matchMedia("(hover)"),o=H(()=>{let n=new x;if(n.subscribe(({scrollable:s})=>{s&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")}),yn.default.isSupported()&&(e.closest(".copy")||te("content.code.copy")&&!e.closest(".no-copy"))){let s=e.closest("pre");s.id=`__code_${xa++}`,s.insertBefore(mn(s.id),e)}let i=e.closest(".highlight");if(i instanceof HTMLElement){let s=En(i);if(typeof s!="undefined"&&(i.classList.contains("annotate")||te("content.code.annotate"))){let a=cr(s,e,t);return xn(e).pipe(w(c=>n.next(c)),A(()=>n.complete()),m(c=>R({ref:e},c)),qe(ye(i).pipe(m(({width:c,height:p})=>c&&p),X(),E(c=>c?a:L))))}}return xn(e).pipe(w(s=>n.next(s)),A(()=>n.complete()),m(s=>R({ref:e},s)))});return te("content.lazy")?or(e).pipe(M(n=>n),xe(1),E(()=>o)):o}function ya(e,{target$:t,print$:r}){let o=!0;return _(t.pipe(m(n=>n.closest("details:not([open])")),M(n=>e===n),m(()=>({action:"open",reveal:!0}))),r.pipe(M(n=>n||!o),w(()=>o=e.open),m(n=>({action:n?"open":"close"}))))}function Sn(e,t){return H(()=>{let r=new x;return r.subscribe(({action:o,reveal:n})=>{e.toggleAttribute("open",o==="open"),n&&e.scrollIntoView()}),ya(e,t).pipe(w(o=>r.next(o)),A(()=>r.complete()),m(o=>R({ref:e},o)))})}var Tn=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel rect,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel rect{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color);stroke-width:.05rem}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs #classDiagram-compositionEnd,defs #classDiagram-compositionStart,defs #classDiagram-dependencyEnd,defs #classDiagram-dependencyStart,defs #classDiagram-extensionEnd,defs #classDiagram-extensionStart{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs #classDiagram-aggregationEnd,defs #classDiagram-aggregationStart{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}.attributeBoxEven,.attributeBoxOdd{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityBox{fill:var(--md-mermaid-label-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityLabel{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.relationshipLabelBox{fill:var(--md-mermaid-label-bg-color);fill-opacity:1;background-color:var(--md-mermaid-label-bg-color);opacity:1}.relationshipLabel{fill:var(--md-mermaid-label-fg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs #ONE_OR_MORE_END *,defs #ONE_OR_MORE_START *,defs #ONLY_ONE_END *,defs #ONLY_ONE_START *,defs #ZERO_OR_MORE_END *,defs #ZERO_OR_MORE_START *,defs #ZERO_OR_ONE_END *,defs #ZERO_OR_ONE_START *{stroke:var(--md-mermaid-edge-color)!important}defs #ZERO_OR_MORE_END circle,defs #ZERO_OR_MORE_START circle{fill:var(--md-mermaid-label-bg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var qr,wa=0;function Sa(){return typeof mermaid=="undefined"||mermaid instanceof Element?ht("https://unpkg.com/mermaid@9.4.3/dist/mermaid.min.js"):j(void 0)}function On(e){return e.classList.remove("mermaid"),qr||(qr=Sa().pipe(w(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Tn,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),m(()=>{}),J(1))),qr.subscribe(()=>{e.classList.add("mermaid");let t=`__mermaid_${wa++}`,r=T("div",{class:"mermaid"}),o=e.textContent;mermaid.mermaidAPI.render(t,o,(n,i)=>{let s=r.attachShadow({mode:"closed"});s.innerHTML=n,e.replaceWith(r),i==null||i(s)})}),qr.pipe(m(()=>({ref:e})))}var Mn=T("table");function Ln(e){return e.replaceWith(Mn),Mn.replaceWith(dn(e)),j({ref:e})}function Ta(e){let t=q(":scope > input",e),r=t.find(o=>o.checked)||t[0];return _(...t.map(o=>h(o,"change").pipe(m(()=>W(`label[for="${o.id}"]`))))).pipe(V(W(`label[for="${r.id}"]`)),m(o=>({active:o})))}function _n(e,{viewport$:t}){let r=Dr("prev");e.append(r);let o=Dr("next");e.append(o);let n=W(".tabbed-labels",e);return H(()=>{let i=new x,s=i.pipe(Z(),re(!0));return B([i,ye(e)]).pipe(Ce(1,Oe),Y(s)).subscribe({next([{active:a},c]){let p=Je(a),{width:l}=he(a);e.style.setProperty("--md-indicator-x",`${p.x}px`),e.style.setProperty("--md-indicator-width",`${l}px`);let f=er(n);(p.xf.x+c.width)&&n.scrollTo({left:Math.max(0,p.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),B([dt(n),ye(n)]).pipe(Y(s)).subscribe(([a,c])=>{let p=bt(n);r.hidden=a.x<16,o.hidden=a.x>p.width-c.width-16}),_(h(r,"click").pipe(m(()=>-1)),h(o,"click").pipe(m(()=>1))).pipe(Y(s)).subscribe(a=>{let{width:c}=he(n);n.scrollBy({left:c*a,behavior:"smooth"})}),te("content.tabs.link")&&i.pipe(je(1),ne(t)).subscribe(([{active:a},{offset:c}])=>{let p=a.innerText.trim();if(a.hasAttribute("data-md-switching"))a.removeAttribute("data-md-switching");else{let l=e.offsetTop-c.y;for(let u of q("[data-tabs]"))for(let d of q(":scope > input",u)){let v=W(`label[for="${d.id}"]`);if(v!==a&&v.innerText.trim()===p){v.setAttribute("data-md-switching",""),d.click();break}}window.scrollTo({top:e.offsetTop-l});let f=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([p,...f])])}}),i.pipe(Y(s)).subscribe(()=>{for(let a of q("audio, video",e))a.pause()}),Ta(e).pipe(w(a=>i.next(a)),A(()=>i.complete()),m(a=>R({ref:e},a)))}).pipe(rt(ae))}function An(e,{viewport$:t,target$:r,print$:o}){return _(...q(".annotate:not(.highlight)",e).map(n=>gn(n,{target$:r,print$:o})),...q("pre:not(.mermaid) > code",e).map(n=>wn(n,{target$:r,print$:o})),...q("pre.mermaid",e).map(n=>On(n)),...q("table:not([class])",e).map(n=>Ln(n)),...q("details",e).map(n=>Sn(n,{target$:r,print$:o})),...q("[data-tabs]",e).map(n=>_n(n,{viewport$:t})))}function Oa(e,{alert$:t}){return t.pipe(E(r=>_(j(!0),j(!1).pipe(ze(2e3))).pipe(m(o=>({message:r,active:o})))))}function Cn(e,t){let r=W(".md-typeset",e);return H(()=>{let o=new x;return o.subscribe(({message:n,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=n}),Oa(e,t).pipe(w(n=>o.next(n)),A(()=>o.complete()),m(n=>R({ref:e},n)))})}function Ma({viewport$:e}){if(!te("header.autohide"))return j(!1);let t=e.pipe(m(({offset:{y:n}})=>n),Le(2,1),m(([n,i])=>[nMath.abs(i-n.y)>100),m(([,[n]])=>n),X()),o=We("search");return B([e,o]).pipe(m(([{offset:n},i])=>n.y>400&&!i),X(),E(n=>n?r:j(!1)),V(!1))}function kn(e,t){return H(()=>B([ye(e),Ma(t)])).pipe(m(([{height:r},o])=>({height:r,hidden:o})),X((r,o)=>r.height===o.height&&r.hidden===o.hidden),J(1))}function Hn(e,{header$:t,main$:r}){return H(()=>{let o=new x,n=o.pipe(Z(),re(!0));return o.pipe(ee("active"),Ge(t)).subscribe(([{active:i},{hidden:s}])=>{e.classList.toggle("md-header--shadow",i&&!s),e.hidden=s}),r.subscribe(o),t.pipe(Y(n),m(i=>R({ref:e},i)))})}function La(e,{viewport$:t,header$:r}){return sr(e,{viewport$:t,header$:r}).pipe(m(({offset:{y:o}})=>{let{height:n}=he(e);return{active:o>=n}}),ee("active"))}function $n(e,t){return H(()=>{let r=new x;r.subscribe({next({active:n}){e.classList.toggle("md-header__title--active",n)},complete(){e.classList.remove("md-header__title--active")}});let o=ce(".md-content h1");return typeof o=="undefined"?L:La(o,t).pipe(w(n=>r.next(n)),A(()=>r.complete()),m(n=>R({ref:e},n)))})}function Rn(e,{viewport$:t,header$:r}){let o=r.pipe(m(({height:i})=>i),X()),n=o.pipe(E(()=>ye(e).pipe(m(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),ee("bottom"))));return B([o,n,t]).pipe(m(([i,{top:s,bottom:a},{offset:{y:c},size:{height:p}}])=>(p=Math.max(0,p-Math.max(0,s-c,i)-Math.max(0,p+c-a)),{offset:s-i,height:p,active:s-i<=c})),X((i,s)=>i.offset===s.offset&&i.height===s.height&&i.active===s.active))}function _a(e){let t=__md_get("__palette")||{index:e.findIndex(r=>matchMedia(r.getAttribute("data-md-color-media")).matches)};return j(...e).pipe(se(r=>h(r,"change").pipe(m(()=>r))),V(e[Math.max(0,t.index)]),m(r=>({index:e.indexOf(r),color:{scheme:r.getAttribute("data-md-color-scheme"),primary:r.getAttribute("data-md-color-primary"),accent:r.getAttribute("data-md-color-accent")}})),J(1))}function Pn(e){let t=T("meta",{name:"theme-color"});document.head.appendChild(t);let r=T("meta",{name:"color-scheme"});return document.head.appendChild(r),H(()=>{let o=new x;o.subscribe(i=>{document.body.setAttribute("data-md-color-switching","");for(let[s,a]of Object.entries(i.color))document.body.setAttribute(`data-md-color-${s}`,a);for(let s=0;s{let i=Ee("header"),s=window.getComputedStyle(i);return r.content=s.colorScheme,s.backgroundColor.match(/\d+/g).map(a=>(+a).toString(16).padStart(2,"0")).join("")})).subscribe(i=>t.content=`#${i}`),o.pipe(Se(ae)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")});let n=q("input",e);return _a(n).pipe(w(i=>o.next(i)),A(()=>o.complete()),m(i=>R({ref:e},i)))})}function In(e,{progress$:t}){return H(()=>{let r=new x;return r.subscribe(({value:o})=>{e.style.setProperty("--md-progress-value",`${o}`)}),t.pipe(w(o=>r.next({value:o})),A(()=>r.complete()),m(o=>({ref:e,value:o})))})}var Kr=Ht(zr());function Aa(e){e.setAttribute("data-md-copying","");let t=e.innerText;return e.removeAttribute("data-md-copying"),t}function Fn({alert$:e}){Kr.default.isSupported()&&new P(t=>{new Kr.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||Aa(W(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(w(t=>{t.trigger.focus()}),m(()=>be("clipboard.copied"))).subscribe(e)}function Ca(e){if(e.length<2)return[""];let[t,r]=[...e].sort((n,i)=>n.length-i.length).map(n=>n.replace(/[^/]+$/,"")),o=0;if(t===r)o=t.length;else for(;t.charCodeAt(o)===r.charCodeAt(o);)o++;return e.map(n=>n.replace(t.slice(0,o),""))}function pr(e){let t=__md_get("__sitemap",sessionStorage,e);if(t)return j(t);{let r=me();return Zo(new URL("sitemap.xml",e||r.base)).pipe(m(o=>Ca(q("loc",o).map(n=>n.textContent))),de(()=>L),He([]),w(o=>__md_set("__sitemap",o,sessionStorage,e)))}}function jn(e){let t=W("[rel=canonical]",e);t.href=t.href.replace("//localhost:","//127.0.0.1");let r=new Map;for(let o of q(":scope > *",e)){let n=o.outerHTML;for(let i of["href","src"]){let s=o.getAttribute(i);if(s===null)continue;let a=new URL(s,t.href),c=o.cloneNode();c.setAttribute(i,`${a}`),n=c.outerHTML;break}r.set(n,o)}return r}function Wn({location$:e,viewport$:t,progress$:r}){let o=me();if(location.protocol==="file:")return L;let n=pr().pipe(m(l=>l.map(f=>`${new URL(f,o.base)}`))),i=h(document.body,"click").pipe(ne(n),E(([l,f])=>{if(!(l.target instanceof Element))return L;let u=l.target.closest("a");if(u===null)return L;if(u.target||l.metaKey||l.ctrlKey)return L;let d=new URL(u.href);return d.search=d.hash="",f.includes(`${d}`)?(l.preventDefault(),j(new URL(u.href))):L}),le());i.pipe(xe(1)).subscribe(()=>{let l=ce("link[rel=icon]");typeof l!="undefined"&&(l.href=l.href)}),h(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),i.pipe(ne(t)).subscribe(([l,{offset:f}])=>{history.scrollRestoration="manual",history.replaceState(f,""),history.pushState(null,"",l)}),i.subscribe(e);let s=e.pipe(V(pe()),ee("pathname"),je(1),E(l=>ar(l,{progress$:r}).pipe(de(()=>(ot(l,!0),L))))),a=new DOMParser,c=s.pipe(E(l=>l.text()),E(l=>{let f=a.parseFromString(l,"text/html");for(let b of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...te("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let z=ce(b),K=ce(b,f);typeof z!="undefined"&&typeof K!="undefined"&&z.replaceWith(K)}let u=jn(document.head),d=jn(f.head);for(let[b,z]of d)z.getAttribute("rel")==="stylesheet"||z.hasAttribute("src")||(u.has(b)?u.delete(b):document.head.appendChild(z));for(let b of u.values())b.getAttribute("rel")==="stylesheet"||b.hasAttribute("src")||b.remove();let v=Ee("container");return Fe(q("script",v)).pipe(E(b=>{let z=f.createElement("script");if(b.src){for(let K of b.getAttributeNames())z.setAttribute(K,b.getAttribute(K));return b.replaceWith(z),new P(K=>{z.onload=()=>K.complete()})}else return z.textContent=b.textContent,b.replaceWith(z),L}),Z(),re(f))}),le());return h(window,"popstate").pipe(m(pe)).subscribe(e),e.pipe(V(pe()),Le(2,1),M(([l,f])=>l.pathname===f.pathname&&l.hash!==f.hash),m(([,l])=>l)).subscribe(l=>{var f,u;history.state!==null||!l.hash?window.scrollTo(0,(u=(f=history.state)==null?void 0:f.y)!=null?u:0):(history.scrollRestoration="auto",ir(l.hash),history.scrollRestoration="manual")}),e.pipe(kr(i),V(pe()),Le(2,1),M(([l,f])=>l.pathname===f.pathname&&l.hash===f.hash),m(([,l])=>l)).subscribe(l=>{history.scrollRestoration="auto",ir(l.hash),history.scrollRestoration="manual",history.back()}),c.pipe(ne(e)).subscribe(([,l])=>{var f,u;history.state!==null||!l.hash?window.scrollTo(0,(u=(f=history.state)==null?void 0:f.y)!=null?u:0):ir(l.hash)}),t.pipe(ee("offset"),ke(100)).subscribe(({offset:l})=>{history.replaceState(l,"")}),c}var Dn=Ht(Nn());function Vn(e){let t=e.separator.split("|").map(n=>n.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":n).join("|"),r=new RegExp(t,"img"),o=(n,i,s)=>`${i}${s}`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${e.separator}|)(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return s=>(0,Dn.default)(s).replace(i,o).replace(/<\/mark>(\s+)]*>/img,"$1")}}function Mt(e){return e.type===1}function lr(e){return e.type===3}function zn(e,t){let r=an(e);return _(j(location.protocol!=="file:"),We("search")).pipe($e(o=>o),E(()=>t)).subscribe(({config:o,docs:n})=>r.next({type:0,data:{config:o,docs:n,options:{suggest:te("search.suggest")}}})),r}function qn({document$:e}){let t=me(),r=Ue(new URL("../versions.json",t.base)).pipe(de(()=>L)),o=r.pipe(m(n=>{let[,i]=t.base.match(/([^/]+)\/?$/);return n.find(({version:s,aliases:a})=>s===i||a.includes(i))||n[0]}));r.pipe(m(n=>new Map(n.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),E(n=>h(document.body,"click").pipe(M(i=>!i.metaKey&&!i.ctrlKey),ne(o),E(([i,s])=>{if(i.target instanceof Element){let a=i.target.closest("a");if(a&&!a.target&&n.has(a.href)){let c=a.href;return!i.target.closest(".md-version")&&n.get(c)===s?L:(i.preventDefault(),j(c))}}return L}),E(i=>{let{version:s}=n.get(i);return pr(new URL(i)).pipe(m(a=>{let p=pe().href.replace(t.base,"");return a.includes(p.split("#")[0])?new URL(`../${s}/${p}`,t.base):new URL(i)}))})))).subscribe(n=>ot(n,!0)),B([r,o]).subscribe(([n,i])=>{W(".md-header__topic").appendChild(hn(n,i))}),e.pipe(E(()=>o)).subscribe(n=>{var s;let i=__md_get("__outdated",sessionStorage);if(i===null){i=!0;let a=((s=t.version)==null?void 0:s.default)||"latest";Array.isArray(a)||(a=[a]);e:for(let c of a)for(let p of n.aliases)if(new RegExp(c,"i").test(p)){i=!1;break e}__md_set("__outdated",i,sessionStorage)}if(i)for(let a of oe("outdated"))a.hidden=!1})}function Pa(e,{worker$:t}){let{searchParams:r}=pe();r.has("q")&&(Ke("search",!0),e.value=r.get("q"),e.focus(),We("search").pipe($e(i=>!i)).subscribe(()=>{let i=pe();i.searchParams.delete("q"),history.replaceState({},"",`${i}`)}));let o=Zt(e),n=_(t.pipe($e(Mt)),h(e,"keyup"),o).pipe(m(()=>e.value),X());return B([n,o]).pipe(m(([i,s])=>({value:i,focus:s})),J(1))}function Kn(e,{worker$:t}){let r=new x,o=r.pipe(Z(),re(!0));B([t.pipe($e(Mt)),r],(i,s)=>s).pipe(ee("value")).subscribe(({value:i})=>t.next({type:2,data:i})),r.pipe(ee("focus")).subscribe(({focus:i})=>{i&&Ke("search",i)}),h(e.form,"reset").pipe(Y(o)).subscribe(()=>e.focus());let n=W("header [for=__search]");return h(n,"click").subscribe(()=>e.focus()),Pa(e,{worker$:t}).pipe(w(i=>r.next(i)),A(()=>r.complete()),m(i=>R({ref:e},i)),J(1))}function Qn(e,{worker$:t,query$:r}){let o=new x,n=Ko(e.parentElement).pipe(M(Boolean)),i=e.parentElement,s=W(":scope > :first-child",e),a=W(":scope > :last-child",e);We("search").subscribe(l=>a.setAttribute("role",l?"list":"presentation")),o.pipe(ne(r),Rr(t.pipe($e(Mt)))).subscribe(([{items:l},{value:f}])=>{switch(l.length){case 0:s.textContent=f.length?be("search.result.none"):be("search.result.placeholder");break;case 1:s.textContent=be("search.result.one");break;default:let u=tr(l.length);s.textContent=be("search.result.other",u)}});let c=o.pipe(w(()=>a.innerHTML=""),E(({items:l})=>_(j(...l.slice(0,10)),j(...l.slice(10)).pipe(Le(4),Fr(n),E(([f])=>f)))),m(fn),le());return c.subscribe(l=>a.appendChild(l)),c.pipe(se(l=>{let f=ce("details",l);return typeof f=="undefined"?L:h(f,"toggle").pipe(Y(o),m(()=>f))})).subscribe(l=>{l.open===!1&&l.offsetTop<=i.scrollTop&&i.scrollTo({top:l.offsetTop})}),t.pipe(M(lr),m(({data:l})=>l)).pipe(w(l=>o.next(l)),A(()=>o.complete()),m(l=>R({ref:e},l)))}function Ia(e,{query$:t}){return t.pipe(m(({value:r})=>{let o=pe();return o.hash="",r=r.replace(/\s+/g,"+").replace(/&/g,"%26").replace(/=/g,"%3D"),o.search=`q=${r}`,{url:o}}))}function Yn(e,t){let r=new x,o=r.pipe(Z(),re(!0));return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),h(e,"click").pipe(Y(o)).subscribe(n=>n.preventDefault()),Ia(e,t).pipe(w(n=>r.next(n)),A(()=>r.complete()),m(n=>R({ref:e},n)))}function Bn(e,{worker$:t,keyboard$:r}){let o=new x,n=Ee("search-query"),i=_(h(n,"keydown"),h(n,"focus")).pipe(Se(ae),m(()=>n.value),X());return o.pipe(Ge(i),m(([{suggest:a},c])=>{let p=c.split(/([\s-]+)/);if(a!=null&&a.length&&p[p.length-1]){let l=a[a.length-1];l.startsWith(p[p.length-1])&&(p[p.length-1]=l)}else p.length=0;return p})).subscribe(a=>e.innerHTML=a.join("").replace(/\s/g," ")),r.pipe(M(({mode:a})=>a==="search")).subscribe(a=>{switch(a.type){case"ArrowRight":e.innerText.length&&n.selectionStart===n.value.length&&(n.value=e.innerText);break}}),t.pipe(M(lr),m(({data:a})=>a)).pipe(w(a=>o.next(a)),A(()=>o.complete()),m(()=>({ref:e})))}function Gn(e,{index$:t,keyboard$:r}){let o=me();try{let n=zn(o.search,t),i=Ee("search-query",e),s=Ee("search-result",e);h(e,"click").pipe(M(({target:c})=>c instanceof Element&&!!c.closest("a"))).subscribe(()=>Ke("search",!1)),r.pipe(M(({mode:c})=>c==="search")).subscribe(c=>{let p=Re();switch(c.type){case"Enter":if(p===i){let l=new Map;for(let f of q(":first-child [href]",s)){let u=f.firstElementChild;l.set(f,parseFloat(u.getAttribute("data-md-score")))}if(l.size){let[[f]]=[...l].sort(([,u],[,d])=>d-u);f.click()}c.claim()}break;case"Escape":case"Tab":Ke("search",!1),i.blur();break;case"ArrowUp":case"ArrowDown":if(typeof p=="undefined")i.focus();else{let l=[i,...q(":not(details) > [href], summary, details[open] [href]",s)],f=Math.max(0,(Math.max(0,l.indexOf(p))+l.length+(c.type==="ArrowUp"?-1:1))%l.length);l[f].focus()}c.claim();break;default:i!==Re()&&i.focus()}}),r.pipe(M(({mode:c})=>c==="global")).subscribe(c=>{switch(c.type){case"f":case"s":case"/":i.focus(),i.select(),c.claim();break}});let a=Kn(i,{worker$:n});return _(a,Qn(s,{worker$:n,query$:a})).pipe(qe(...oe("search-share",e).map(c=>Yn(c,{query$:a})),...oe("search-suggest",e).map(c=>Bn(c,{worker$:n,keyboard$:r}))))}catch(n){return e.hidden=!0,Ve}}function Jn(e,{index$:t,location$:r}){return B([t,r.pipe(V(pe()),M(o=>!!o.searchParams.get("h")))]).pipe(m(([o,n])=>Vn(o.config)(n.searchParams.get("h"))),m(o=>{var s;let n=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let a=i.nextNode();a;a=i.nextNode())if((s=a.parentElement)!=null&&s.offsetHeight){let c=a.textContent,p=o(c);p.length>c.length&&n.set(a,p)}for(let[a,c]of n){let{childNodes:p}=T("span",null,c);a.replaceWith(...Array.from(p))}return{ref:e,nodes:n}}))}function Fa(e,{viewport$:t,main$:r}){let o=e.closest(".md-grid"),n=o.offsetTop-o.parentElement.offsetTop;return B([r,t]).pipe(m(([{offset:i,height:s},{offset:{y:a}}])=>(s=s+Math.min(n,Math.max(0,a-i))-n,{height:s,locked:a>=i+n})),X((i,s)=>i.height===s.height&&i.locked===s.locked))}function Qr(e,o){var n=o,{header$:t}=n,r=to(n,["header$"]);let i=W(".md-sidebar__scrollwrap",e),{y:s}=Je(i);return H(()=>{let a=new x,c=a.pipe(Z(),re(!0)),p=a.pipe(Ce(0,Oe));return p.pipe(ne(t)).subscribe({next([{height:l},{height:f}]){i.style.height=`${l-2*s}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),p.pipe($e()).subscribe(()=>{for(let l of q(".md-nav__link--active[href]",e)){let f=rr(l);if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=he(f);f.scrollTo({top:u-d/2})}}}),ge(q("label[tabindex]",e)).pipe(se(l=>h(l,"click").pipe(Se(ae),m(()=>l),Y(c)))).subscribe(l=>{let f=W(`[id="${l.htmlFor}"]`);W(`[aria-labelledby="${l.id}"]`).setAttribute("aria-expanded",`${f.checked}`)}),Fa(e,r).pipe(w(l=>a.next(l)),A(()=>a.complete()),m(l=>R({ref:e},l)))})}function Xn(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return St(Ue(`${r}/releases/latest`).pipe(de(()=>L),m(o=>({version:o.tag_name})),He({})),Ue(r).pipe(de(()=>L),m(o=>({stars:o.stargazers_count,forks:o.forks_count})),He({}))).pipe(m(([o,n])=>R(R({},o),n)))}else{let r=`https://api.github.com/users/${e}`;return Ue(r).pipe(m(o=>({repositories:o.public_repos})),He({}))}}function Zn(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return Ue(r).pipe(de(()=>L),m(({star_count:o,forks_count:n})=>({stars:o,forks:n})),He({}))}function ei(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,o]=t;return Xn(r,o)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,o]=t;return Zn(r,o)}return L}var ja;function Wa(e){return ja||(ja=H(()=>{let t=__md_get("__source",sessionStorage);if(t)return j(t);if(oe("consent").length){let o=__md_get("__consent");if(!(o&&o.github))return L}return ei(e.href).pipe(w(o=>__md_set("__source",o,sessionStorage)))}).pipe(de(()=>L),M(t=>Object.keys(t).length>0),m(t=>({facts:t})),J(1)))}function ti(e){let t=W(":scope > :last-child",e);return H(()=>{let r=new x;return r.subscribe(({facts:o})=>{t.appendChild(un(o)),t.classList.add("md-source__repository--active")}),Wa(e).pipe(w(o=>r.next(o)),A(()=>r.complete()),m(o=>R({ref:e},o)))})}function Ua(e,{viewport$:t,header$:r}){return ye(document.body).pipe(E(()=>sr(e,{header$:r,viewport$:t})),m(({offset:{y:o}})=>({hidden:o>=10})),ee("hidden"))}function ri(e,t){return H(()=>{let r=new x;return r.subscribe({next({hidden:o}){e.hidden=o},complete(){e.hidden=!1}}),(te("navigation.tabs.sticky")?j({hidden:!1}):Ua(e,t)).pipe(w(o=>r.next(o)),A(()=>r.complete()),m(o=>R({ref:e},o)))})}function Na(e,{viewport$:t,header$:r}){let o=new Map,n=q("[href^=\\#]",e);for(let a of n){let c=decodeURIComponent(a.hash.substring(1)),p=ce(`[id="${c}"]`);typeof p!="undefined"&&o.set(a,p)}let i=r.pipe(ee("height"),m(({height:a})=>{let c=Ee("main"),p=W(":scope > :first-child",c);return a+.8*(p.offsetTop-c.offsetTop)}),le());return ye(document.body).pipe(ee("height"),E(a=>H(()=>{let c=[];return j([...o].reduce((p,[l,f])=>{for(;c.length&&o.get(c[c.length-1]).tagName>=f.tagName;)c.pop();let u=f.offsetTop;for(;!u&&f.parentElement;)f=f.parentElement,u=f.offsetTop;let d=f.offsetParent;for(;d;d=d.offsetParent)u+=d.offsetTop;return p.set([...c=[...c,l]].reverse(),u)},new Map))}).pipe(m(c=>new Map([...c].sort(([,p],[,l])=>p-l))),Ge(i),E(([c,p])=>t.pipe(Hr(([l,f],{offset:{y:u},size:d})=>{let v=u+d.height>=Math.floor(a.height);for(;f.length;){let[,b]=f[0];if(b-p=u&&!v)f=[l.pop(),...f];else break}return[l,f]},[[],[...c]]),X((l,f)=>l[0]===f[0]&&l[1]===f[1])))))).pipe(m(([a,c])=>({prev:a.map(([p])=>p),next:c.map(([p])=>p)})),V({prev:[],next:[]}),Le(2,1),m(([a,c])=>a.prev.length{let i=new x,s=i.pipe(Z(),re(!0));if(i.subscribe(({prev:a,next:c})=>{for(let[p]of c)p.classList.remove("md-nav__link--passed"),p.classList.remove("md-nav__link--active");for(let[p,[l]]of a.entries())l.classList.add("md-nav__link--passed"),l.classList.toggle("md-nav__link--active",p===a.length-1)}),te("toc.follow")){let a=_(t.pipe(ke(1),m(()=>{})),t.pipe(ke(250),m(()=>"smooth")));i.pipe(M(({prev:c})=>c.length>0),Ge(o.pipe(Se(ae))),ne(a)).subscribe(([[{prev:c}],p])=>{let[l]=c[c.length-1];if(l.offsetHeight){let f=rr(l);if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=he(f);f.scrollTo({top:u-d/2,behavior:p})}}})}return te("navigation.tracking")&&t.pipe(Y(s),ee("offset"),ke(250),je(1),Y(n.pipe(je(1))),Tt({delay:250}),ne(i)).subscribe(([,{prev:a}])=>{let c=pe(),p=a[a.length-1];if(p&&p.length){let[l]=p,{hash:f}=new URL(l.href);c.hash!==f&&(c.hash=f,history.replaceState({},"",`${c}`))}else c.hash="",history.replaceState({},"",`${c}`)}),Na(e,{viewport$:t,header$:r}).pipe(w(a=>i.next(a)),A(()=>i.complete()),m(a=>R({ref:e},a)))})}function Da(e,{viewport$:t,main$:r,target$:o}){let n=t.pipe(m(({offset:{y:s}})=>s),Le(2,1),m(([s,a])=>s>a&&a>0),X()),i=r.pipe(m(({active:s})=>s));return B([i,n]).pipe(m(([s,a])=>!(s&&a)),X(),Y(o.pipe(je(1))),re(!0),Tt({delay:250}),m(s=>({hidden:s})))}function ni(e,{viewport$:t,header$:r,main$:o,target$:n}){let i=new x,s=i.pipe(Z(),re(!0));return i.subscribe({next({hidden:a}){e.hidden=a,a?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(Y(s),ee("height")).subscribe(({height:a})=>{e.style.top=`${a+16}px`}),h(e,"click").subscribe(a=>{a.preventDefault(),window.scrollTo({top:0})}),Da(e,{viewport$:t,main$:o,target$:n}).pipe(w(a=>i.next(a)),A(()=>i.complete()),m(a=>R({ref:e},a)))}function ii({document$:e,tablet$:t}){e.pipe(E(()=>q(".md-toggle--indeterminate")),w(r=>{r.indeterminate=!0,r.checked=!1}),se(r=>h(r,"change").pipe(Pr(()=>r.classList.contains("md-toggle--indeterminate")),m(()=>r))),ne(t)).subscribe(([r,o])=>{r.classList.remove("md-toggle--indeterminate"),o&&(r.checked=!1)})}function Va(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function ai({document$:e}){e.pipe(E(()=>q("[data-md-scrollfix]")),w(t=>t.removeAttribute("data-md-scrollfix")),M(Va),se(t=>h(t,"touchstart").pipe(m(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function si({viewport$:e,tablet$:t}){B([We("search"),t]).pipe(m(([r,o])=>r&&!o),E(r=>j(r).pipe(ze(r?400:100))),ne(e)).subscribe(([r,{offset:{y:o}}])=>{if(r)document.body.setAttribute("data-md-scrolllock",""),document.body.style.top=`-${o}px`;else{let n=-1*parseInt(document.body.style.top,10);document.body.removeAttribute("data-md-scrolllock"),document.body.style.top="",n&&window.scrollTo(0,n)}})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let o=e[r];typeof o=="string"?o=document.createTextNode(o):o.parentNode&&o.parentNode.removeChild(o),r?t.insertBefore(this.previousSibling,o):t.replaceChild(o,this)}}}));function za(){return location.protocol==="file:"?ht(`${new URL("search/search_index.js",Yr.base)}`).pipe(m(()=>__index),J(1)):Ue(new URL("search/search_index.json",Yr.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var nt=No(),_t=Bo(),gt=Jo(_t),Br=Yo(),Te=nn(),mr=jr("(min-width: 960px)"),pi=jr("(min-width: 1220px)"),li=Xo(),Yr=me(),mi=document.forms.namedItem("search")?za():Ve,Gr=new x;Fn({alert$:Gr});var Jr=new x;te("navigation.instant")&&Wn({location$:_t,viewport$:Te,progress$:Jr}).subscribe(nt);var ci;((ci=Yr.version)==null?void 0:ci.provider)==="mike"&&qn({document$:nt});_(_t,gt).pipe(ze(125)).subscribe(()=>{Ke("drawer",!1),Ke("search",!1)});Br.pipe(M(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=ce("link[rel=prev]");typeof t!="undefined"&&ot(t);break;case"n":case".":let r=ce("link[rel=next]");typeof r!="undefined"&&ot(r);break;case"Enter":let o=Re();o instanceof HTMLLabelElement&&o.click()}});ii({document$:nt,tablet$:mr});ai({document$:nt});si({viewport$:Te,tablet$:mr});var Xe=kn(Ee("header"),{viewport$:Te}),Lt=nt.pipe(m(()=>Ee("main")),E(e=>Rn(e,{viewport$:Te,header$:Xe})),J(1)),qa=_(...oe("consent").map(e=>cn(e,{target$:gt})),...oe("dialog").map(e=>Cn(e,{alert$:Gr})),...oe("header").map(e=>Hn(e,{viewport$:Te,header$:Xe,main$:Lt})),...oe("palette").map(e=>Pn(e)),...oe("progress").map(e=>In(e,{progress$:Jr})),...oe("search").map(e=>Gn(e,{index$:mi,keyboard$:Br})),...oe("source").map(e=>ti(e))),Ka=H(()=>_(...oe("announce").map(e=>sn(e)),...oe("content").map(e=>An(e,{viewport$:Te,target$:gt,print$:li})),...oe("content").map(e=>te("search.highlight")?Jn(e,{index$:mi,location$:_t}):L),...oe("header-title").map(e=>$n(e,{viewport$:Te,header$:Xe})),...oe("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?Wr(pi,()=>Qr(e,{viewport$:Te,header$:Xe,main$:Lt})):Wr(mr,()=>Qr(e,{viewport$:Te,header$:Xe,main$:Lt}))),...oe("tabs").map(e=>ri(e,{viewport$:Te,header$:Xe})),...oe("toc").map(e=>oi(e,{viewport$:Te,header$:Xe,main$:Lt,target$:gt})),...oe("top").map(e=>ni(e,{viewport$:Te,header$:Xe,main$:Lt,target$:gt})))),fi=nt.pipe(E(()=>Ka),qe(qa),J(1));fi.subscribe();window.document$=nt;window.location$=_t;window.target$=gt;window.keyboard$=Br;window.viewport$=Te;window.tablet$=mr;window.screen$=pi;window.print$=li;window.alert$=Gr;window.progress$=Jr;window.component$=fi;})(); +//# sourceMappingURL=bundle.6eac0284.min.js.map + diff --git a/assets/javascripts/bundle.6eac0284.min.js.map b/assets/javascripts/bundle.6eac0284.min.js.map new file mode 100644 index 00000000..da5d4388 --- /dev/null +++ b/assets/javascripts/bundle.6eac0284.min.js.map @@ -0,0 +1,8 @@ +{ + "version": 3, + "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/clipboard/dist/clipboard.js", "node_modules/escape-html/index.js", "src/templates/assets/javascripts/bundle.ts", "node_modules/rxjs/node_modules/tslib/tslib.es6.js", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/util/errorContext.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/util/isReadableStreamLike.ts", "node_modules/rxjs/src/internal/observable/innerFrom.ts", "node_modules/rxjs/src/internal/util/executeSchedule.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/operators/subscribeOn.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleReadableStreamLike.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/observable/throwError.ts", "node_modules/rxjs/src/internal/util/EmptyError.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/fromEventPattern.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/audit.ts", "node_modules/rxjs/src/internal/operators/auditTime.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/throwIfEmpty.ts", "node_modules/rxjs/src/internal/operators/endWith.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/first.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/repeat.ts", "node_modules/rxjs/src/internal/operators/sample.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/throttleTime.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/templates/assets/javascripts/browser/document/index.ts", "src/templates/assets/javascripts/browser/element/_/index.ts", "src/templates/assets/javascripts/browser/element/focus/index.ts", "src/templates/assets/javascripts/browser/element/offset/_/index.ts", "src/templates/assets/javascripts/browser/element/offset/content/index.ts", "src/templates/assets/javascripts/utilities/h/index.ts", "src/templates/assets/javascripts/utilities/round/index.ts", "src/templates/assets/javascripts/browser/script/index.ts", "src/templates/assets/javascripts/browser/element/size/_/index.ts", "src/templates/assets/javascripts/browser/element/size/content/index.ts", "src/templates/assets/javascripts/browser/element/visibility/index.ts", "src/templates/assets/javascripts/browser/toggle/index.ts", "src/templates/assets/javascripts/browser/keyboard/index.ts", "src/templates/assets/javascripts/browser/location/_/index.ts", "src/templates/assets/javascripts/browser/location/hash/index.ts", "src/templates/assets/javascripts/browser/media/index.ts", "src/templates/assets/javascripts/browser/request/index.ts", "src/templates/assets/javascripts/browser/viewport/offset/index.ts", "src/templates/assets/javascripts/browser/viewport/size/index.ts", "src/templates/assets/javascripts/browser/viewport/_/index.ts", "src/templates/assets/javascripts/browser/viewport/at/index.ts", "src/templates/assets/javascripts/browser/worker/index.ts", "src/templates/assets/javascripts/_/index.ts", "src/templates/assets/javascripts/components/_/index.ts", "src/templates/assets/javascripts/components/announce/index.ts", "src/templates/assets/javascripts/components/consent/index.ts", "src/templates/assets/javascripts/components/content/annotation/_/index.ts", "src/templates/assets/javascripts/templates/tooltip/index.tsx", "src/templates/assets/javascripts/templates/annotation/index.tsx", "src/templates/assets/javascripts/templates/clipboard/index.tsx", "src/templates/assets/javascripts/templates/search/index.tsx", "src/templates/assets/javascripts/templates/source/index.tsx", "src/templates/assets/javascripts/templates/tabbed/index.tsx", "src/templates/assets/javascripts/templates/table/index.tsx", "src/templates/assets/javascripts/templates/version/index.tsx", "src/templates/assets/javascripts/components/content/annotation/list/index.ts", "src/templates/assets/javascripts/components/content/annotation/block/index.ts", "src/templates/assets/javascripts/components/content/code/_/index.ts", "src/templates/assets/javascripts/components/content/details/index.ts", "src/templates/assets/javascripts/components/content/mermaid/index.css", "src/templates/assets/javascripts/components/content/mermaid/index.ts", "src/templates/assets/javascripts/components/content/table/index.ts", "src/templates/assets/javascripts/components/content/tabs/index.ts", "src/templates/assets/javascripts/components/content/_/index.ts", "src/templates/assets/javascripts/components/dialog/index.ts", "src/templates/assets/javascripts/components/header/_/index.ts", "src/templates/assets/javascripts/components/header/title/index.ts", "src/templates/assets/javascripts/components/main/index.ts", "src/templates/assets/javascripts/components/palette/index.ts", "src/templates/assets/javascripts/components/progress/index.ts", "src/templates/assets/javascripts/integrations/clipboard/index.ts", "src/templates/assets/javascripts/integrations/sitemap/index.ts", "src/templates/assets/javascripts/integrations/instant/index.ts", "src/templates/assets/javascripts/integrations/search/highlighter/index.ts", "src/templates/assets/javascripts/integrations/search/worker/message/index.ts", "src/templates/assets/javascripts/integrations/search/worker/_/index.ts", "src/templates/assets/javascripts/integrations/version/index.ts", "src/templates/assets/javascripts/components/search/query/index.ts", "src/templates/assets/javascripts/components/search/result/index.ts", "src/templates/assets/javascripts/components/search/share/index.ts", "src/templates/assets/javascripts/components/search/suggest/index.ts", "src/templates/assets/javascripts/components/search/_/index.ts", "src/templates/assets/javascripts/components/search/highlight/index.ts", "src/templates/assets/javascripts/components/sidebar/index.ts", "src/templates/assets/javascripts/components/source/facts/github/index.ts", "src/templates/assets/javascripts/components/source/facts/gitlab/index.ts", "src/templates/assets/javascripts/components/source/facts/_/index.ts", "src/templates/assets/javascripts/components/source/_/index.ts", "src/templates/assets/javascripts/components/tabs/index.ts", "src/templates/assets/javascripts/components/toc/index.ts", "src/templates/assets/javascripts/components/top/index.ts", "src/templates/assets/javascripts/patches/indeterminate/index.ts", "src/templates/assets/javascripts/patches/scrollfix/index.ts", "src/templates/assets/javascripts/patches/scrolllock/index.ts", "src/templates/assets/javascripts/polyfills/index.ts"], + "sourceRoot": "../../../..", + "sourcesContent": ["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (factory());\n}(this, (function () { 'use strict';\n\n /**\n * Applies the :focus-visible polyfill at the given scope.\n * A scope in this case is either the top-level Document or a Shadow Root.\n *\n * @param {(Document|ShadowRoot)} scope\n * @see https://github.com/WICG/focus-visible\n */\n function applyFocusVisiblePolyfill(scope) {\n var hadKeyboardEvent = true;\n var hadFocusVisibleRecently = false;\n var hadFocusVisibleRecentlyTimeout = null;\n\n var inputTypesAllowlist = {\n text: true,\n search: true,\n url: true,\n tel: true,\n email: true,\n password: true,\n number: true,\n date: true,\n month: true,\n week: true,\n time: true,\n datetime: true,\n 'datetime-local': true\n };\n\n /**\n * Helper function for legacy browsers and iframes which sometimes focus\n * elements like document, body, and non-interactive SVG.\n * @param {Element} el\n */\n function isValidFocusTarget(el) {\n if (\n el &&\n el !== document &&\n el.nodeName !== 'HTML' &&\n el.nodeName !== 'BODY' &&\n 'classList' in el &&\n 'contains' in el.classList\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Computes whether the given element should automatically trigger the\n * `focus-visible` class being added, i.e. whether it should always match\n * `:focus-visible` when focused.\n * @param {Element} el\n * @return {boolean}\n */\n function focusTriggersKeyboardModality(el) {\n var type = el.type;\n var tagName = el.tagName;\n\n if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n return true;\n }\n\n if (tagName === 'TEXTAREA' && !el.readOnly) {\n return true;\n }\n\n if (el.isContentEditable) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Add the `focus-visible` class to the given element if it was not added by\n * the author.\n * @param {Element} el\n */\n function addFocusVisibleClass(el) {\n if (el.classList.contains('focus-visible')) {\n return;\n }\n el.classList.add('focus-visible');\n el.setAttribute('data-focus-visible-added', '');\n }\n\n /**\n * Remove the `focus-visible` class from the given element if it was not\n * originally added by the author.\n * @param {Element} el\n */\n function removeFocusVisibleClass(el) {\n if (!el.hasAttribute('data-focus-visible-added')) {\n return;\n }\n el.classList.remove('focus-visible');\n el.removeAttribute('data-focus-visible-added');\n }\n\n /**\n * If the most recent user interaction was via the keyboard;\n * and the key press did not include a meta, alt/option, or control key;\n * then the modality is keyboard. Otherwise, the modality is not keyboard.\n * Apply `focus-visible` to any current active element and keep track\n * of our keyboard modality state with `hadKeyboardEvent`.\n * @param {KeyboardEvent} e\n */\n function onKeyDown(e) {\n if (e.metaKey || e.altKey || e.ctrlKey) {\n return;\n }\n\n if (isValidFocusTarget(scope.activeElement)) {\n addFocusVisibleClass(scope.activeElement);\n }\n\n hadKeyboardEvent = true;\n }\n\n /**\n * If at any point a user clicks with a pointing device, ensure that we change\n * the modality away from keyboard.\n * This avoids the situation where a user presses a key on an already focused\n * element, and then clicks on a different element, focusing it with a\n * pointing device, while we still think we're in keyboard modality.\n * @param {Event} e\n */\n function onPointerDown(e) {\n hadKeyboardEvent = false;\n }\n\n /**\n * On `focus`, add the `focus-visible` class to the target if:\n * - the target received focus as a result of keyboard navigation, or\n * - the event target is an element that will likely require interaction\n * via the keyboard (e.g. a text box)\n * @param {Event} e\n */\n function onFocus(e) {\n // Prevent IE from focusing the document or HTML element.\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n addFocusVisibleClass(e.target);\n }\n }\n\n /**\n * On `blur`, remove the `focus-visible` class from the target.\n * @param {Event} e\n */\n function onBlur(e) {\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (\n e.target.classList.contains('focus-visible') ||\n e.target.hasAttribute('data-focus-visible-added')\n ) {\n // To detect a tab/window switch, we look for a blur event followed\n // rapidly by a visibility change.\n // If we don't see a visibility change within 100ms, it's probably a\n // regular focus change.\n hadFocusVisibleRecently = true;\n window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n hadFocusVisibleRecently = false;\n }, 100);\n removeFocusVisibleClass(e.target);\n }\n }\n\n /**\n * If the user changes tabs, keep track of whether or not the previously\n * focused element had .focus-visible.\n * @param {Event} e\n */\n function onVisibilityChange(e) {\n if (document.visibilityState === 'hidden') {\n // If the tab becomes active again, the browser will handle calling focus\n // on the element (Safari actually calls it twice).\n // If this tab change caused a blur on an element with focus-visible,\n // re-apply the class when the user switches back to the tab.\n if (hadFocusVisibleRecently) {\n hadKeyboardEvent = true;\n }\n addInitialPointerMoveListeners();\n }\n }\n\n /**\n * Add a group of listeners to detect usage of any pointing devices.\n * These listeners will be added when the polyfill first loads, and anytime\n * the window is blurred, so that they are active when the window regains\n * focus.\n */\n function addInitialPointerMoveListeners() {\n document.addEventListener('mousemove', onInitialPointerMove);\n document.addEventListener('mousedown', onInitialPointerMove);\n document.addEventListener('mouseup', onInitialPointerMove);\n document.addEventListener('pointermove', onInitialPointerMove);\n document.addEventListener('pointerdown', onInitialPointerMove);\n document.addEventListener('pointerup', onInitialPointerMove);\n document.addEventListener('touchmove', onInitialPointerMove);\n document.addEventListener('touchstart', onInitialPointerMove);\n document.addEventListener('touchend', onInitialPointerMove);\n }\n\n function removeInitialPointerMoveListeners() {\n document.removeEventListener('mousemove', onInitialPointerMove);\n document.removeEventListener('mousedown', onInitialPointerMove);\n document.removeEventListener('mouseup', onInitialPointerMove);\n document.removeEventListener('pointermove', onInitialPointerMove);\n document.removeEventListener('pointerdown', onInitialPointerMove);\n document.removeEventListener('pointerup', onInitialPointerMove);\n document.removeEventListener('touchmove', onInitialPointerMove);\n document.removeEventListener('touchstart', onInitialPointerMove);\n document.removeEventListener('touchend', onInitialPointerMove);\n }\n\n /**\n * When the polfyill first loads, assume the user is in keyboard modality.\n * If any event is received from a pointing device (e.g. mouse, pointer,\n * touch), turn off keyboard modality.\n * This accounts for situations where focus enters the page from the URL bar.\n * @param {Event} e\n */\n function onInitialPointerMove(e) {\n // Work around a Safari quirk that fires a mousemove on whenever the\n // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n return;\n }\n\n hadKeyboardEvent = false;\n removeInitialPointerMoveListeners();\n }\n\n // For some kinds of state, we are interested in changes at the global scope\n // only. For example, global pointer input, global key presses and global\n // visibility change should affect the state at every scope:\n document.addEventListener('keydown', onKeyDown, true);\n document.addEventListener('mousedown', onPointerDown, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('touchstart', onPointerDown, true);\n document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n addInitialPointerMoveListeners();\n\n // For focus and blur, we specifically care about state changes in the local\n // scope. This is because focus / blur events that originate from within a\n // shadow root are not re-dispatched from the host element if it was already\n // the active element in its own scope:\n scope.addEventListener('focus', onFocus, true);\n scope.addEventListener('blur', onBlur, true);\n\n // We detect that a node is a ShadowRoot by ensuring that it is a\n // DocumentFragment and also has a host property. This check covers native\n // implementation and polyfill implementation transparently. If we only cared\n // about the native implementation, we could just check if the scope was\n // an instance of a ShadowRoot.\n if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n // have a root element to add a class to. So, we add this attribute to the\n // host element instead:\n scope.host.setAttribute('data-js-focus-visible', '');\n } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n document.documentElement.classList.add('js-focus-visible');\n document.documentElement.setAttribute('data-js-focus-visible', '');\n }\n }\n\n // It is important to wrap all references to global window and document in\n // these checks to support server-side rendering use cases\n // @see https://github.com/WICG/focus-visible/issues/199\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n // Make the polyfill helper globally available. This can be used as a signal\n // to interested libraries that wish to coordinate with the polyfill for e.g.,\n // applying the polyfill to a shadow root:\n window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n // Notify interested libraries of the polyfill's presence, in case the\n // polyfill was loaded lazily:\n var event;\n\n try {\n event = new CustomEvent('focus-visible-polyfill-ready');\n } catch (error) {\n // IE11 does not support using CustomEvent as a constructor directly:\n event = document.createEvent('CustomEvent');\n event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n }\n\n window.dispatchEvent(event);\n }\n\n if (typeof document !== 'undefined') {\n // Apply the polyfill to the global document, so that no JavaScript\n // coordination is required to use the polyfill in the top-level document:\n applyFocusVisiblePolyfill(document);\n }\n\n})));\n", "/*!\n * clipboard.js v2.0.11\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 686:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/common/command.js\n/**\n * Executes a given operation type.\n * @param {String} type\n * @return {Boolean}\n */\nfunction command(type) {\n try {\n return document.execCommand(type);\n } catch (err) {\n return false;\n }\n}\n;// CONCATENATED MODULE: ./src/actions/cut.js\n\n\n/**\n * Cut action wrapper.\n * @param {String|HTMLElement} target\n * @return {String}\n */\n\nvar ClipboardActionCut = function ClipboardActionCut(target) {\n var selectedText = select_default()(target);\n command('cut');\n return selectedText;\n};\n\n/* harmony default export */ var actions_cut = (ClipboardActionCut);\n;// CONCATENATED MODULE: ./src/common/create-fake-element.js\n/**\n * Creates a fake textarea element with a value.\n * @param {String} value\n * @return {HTMLElement}\n */\nfunction createFakeElement(value) {\n var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS\n\n fakeElement.style.fontSize = '12pt'; // Reset box model\n\n fakeElement.style.border = '0';\n fakeElement.style.padding = '0';\n fakeElement.style.margin = '0'; // Move element out of screen horizontally\n\n fakeElement.style.position = 'absolute';\n fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n fakeElement.style.top = \"\".concat(yPosition, \"px\");\n fakeElement.setAttribute('readonly', '');\n fakeElement.value = value;\n return fakeElement;\n}\n;// CONCATENATED MODULE: ./src/actions/copy.js\n\n\n\n/**\n * Create fake copy action wrapper using a fake element.\n * @param {String} target\n * @param {Object} options\n * @return {String}\n */\n\nvar fakeCopyAction = function fakeCopyAction(value, options) {\n var fakeElement = createFakeElement(value);\n options.container.appendChild(fakeElement);\n var selectedText = select_default()(fakeElement);\n command('copy');\n fakeElement.remove();\n return selectedText;\n};\n/**\n * Copy action wrapper.\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @return {String}\n */\n\n\nvar ClipboardActionCopy = function ClipboardActionCopy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n var selectedText = '';\n\n if (typeof target === 'string') {\n selectedText = fakeCopyAction(target, options);\n } else if (target instanceof HTMLInputElement && !['text', 'search', 'url', 'tel', 'password'].includes(target === null || target === void 0 ? void 0 : target.type)) {\n // If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n selectedText = fakeCopyAction(target.value, options);\n } else {\n selectedText = select_default()(target);\n command('copy');\n }\n\n return selectedText;\n};\n\n/* harmony default export */ var actions_copy = (ClipboardActionCopy);\n;// CONCATENATED MODULE: ./src/actions/default.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\n\n\n/**\n * Inner function which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n * @param {Object} options\n */\n\nvar ClipboardActionDefault = function ClipboardActionDefault() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n // Defines base properties passed from constructor.\n var _options$action = options.action,\n action = _options$action === void 0 ? 'copy' : _options$action,\n container = options.container,\n target = options.target,\n text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'.\n\n if (action !== 'copy' && action !== 'cut') {\n throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n } // Sets the `target` property using an element that will be have its content copied.\n\n\n if (target !== undefined) {\n if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n if (action === 'copy' && target.hasAttribute('disabled')) {\n throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n }\n\n if (action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n }\n } else {\n throw new Error('Invalid \"target\" value, use a valid Element');\n }\n } // Define selection strategy based on `text` property.\n\n\n if (text) {\n return actions_copy(text, {\n container: container\n });\n } // Defines which selection strategy based on `target` property.\n\n\n if (target) {\n return action === 'cut' ? actions_cut(target) : actions_copy(target, {\n container: container\n });\n }\n};\n\n/* harmony default export */ var actions_default = (ClipboardActionDefault);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n var attribute = \"data-clipboard-\".concat(suffix);\n\n if (!element.hasAttribute(attribute)) {\n return;\n }\n\n return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n _inherits(Clipboard, _Emitter);\n\n var _super = _createSuper(Clipboard);\n\n /**\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n * @param {Object} options\n */\n function Clipboard(trigger, options) {\n var _this;\n\n _classCallCheck(this, Clipboard);\n\n _this = _super.call(this);\n\n _this.resolveOptions(options);\n\n _this.listenClick(trigger);\n\n return _this;\n }\n /**\n * Defines if attributes would be resolved using internal setter functions\n * or custom functions that were passed in the constructor.\n * @param {Object} options\n */\n\n\n _createClass(Clipboard, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n }\n /**\n * Adds a click event listener to the passed trigger.\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n */\n\n }, {\n key: \"listenClick\",\n value: function listenClick(trigger) {\n var _this2 = this;\n\n this.listener = listen_default()(trigger, 'click', function (e) {\n return _this2.onClick(e);\n });\n }\n /**\n * Defines a new `ClipboardAction` on each click event.\n * @param {Event} e\n */\n\n }, {\n key: \"onClick\",\n value: function onClick(e) {\n var trigger = e.delegateTarget || e.currentTarget;\n var action = this.action(trigger) || 'copy';\n var text = actions_default({\n action: action,\n container: this.container,\n target: this.target(trigger),\n text: this.text(trigger)\n }); // Fires an event based on the copy operation result.\n\n this.emit(text ? 'success' : 'error', {\n action: action,\n text: text,\n trigger: trigger,\n clearSelection: function clearSelection() {\n if (trigger) {\n trigger.focus();\n }\n\n window.getSelection().removeAllRanges();\n }\n });\n }\n /**\n * Default `action` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultAction\",\n value: function defaultAction(trigger) {\n return getAttributeValue('action', trigger);\n }\n /**\n * Default `target` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultTarget\",\n value: function defaultTarget(trigger) {\n var selector = getAttributeValue('target', trigger);\n\n if (selector) {\n return document.querySelector(selector);\n }\n }\n /**\n * Allow fire programmatically a copy action\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @returns Text copied.\n */\n\n }, {\n key: \"defaultText\",\n\n /**\n * Default `text` lookup function.\n * @param {Element} trigger\n */\n value: function defaultText(trigger) {\n return getAttributeValue('text', trigger);\n }\n /**\n * Destroy lifecycle.\n */\n\n }, {\n key: \"destroy\",\n value: function destroy() {\n this.listener.destroy();\n }\n }], [{\n key: \"copy\",\n value: function copy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n return actions_copy(target, options);\n }\n /**\n * Allow fire programmatically a cut action\n * @param {String|HTMLElement} target\n * @returns Text cutted.\n */\n\n }, {\n key: \"cut\",\n value: function cut(target) {\n return actions_cut(target);\n }\n /**\n * Returns the support of the given action, or all actions if no action is\n * given.\n * @param {String} [action]\n */\n\n }, {\n key: \"isSupported\",\n value: function isSupported() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n var actions = typeof action === 'string' ? [action] : action;\n var support = !!document.queryCommandSupported;\n actions.forEach(function (action) {\n support = support && !!document.queryCommandSupported(action);\n });\n return support;\n }\n }]);\n\n return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n var proto = Element.prototype;\n\n proto.matches = proto.matchesSelector ||\n proto.mozMatchesSelector ||\n proto.msMatchesSelector ||\n proto.oMatchesSelector ||\n proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n if (typeof element.matches === 'function' &&\n element.matches(selector)) {\n return element;\n }\n element = element.parentNode;\n }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n var listenerFn = listener.apply(this, arguments);\n\n element.addEventListener(type, listenerFn, useCapture);\n\n return {\n destroy: function() {\n element.removeEventListener(type, listenerFn, useCapture);\n }\n }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n // Handle the regular Element usage\n if (typeof elements.addEventListener === 'function') {\n return _delegate.apply(null, arguments);\n }\n\n // Handle Element-less usage, it defaults to global delegation\n if (typeof type === 'function') {\n // Use `document` as the first parameter, then apply arguments\n // This is a short way to .unshift `arguments` without running into deoptimizations\n return _delegate.bind(null, document).apply(null, arguments);\n }\n\n // Handle Selector-based usage\n if (typeof elements === 'string') {\n elements = document.querySelectorAll(elements);\n }\n\n // Handle Array-like based usage\n return Array.prototype.map.call(elements, function (element) {\n return _delegate(element, selector, type, callback, useCapture);\n });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n return function(e) {\n e.delegateTarget = closest(e.target, selector);\n\n if (e.delegateTarget) {\n callback.call(element, e);\n }\n }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n return value !== undefined\n && value instanceof HTMLElement\n && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return value !== undefined\n && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n && ('length' in value)\n && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n return typeof value === 'string'\n || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n if (!target && !type && !callback) {\n throw new Error('Missing required arguments');\n }\n\n if (!is.string(type)) {\n throw new TypeError('Second argument must be a String');\n }\n\n if (!is.fn(callback)) {\n throw new TypeError('Third argument must be a Function');\n }\n\n if (is.node(target)) {\n return listenNode(target, type, callback);\n }\n else if (is.nodeList(target)) {\n return listenNodeList(target, type, callback);\n }\n else if (is.string(target)) {\n return listenSelector(target, type, callback);\n }\n else {\n throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n node.addEventListener(type, callback);\n\n return {\n destroy: function() {\n node.removeEventListener(type, callback);\n }\n }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.addEventListener(type, callback);\n });\n\n return {\n destroy: function() {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.removeEventListener(type, callback);\n });\n }\n }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n var selectedText;\n\n if (element.nodeName === 'SELECT') {\n element.focus();\n\n selectedText = element.value;\n }\n else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n var isReadOnly = element.hasAttribute('readonly');\n\n if (!isReadOnly) {\n element.setAttribute('readonly', '');\n }\n\n element.select();\n element.setSelectionRange(0, element.value.length);\n\n if (!isReadOnly) {\n element.removeAttribute('readonly');\n }\n\n selectedText = element.value;\n }\n else {\n if (element.hasAttribute('contenteditable')) {\n element.focus();\n }\n\n var selection = window.getSelection();\n var range = document.createRange();\n\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n\n selectedText = selection.toString();\n }\n\n return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n // Keep this empty so it's easier to inherit from\n // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n on: function (name, callback, ctx) {\n var e = this.e || (this.e = {});\n\n (e[name] || (e[name] = [])).push({\n fn: callback,\n ctx: ctx\n });\n\n return this;\n },\n\n once: function (name, callback, ctx) {\n var self = this;\n function listener () {\n self.off(name, listener);\n callback.apply(ctx, arguments);\n };\n\n listener._ = callback\n return this.on(name, listener, ctx);\n },\n\n emit: function (name) {\n var data = [].slice.call(arguments, 1);\n var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n var i = 0;\n var len = evtArr.length;\n\n for (i; i < len; i++) {\n evtArr[i].fn.apply(evtArr[i].ctx, data);\n }\n\n return this;\n },\n\n off: function (name, callback) {\n var e = this.e || (this.e = {});\n var evts = e[name];\n var liveEvents = [];\n\n if (evts && callback) {\n for (var i = 0, len = evts.length; i < len; i++) {\n if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n liveEvents.push(evts[i]);\n }\n }\n\n // Remove event from queue to prevent memory leak\n // Suggested by https://github.com/lazd\n // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n (liveEvents.length)\n ? e[name] = liveEvents\n : delete e[name];\n\n return this;\n }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(686);\n/******/ })()\n.default;\n});", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*\n * Copyright (c) 2016-2023 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\n\nimport {\n EMPTY,\n NEVER,\n Observable,\n Subject,\n defer,\n delay,\n filter,\n map,\n merge,\n mergeWith,\n shareReplay,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n at,\n getActiveElement,\n getOptionalElement,\n requestJSON,\n setLocation,\n setToggle,\n watchDocument,\n watchKeyboard,\n watchLocation,\n watchLocationTarget,\n watchMedia,\n watchPrint,\n watchScript,\n watchViewport\n} from \"./browser\"\nimport {\n getComponentElement,\n getComponentElements,\n mountAnnounce,\n mountBackToTop,\n mountConsent,\n mountContent,\n mountDialog,\n mountHeader,\n mountHeaderTitle,\n mountPalette,\n mountProgress,\n mountSearch,\n mountSearchHiglight,\n mountSidebar,\n mountSource,\n mountTableOfContents,\n mountTabs,\n watchHeader,\n watchMain\n} from \"./components\"\nimport {\n SearchIndex,\n setupClipboardJS,\n setupInstantNavigation,\n setupVersionSelector\n} from \"./integrations\"\nimport {\n patchIndeterminate,\n patchScrollfix,\n patchScrolllock\n} from \"./patches\"\nimport \"./polyfills\"\n\n/* ----------------------------------------------------------------------------\n * Functions - @todo refactor\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch search index\n *\n * @returns Search index observable\n */\nfunction fetchSearchIndex(): Observable {\n if (location.protocol === \"file:\") {\n return watchScript(\n `${new URL(\"search/search_index.js\", config.base)}`\n )\n .pipe(\n // @ts-ignore - @todo fix typings\n map(() => __index),\n shareReplay(1)\n )\n } else {\n return requestJSON(\n new URL(\"search/search_index.json\", config.base)\n )\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$ = watchLocationTarget(location$)\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$ = watchMedia(\"(min-width: 960px)\")\nconst screen$ = watchMedia(\"(min-width: 1220px)\")\nconst print$ = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n ? fetchSearchIndex()\n : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject()\nsetupClipboardJS({ alert$ })\n\n/* Set up progress indicator */\nconst progress$ = new Subject()\n\n/* Set up instant navigation, if enabled */\nif (feature(\"navigation.instant\"))\n setupInstantNavigation({ location$, viewport$, progress$ })\n .subscribe(document$)\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n setupVersionSelector({ document$ })\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n .pipe(\n delay(125)\n )\n .subscribe(() => {\n setToggle(\"drawer\", false)\n setToggle(\"search\", false)\n })\n\n/* Set up global keyboard handlers */\nkeyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Go to previous page */\n case \"p\":\n case \",\":\n const prev = getOptionalElement(\"link[rel=prev]\")\n if (typeof prev !== \"undefined\")\n setLocation(prev)\n break\n\n /* Go to next page */\n case \"n\":\n case \".\":\n const next = getOptionalElement(\"link[rel=next]\")\n if (typeof next !== \"undefined\")\n setLocation(next)\n break\n\n /* Expand navigation, see https://bit.ly/3ZjG5io */\n case \"Enter\":\n const active = getActiveElement()\n if (active instanceof HTMLLabelElement)\n active.click()\n }\n })\n\n/* Set up patches */\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n .pipe(\n map(() => getComponentElement(\"main\")),\n switchMap(el => watchMain(el, { viewport$, header$ })),\n shareReplay(1)\n )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n /* Consent */\n ...getComponentElements(\"consent\")\n .map(el => mountConsent(el, { target$ })),\n\n /* Dialog */\n ...getComponentElements(\"dialog\")\n .map(el => mountDialog(el, { alert$ })),\n\n /* Header */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n /* Color palette */\n ...getComponentElements(\"palette\")\n .map(el => mountPalette(el)),\n\n /* Progress bar */\n ...getComponentElements(\"progress\")\n .map(el => mountProgress(el, { progress$ })),\n\n /* Search */\n ...getComponentElements(\"search\")\n .map(el => mountSearch(el, { index$, keyboard$ })),\n\n /* Repository information */\n ...getComponentElements(\"source\")\n .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n /* Announcement bar */\n ...getComponentElements(\"announce\")\n .map(el => mountAnnounce(el)),\n\n /* Content */\n ...getComponentElements(\"content\")\n .map(el => mountContent(el, { viewport$, target$, print$ })),\n\n /* Search highlighting */\n ...getComponentElements(\"content\")\n .map(el => feature(\"search.highlight\")\n ? mountSearchHiglight(el, { index$, location$ })\n : EMPTY\n ),\n\n /* Header title */\n ...getComponentElements(\"header-title\")\n .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n /* Sidebar */\n ...getComponentElements(\"sidebar\")\n .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n ),\n\n /* Navigation tabs */\n ...getComponentElements(\"tabs\")\n .map(el => mountTabs(el, { viewport$, header$ })),\n\n /* Table of contents */\n ...getComponentElements(\"toc\")\n .map(el => mountTableOfContents(el, {\n viewport$, header$, main$, target$\n })),\n\n /* Back-to-top button */\n ...getComponentElements(\"top\")\n .map(el => mountBackToTop(el, { viewport$, header$, main$, target$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n .pipe(\n switchMap(() => content$),\n mergeWith(control$),\n shareReplay(1)\n )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$ = document$ /* Document observable */\nwindow.location$ = location$ /* Location subject */\nwindow.target$ = target$ /* Location target observable */\nwindow.keyboard$ = keyboard$ /* Keyboard observable */\nwindow.viewport$ = viewport$ /* Viewport observable */\nwindow.tablet$ = tablet$ /* Media tablet observable */\nwindow.screen$ = screen$ /* Media screen observable */\nwindow.print$ = print$ /* Media print observable */\nwindow.alert$ = alert$ /* Alert subject */\nwindow.progress$ = progress$ /* Progress indicator subject */\nwindow.component$ = component$ /* Component observable */\n", "/*! *****************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __metadata(metadataKey, metadataValue) {\r\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nexport function __awaiter(thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n}\r\n\r\nexport function __generator(thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (_) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n}\r\n\r\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });\r\n}) : (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n o[k2] = m[k];\r\n});\r\n\r\nexport function __exportStar(m, o) {\r\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n}\r\n\r\nexport function __values(o) {\r\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === \"number\") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n}\r\n\r\nexport function __read(o, n) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spread() {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spreadArrays() {\r\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n r[k] = a[j];\r\n return r;\r\n}\r\n\r\nexport function __spreadArray(to, from, pack) {\r\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\r\n if (ar || !(i in from)) {\r\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\r\n ar[i] = from[i];\r\n }\r\n }\r\n return to.concat(ar || Array.prototype.slice.call(from));\r\n}\r\n\r\nexport function __await(v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nexport function __asyncGenerator(thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nexport function __asyncDelegator(o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === \"return\" } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nexport function __asyncValues(o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nexport function __makeTemplateObject(cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n};\r\n\r\nvar __setModuleDefault = Object.create ? (function(o, v) {\r\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n}) : function(o, v) {\r\n o[\"default\"] = v;\r\n};\r\n\r\nexport function __importStar(mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\r\n __setModuleDefault(result, mod);\r\n return result;\r\n}\r\n\r\nexport function __importDefault(mod) {\r\n return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n\r\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n}\r\n\r\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n}\r\n", "/**\n * Returns true if the object is a function.\n * @param value The value to check\n */\nexport function isFunction(value: any): value is (...args: any[]) => any {\n return typeof value === 'function';\n}\n", "/**\n * Used to create Error subclasses until the community moves away from ES5.\n *\n * This is because compiling from TypeScript down to ES5 has issues with subclassing Errors\n * as well as other built-in types: https://github.com/Microsoft/TypeScript/issues/12123\n *\n * @param createImpl A factory function to create the actual constructor implementation. The returned\n * function should be a named function that calls `_super` internally.\n */\nexport function createErrorClass(createImpl: (_super: any) => any): T {\n const _super = (instance: any) => {\n Error.call(instance);\n instance.stack = new Error().stack;\n };\n\n const ctorFunc = createImpl(_super);\n ctorFunc.prototype = Object.create(Error.prototype);\n ctorFunc.prototype.constructor = ctorFunc;\n return ctorFunc;\n}\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface UnsubscriptionError extends Error {\n readonly errors: any[];\n}\n\nexport interface UnsubscriptionErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (errors: any[]): UnsubscriptionError;\n}\n\n/**\n * An error thrown when one or more errors have occurred during the\n * `unsubscribe` of a {@link Subscription}.\n */\nexport const UnsubscriptionError: UnsubscriptionErrorCtor = createErrorClass(\n (_super) =>\n function UnsubscriptionErrorImpl(this: any, errors: (Error | string)[]) {\n _super(this);\n this.message = errors\n ? `${errors.length} errors occurred during unsubscription:\n${errors.map((err, i) => `${i + 1}) ${err.toString()}`).join('\\n ')}`\n : '';\n this.name = 'UnsubscriptionError';\n this.errors = errors;\n }\n);\n", "/**\n * Removes an item from an array, mutating it.\n * @param arr The array to remove the item from\n * @param item The item to remove\n */\nexport function arrRemove(arr: T[] | undefined | null, item: T) {\n if (arr) {\n const index = arr.indexOf(item);\n 0 <= index && arr.splice(index, 1);\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { UnsubscriptionError } from './util/UnsubscriptionError';\nimport { SubscriptionLike, TeardownLogic, Unsubscribable } from './types';\nimport { arrRemove } from './util/arrRemove';\n\n/**\n * Represents a disposable resource, such as the execution of an Observable. A\n * Subscription has one important method, `unsubscribe`, that takes no argument\n * and just disposes the resource held by the subscription.\n *\n * Additionally, subscriptions may be grouped together through the `add()`\n * method, which will attach a child Subscription to the current Subscription.\n * When a Subscription is unsubscribed, all its children (and its grandchildren)\n * will be unsubscribed as well.\n *\n * @class Subscription\n */\nexport class Subscription implements SubscriptionLike {\n /** @nocollapse */\n public static EMPTY = (() => {\n const empty = new Subscription();\n empty.closed = true;\n return empty;\n })();\n\n /**\n * A flag to indicate whether this Subscription has already been unsubscribed.\n */\n public closed = false;\n\n private _parentage: Subscription[] | Subscription | null = null;\n\n /**\n * The list of registered finalizers to execute upon unsubscription. Adding and removing from this\n * list occurs in the {@link #add} and {@link #remove} methods.\n */\n private _finalizers: Exclude[] | null = null;\n\n /**\n * @param initialTeardown A function executed first as part of the finalization\n * process that is kicked off when {@link #unsubscribe} is called.\n */\n constructor(private initialTeardown?: () => void) {}\n\n /**\n * Disposes the resources held by the subscription. May, for instance, cancel\n * an ongoing Observable execution or cancel any other type of work that\n * started when the Subscription was created.\n * @return {void}\n */\n unsubscribe(): void {\n let errors: any[] | undefined;\n\n if (!this.closed) {\n this.closed = true;\n\n // Remove this from it's parents.\n const { _parentage } = this;\n if (_parentage) {\n this._parentage = null;\n if (Array.isArray(_parentage)) {\n for (const parent of _parentage) {\n parent.remove(this);\n }\n } else {\n _parentage.remove(this);\n }\n }\n\n const { initialTeardown: initialFinalizer } = this;\n if (isFunction(initialFinalizer)) {\n try {\n initialFinalizer();\n } catch (e) {\n errors = e instanceof UnsubscriptionError ? e.errors : [e];\n }\n }\n\n const { _finalizers } = this;\n if (_finalizers) {\n this._finalizers = null;\n for (const finalizer of _finalizers) {\n try {\n execFinalizer(finalizer);\n } catch (err) {\n errors = errors ?? [];\n if (err instanceof UnsubscriptionError) {\n errors = [...errors, ...err.errors];\n } else {\n errors.push(err);\n }\n }\n }\n }\n\n if (errors) {\n throw new UnsubscriptionError(errors);\n }\n }\n }\n\n /**\n * Adds a finalizer to this subscription, so that finalization will be unsubscribed/called\n * when this subscription is unsubscribed. If this subscription is already {@link #closed},\n * because it has already been unsubscribed, then whatever finalizer is passed to it\n * will automatically be executed (unless the finalizer itself is also a closed subscription).\n *\n * Closed Subscriptions cannot be added as finalizers to any subscription. Adding a closed\n * subscription to a any subscription will result in no operation. (A noop).\n *\n * Adding a subscription to itself, or adding `null` or `undefined` will not perform any\n * operation at all. (A noop).\n *\n * `Subscription` instances that are added to this instance will automatically remove themselves\n * if they are unsubscribed. Functions and {@link Unsubscribable} objects that you wish to remove\n * will need to be removed manually with {@link #remove}\n *\n * @param teardown The finalization logic to add to this subscription.\n */\n add(teardown: TeardownLogic): void {\n // Only add the finalizer if it's not undefined\n // and don't add a subscription to itself.\n if (teardown && teardown !== this) {\n if (this.closed) {\n // If this subscription is already closed,\n // execute whatever finalizer is handed to it automatically.\n execFinalizer(teardown);\n } else {\n if (teardown instanceof Subscription) {\n // We don't add closed subscriptions, and we don't add the same subscription\n // twice. Subscription unsubscribe is idempotent.\n if (teardown.closed || teardown._hasParent(this)) {\n return;\n }\n teardown._addParent(this);\n }\n (this._finalizers = this._finalizers ?? []).push(teardown);\n }\n }\n }\n\n /**\n * Checks to see if a this subscription already has a particular parent.\n * This will signal that this subscription has already been added to the parent in question.\n * @param parent the parent to check for\n */\n private _hasParent(parent: Subscription) {\n const { _parentage } = this;\n return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));\n }\n\n /**\n * Adds a parent to this subscription so it can be removed from the parent if it\n * unsubscribes on it's own.\n *\n * NOTE: THIS ASSUMES THAT {@link _hasParent} HAS ALREADY BEEN CHECKED.\n * @param parent The parent subscription to add\n */\n private _addParent(parent: Subscription) {\n const { _parentage } = this;\n this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;\n }\n\n /**\n * Called on a child when it is removed via {@link #remove}.\n * @param parent The parent to remove\n */\n private _removeParent(parent: Subscription) {\n const { _parentage } = this;\n if (_parentage === parent) {\n this._parentage = null;\n } else if (Array.isArray(_parentage)) {\n arrRemove(_parentage, parent);\n }\n }\n\n /**\n * Removes a finalizer from this subscription that was previously added with the {@link #add} method.\n *\n * Note that `Subscription` instances, when unsubscribed, will automatically remove themselves\n * from every other `Subscription` they have been added to. This means that using the `remove` method\n * is not a common thing and should be used thoughtfully.\n *\n * If you add the same finalizer instance of a function or an unsubscribable object to a `Subscription` instance\n * more than once, you will need to call `remove` the same number of times to remove all instances.\n *\n * All finalizer instances are removed to free up memory upon unsubscription.\n *\n * @param teardown The finalizer to remove from this subscription\n */\n remove(teardown: Exclude): void {\n const { _finalizers } = this;\n _finalizers && arrRemove(_finalizers, teardown);\n\n if (teardown instanceof Subscription) {\n teardown._removeParent(this);\n }\n }\n}\n\nexport const EMPTY_SUBSCRIPTION = Subscription.EMPTY;\n\nexport function isSubscription(value: any): value is Subscription {\n return (\n value instanceof Subscription ||\n (value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe))\n );\n}\n\nfunction execFinalizer(finalizer: Unsubscribable | (() => void)) {\n if (isFunction(finalizer)) {\n finalizer();\n } else {\n finalizer.unsubscribe();\n }\n}\n", "import { Subscriber } from './Subscriber';\nimport { ObservableNotification } from './types';\n\n/**\n * The {@link GlobalConfig} object for RxJS. It is used to configure things\n * like how to react on unhandled errors.\n */\nexport const config: GlobalConfig = {\n onUnhandledError: null,\n onStoppedNotification: null,\n Promise: undefined,\n useDeprecatedSynchronousErrorHandling: false,\n useDeprecatedNextContext: false,\n};\n\n/**\n * The global configuration object for RxJS, used to configure things\n * like how to react on unhandled errors. Accessible via {@link config}\n * object.\n */\nexport interface GlobalConfig {\n /**\n * A registration point for unhandled errors from RxJS. These are errors that\n * cannot were not handled by consuming code in the usual subscription path. For\n * example, if you have this configured, and you subscribe to an observable without\n * providing an error handler, errors from that subscription will end up here. This\n * will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onUnhandledError: ((err: any) => void) | null;\n\n /**\n * A registration point for notifications that cannot be sent to subscribers because they\n * have completed, errored or have been explicitly unsubscribed. By default, next, complete\n * and error notifications sent to stopped subscribers are noops. However, sometimes callers\n * might want a different behavior. For example, with sources that attempt to report errors\n * to stopped subscribers, a caller can configure RxJS to throw an unhandled error instead.\n * This will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onStoppedNotification: ((notification: ObservableNotification, subscriber: Subscriber) => void) | null;\n\n /**\n * The promise constructor used by default for {@link Observable#toPromise toPromise} and {@link Observable#forEach forEach}\n * methods.\n *\n * @deprecated As of version 8, RxJS will no longer support this sort of injection of a\n * Promise constructor. If you need a Promise implementation other than native promises,\n * please polyfill/patch Promise as you see appropriate. Will be removed in v8.\n */\n Promise?: PromiseConstructorLike;\n\n /**\n * If true, turns on synchronous error rethrowing, which is a deprecated behavior\n * in v6 and higher. This behavior enables bad patterns like wrapping a subscribe\n * call in a try/catch block. It also enables producer interference, a nasty bug\n * where a multicast can be broken for all observers by a downstream consumer with\n * an unhandled error. DO NOT USE THIS FLAG UNLESS IT'S NEEDED TO BUY TIME\n * FOR MIGRATION REASONS.\n *\n * @deprecated As of version 8, RxJS will no longer support synchronous throwing\n * of unhandled errors. All errors will be thrown on a separate call stack to prevent bad\n * behaviors described above. Will be removed in v8.\n */\n useDeprecatedSynchronousErrorHandling: boolean;\n\n /**\n * If true, enables an as-of-yet undocumented feature from v5: The ability to access\n * `unsubscribe()` via `this` context in `next` functions created in observers passed\n * to `subscribe`.\n *\n * This is being removed because the performance was severely problematic, and it could also cause\n * issues when types other than POJOs are passed to subscribe as subscribers, as they will likely have\n * their `this` context overwritten.\n *\n * @deprecated As of version 8, RxJS will no longer support altering the\n * context of next functions provided as part of an observer to Subscribe. Instead,\n * you will have access to a subscription or a signal or token that will allow you to do things like\n * unsubscribe and test closed status. Will be removed in v8.\n */\n useDeprecatedNextContext: boolean;\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetTimeoutFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearTimeoutFunction = (handle: TimerHandle) => void;\n\ninterface TimeoutProvider {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n delegate:\n | {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n }\n | undefined;\n}\n\nexport const timeoutProvider: TimeoutProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setTimeout(handler: () => void, timeout?: number, ...args) {\n const { delegate } = timeoutProvider;\n if (delegate?.setTimeout) {\n return delegate.setTimeout(handler, timeout, ...args);\n }\n return setTimeout(handler, timeout, ...args);\n },\n clearTimeout(handle) {\n const { delegate } = timeoutProvider;\n return (delegate?.clearTimeout || clearTimeout)(handle as any);\n },\n delegate: undefined,\n};\n", "import { config } from '../config';\nimport { timeoutProvider } from '../scheduler/timeoutProvider';\n\n/**\n * Handles an error on another job either with the user-configured {@link onUnhandledError},\n * or by throwing it on that new job so it can be picked up by `window.onerror`, `process.on('error')`, etc.\n *\n * This should be called whenever there is an error that is out-of-band with the subscription\n * or when an error hits a terminal boundary of the subscription and no error handler was provided.\n *\n * @param err the error to report\n */\nexport function reportUnhandledError(err: any) {\n timeoutProvider.setTimeout(() => {\n const { onUnhandledError } = config;\n if (onUnhandledError) {\n // Execute the user-configured error handler.\n onUnhandledError(err);\n } else {\n // Throw so it is picked up by the runtime's uncaught error mechanism.\n throw err;\n }\n });\n}\n", "/* tslint:disable:no-empty */\nexport function noop() { }\n", "import { CompleteNotification, NextNotification, ErrorNotification } from './types';\n\n/**\n * A completion object optimized for memory use and created to be the\n * same \"shape\" as other notifications in v8.\n * @internal\n */\nexport const COMPLETE_NOTIFICATION = (() => createNotification('C', undefined, undefined) as CompleteNotification)();\n\n/**\n * Internal use only. Creates an optimized error notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function errorNotification(error: any): ErrorNotification {\n return createNotification('E', undefined, error) as any;\n}\n\n/**\n * Internal use only. Creates an optimized next notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function nextNotification(value: T) {\n return createNotification('N', value, undefined) as NextNotification;\n}\n\n/**\n * Ensures that all notifications created internally have the same \"shape\" in v8.\n *\n * TODO: This is only exported to support a crazy legacy test in `groupBy`.\n * @internal\n */\nexport function createNotification(kind: 'N' | 'E' | 'C', value: any, error: any) {\n return {\n kind,\n value,\n error,\n };\n}\n", "import { config } from '../config';\n\nlet context: { errorThrown: boolean; error: any } | null = null;\n\n/**\n * Handles dealing with errors for super-gross mode. Creates a context, in which\n * any synchronously thrown errors will be passed to {@link captureError}. Which\n * will record the error such that it will be rethrown after the call back is complete.\n * TODO: Remove in v8\n * @param cb An immediately executed function.\n */\nexport function errorContext(cb: () => void) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n const isRoot = !context;\n if (isRoot) {\n context = { errorThrown: false, error: null };\n }\n cb();\n if (isRoot) {\n const { errorThrown, error } = context!;\n context = null;\n if (errorThrown) {\n throw error;\n }\n }\n } else {\n // This is the general non-deprecated path for everyone that\n // isn't crazy enough to use super-gross mode (useDeprecatedSynchronousErrorHandling)\n cb();\n }\n}\n\n/**\n * Captures errors only in super-gross mode.\n * @param err the error to capture\n */\nexport function captureError(err: any) {\n if (config.useDeprecatedSynchronousErrorHandling && context) {\n context.errorThrown = true;\n context.error = err;\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { Observer, ObservableNotification } from './types';\nimport { isSubscription, Subscription } from './Subscription';\nimport { config } from './config';\nimport { reportUnhandledError } from './util/reportUnhandledError';\nimport { noop } from './util/noop';\nimport { nextNotification, errorNotification, COMPLETE_NOTIFICATION } from './NotificationFactories';\nimport { timeoutProvider } from './scheduler/timeoutProvider';\nimport { captureError } from './util/errorContext';\n\n/**\n * Implements the {@link Observer} interface and extends the\n * {@link Subscription} class. While the {@link Observer} is the public API for\n * consuming the values of an {@link Observable}, all Observers get converted to\n * a Subscriber, in order to provide Subscription-like capabilities such as\n * `unsubscribe`. Subscriber is a common type in RxJS, and crucial for\n * implementing operators, but it is rarely used as a public API.\n *\n * @class Subscriber\n */\nexport class Subscriber extends Subscription implements Observer {\n /**\n * A static factory for a Subscriber, given a (potentially partial) definition\n * of an Observer.\n * @param next The `next` callback of an Observer.\n * @param error The `error` callback of an\n * Observer.\n * @param complete The `complete` callback of an\n * Observer.\n * @return A Subscriber wrapping the (partially defined)\n * Observer represented by the given arguments.\n * @nocollapse\n * @deprecated Do not use. Will be removed in v8. There is no replacement for this\n * method, and there is no reason to be creating instances of `Subscriber` directly.\n * If you have a specific use case, please file an issue.\n */\n static create(next?: (x?: T) => void, error?: (e?: any) => void, complete?: () => void): Subscriber {\n return new SafeSubscriber(next, error, complete);\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected isStopped: boolean = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected destination: Subscriber | Observer; // this `any` is the escape hatch to erase extra type param (e.g. R)\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * There is no reason to directly create an instance of Subscriber. This type is exported for typings reasons.\n */\n constructor(destination?: Subscriber | Observer) {\n super();\n if (destination) {\n this.destination = destination;\n // Automatically chain subscriptions together here.\n // if destination is a Subscription, then it is a Subscriber.\n if (isSubscription(destination)) {\n destination.add(this);\n }\n } else {\n this.destination = EMPTY_OBSERVER;\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `next` from\n * the Observable, with a value. The Observable may call this method 0 or more\n * times.\n * @param {T} [value] The `next` value.\n * @return {void}\n */\n next(value?: T): void {\n if (this.isStopped) {\n handleStoppedNotification(nextNotification(value), this);\n } else {\n this._next(value!);\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `error` from\n * the Observable, with an attached `Error`. Notifies the Observer that\n * the Observable has experienced an error condition.\n * @param {any} [err] The `error` exception.\n * @return {void}\n */\n error(err?: any): void {\n if (this.isStopped) {\n handleStoppedNotification(errorNotification(err), this);\n } else {\n this.isStopped = true;\n this._error(err);\n }\n }\n\n /**\n * The {@link Observer} callback to receive a valueless notification of type\n * `complete` from the Observable. Notifies the Observer that the Observable\n * has finished sending push-based notifications.\n * @return {void}\n */\n complete(): void {\n if (this.isStopped) {\n handleStoppedNotification(COMPLETE_NOTIFICATION, this);\n } else {\n this.isStopped = true;\n this._complete();\n }\n }\n\n unsubscribe(): void {\n if (!this.closed) {\n this.isStopped = true;\n super.unsubscribe();\n this.destination = null!;\n }\n }\n\n protected _next(value: T): void {\n this.destination.next(value);\n }\n\n protected _error(err: any): void {\n try {\n this.destination.error(err);\n } finally {\n this.unsubscribe();\n }\n }\n\n protected _complete(): void {\n try {\n this.destination.complete();\n } finally {\n this.unsubscribe();\n }\n }\n}\n\n/**\n * This bind is captured here because we want to be able to have\n * compatibility with monoid libraries that tend to use a method named\n * `bind`. In particular, a library called Monio requires this.\n */\nconst _bind = Function.prototype.bind;\n\nfunction bind any>(fn: Fn, thisArg: any): Fn {\n return _bind.call(fn, thisArg);\n}\n\n/**\n * Internal optimization only, DO NOT EXPOSE.\n * @internal\n */\nclass ConsumerObserver implements Observer {\n constructor(private partialObserver: Partial>) {}\n\n next(value: T): void {\n const { partialObserver } = this;\n if (partialObserver.next) {\n try {\n partialObserver.next(value);\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n\n error(err: any): void {\n const { partialObserver } = this;\n if (partialObserver.error) {\n try {\n partialObserver.error(err);\n } catch (error) {\n handleUnhandledError(error);\n }\n } else {\n handleUnhandledError(err);\n }\n }\n\n complete(): void {\n const { partialObserver } = this;\n if (partialObserver.complete) {\n try {\n partialObserver.complete();\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n}\n\nexport class SafeSubscriber extends Subscriber {\n constructor(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((e?: any) => void) | null,\n complete?: (() => void) | null\n ) {\n super();\n\n let partialObserver: Partial>;\n if (isFunction(observerOrNext) || !observerOrNext) {\n // The first argument is a function, not an observer. The next\n // two arguments *could* be observers, or they could be empty.\n partialObserver = {\n next: (observerOrNext ?? undefined) as (((value: T) => void) | undefined),\n error: error ?? undefined,\n complete: complete ?? undefined,\n };\n } else {\n // The first argument is a partial observer.\n let context: any;\n if (this && config.useDeprecatedNextContext) {\n // This is a deprecated path that made `this.unsubscribe()` available in\n // next handler functions passed to subscribe. This only exists behind a flag\n // now, as it is *very* slow.\n context = Object.create(observerOrNext);\n context.unsubscribe = () => this.unsubscribe();\n partialObserver = {\n next: observerOrNext.next && bind(observerOrNext.next, context),\n error: observerOrNext.error && bind(observerOrNext.error, context),\n complete: observerOrNext.complete && bind(observerOrNext.complete, context),\n };\n } else {\n // The \"normal\" path. Just use the partial observer directly.\n partialObserver = observerOrNext;\n }\n }\n\n // Wrap the partial observer to ensure it's a full observer, and\n // make sure proper error handling is accounted for.\n this.destination = new ConsumerObserver(partialObserver);\n }\n}\n\nfunction handleUnhandledError(error: any) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n captureError(error);\n } else {\n // Ideal path, we report this as an unhandled error,\n // which is thrown on a new call stack.\n reportUnhandledError(error);\n }\n}\n\n/**\n * An error handler used when no error handler was supplied\n * to the SafeSubscriber -- meaning no error handler was supplied\n * do the `subscribe` call on our observable.\n * @param err The error to handle\n */\nfunction defaultErrorHandler(err: any) {\n throw err;\n}\n\n/**\n * A handler for notifications that cannot be sent to a stopped subscriber.\n * @param notification The notification being sent\n * @param subscriber The stopped subscriber\n */\nfunction handleStoppedNotification(notification: ObservableNotification, subscriber: Subscriber) {\n const { onStoppedNotification } = config;\n onStoppedNotification && timeoutProvider.setTimeout(() => onStoppedNotification(notification, subscriber));\n}\n\n/**\n * The observer used as a stub for subscriptions where the user did not\n * pass any arguments to `subscribe`. Comes with the default error handling\n * behavior.\n */\nexport const EMPTY_OBSERVER: Readonly> & { closed: true } = {\n closed: true,\n next: noop,\n error: defaultErrorHandler,\n complete: noop,\n};\n", "/**\n * Symbol.observable or a string \"@@observable\". Used for interop\n *\n * @deprecated We will no longer be exporting this symbol in upcoming versions of RxJS.\n * Instead polyfill and use Symbol.observable directly *or* use https://www.npmjs.com/package/symbol-observable\n */\nexport const observable: string | symbol = (() => (typeof Symbol === 'function' && Symbol.observable) || '@@observable')();\n", "/**\n * This function takes one parameter and just returns it. Simply put,\n * this is like `(x: T): T => x`.\n *\n * ## Examples\n *\n * This is useful in some cases when using things like `mergeMap`\n *\n * ```ts\n * import { interval, take, map, range, mergeMap, identity } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(5));\n *\n * const result$ = source$.pipe(\n * map(i => range(i)),\n * mergeMap(identity) // same as mergeMap(x => x)\n * );\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * Or when you want to selectively apply an operator\n *\n * ```ts\n * import { interval, take, identity } from 'rxjs';\n *\n * const shouldLimit = () => Math.random() < 0.5;\n *\n * const source$ = interval(1000);\n *\n * const result$ = source$.pipe(shouldLimit() ? take(5) : identity);\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * @param x Any value that is returned by this function\n * @returns The value passed as the first parameter to this function\n */\nexport function identity(x: T): T {\n return x;\n}\n", "import { identity } from './identity';\nimport { UnaryFunction } from '../types';\n\nexport function pipe(): typeof identity;\nexport function pipe(fn1: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction, fn3: UnaryFunction): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction,\n ...fns: UnaryFunction[]\n): UnaryFunction;\n\n/**\n * pipe() can be called on one or more functions, each of which can take one argument (\"UnaryFunction\")\n * and uses it to return a value.\n * It returns a function that takes one argument, passes it to the first UnaryFunction, and then\n * passes the result to the next one, passes that result to the next one, and so on. \n */\nexport function pipe(...fns: Array>): UnaryFunction {\n return pipeFromArray(fns);\n}\n\n/** @internal */\nexport function pipeFromArray(fns: Array>): UnaryFunction {\n if (fns.length === 0) {\n return identity as UnaryFunction;\n }\n\n if (fns.length === 1) {\n return fns[0];\n }\n\n return function piped(input: T): R {\n return fns.reduce((prev: any, fn: UnaryFunction) => fn(prev), input as any);\n };\n}\n", "import { Operator } from './Operator';\nimport { SafeSubscriber, Subscriber } from './Subscriber';\nimport { isSubscription, Subscription } from './Subscription';\nimport { TeardownLogic, OperatorFunction, Subscribable, Observer } from './types';\nimport { observable as Symbol_observable } from './symbol/observable';\nimport { pipeFromArray } from './util/pipe';\nimport { config } from './config';\nimport { isFunction } from './util/isFunction';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A representation of any set of values over any amount of time. This is the most basic building block\n * of RxJS.\n *\n * @class Observable\n */\nexport class Observable implements Subscribable {\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n source: Observable | undefined;\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n operator: Operator | undefined;\n\n /**\n * @constructor\n * @param {Function} subscribe the function that is called when the Observable is\n * initially subscribed to. This function is given a Subscriber, to which new values\n * can be `next`ed, or an `error` method can be called to raise an error, or\n * `complete` can be called to notify of a successful completion.\n */\n constructor(subscribe?: (this: Observable, subscriber: Subscriber) => TeardownLogic) {\n if (subscribe) {\n this._subscribe = subscribe;\n }\n }\n\n // HACK: Since TypeScript inherits static properties too, we have to\n // fight against TypeScript here so Subject can have a different static create signature\n /**\n * Creates a new Observable by calling the Observable constructor\n * @owner Observable\n * @method create\n * @param {Function} subscribe? the subscriber function to be passed to the Observable constructor\n * @return {Observable} a new observable\n * @nocollapse\n * @deprecated Use `new Observable()` instead. Will be removed in v8.\n */\n static create: (...args: any[]) => any = (subscribe?: (subscriber: Subscriber) => TeardownLogic) => {\n return new Observable(subscribe);\n };\n\n /**\n * Creates a new Observable, with this Observable instance as the source, and the passed\n * operator defined as the new observable's operator.\n * @method lift\n * @param operator the operator defining the operation to take on the observable\n * @return a new observable with the Operator applied\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * If you have implemented an operator using `lift`, it is recommended that you create an\n * operator by simply returning `new Observable()` directly. See \"Creating new operators from\n * scratch\" section here: https://rxjs.dev/guide/operators\n */\n lift(operator?: Operator): Observable {\n const observable = new Observable();\n observable.source = this;\n observable.operator = operator;\n return observable;\n }\n\n subscribe(observerOrNext?: Partial> | ((value: T) => void)): Subscription;\n /** @deprecated Instead of passing separate callback arguments, use an observer argument. Signatures taking separate callback arguments will be removed in v8. Details: https://rxjs.dev/deprecations/subscribe-arguments */\n subscribe(next?: ((value: T) => void) | null, error?: ((error: any) => void) | null, complete?: (() => void) | null): Subscription;\n /**\n * Invokes an execution of an Observable and registers Observer handlers for notifications it will emit.\n *\n * Use it when you have all these Observables, but still nothing is happening.\n *\n * `subscribe` is not a regular operator, but a method that calls Observable's internal `subscribe` function. It\n * might be for example a function that you passed to Observable's constructor, but most of the time it is\n * a library implementation, which defines what will be emitted by an Observable, and when it be will emitted. This means\n * that calling `subscribe` is actually the moment when Observable starts its work, not when it is created, as it is often\n * the thought.\n *\n * Apart from starting the execution of an Observable, this method allows you to listen for values\n * that an Observable emits, as well as for when it completes or errors. You can achieve this in two\n * of the following ways.\n *\n * The first way is creating an object that implements {@link Observer} interface. It should have methods\n * defined by that interface, but note that it should be just a regular JavaScript object, which you can create\n * yourself in any way you want (ES6 class, classic function constructor, object literal etc.). In particular, do\n * not attempt to use any RxJS implementation details to create Observers - you don't need them. Remember also\n * that your object does not have to implement all methods. If you find yourself creating a method that doesn't\n * do anything, you can simply omit it. Note however, if the `error` method is not provided and an error happens,\n * it will be thrown asynchronously. Errors thrown asynchronously cannot be caught using `try`/`catch`. Instead,\n * use the {@link onUnhandledError} configuration option or use a runtime handler (like `window.onerror` or\n * `process.on('error)`) to be notified of unhandled errors. Because of this, it's recommended that you provide\n * an `error` method to avoid missing thrown errors.\n *\n * The second way is to give up on Observer object altogether and simply provide callback functions in place of its methods.\n * This means you can provide three functions as arguments to `subscribe`, where the first function is equivalent\n * of a `next` method, the second of an `error` method and the third of a `complete` method. Just as in case of an Observer,\n * if you do not need to listen for something, you can omit a function by passing `undefined` or `null`,\n * since `subscribe` recognizes these functions by where they were placed in function call. When it comes\n * to the `error` function, as with an Observer, if not provided, errors emitted by an Observable will be thrown asynchronously.\n *\n * You can, however, subscribe with no parameters at all. This may be the case where you're not interested in terminal events\n * and you also handled emissions internally by using operators (e.g. using `tap`).\n *\n * Whichever style of calling `subscribe` you use, in both cases it returns a Subscription object.\n * This object allows you to call `unsubscribe` on it, which in turn will stop the work that an Observable does and will clean\n * up all resources that an Observable used. Note that cancelling a subscription will not call `complete` callback\n * provided to `subscribe` function, which is reserved for a regular completion signal that comes from an Observable.\n *\n * Remember that callbacks provided to `subscribe` are not guaranteed to be called asynchronously.\n * It is an Observable itself that decides when these functions will be called. For example {@link of}\n * by default emits all its values synchronously. Always check documentation for how given Observable\n * will behave when subscribed and if its default behavior can be modified with a `scheduler`.\n *\n * #### Examples\n *\n * Subscribe with an {@link guide/observer Observer}\n *\n * ```ts\n * import { of } from 'rxjs';\n *\n * const sumObserver = {\n * sum: 0,\n * next(value) {\n * console.log('Adding: ' + value);\n * this.sum = this.sum + value;\n * },\n * error() {\n * // We actually could just remove this method,\n * // since we do not really care about errors right now.\n * },\n * complete() {\n * console.log('Sum equals: ' + this.sum);\n * }\n * };\n *\n * of(1, 2, 3) // Synchronously emits 1, 2, 3 and then completes.\n * .subscribe(sumObserver);\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Subscribe with functions ({@link deprecations/subscribe-arguments deprecated})\n *\n * ```ts\n * import { of } from 'rxjs'\n *\n * let sum = 0;\n *\n * of(1, 2, 3).subscribe(\n * value => {\n * console.log('Adding: ' + value);\n * sum = sum + value;\n * },\n * undefined,\n * () => console.log('Sum equals: ' + sum)\n * );\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Cancel a subscription\n *\n * ```ts\n * import { interval } from 'rxjs';\n *\n * const subscription = interval(1000).subscribe({\n * next(num) {\n * console.log(num)\n * },\n * complete() {\n * // Will not be called, even when cancelling subscription.\n * console.log('completed!');\n * }\n * });\n *\n * setTimeout(() => {\n * subscription.unsubscribe();\n * console.log('unsubscribed!');\n * }, 2500);\n *\n * // Logs:\n * // 0 after 1s\n * // 1 after 2s\n * // 'unsubscribed!' after 2.5s\n * ```\n *\n * @param {Observer|Function} observerOrNext (optional) Either an observer with methods to be called,\n * or the first of three possible handlers, which is the handler for each value emitted from the subscribed\n * Observable.\n * @param {Function} error (optional) A handler for a terminal event resulting from an error. If no error handler is provided,\n * the error will be thrown asynchronously as unhandled.\n * @param {Function} complete (optional) A handler for a terminal event resulting from successful completion.\n * @return {Subscription} a subscription reference to the registered handlers\n * @method subscribe\n */\n subscribe(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((error: any) => void) | null,\n complete?: (() => void) | null\n ): Subscription {\n const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);\n\n errorContext(() => {\n const { operator, source } = this;\n subscriber.add(\n operator\n ? // We're dealing with a subscription in the\n // operator chain to one of our lifted operators.\n operator.call(subscriber, source)\n : source\n ? // If `source` has a value, but `operator` does not, something that\n // had intimate knowledge of our API, like our `Subject`, must have\n // set it. We're going to just call `_subscribe` directly.\n this._subscribe(subscriber)\n : // In all other cases, we're likely wrapping a user-provided initializer\n // function, so we need to catch errors and handle them appropriately.\n this._trySubscribe(subscriber)\n );\n });\n\n return subscriber;\n }\n\n /** @internal */\n protected _trySubscribe(sink: Subscriber): TeardownLogic {\n try {\n return this._subscribe(sink);\n } catch (err) {\n // We don't need to return anything in this case,\n // because it's just going to try to `add()` to a subscription\n // above.\n sink.error(err);\n }\n }\n\n /**\n * Used as a NON-CANCELLABLE means of subscribing to an observable, for use with\n * APIs that expect promises, like `async/await`. You cannot unsubscribe from this.\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * #### Example\n *\n * ```ts\n * import { interval, take } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(4));\n *\n * async function getTotal() {\n * let total = 0;\n *\n * await source$.forEach(value => {\n * total += value;\n * console.log('observable -> ' + value);\n * });\n *\n * return total;\n * }\n *\n * getTotal().then(\n * total => console.log('Total: ' + total)\n * );\n *\n * // Expected:\n * // 'observable -> 0'\n * // 'observable -> 1'\n * // 'observable -> 2'\n * // 'observable -> 3'\n * // 'Total: 6'\n * ```\n *\n * @param next a handler for each value emitted by the observable\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n */\n forEach(next: (value: T) => void): Promise;\n\n /**\n * @param next a handler for each value emitted by the observable\n * @param promiseCtor a constructor function used to instantiate the Promise\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n * @deprecated Passing a Promise constructor will no longer be available\n * in upcoming versions of RxJS. This is because it adds weight to the library, for very\n * little benefit. If you need this functionality, it is recommended that you either\n * polyfill Promise, or you create an adapter to convert the returned native promise\n * to whatever promise implementation you wanted. Will be removed in v8.\n */\n forEach(next: (value: T) => void, promiseCtor: PromiseConstructorLike): Promise;\n\n forEach(next: (value: T) => void, promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n const subscriber = new SafeSubscriber({\n next: (value) => {\n try {\n next(value);\n } catch (err) {\n reject(err);\n subscriber.unsubscribe();\n }\n },\n error: reject,\n complete: resolve,\n });\n this.subscribe(subscriber);\n }) as Promise;\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): TeardownLogic {\n return this.source?.subscribe(subscriber);\n }\n\n /**\n * An interop point defined by the es7-observable spec https://github.com/zenparsing/es-observable\n * @method Symbol.observable\n * @return {Observable} this instance of the observable\n */\n [Symbol_observable]() {\n return this;\n }\n\n /* tslint:disable:max-line-length */\n pipe(): Observable;\n pipe(op1: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction,\n ...operations: OperatorFunction[]\n ): Observable;\n /* tslint:enable:max-line-length */\n\n /**\n * Used to stitch together functional operators into a chain.\n * @method pipe\n * @return {Observable} the Observable result of all of the operators having\n * been called in the order they were passed in.\n *\n * ## Example\n *\n * ```ts\n * import { interval, filter, map, scan } from 'rxjs';\n *\n * interval(1000)\n * .pipe(\n * filter(x => x % 2 === 0),\n * map(x => x + x),\n * scan((acc, x) => acc + x)\n * )\n * .subscribe(x => console.log(x));\n * ```\n */\n pipe(...operations: OperatorFunction[]): Observable {\n return pipeFromArray(operations)(this);\n }\n\n /* tslint:disable:max-line-length */\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: typeof Promise): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: PromiseConstructorLike): Promise;\n /* tslint:enable:max-line-length */\n\n /**\n * Subscribe to this Observable and get a Promise resolving on\n * `complete` with the last emission (if any).\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * @method toPromise\n * @param [promiseCtor] a constructor function used to instantiate\n * the Promise\n * @return A Promise that resolves with the last value emit, or\n * rejects on an error. If there were no emissions, Promise\n * resolves with undefined.\n * @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise\n */\n toPromise(promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n let value: T | undefined;\n this.subscribe(\n (x: T) => (value = x),\n (err: any) => reject(err),\n () => resolve(value)\n );\n }) as Promise;\n }\n}\n\n/**\n * Decides between a passed promise constructor from consuming code,\n * A default configured promise constructor, and the native promise\n * constructor and returns it. If nothing can be found, it will throw\n * an error.\n * @param promiseCtor The optional promise constructor to passed by consuming code\n */\nfunction getPromiseCtor(promiseCtor: PromiseConstructorLike | undefined) {\n return promiseCtor ?? config.Promise ?? Promise;\n}\n\nfunction isObserver(value: any): value is Observer {\n return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);\n}\n\nfunction isSubscriber(value: any): value is Subscriber {\n return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));\n}\n", "import { Observable } from '../Observable';\nimport { Subscriber } from '../Subscriber';\nimport { OperatorFunction } from '../types';\nimport { isFunction } from './isFunction';\n\n/**\n * Used to determine if an object is an Observable with a lift function.\n */\nexport function hasLift(source: any): source is { lift: InstanceType['lift'] } {\n return isFunction(source?.lift);\n}\n\n/**\n * Creates an `OperatorFunction`. Used to define operators throughout the library in a concise way.\n * @param init The logic to connect the liftedSource to the subscriber at the moment of subscription.\n */\nexport function operate(\n init: (liftedSource: Observable, subscriber: Subscriber) => (() => void) | void\n): OperatorFunction {\n return (source: Observable) => {\n if (hasLift(source)) {\n return source.lift(function (this: Subscriber, liftedSource: Observable) {\n try {\n return init(liftedSource, this);\n } catch (err) {\n this.error(err);\n }\n });\n }\n throw new TypeError('Unable to lift unknown Observable type');\n };\n}\n", "import { Subscriber } from '../Subscriber';\n\n/**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional teardown logic here. This will only be called on teardown if the\n * subscriber itself is not already closed. This is called after all other teardown logic is executed.\n */\nexport function createOperatorSubscriber(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n onFinalize?: () => void\n): Subscriber {\n return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize);\n}\n\n/**\n * A generic helper for allowing operators to be created with a Subscriber and\n * use closures to capture necessary state from the operator function itself.\n */\nexport class OperatorSubscriber extends Subscriber {\n /**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional finalization logic here. This will only be called on finalization if the\n * subscriber itself is not already closed. This is called after all other finalization logic is executed.\n * @param shouldUnsubscribe An optional check to see if an unsubscribe call should truly unsubscribe.\n * NOTE: This currently **ONLY** exists to support the strange behavior of {@link groupBy}, where unsubscription\n * to the resulting observable does not actually disconnect from the source if there are active subscriptions\n * to any grouped observable. (DO NOT EXPOSE OR USE EXTERNALLY!!!)\n */\n constructor(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n private onFinalize?: () => void,\n private shouldUnsubscribe?: () => boolean\n ) {\n // It's important - for performance reasons - that all of this class's\n // members are initialized and that they are always initialized in the same\n // order. This will ensure that all OperatorSubscriber instances have the\n // same hidden class in V8. This, in turn, will help keep the number of\n // hidden classes involved in property accesses within the base class as\n // low as possible. If the number of hidden classes involved exceeds four,\n // the property accesses will become megamorphic and performance penalties\n // will be incurred - i.e. inline caches won't be used.\n //\n // The reasons for ensuring all instances have the same hidden class are\n // further discussed in this blog post from Benedikt Meurer:\n // https://benediktmeurer.de/2018/03/23/impact-of-polymorphism-on-component-based-frameworks-like-react/\n super(destination);\n this._next = onNext\n ? function (this: OperatorSubscriber, value: T) {\n try {\n onNext(value);\n } catch (err) {\n destination.error(err);\n }\n }\n : super._next;\n this._error = onError\n ? function (this: OperatorSubscriber, err: any) {\n try {\n onError(err);\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._error;\n this._complete = onComplete\n ? function (this: OperatorSubscriber) {\n try {\n onComplete();\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._complete;\n }\n\n unsubscribe() {\n if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) {\n const { closed } = this;\n super.unsubscribe();\n // Execute additional teardown if we have any and we didn't already do so.\n !closed && this.onFinalize?.();\n }\n }\n}\n", "import { Subscription } from '../Subscription';\n\ninterface AnimationFrameProvider {\n schedule(callback: FrameRequestCallback): Subscription;\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n delegate:\n | {\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n }\n | undefined;\n}\n\nexport const animationFrameProvider: AnimationFrameProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n schedule(callback) {\n let request = requestAnimationFrame;\n let cancel: typeof cancelAnimationFrame | undefined = cancelAnimationFrame;\n const { delegate } = animationFrameProvider;\n if (delegate) {\n request = delegate.requestAnimationFrame;\n cancel = delegate.cancelAnimationFrame;\n }\n const handle = request((timestamp) => {\n // Clear the cancel function. The request has been fulfilled, so\n // attempting to cancel the request upon unsubscription would be\n // pointless.\n cancel = undefined;\n callback(timestamp);\n });\n return new Subscription(() => cancel?.(handle));\n },\n requestAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.requestAnimationFrame || requestAnimationFrame)(...args);\n },\n cancelAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.cancelAnimationFrame || cancelAnimationFrame)(...args);\n },\n delegate: undefined,\n};\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface ObjectUnsubscribedError extends Error {}\n\nexport interface ObjectUnsubscribedErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (): ObjectUnsubscribedError;\n}\n\n/**\n * An error thrown when an action is invalid because the object has been\n * unsubscribed.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n *\n * @class ObjectUnsubscribedError\n */\nexport const ObjectUnsubscribedError: ObjectUnsubscribedErrorCtor = createErrorClass(\n (_super) =>\n function ObjectUnsubscribedErrorImpl(this: any) {\n _super(this);\n this.name = 'ObjectUnsubscribedError';\n this.message = 'object unsubscribed';\n }\n);\n", "import { Operator } from './Operator';\nimport { Observable } from './Observable';\nimport { Subscriber } from './Subscriber';\nimport { Subscription, EMPTY_SUBSCRIPTION } from './Subscription';\nimport { Observer, SubscriptionLike, TeardownLogic } from './types';\nimport { ObjectUnsubscribedError } from './util/ObjectUnsubscribedError';\nimport { arrRemove } from './util/arrRemove';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A Subject is a special type of Observable that allows values to be\n * multicasted to many Observers. Subjects are like EventEmitters.\n *\n * Every Subject is an Observable and an Observer. You can subscribe to a\n * Subject, and you can call next to feed values as well as error and complete.\n */\nexport class Subject extends Observable implements SubscriptionLike {\n closed = false;\n\n private currentObservers: Observer[] | null = null;\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n observers: Observer[] = [];\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n isStopped = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n hasError = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n thrownError: any = null;\n\n /**\n * Creates a \"subject\" by basically gluing an observer to an observable.\n *\n * @nocollapse\n * @deprecated Recommended you do not use. Will be removed at some point in the future. Plans for replacement still under discussion.\n */\n static create: (...args: any[]) => any = (destination: Observer, source: Observable): AnonymousSubject => {\n return new AnonymousSubject(destination, source);\n };\n\n constructor() {\n // NOTE: This must be here to obscure Observable's constructor.\n super();\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n lift(operator: Operator): Observable {\n const subject = new AnonymousSubject(this, this);\n subject.operator = operator as any;\n return subject as any;\n }\n\n /** @internal */\n protected _throwIfClosed() {\n if (this.closed) {\n throw new ObjectUnsubscribedError();\n }\n }\n\n next(value: T) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n if (!this.currentObservers) {\n this.currentObservers = Array.from(this.observers);\n }\n for (const observer of this.currentObservers) {\n observer.next(value);\n }\n }\n });\n }\n\n error(err: any) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.hasError = this.isStopped = true;\n this.thrownError = err;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.error(err);\n }\n }\n });\n }\n\n complete() {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.isStopped = true;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.complete();\n }\n }\n });\n }\n\n unsubscribe() {\n this.isStopped = this.closed = true;\n this.observers = this.currentObservers = null!;\n }\n\n get observed() {\n return this.observers?.length > 0;\n }\n\n /** @internal */\n protected _trySubscribe(subscriber: Subscriber): TeardownLogic {\n this._throwIfClosed();\n return super._trySubscribe(subscriber);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._checkFinalizedStatuses(subscriber);\n return this._innerSubscribe(subscriber);\n }\n\n /** @internal */\n protected _innerSubscribe(subscriber: Subscriber) {\n const { hasError, isStopped, observers } = this;\n if (hasError || isStopped) {\n return EMPTY_SUBSCRIPTION;\n }\n this.currentObservers = null;\n observers.push(subscriber);\n return new Subscription(() => {\n this.currentObservers = null;\n arrRemove(observers, subscriber);\n });\n }\n\n /** @internal */\n protected _checkFinalizedStatuses(subscriber: Subscriber) {\n const { hasError, thrownError, isStopped } = this;\n if (hasError) {\n subscriber.error(thrownError);\n } else if (isStopped) {\n subscriber.complete();\n }\n }\n\n /**\n * Creates a new Observable with this Subject as the source. You can do this\n * to create custom Observer-side logic of the Subject and conceal it from\n * code that uses the Observable.\n * @return {Observable} Observable that the Subject casts to\n */\n asObservable(): Observable {\n const observable: any = new Observable();\n observable.source = this;\n return observable;\n }\n}\n\n/**\n * @class AnonymousSubject\n */\nexport class AnonymousSubject extends Subject {\n constructor(\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n public destination?: Observer,\n source?: Observable\n ) {\n super();\n this.source = source;\n }\n\n next(value: T) {\n this.destination?.next?.(value);\n }\n\n error(err: any) {\n this.destination?.error?.(err);\n }\n\n complete() {\n this.destination?.complete?.();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n return this.source?.subscribe(subscriber) ?? EMPTY_SUBSCRIPTION;\n }\n}\n", "import { TimestampProvider } from '../types';\n\ninterface DateTimestampProvider extends TimestampProvider {\n delegate: TimestampProvider | undefined;\n}\n\nexport const dateTimestampProvider: DateTimestampProvider = {\n now() {\n // Use the variable rather than `this` so that the function can be called\n // without being bound to the provider.\n return (dateTimestampProvider.delegate || Date).now();\n },\n delegate: undefined,\n};\n", "import { Subject } from './Subject';\nimport { TimestampProvider } from './types';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * A variant of {@link Subject} that \"replays\" old values to new subscribers by emitting them when they first subscribe.\n *\n * `ReplaySubject` has an internal buffer that will store a specified number of values that it has observed. Like `Subject`,\n * `ReplaySubject` \"observes\" values by having them passed to its `next` method. When it observes a value, it will store that\n * value for a time determined by the configuration of the `ReplaySubject`, as passed to its constructor.\n *\n * When a new subscriber subscribes to the `ReplaySubject` instance, it will synchronously emit all values in its buffer in\n * a First-In-First-Out (FIFO) manner. The `ReplaySubject` will also complete, if it has observed completion; and it will\n * error if it has observed an error.\n *\n * There are two main configuration items to be concerned with:\n *\n * 1. `bufferSize` - This will determine how many items are stored in the buffer, defaults to infinite.\n * 2. `windowTime` - The amount of time to hold a value in the buffer before removing it from the buffer.\n *\n * Both configurations may exist simultaneously. So if you would like to buffer a maximum of 3 values, as long as the values\n * are less than 2 seconds old, you could do so with a `new ReplaySubject(3, 2000)`.\n *\n * ### Differences with BehaviorSubject\n *\n * `BehaviorSubject` is similar to `new ReplaySubject(1)`, with a couple of exceptions:\n *\n * 1. `BehaviorSubject` comes \"primed\" with a single value upon construction.\n * 2. `ReplaySubject` will replay values, even after observing an error, where `BehaviorSubject` will not.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n * @see {@link shareReplay}\n */\nexport class ReplaySubject extends Subject {\n private _buffer: (T | number)[] = [];\n private _infiniteTimeWindow = true;\n\n /**\n * @param bufferSize The size of the buffer to replay on subscription\n * @param windowTime The amount of time the buffered items will stay buffered\n * @param timestampProvider An object with a `now()` method that provides the current timestamp. This is used to\n * calculate the amount of time something has been buffered.\n */\n constructor(\n private _bufferSize = Infinity,\n private _windowTime = Infinity,\n private _timestampProvider: TimestampProvider = dateTimestampProvider\n ) {\n super();\n this._infiniteTimeWindow = _windowTime === Infinity;\n this._bufferSize = Math.max(1, _bufferSize);\n this._windowTime = Math.max(1, _windowTime);\n }\n\n next(value: T): void {\n const { isStopped, _buffer, _infiniteTimeWindow, _timestampProvider, _windowTime } = this;\n if (!isStopped) {\n _buffer.push(value);\n !_infiniteTimeWindow && _buffer.push(_timestampProvider.now() + _windowTime);\n }\n this._trimBuffer();\n super.next(value);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._trimBuffer();\n\n const subscription = this._innerSubscribe(subscriber);\n\n const { _infiniteTimeWindow, _buffer } = this;\n // We use a copy here, so reentrant code does not mutate our array while we're\n // emitting it to a new subscriber.\n const copy = _buffer.slice();\n for (let i = 0; i < copy.length && !subscriber.closed; i += _infiniteTimeWindow ? 1 : 2) {\n subscriber.next(copy[i] as T);\n }\n\n this._checkFinalizedStatuses(subscriber);\n\n return subscription;\n }\n\n private _trimBuffer() {\n const { _bufferSize, _timestampProvider, _buffer, _infiniteTimeWindow } = this;\n // If we don't have an infinite buffer size, and we're over the length,\n // use splice to truncate the old buffer values off. Note that we have to\n // double the size for instances where we're not using an infinite time window\n // because we're storing the values and the timestamps in the same array.\n const adjustedBufferSize = (_infiniteTimeWindow ? 1 : 2) * _bufferSize;\n _bufferSize < Infinity && adjustedBufferSize < _buffer.length && _buffer.splice(0, _buffer.length - adjustedBufferSize);\n\n // Now, if we're not in an infinite time window, remove all values where the time is\n // older than what is allowed.\n if (!_infiniteTimeWindow) {\n const now = _timestampProvider.now();\n let last = 0;\n // Search the array for the first timestamp that isn't expired and\n // truncate the buffer up to that point.\n for (let i = 1; i < _buffer.length && (_buffer[i] as number) <= now; i += 2) {\n last = i;\n }\n last && _buffer.splice(0, last + 1);\n }\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Subscription } from '../Subscription';\nimport { SchedulerAction } from '../types';\n\n/**\n * A unit of work to be executed in a `scheduler`. An action is typically\n * created from within a {@link SchedulerLike} and an RxJS user does not need to concern\n * themselves about creating and manipulating an Action.\n *\n * ```ts\n * class Action extends Subscription {\n * new (scheduler: Scheduler, work: (state?: T) => void);\n * schedule(state?: T, delay: number = 0): Subscription;\n * }\n * ```\n *\n * @class Action\n */\nexport class Action extends Subscription {\n constructor(scheduler: Scheduler, work: (this: SchedulerAction, state?: T) => void) {\n super();\n }\n /**\n * Schedules this action on its parent {@link SchedulerLike} for execution. May be passed\n * some context object, `state`. May happen at some point in the future,\n * according to the `delay` parameter, if specified.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler.\n * @return {void}\n */\n public schedule(state?: T, delay: number = 0): Subscription {\n return this;\n }\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetIntervalFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearIntervalFunction = (handle: TimerHandle) => void;\n\ninterface IntervalProvider {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n delegate:\n | {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n }\n | undefined;\n}\n\nexport const intervalProvider: IntervalProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setInterval(handler: () => void, timeout?: number, ...args) {\n const { delegate } = intervalProvider;\n if (delegate?.setInterval) {\n return delegate.setInterval(handler, timeout, ...args);\n }\n return setInterval(handler, timeout, ...args);\n },\n clearInterval(handle) {\n const { delegate } = intervalProvider;\n return (delegate?.clearInterval || clearInterval)(handle as any);\n },\n delegate: undefined,\n};\n", "import { Action } from './Action';\nimport { SchedulerAction } from '../types';\nimport { Subscription } from '../Subscription';\nimport { AsyncScheduler } from './AsyncScheduler';\nimport { intervalProvider } from './intervalProvider';\nimport { arrRemove } from '../util/arrRemove';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncAction extends Action {\n public id: TimerHandle | undefined;\n public state?: T;\n // @ts-ignore: Property has no initializer and is not definitely assigned\n public delay: number;\n protected pending: boolean = false;\n\n constructor(protected scheduler: AsyncScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (this.closed) {\n return this;\n }\n\n // Always replace the current state with the new state.\n this.state = state;\n\n const id = this.id;\n const scheduler = this.scheduler;\n\n //\n // Important implementation note:\n //\n // Actions only execute once by default, unless rescheduled from within the\n // scheduled callback. This allows us to implement single and repeat\n // actions via the same code path, without adding API surface area, as well\n // as mimic traditional recursion but across asynchronous boundaries.\n //\n // However, JS runtimes and timers distinguish between intervals achieved by\n // serial `setTimeout` calls vs. a single `setInterval` call. An interval of\n // serial `setTimeout` calls can be individually delayed, which delays\n // scheduling the next `setTimeout`, and so on. `setInterval` attempts to\n // guarantee the interval callback will be invoked more precisely to the\n // interval period, regardless of load.\n //\n // Therefore, we use `setInterval` to schedule single and repeat actions.\n // If the action reschedules itself with the same delay, the interval is not\n // canceled. If the action doesn't reschedule, or reschedules with a\n // different delay, the interval will be canceled after scheduled callback\n // execution.\n //\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, delay);\n }\n\n // Set the pending flag indicating that this action has been scheduled, or\n // has recursively rescheduled itself.\n this.pending = true;\n\n this.delay = delay;\n // If this action has already an async Id, don't request a new one.\n this.id = this.id ?? this.requestAsyncId(scheduler, this.id, delay);\n\n return this;\n }\n\n protected requestAsyncId(scheduler: AsyncScheduler, _id?: TimerHandle, delay: number = 0): TimerHandle {\n return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay);\n }\n\n protected recycleAsyncId(_scheduler: AsyncScheduler, id?: TimerHandle, delay: number | null = 0): TimerHandle | undefined {\n // If this action is rescheduled with the same delay time, don't clear the interval id.\n if (delay != null && this.delay === delay && this.pending === false) {\n return id;\n }\n // Otherwise, if the action's delay time is different from the current delay,\n // or the action has been rescheduled before it's executed, clear the interval id\n if (id != null) {\n intervalProvider.clearInterval(id);\n }\n\n return undefined;\n }\n\n /**\n * Immediately executes this action and the `work` it contains.\n * @return {any}\n */\n public execute(state: T, delay: number): any {\n if (this.closed) {\n return new Error('executing a cancelled action');\n }\n\n this.pending = false;\n const error = this._execute(state, delay);\n if (error) {\n return error;\n } else if (this.pending === false && this.id != null) {\n // Dequeue if the action didn't reschedule itself. Don't call\n // unsubscribe(), because the action could reschedule later.\n // For example:\n // ```\n // scheduler.schedule(function doWork(counter) {\n // /* ... I'm a busy worker bee ... */\n // var originalAction = this;\n // /* wait 100ms before rescheduling the action */\n // setTimeout(function () {\n // originalAction.schedule(counter + 1);\n // }, 100);\n // }, 1000);\n // ```\n this.id = this.recycleAsyncId(this.scheduler, this.id, null);\n }\n }\n\n protected _execute(state: T, _delay: number): any {\n let errored: boolean = false;\n let errorValue: any;\n try {\n this.work(state);\n } catch (e) {\n errored = true;\n // HACK: Since code elsewhere is relying on the \"truthiness\" of the\n // return here, we can't have it return \"\" or 0 or false.\n // TODO: Clean this up when we refactor schedulers mid-version-8 or so.\n errorValue = e ? e : new Error('Scheduled action threw falsy error');\n }\n if (errored) {\n this.unsubscribe();\n return errorValue;\n }\n }\n\n unsubscribe() {\n if (!this.closed) {\n const { id, scheduler } = this;\n const { actions } = scheduler;\n\n this.work = this.state = this.scheduler = null!;\n this.pending = false;\n\n arrRemove(actions, this);\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, null);\n }\n\n this.delay = null!;\n super.unsubscribe();\n }\n }\n}\n", "import { Action } from './scheduler/Action';\nimport { Subscription } from './Subscription';\nimport { SchedulerLike, SchedulerAction } from './types';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * An execution context and a data structure to order tasks and schedule their\n * execution. Provides a notion of (potentially virtual) time, through the\n * `now()` getter method.\n *\n * Each unit of work in a Scheduler is called an `Action`.\n *\n * ```ts\n * class Scheduler {\n * now(): number;\n * schedule(work, delay?, state?): Subscription;\n * }\n * ```\n *\n * @class Scheduler\n * @deprecated Scheduler is an internal implementation detail of RxJS, and\n * should not be used directly. Rather, create your own class and implement\n * {@link SchedulerLike}. Will be made internal in v8.\n */\nexport class Scheduler implements SchedulerLike {\n public static now: () => number = dateTimestampProvider.now;\n\n constructor(private schedulerActionCtor: typeof Action, now: () => number = Scheduler.now) {\n this.now = now;\n }\n\n /**\n * A getter method that returns a number representing the current time\n * (at the time this function was called) according to the scheduler's own\n * internal clock.\n * @return {number} A number that represents the current time. May or may not\n * have a relation to wall-clock time. May or may not refer to a time unit\n * (e.g. milliseconds).\n */\n public now: () => number;\n\n /**\n * Schedules a function, `work`, for execution. May happen at some point in\n * the future, according to the `delay` parameter, if specified. May be passed\n * some context object, `state`, which will be passed to the `work` function.\n *\n * The given arguments will be processed an stored as an Action object in a\n * queue of actions.\n *\n * @param {function(state: ?T): ?Subscription} work A function representing a\n * task, or some unit of work to be executed by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler itself.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @return {Subscription} A subscription in order to be able to unsubscribe\n * the scheduled work.\n */\n public schedule(work: (this: SchedulerAction, state?: T) => void, delay: number = 0, state?: T): Subscription {\n return new this.schedulerActionCtor(this, work).schedule(state, delay);\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Action } from './Action';\nimport { AsyncAction } from './AsyncAction';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncScheduler extends Scheduler {\n public actions: Array> = [];\n /**\n * A flag to indicate whether the Scheduler is currently executing a batch of\n * queued actions.\n * @type {boolean}\n * @internal\n */\n public _active: boolean = false;\n /**\n * An internal ID used to track the latest asynchronous task such as those\n * coming from `setTimeout`, `setInterval`, `requestAnimationFrame`, and\n * others.\n * @type {any}\n * @internal\n */\n public _scheduled: TimerHandle | undefined;\n\n constructor(SchedulerAction: typeof Action, now: () => number = Scheduler.now) {\n super(SchedulerAction, now);\n }\n\n public flush(action: AsyncAction): void {\n const { actions } = this;\n\n if (this._active) {\n actions.push(action);\n return;\n }\n\n let error: any;\n this._active = true;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions.shift()!)); // exhaust the scheduler queue\n\n this._active = false;\n\n if (error) {\n while ((action = actions.shift()!)) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\n/**\n *\n * Async Scheduler\n *\n * Schedule task as if you used setTimeout(task, duration)\n *\n * `async` scheduler schedules tasks asynchronously, by putting them on the JavaScript\n * event loop queue. It is best used to delay tasks in time or to schedule tasks repeating\n * in intervals.\n *\n * If you just want to \"defer\" task, that is to perform it right after currently\n * executing synchronous code ends (commonly achieved by `setTimeout(deferredTask, 0)`),\n * better choice will be the {@link asapScheduler} scheduler.\n *\n * ## Examples\n * Use async scheduler to delay task\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * const task = () => console.log('it works!');\n *\n * asyncScheduler.schedule(task, 2000);\n *\n * // After 2 seconds logs:\n * // \"it works!\"\n * ```\n *\n * Use async scheduler to repeat task in intervals\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * function task(state) {\n * console.log(state);\n * this.schedule(state + 1, 1000); // `this` references currently executing Action,\n * // which we reschedule with new state and delay\n * }\n *\n * asyncScheduler.schedule(task, 3000, 0);\n *\n * // Logs:\n * // 0 after 3s\n * // 1 after 4s\n * // 2 after 5s\n * // 3 after 6s\n * ```\n */\n\nexport const asyncScheduler = new AsyncScheduler(AsyncAction);\n\n/**\n * @deprecated Renamed to {@link asyncScheduler}. Will be removed in v8.\n */\nexport const async = asyncScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\nimport { SchedulerAction } from '../types';\nimport { animationFrameProvider } from './animationFrameProvider';\nimport { TimerHandle } from './timerHandle';\n\nexport class AnimationFrameAction extends AsyncAction {\n constructor(protected scheduler: AnimationFrameScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n protected requestAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay is greater than 0, request as an async action.\n if (delay !== null && delay > 0) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n // Push the action to the end of the scheduler queue.\n scheduler.actions.push(this);\n // If an animation frame has already been requested, don't request another\n // one. If an animation frame hasn't been requested yet, request one. Return\n // the current animation frame request id.\n return scheduler._scheduled || (scheduler._scheduled = animationFrameProvider.requestAnimationFrame(() => scheduler.flush(undefined)));\n }\n\n protected recycleAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle | undefined {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n if (delay != null ? delay > 0 : this.delay > 0) {\n return super.recycleAsyncId(scheduler, id, delay);\n }\n // If the scheduler queue has no remaining actions with the same async id,\n // cancel the requested animation frame and set the scheduled flag to\n // undefined so the next AnimationFrameAction will request its own.\n const { actions } = scheduler;\n if (id != null && actions[actions.length - 1]?.id !== id) {\n animationFrameProvider.cancelAnimationFrame(id as number);\n scheduler._scheduled = undefined;\n }\n // Return undefined so the action knows to request a new async id if it's rescheduled.\n return undefined;\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\nexport class AnimationFrameScheduler extends AsyncScheduler {\n public flush(action?: AsyncAction): void {\n this._active = true;\n // The async id that effects a call to flush is stored in _scheduled.\n // Before executing an action, it's necessary to check the action's async\n // id to determine whether it's supposed to be executed in the current\n // flush.\n // Previous implementations of this method used a count to determine this,\n // but that was unsound, as actions that are unsubscribed - i.e. cancelled -\n // are removed from the actions array and that can shift actions that are\n // scheduled to be executed in a subsequent flush into positions at which\n // they are executed within the current flush.\n const flushId = this._scheduled;\n this._scheduled = undefined;\n\n const { actions } = this;\n let error: any;\n action = action || actions.shift()!;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions[0]) && action.id === flushId && actions.shift());\n\n this._active = false;\n\n if (error) {\n while ((action = actions[0]) && action.id === flushId && actions.shift()) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AnimationFrameAction } from './AnimationFrameAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\n\n/**\n *\n * Animation Frame Scheduler\n *\n * Perform task when `window.requestAnimationFrame` would fire\n *\n * When `animationFrame` scheduler is used with delay, it will fall back to {@link asyncScheduler} scheduler\n * behaviour.\n *\n * Without delay, `animationFrame` scheduler can be used to create smooth browser animations.\n * It makes sure scheduled task will happen just before next browser content repaint,\n * thus performing animations as efficiently as possible.\n *\n * ## Example\n * Schedule div height animation\n * ```ts\n * // html:
\n * import { animationFrameScheduler } from 'rxjs';\n *\n * const div = document.querySelector('div');\n *\n * animationFrameScheduler.schedule(function(height) {\n * div.style.height = height + \"px\";\n *\n * this.schedule(height + 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * }, 0, 0);\n *\n * // You will see a div element growing in height\n * ```\n */\n\nexport const animationFrameScheduler = new AnimationFrameScheduler(AnimationFrameAction);\n\n/**\n * @deprecated Renamed to {@link animationFrameScheduler}. Will be removed in v8.\n */\nexport const animationFrame = animationFrameScheduler;\n", "import { Observable } from '../Observable';\nimport { SchedulerLike } from '../types';\n\n/**\n * A simple Observable that emits no items to the Observer and immediately\n * emits a complete notification.\n *\n * Just emits 'complete', and nothing else.\n *\n * ![](empty.png)\n *\n * A simple Observable that only emits the complete notification. It can be used\n * for composing with other Observables, such as in a {@link mergeMap}.\n *\n * ## Examples\n *\n * Log complete notification\n *\n * ```ts\n * import { EMPTY } from 'rxjs';\n *\n * EMPTY.subscribe({\n * next: () => console.log('Next'),\n * complete: () => console.log('Complete!')\n * });\n *\n * // Outputs\n * // Complete!\n * ```\n *\n * Emit the number 7, then complete\n *\n * ```ts\n * import { EMPTY, startWith } from 'rxjs';\n *\n * const result = EMPTY.pipe(startWith(7));\n * result.subscribe(x => console.log(x));\n *\n * // Outputs\n * // 7\n * ```\n *\n * Map and flatten only odd numbers to the sequence `'a'`, `'b'`, `'c'`\n *\n * ```ts\n * import { interval, mergeMap, of, EMPTY } from 'rxjs';\n *\n * const interval$ = interval(1000);\n * const result = interval$.pipe(\n * mergeMap(x => x % 2 === 1 ? of('a', 'b', 'c') : EMPTY),\n * );\n * result.subscribe(x => console.log(x));\n *\n * // Results in the following to the console:\n * // x is equal to the count on the interval, e.g. (0, 1, 2, 3, ...)\n * // x will occur every 1000ms\n * // if x % 2 is equal to 1, print a, b, c (each on its own)\n * // if x % 2 is not equal to 1, nothing will be output\n * ```\n *\n * @see {@link Observable}\n * @see {@link NEVER}\n * @see {@link of}\n * @see {@link throwError}\n */\nexport const EMPTY = new Observable((subscriber) => subscriber.complete());\n\n/**\n * @param scheduler A {@link SchedulerLike} to use for scheduling\n * the emission of the complete notification.\n * @deprecated Replaced with the {@link EMPTY} constant or {@link scheduled} (e.g. `scheduled([], scheduler)`). Will be removed in v8.\n */\nexport function empty(scheduler?: SchedulerLike) {\n return scheduler ? emptyScheduled(scheduler) : EMPTY;\n}\n\nfunction emptyScheduled(scheduler: SchedulerLike) {\n return new Observable((subscriber) => scheduler.schedule(() => subscriber.complete()));\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport function isScheduler(value: any): value is SchedulerLike {\n return value && isFunction(value.schedule);\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\nimport { isScheduler } from './isScheduler';\n\nfunction last(arr: T[]): T | undefined {\n return arr[arr.length - 1];\n}\n\nexport function popResultSelector(args: any[]): ((...args: unknown[]) => unknown) | undefined {\n return isFunction(last(args)) ? args.pop() : undefined;\n}\n\nexport function popScheduler(args: any[]): SchedulerLike | undefined {\n return isScheduler(last(args)) ? args.pop() : undefined;\n}\n\nexport function popNumber(args: any[], defaultValue: number): number {\n return typeof last(args) === 'number' ? args.pop()! : defaultValue;\n}\n", "export const isArrayLike = ((x: any): x is ArrayLike => x && typeof x.length === 'number' && typeof x !== 'function');", "import { isFunction } from \"./isFunction\";\n\n/**\n * Tests to see if the object is \"thennable\".\n * @param value the object to test\n */\nexport function isPromise(value: any): value is PromiseLike {\n return isFunction(value?.then);\n}\n", "import { InteropObservable } from '../types';\nimport { observable as Symbol_observable } from '../symbol/observable';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being Observable (but not necessary an Rx Observable) */\nexport function isInteropObservable(input: any): input is InteropObservable {\n return isFunction(input[Symbol_observable]);\n}\n", "import { isFunction } from './isFunction';\n\nexport function isAsyncIterable(obj: any): obj is AsyncIterable {\n return Symbol.asyncIterator && isFunction(obj?.[Symbol.asyncIterator]);\n}\n", "/**\n * Creates the TypeError to throw if an invalid object is passed to `from` or `scheduled`.\n * @param input The object that was passed.\n */\nexport function createInvalidObservableTypeError(input: any) {\n // TODO: We should create error codes that can be looked up, so this can be less verbose.\n return new TypeError(\n `You provided ${\n input !== null && typeof input === 'object' ? 'an invalid object' : `'${input}'`\n } where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.`\n );\n}\n", "export function getSymbolIterator(): symbol {\n if (typeof Symbol !== 'function' || !Symbol.iterator) {\n return '@@iterator' as any;\n }\n\n return Symbol.iterator;\n}\n\nexport const iterator = getSymbolIterator();\n", "import { iterator as Symbol_iterator } from '../symbol/iterator';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being an Iterable */\nexport function isIterable(input: any): input is Iterable {\n return isFunction(input?.[Symbol_iterator]);\n}\n", "import { ReadableStreamLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport async function* readableStreamLikeToAsyncGenerator(readableStream: ReadableStreamLike): AsyncGenerator {\n const reader = readableStream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n return;\n }\n yield value!;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nexport function isReadableStreamLike(obj: any): obj is ReadableStreamLike {\n // We don't want to use instanceof checks because they would return\n // false for instances from another Realm, like an --> + + + + + +

What is free5GC?

+

+

The free5GC is an open-source project for 5th generation (5G) mobile core networks. The ultimate goal of this project is to implement the 5G core network (5GC) defined in 3GPP Release 15 (R15) and beyond.

+
+
    +
  • The source code of the latest version of free5GC can be downloaded from here.
  • +
  • Follow our LinkedIn page to get the news about free5GC.
  • +
+
+

Note

+

Thank you very much for your interest in free5GC. The license of free5GC follows Apache 2.0. That is, anyone can use free5GC for commercial purposes for free.

+
+

Sponsors

+

Platinum

+
+ + + + +
+ +

Gold

+
+ +
+ +

Silver

+
+ +
+ +

Hardware Sponsors

+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/mathjaxhelper.js b/mathjaxhelper.js new file mode 100644 index 00000000..f0d0d3c3 --- /dev/null +++ b/mathjaxhelper.js @@ -0,0 +1,5 @@ +MathJax.Hub.Config({ + config: ["MMLorHTML.js"], + jax: ["input/TeX", "output/HTML-CSS", "output/NativeMML"], + extensions: ["MathMenu.js", "MathZoom.js"] + }); \ No newline at end of file diff --git a/membership/index.html b/membership/index.html new file mode 100644 index 00000000..48572df0 --- /dev/null +++ b/membership/index.html @@ -0,0 +1,826 @@ + + + + + + + + + + + + + + + + + + + Index - free5GC + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +

Sponsorship Info

+

Sponsorship Tiers

+

free5GC is a nonprofit organization dedicated to developing innovative and next-generation features for open-source code of the 5G Core (5GC) Network under Apache 2.0 license. We are tirelessly working on the development of R16, R17, and so on, towards 6G. Your generous support and sponsorship will sustain our technology development and the operation of the community. Your company/organization logo will be displayed on the free5GC website and listed as the sponsorship you participate. Here are the sponsorship tiers we offer.

+
    +
  • Sponsor – donation under US $17,000
  • +
  • Bronze Sponsor – donation from US 34,000
  • +
  • Silver Sponsor – donation from US 68,000
  • +
  • Gold Sponsor – donation from US 102,000
  • +
  • Platinum Sponsor – donation for more than US $102,000
  • +
+

You can now support free5GC with your credit card. Simply follow the two-step instructions provided here and use the link. (Download instruction PDF). Donations made in the US can be tax-deductible.

+

Sponsors who can read Chinese can use this link to donate.

+

Your generosity is appreciated.

+
+

Tips

+
    +
  • You don’t need to donate to use free5GC. You can download free5GC from https://github.com/free5gc/free5gc.
  • +
  • The license of free5GC follows Apache 2.0. That is, anyone can use free5GC for commercial purposes for free. We will not charge any license fee.
  • +
  • If you have any questions or problems regarding donation and sponsorship, please email us at free5GC.org@gmail.com.
  • +
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/publication/index.html b/publication/index.html new file mode 100644 index 00000000..ebf7c0b6 --- /dev/null +++ b/publication/index.html @@ -0,0 +1,981 @@ + + + + + + + + + + + + + + + + + + + + + + + Relavent Publications - free5GC + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +

Publications using free5GC

+

Not from NYCU

+
    +
  1. Ralf Kundel, et al., "User plane hardware acceleration in access networks: Experiences in offloading network functions in real 5g deployments," Hawaii International Conference on System Sciences. Computer Society Press, 2022.
  2. +
  3. Taeyun Kim, et al., "An Implementation Study of Network Data Analytic Function in 5G," IEEE International Conference on Consumer Electronics, 2022.
  4. +
  5. Zhou Cong, et al., "CeUPF: Offloading 5G User Plane Function to Programmable Hardware Base on Co-existence Architecture," ACM International Conference on Intelligent Computing and its Emerging Applications, 2021.
  6. +
  7. Gholamreza Ramezan, et al., "EAP-ZKP: A Zero-Knowledge Proof based Authentication Protocol to Prevent DDoS Attacks at the Edge in Beyond 5G," IEEE 4th 5G World Forum, 2021.
  8. +
  9. Ryan Pepito and Ashutosh Dutta, "Open Source 5G Security Testbed for Edge Computing," IEEE 5G World Forum (5GWF), 2021.
  10. +
  11. Robert MacDavid, et al., "A P4-based 5G User Plane Function," 2021.
  12. +
  13. Joe Breen, et al., "Powder: Platform for Open Wireless Data-driven Experimental Research"," Computer Networks, 2021.
  14. +
  15. Endri Goshi, et al,, "Investigating Inter-NF Dependencies in Cloud-Native 5G Core Network," International Conference on Network and Service Management, 2021.
  16. +
  17. Zhi-Li Zhang, et al., "Towards aSoftware-Defined, Fine-Grained QoS Framework for 5G and Beyond Networks," ACM SIGCOMM Workshop on Network-Application Integration, 2021.
  18. +
  19. Zujany Salazar, et al, "5Greplay: a 5G Network Traffic Fuzzer - Application to Attack Injection," International Conference on Availability, Reliability and Security, 2021.
  20. +
  21. M.J. Kim, et al., "Analysis of Current 5G Open-Source Projects," Electronics and Telecommunications Trends, 36(2), p.83-92, 2021.
  22. +
  23. Yang Hu, et al., "Fuzzing Method Based on Selection Mutation of Partition Weight Table for 5G Core Network NGAP Protocol," International Conference on Innovative Mobile and Internet Services in Ubiquitous Computing. Springer, Cham, 2021.
  24. +
  25. Ayoub Bergaoui, et al., "Demonstration of Orchestration of 5G Core Network Functions with a Satellite Emulator," 2021.
  26. +
  27. Dener Kraus, "Computação de borda para indústria utilizando a rede 5G," 2021.
  28. +
  29. Iria Míguez González, "Virtualized cellular networks with native cloud functions," Master Thesis, Telecommunications Engineering School, 2021.
  30. +
  31. Cameron MacLeod, "Kubernetes for the Deployment of Mobile Core Networks," 2020.
  32. +
  33. Alireza Hosseini Vasoukolaei, Danish Sattar, and Ashraf Matrawy, "TLS Performance Evaluation in the Control Plane of a 5G Core Network Slice," 2021.
  34. +
  35. Rui Silva, et al., "A hybrid SDN solution for mobile networks," Computer Networks, 2021.
  36. +
  37. Wei-Lun Lin, Chien-Hsuan Chen, and Huai-Sheng Huang, "Study on the Online Charging System in B5G Era," IEEE Asia-Pacific Network Operations and Management Symposium, 2021.
  38. +
  39. David Lake, et al. ", "Softwarization of 5G Networks – Implications to Open Platforms and Standardizations," IEEE access 9, 2021.
  40. +
  41. Ali Esmaeily, and Katina Kralevska, "Small-Scale 5G Testbeds for Network Slicing Deployment: A Systematic Review," Wireless Communications and Mobile Computing, 2021.
  42. +
  43. Ashok Kumar Murthy, Ranjani Parthasarathi, and V. Vetriselvi, "Security Testbed for Next Generation Mobile Networks," IEEE Third ISEA Conference on Security and Privacy, p.122-129, 2020.
  44. +
  45. Merlin Chlosta, et al., "5G SUCI-Catchers: Still catching them all?" Annual International Conference on Mobile Computing and Networking, 2020.
  46. +
  47. Žiga Berčič, et al., "Raziskava in praktični preizkus odprtokodnih mobilnih jedrnih sistemov 4G in 5G," Diss. Univerza v Ljubljani, Fakulteta za elektrotehniko, 2020.
  48. +
  49. Christian Mailer, "Plataforma de CORE 5G em nuvem para disponibilização de funções de rede como serviço," 2020.
  50. +
  51. Leonardo Bonati, et al., "Open, Programmable, and Virtualized 5G Networks: State-of-the-Art and the Road Ahead," Computer Networks, 2020.
  52. +
  53. Hung-Yen Weng, Ren-Hung Hwang, and Chin-Feng Lai, "Live MPEG-DASH video streaming cache management with cognitive mobile edge computing," Ambient Intelligence and Humanized Computing p.1-18, 2020.
  54. +
  55. Junaid Jalal, "Enabling Edge Computing In 5G Via Local Area Data Network: Implementation and Experiments," Master Thesis, University of Agder, 2019.
  56. +
  57. David Soldani, "eBPF: A New Approach to Cloud-Native Observability, Networking and Security for Current (5G) and Future Mobile Networks (6G and Beyond)"
  58. +
  59. Matan Broner, Sangwoo Lee, Liuyi Jin, and Radu Stoleru, "Poster: Towards Multi-Radio Access in 5G Networks," MobiSys '23: Proceedings of the 21st Annual International Conference on Mobile Systems, Applications and ServicesJune, p.575–576, 2023
  60. +
  61. Francesco Mancini, Leonardo Tamiano, and Giuseppe Bianchi, "5GShell: a plug-and-play framework for automating the deployment of 5G cellular networks," 26th Conference on Innovation in Clouds, Internet and Networks and Workshops (ICIN), 2023
  62. +
  63. Branislava Zivkovic, and Zoran Cica, "Network Traffic Capturing in Open-Source 5G Core Network Platform," 10th International Conference on Electrical, Electronic and Computing Engineering (IcETRAN), 2023
  64. +
  65. Gabriel Lando, Lucas Augusto Fonseca Schierholt, Mateus Paludo Milesi, and Juliano Araujo Wickboldt, "Evaluating the performance of open source software implementations of the 5G network core," NOMS 2023-2023 IEEE/IFIP Network Operations and Management Symposium, 2023
  66. +
  67. Zixu Tian, Rajendra Patil, Mohan Gurusamy, and Joshua McCloud, "ADSeq-5GCN: Anomaly Detection from Network Traffic Sequences in 5G Core Network Control Plane," IEEE 24th International Conference on High Performance Switching and Routing (HPSR), 2023
  68. +
  69. Taeho Park, Hochan Lee, Heewon Kim, Subin Han, Taeyun Kim, and Sangheon Pack, "Divide and Cache: A Novel Control Plane Framework for Private 5G Networks," IEEE 20th Consumer Communications & Networking Conference (CCNC), 2023
  70. +
  71. Seungchan Woo, Jaehyoung Park, Soonhong Kwon, Kyungmin Park, Jonghyun Kim, and Jong-Hyouk Lee, "Simulation of Data Hijacking Attacks for a 5G-Advanced Core Network," Joint European Conference on Networks and Communications & 6G Summit (EuCNC/6G Summit), 2023
  72. +
  73. Zhao Liu, Ping Wang, Yongjing Li, Zhan Xu, and Ming Zhao, "A Blockchain-based Method for Monitoring Userplane Congestion in Mobile Core Network," IEEE 13th International Conference on Electronics Information and Emergency Communication (ICEIEC), 2023
  74. +
  75. Domenico Scotece, Asma Noor, Luca Foschini, and Antonio Corradi, "5G-Kube: Complex Telco Core Infrastructure Deployment Made Low-Cost," IEEE Communications Magazine, p.26-30, ( Volume: 61, Issue: 7, July 2023)
  76. +
  77. Serkut Ayvaşık, Edwin Babaians, Arled Papa, Yash Deshpande, Alba Jano, Wolfgang Kellerer, and Eckehard Steinbach, "Demo: Remote Robot Control with Haptic Feedback over the Munich 5G Research Hub Testbed," IEEE 24th International Symposium on a World of Wireless, Mobile and Multimedia Networks (WoWMoM), 2023
  78. +
  79. Robert Pell, Mohammad Shojafar, Dimitrios Kosmanos, and Sotiris Moschoyiannis, "Service Classification of Network Traffic in 5G Core Networks using Machine Learning, "IEEE International Conference on Edge Computing and Communications (EDGE)," IEEE International Conference on Edge Computing and Communications (EDGE), 2023
  80. +
  81. Nathalie Wehbe, Hyame Assem Alameddine, Makan Pourzandi, and Chadi Assi, "5GShield: HTTP/2 Anomaly Detection in 5G Service-Based Architecture," 2023 IFIP Networking Conference (IFIP Networking), 2023
  82. +
  83. Francesco Mancini, and Giuseppe Bianchi, "ScasDK - A Development Kit for Security Assurance test in Multi-Network-Function 5G," ARES '23: Proceedings of the 18th International Conference on Availability, Reliability and Security, 2023
  84. +
  85. Jiasheng Wu, Yue Gao, Lin Wang, Jingjing Zhang, and Dapeng Oliver Wu, "How to Allocate Resources in Cloud Native Networks Towards 6G," IEEE Network, p.1-7, 2023
  86. +
  87. Koya Ito, and Noboru Izuka, "Proposal of Client-Server Based Vertical Handover Scheme Using Virtual Routers for Edge Computing in Local 5G Networks and WLANs," IEEE 13th Annual Computing and Communication Workshop and Conference (CCWC), 2023
  88. +
  89. Zujany Salazar, Fatiha Zaïdi, Huu-Nghia Nguyen, Wissam Mallouli, Ana Rosa Cavalli, and Edgardo Montes De Oca, "A Network Traffic Mutation Based Ontology, and Its Application to 5G Networks," IEEE Access ( Volume: 11), 2023
  90. +
  91. Jon Larrea, Andrew E. Ferguson, and Mahesh K. Marina "CoreKube: An Efficient, Autoscaling and Resilient Mobile Core System" ACM MobiCom 2023
  92. +
  93. Ruoting Xiong, Kit-Lun Tong, Yi Ren, Wei Ren, and Gerard Parr, "From 5G to 6G: It is Time to Sniff the Communications between a Base Station and Core Networks" ACM MobiCom 2023
  94. +
  95. Bhavishya Sharma, Shwetha Vittal, and A Antony Franklin "FlexCore: Leveraging XDP-SCTP for Scalable and Resilient Network Slice Service in Future 5G Core" APNET 2023
  96. +
  97. Francesco Mancini, Giuseppe Bianchi "ScasDK - A Development Kit for Security Assurance test in Multi-Network-Function 5G" ARES 2023
  98. +
  99. Endri Goshi, Michael Jarschel, Rastin Pries, Mu He, Wolfgang Kellerer, "Investigating Inter-NF Dependencies in Cloud-Native 5G Core Networks" 2021 17th International Conference on Network and Service Management (CNSM) IEEE
  100. +
  101. Weihong Cai, Hongyue Lv, Fanfeng Kong, "A Concurrent Improvement Design for UDM Architecture in Free5GC" 2023 8th International Conference on Information Systems Engineering (ICISE) IEEE
  102. +
+

From NYCU

+
    +
  1. V. Jain, H.-T. Chu, S. Qi, C.-A. Lee, H.-C. Chang, C.-Y. Hsieh, K. K. Ramakrishnan, and J.-C. Chen, "L25GC: A Low Latency 5G Core Network based on High-Performance NFV Platforms," full paper, ACM SIGCOMM Conference (SIGCOMM ‘22), (Amsterdam, Netherlands), pp. 143–157, Aug. 2022.
  2. +
  3. C.-Y. Hsieh, Y.-W. Chang, C. Chen, and J.-C. Chen, "Poster: Design and Implementation of a Generic 5G User Plane Function Development Framework," ACM International Conference on Mobile Computing and Networking (MobiCom ‘21), (New Orleans, United States), pp. 846–848, Oct. 2021. (Won second place of the ACM MobiCom 2021 Student Research Competition)
  4. +
  5. Yu-Herng Chai, and Fuchun Joseph Lin, "Evaluating Dedicated Slices of Different Configurations in 5G Core," Computer and Communications, 9(7), p.55-72, 2021.
  6. +
  7. Cheng-Chin Tsai, Fuchun Joseph Lin, and Hiroshige Tanaka, "Evaluation of 5G Core Slicing on User Plane Function," Communications and Network, 13(3), p.79-92, 2021.
  8. +
  9. Wei-Cheng Chang, and Fuchun Joseph Lin, "Coordinated Management of 5G Core Slices by MANO and OSS/BSS," Computer and Communications, 9(6), p.52-72, 2021.
  10. +
  11. Seungjoon Seok, et al, "Towards Service and Networking Intelligence for Humanity: A Report on APNOMS 2020," Network and Systems Management, 29(4), p.1-11, 2021.
  12. +
  13. Yi-Bing Lin, Chien-Chao Tseng, and Ming-Hung Wang, "Effects of Transport Network Slicing on 5G Applications," Future Internet, 13(3), p.69, 2021.
  14. +
  15. Tze-Jie Tan, et al., "A Reliable Intelligent Routing Mechanism in 5G Core Network (5GC)," Annual International Conference on Mobile Computing and Networking, 2020.
  16. +
  17. Chia-Wei Liao, Fuchun Joseph Lin, and Yoichi Sato, "Evaluating NFV-enabled Network Slicing for 5G Core," IEEE Asia-Pacific Network Operations and Management Symposium, 2020.
  18. +
  19. Yin-Chi Li, Ping-Tsan Liu, Yi-An Tai, Che-Hung Liu, Man-Hsin Chen, Chi-Yu Li, and Guan-Hua Tu, "A Runtime Anomaly Detector via Service Communication Proxy for 5G Mobile Networks," IEEE INFOCOM 2023
  20. +
  21. Ho-Cheng Lee, Fuchun Joseph Lin, Jyh-Cheng Chen, Chien Chen, and Patrick Wang, "Enhancing 5G Core with Multi-Access Edge Computing," 32nd Wireless and Optical Communications Conference (WOCC), 2023
  22. +
  23. Yi Liu, Qiaoling Li, Qingping Cao, Zhilan Huang, Yangchun Li, Yongbing Fan, "Evaluation of Free5GC Forwarding Performance on Private and Public Clouds" 2022 IEEE Cloud Summit
  24. +
  25. Muthuraman Elangovan, Muhammad Shahid Iqbal, Chien Chen, Jyh-Cheng Chen, "Accelerating free5GC Data Plane Using Programmable Hardware" 2023 24st Asia-Pacific Network Operations and Management Symposium (APNOMS) IEEE
  26. +
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 00000000..dd067b05 --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Home","text":""},{"location":"#what-is-free5gc","title":"What is free5GC?","text":"

The free5GC is an open-source project for 5th generation (5G) mobile core networks. The ultimate goal of this project is to implement the 5G core network (5GC) defined in 3GPP Release 15 (R15) and beyond.

Currently, the major contributors are from National Yang Ming Chiao Tung University (NYCU). Please refer to our roadmap for the features of each release.

  • The source code of the latest version of free5GC can be downloaded from here.
  • Follow our LinkedIn page to get the news about free5GC.

Note

Thank you very much for your interest in free5GC. The license of free5GC follows Apache 2.0. That is, anyone can use free5GC for commercial purposes for free.

"},{"location":"#sponsors","title":"Sponsors","text":""},{"location":"#platinum","title":"Platinum","text":""},{"location":"#gold","title":"Gold","text":""},{"location":"#silver","title":"Silver","text":""},{"location":"#hardware-sponsors","title":"Hardware Sponsors","text":""},{"location":"publication/","title":"Relavent Publications","text":""},{"location":"publication/#publications-using-free5gc","title":"Publications using free5GC","text":""},{"location":"publication/#not-from-nycu","title":"Not from NYCU","text":"
  1. Ralf Kundel, et al., \"User plane hardware acceleration in access networks: Experiences in offloading network functions in real 5g deployments,\" Hawaii International Conference on System Sciences. Computer Society Press, 2022.
  2. Taeyun Kim, et al., \"An Implementation Study of Network Data Analytic Function in 5G,\" IEEE International Conference on Consumer Electronics, 2022.
  3. Zhou Cong, et al., \"CeUPF: Offloading 5G User Plane Function to Programmable Hardware Base on Co-existence Architecture,\" ACM International Conference on Intelligent Computing and its Emerging Applications, 2021.
  4. Gholamreza Ramezan, et al., \"EAP-ZKP: A Zero-Knowledge Proof based Authentication Protocol to Prevent DDoS Attacks at the Edge in Beyond 5G,\" IEEE 4th 5G World Forum, 2021.
  5. Ryan Pepito and Ashutosh Dutta, \"Open Source 5G Security Testbed for Edge Computing,\" IEEE 5G World Forum (5GWF), 2021.
  6. Robert MacDavid, et al., \"A P4-based 5G User Plane Function,\" 2021.
  7. Joe Breen, et al., \"Powder: Platform for Open Wireless Data-driven Experimental Research\",\" Computer Networks, 2021.
  8. Endri Goshi, et al,, \"Investigating Inter-NF Dependencies in Cloud-Native 5G Core Network,\" International Conference on Network and Service Management, 2021.
  9. Zhi-Li Zhang, et al., \"Towards aSoftware-Defined, Fine-Grained QoS Framework for 5G and Beyond Networks,\" ACM SIGCOMM Workshop on Network-Application Integration, 2021.
  10. Zujany Salazar, et al, \"5Greplay: a 5G Network Traffic Fuzzer - Application to Attack Injection,\" International Conference on Availability, Reliability and Security, 2021.
  11. M.J. Kim, et al., \"Analysis of Current 5G Open-Source Projects,\" Electronics and Telecommunications Trends, 36(2), p.83-92, 2021.
  12. Yang Hu, et al., \"Fuzzing Method Based on Selection Mutation of Partition Weight Table for 5G Core Network NGAP Protocol,\" International Conference on Innovative Mobile and Internet Services in Ubiquitous Computing. Springer, Cham, 2021.
  13. Ayoub Bergaoui, et al., \"Demonstration of Orchestration of 5G Core Network Functions with a Satellite Emulator,\" 2021.
  14. Dener Kraus, \"Computa\u00e7\u00e3o de borda para ind\u00fastria utilizando a rede 5G,\" 2021.
  15. Iria M\u00edguez Gonz\u00e1lez, \"Virtualized cellular networks with native cloud functions,\" Master Thesis, Telecommunications Engineering School, 2021.
  16. Cameron MacLeod, \"Kubernetes for the Deployment of Mobile Core Networks,\" 2020.
  17. Alireza Hosseini Vasoukolaei, Danish Sattar, and Ashraf Matrawy, \"TLS Performance Evaluation in the Control Plane of a 5G Core Network Slice,\" 2021.
  18. Rui Silva, et al., \"A hybrid SDN solution for mobile networks,\" Computer Networks, 2021.
  19. Wei-Lun Lin, Chien-Hsuan Chen, and Huai-Sheng Huang, \"Study on the Online Charging System in B5G Era,\" IEEE Asia-Pacific Network Operations and Management Symposium, 2021.
  20. David Lake, et al. \", \"Softwarization of 5G Networks \u2013 Implications to Open Platforms and Standardizations,\" IEEE access 9, 2021.
  21. Ali Esmaeily, and Katina Kralevska, \"Small-Scale 5G Testbeds for Network Slicing Deployment: A Systematic Review,\" Wireless Communications and Mobile Computing, 2021.
  22. Ashok Kumar Murthy, Ranjani Parthasarathi, and V. Vetriselvi, \"Security Testbed for Next Generation Mobile Networks,\" IEEE Third ISEA Conference on Security and Privacy, p.122-129, 2020.
  23. Merlin Chlosta, et al., \"5G SUCI-Catchers: Still catching them all?\" Annual International Conference on Mobile Computing and Networking, 2020.
  24. \u017diga Ber\u010di\u010d, et al., \"Raziskava in prakti\u010dni preizkus odprtokodnih mobilnih jedrnih sistemov 4G in 5G,\" Diss. Univerza v Ljubljani, Fakulteta za elektrotehniko, 2020.
  25. Christian Mailer, \"Plataforma de CORE 5G em nuvem para disponibiliza\u00e7\u00e3o de fun\u00e7\u00f5es de rede como servi\u00e7o,\" 2020.
  26. Leonardo Bonati, et al., \"Open, Programmable, and Virtualized 5G Networks: State-of-the-Art and the Road Ahead,\" Computer Networks, 2020.
  27. Hung-Yen Weng, Ren-Hung Hwang, and Chin-Feng Lai, \"Live MPEG-DASH video streaming cache management with cognitive mobile edge computing,\" Ambient Intelligence and Humanized Computing p.1-18, 2020.
  28. Junaid Jalal, \"Enabling Edge Computing In 5G Via Local Area Data Network: Implementation and Experiments,\" Master Thesis, University of Agder, 2019.
  29. David Soldani, \"eBPF: A New Approach to Cloud-Native Observability, Networking and Security for Current (5G) and Future Mobile Networks (6G and Beyond)\"
  30. Matan Broner, Sangwoo Lee, Liuyi Jin, and Radu Stoleru, \"Poster: Towards Multi-Radio Access in 5G Networks,\" MobiSys '23: Proceedings of the 21st Annual International Conference on Mobile Systems, Applications and ServicesJune, p.575\u2013576, 2023
  31. Francesco Mancini, Leonardo Tamiano, and Giuseppe Bianchi, \"5GShell: a plug-and-play framework for automating the deployment of 5G cellular networks,\" 26th Conference on Innovation in Clouds, Internet and Networks and Workshops (ICIN), 2023
  32. Branislava Zivkovic, and Zoran Cica, \"Network Traffic Capturing in Open-Source 5G Core Network Platform,\" 10th International Conference on Electrical, Electronic and Computing Engineering (IcETRAN), 2023
  33. Gabriel Lando, Lucas Augusto Fonseca Schierholt, Mateus Paludo Milesi, and Juliano Araujo Wickboldt, \"Evaluating the performance of open source software implementations of the 5G network core,\" NOMS 2023-2023 IEEE/IFIP Network Operations and Management Symposium, 2023
  34. Zixu Tian, Rajendra Patil, Mohan Gurusamy, and Joshua McCloud, \"ADSeq-5GCN: Anomaly Detection from Network Traffic Sequences in 5G Core Network Control Plane,\" IEEE 24th International Conference on High Performance Switching and Routing (HPSR), 2023
  35. Taeho Park, Hochan Lee, Heewon Kim, Subin Han, Taeyun Kim, and Sangheon Pack, \"Divide and Cache: A Novel Control Plane Framework for Private 5G Networks,\" IEEE 20th Consumer Communications & Networking Conference (CCNC), 2023
  36. Seungchan Woo, Jaehyoung Park, Soonhong Kwon, Kyungmin Park, Jonghyun Kim, and Jong-Hyouk Lee, \"Simulation of Data Hijacking Attacks for a 5G-Advanced Core Network,\" Joint European Conference on Networks and Communications & 6G Summit (EuCNC/6G Summit), 2023
  37. Zhao Liu, Ping Wang, Yongjing Li, Zhan Xu, and Ming Zhao, \"A Blockchain-based Method for Monitoring Userplane Congestion in Mobile Core Network,\" IEEE 13th International Conference on Electronics Information and Emergency Communication (ICEIEC), 2023
  38. Domenico Scotece, Asma Noor, Luca Foschini, and Antonio Corradi, \"5G-Kube: Complex Telco Core Infrastructure Deployment Made Low-Cost,\" IEEE Communications Magazine, p.26-30, ( Volume: 61, Issue: 7, July 2023)
  39. Serkut Ayva\u015f\u0131k, Edwin Babaians, Arled Papa, Yash Deshpande, Alba Jano, Wolfgang Kellerer, and Eckehard Steinbach, \"Demo: Remote Robot Control with Haptic Feedback over the Munich 5G Research Hub Testbed,\" IEEE 24th International Symposium on a World of Wireless, Mobile and Multimedia Networks (WoWMoM), 2023
  40. Robert Pell, Mohammad Shojafar, Dimitrios Kosmanos, and Sotiris Moschoyiannis, \"Service Classification of Network Traffic in 5G Core Networks using Machine Learning, \"IEEE International Conference on Edge Computing and Communications (EDGE),\" IEEE International Conference on Edge Computing and Communications (EDGE), 2023
  41. Nathalie Wehbe, Hyame Assem Alameddine, Makan Pourzandi, and Chadi Assi, \"5GShield: HTTP/2 Anomaly Detection in 5G Service-Based Architecture,\" 2023 IFIP Networking Conference (IFIP Networking), 2023
  42. Francesco Mancini, and Giuseppe Bianchi, \"ScasDK - A Development Kit for Security Assurance test in Multi-Network-Function 5G,\" ARES '23: Proceedings of the 18th International Conference on Availability, Reliability and Security, 2023
  43. Jiasheng Wu, Yue Gao, Lin Wang, Jingjing Zhang, and Dapeng Oliver Wu, \"How to Allocate Resources in Cloud Native Networks Towards 6G,\" IEEE Network, p.1-7, 2023
  44. Koya Ito, and Noboru Izuka, \"Proposal of Client-Server Based Vertical Handover Scheme Using Virtual Routers for Edge Computing in Local 5G Networks and WLANs,\" IEEE 13th Annual Computing and Communication Workshop and Conference (CCWC), 2023
  45. Zujany Salazar, Fatiha Za\u00efdi, Huu-Nghia Nguyen, Wissam Mallouli, Ana Rosa Cavalli, and Edgardo Montes De Oca, \"A Network Traffic Mutation Based Ontology, and Its Application to 5G Networks,\" IEEE Access ( Volume: 11), 2023
  46. Jon Larrea, Andrew E. Ferguson, and Mahesh K. Marina \"CoreKube: An Efficient, Autoscaling and Resilient Mobile Core System\" ACM MobiCom 2023
  47. Ruoting Xiong, Kit-Lun Tong, Yi Ren, Wei Ren, and Gerard Parr, \"From 5G to 6G: It is Time to Sniff the Communications between a Base Station and Core Networks\" ACM MobiCom 2023
  48. Bhavishya Sharma, Shwetha Vittal, and A Antony Franklin \"FlexCore: Leveraging XDP-SCTP for Scalable and Resilient Network Slice Service in Future 5G Core\" APNET 2023
  49. Francesco Mancini, Giuseppe Bianchi \"ScasDK - A Development Kit for Security Assurance test in Multi-Network-Function 5G\" ARES 2023
  50. Endri Goshi, Michael Jarschel, Rastin Pries, Mu He, Wolfgang Kellerer, \"Investigating Inter-NF Dependencies in Cloud-Native 5G Core Networks\" 2021 17th International Conference on Network and Service Management (CNSM) IEEE
  51. Weihong Cai, Hongyue Lv, Fanfeng Kong, \"A Concurrent Improvement Design for UDM Architecture in Free5GC\" 2023 8th International Conference on Information Systems Engineering (ICISE) IEEE
"},{"location":"publication/#from-nycu","title":"From NYCU","text":"
  1. V. Jain, H.-T. Chu, S. Qi, C.-A. Lee, H.-C. Chang, C.-Y. Hsieh, K. K. Ramakrishnan, and J.-C. Chen, \"L25GC: A Low Latency 5G Core Network based on High-Performance NFV Platforms,\" full paper, ACM SIGCOMM Conference (SIGCOMM \u201822), (Amsterdam, Netherlands), pp. 143\u2013157, Aug. 2022.
  2. C.-Y. Hsieh, Y.-W. Chang, C. Chen, and J.-C. Chen, \"Poster: Design and Implementation of a Generic 5G User Plane Function Development Framework,\" ACM International Conference on Mobile Computing and Networking (MobiCom \u201821), (New Orleans, United States), pp. 846\u2013848, Oct. 2021. (Won second place of the ACM MobiCom 2021 Student Research Competition)
  3. Yu-Herng Chai, and Fuchun Joseph Lin, \"Evaluating Dedicated Slices of Different Configurations in 5G Core,\" Computer and Communications, 9(7), p.55-72, 2021.
  4. Cheng-Chin Tsai, Fuchun Joseph Lin, and Hiroshige Tanaka, \"Evaluation of 5G Core Slicing on User Plane Function,\" Communications and Network, 13(3), p.79-92, 2021.
  5. Wei-Cheng Chang, and Fuchun Joseph Lin, \"Coordinated Management of 5G Core Slices by MANO and OSS/BSS,\" Computer and Communications, 9(6), p.52-72, 2021.
  6. Seungjoon Seok, et al, \"Towards Service and Networking Intelligence for Humanity: A Report on APNOMS 2020,\" Network and Systems Management, 29(4), p.1-11, 2021.
  7. Yi-Bing Lin, Chien-Chao Tseng, and Ming-Hung Wang, \"Effects of Transport Network Slicing on 5G Applications,\" Future Internet, 13(3), p.69, 2021.
  8. Tze-Jie Tan, et al., \"A Reliable Intelligent Routing Mechanism in 5G Core Network (5GC),\" Annual International Conference on Mobile Computing and Networking, 2020.
  9. Chia-Wei Liao, Fuchun Joseph Lin, and Yoichi Sato, \"Evaluating NFV-enabled Network Slicing for 5G Core,\" IEEE Asia-Pacific Network Operations and Management Symposium, 2020.
  10. Yin-Chi Li, Ping-Tsan Liu, Yi-An Tai, Che-Hung Liu, Man-Hsin Chen, Chi-Yu Li, and Guan-Hua Tu, \"A Runtime Anomaly Detector via Service Communication Proxy for 5G Mobile Networks,\" IEEE INFOCOM 2023
  11. Ho-Cheng Lee, Fuchun Joseph Lin, Jyh-Cheng Chen, Chien Chen, and Patrick Wang, \"Enhancing 5G Core with Multi-Access Edge Computing,\" 32nd Wireless and Optical Communications Conference (WOCC), 2023
  12. Yi Liu, Qiaoling Li, Qingping Cao, Zhilan Huang, Yangchun Li, Yongbing Fan, \"Evaluation of Free5GC Forwarding Performance on Private and Public Clouds\" 2022 IEEE Cloud Summit
  13. Muthuraman Elangovan, Muhammad Shahid Iqbal, Chien Chen, Jyh-Cheng Chen, \"Accelerating free5GC Data Plane Using Programmable Hardware\" 2023 24st Asia-Pacific Network Operations and Management Symposium (APNOMS) IEEE
"},{"location":"videos/","title":"Other Videos","text":""},{"location":"videos/#other-videos-showing-free5gc","title":"Other videos showing free5GC","text":"
  1. Akraino Blueprints: Integrated Cloud Native Private Wireless, The Linux Foundation, October 11, 2021

  2. SD Core Techinar July 7 2021, Open Networking Foundation, July 13, 2021

  3. Aarna Networks MWC 2021 Demo, Aarna Networks Channel, June 27, 2021

  4. OpenStack Tacker Demo, Open Infrastructure Foundation, April 26, 2021

  5. OpenNess Tungsten Fabric free5GC demo, Aarna Networks Channel, February 16, 2021

  6. 5G Core on Diamanti, Diamanti, Inc., February 3, 2021

  7. free5GC (5G Core) Orchestration on Kubernetes with Tungsten Fabric CNI and Testing, Aarna Networks Channel, December 2, 2020

  8. IoT LoRa (sensors and gateway in hardware), RAN in hardware (SDR) and software, and the free5GC, LABORA Research Group, July 3, 2020

  9. UE and eNodeB in Hardware (conventional cell phone + SDR) and free5GC: a pratical approach in 5G, LABORA Research Group, July 3, 2020

  10. OpenAirInterface and free5GC: a pratical approach in 5G networks, LABORA Research Group, June 29, 2020

"},{"location":"blog/","title":"Index","text":""},{"location":"blog/#blogs","title":"Blogs","text":""},{"location":"blog/#official","title":"Official","text":"
  • 2024/01/03: UE-initiated PLR Measurement Procedure in PMFP Procedure
  • 2023/12/13: Article Sharing: Evaluating Dedicated Slices of Different Configurations in 5G Core
  • 2023/12/06: Introduction of IP Multimedia Subsystem Part 2
  • 2023/11/29: Basic concept of RCU: Read, Copy, Update
  • 2023/11/22: Nephio: a Cloud Native Network Automation Linux Foundation Project
  • 2023/11/15: free5GC OAuth2 Procedure
  • 2023/11/08: Introduction of IP Multimedia Subsystem Part 1
  • 2023/10/18: LTE Authentication Introduction: EPS-AKA'
  • 2023/10/4: Introduction of MPTCP
  • 2023/9/27: Support of Time Sensitive Communication and Time Synchronization in 5G system - Introduction (Rel-17)
  • 2023/9/20: Introduction of gtp5g and some kernel concepts
  • 2023/9/13: CHarging Function(CHF) Overview
  • 2023/9/6: The role of VNFD and NSD in 5G Network Slicing
  • 2023/8/30: Article Sharing: eBPF: A New Approach to Cloud-Native Observability, Networking and Security for Current (5G) and Future Mobile Networks (6G and Beyond)
  • 2023/8/23: Web security: CSRF vulnerability in webconsole
  • 2023/8/16: Introduce Kubernetes and Deployment free5GC on Kubernetes with helm
  • 2023/8/9: Fuzz Testing in Go: Discovering Vulnerabilities and Analyzing a Real Case (CVE-2022-43677)
  • 2023/8/2: Authentication Mechanism in NRF: What Is OAuth?
  • 2023/7/26: How to deploy free5GC network slice on OpenStack
  • 2023/7/19: Network function UDM introduction
  • 2023/7/12: Time-Sensitive Networking over 5G system - Introduction (Rel-16)
  • 2023/7/5: Use network namespace to separate the 5GC and RAN simulator
  • 2023/6/21: 5G SCTP LoadBalancer Using LoxiLB Applying on free5GC
"},{"location":"blog/#external","title":"External","text":"
  • free5GC 5GC & UERANSIM UE / RAN Sample Configuration - VPP-UPF with DPDK
  • Deploy eBPF-based UPF with free5GC
  • Introduce to 5GC (written in traditional chinese)
  • Deploying 5G core network with Free5GC, Kubernetes and Helm
  • Running Free5GC on Platform9 Managed Kubernetes

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20230705/1-free5gc-with-namespace/","title":"1 free5gc with namespace","text":""},{"location":"blog/20230705/1-free5gc-with-namespace/#use-network-namespace-to-separate-the-5g-core-5gc-and-ran-simulator","title":"Use network namespace to separate the 5G core (5GC) and RAN simulator","text":"

Note

Author: Jimmy Chang Date: 2023/7/5

"},{"location":"blog/20230705/1-free5gc-with-namespace/#overview","title":"Overview","text":"

This technique leverages namespace to run UERANSIM, an opensource 5G-UE and RAN(gNodeB) simulator, and connect to free5GC. UERANSIM follows the 3GPP specification for developing and can support multiple 5G core (5GC) including free5GC.

Why are we using namespace? Well, you can follow ULCL and free5GC compose to set up the environment with VM and docker, but there are limitations for hardware\u2019s capability. With network namespace, you can have different and separate network instances of network interfaces and routing tables that operate independently.

So, what is network namespace? Network namespace makes a copy of network stack with its own routing table, firewall and devices. A named network namespace is an object at /var/run/netns/. The file descriptor resulting from opening /var/run/netns/ refers to the specified network namespace. Holding that file descriptor open keeps the network namespace alive.

And how to make both namespaces communicating? A virtual Ethernet device (veth) pair provides the abstraction that can be used to create tunnels between network namespaces, and can be used to create bridge to a physical network device in another namespace. Veth pair also be used as standalone network devices. When the namespace freed, veth device which attatch to would be destroyed.

The environment is as follow. Suppose you have already installed as well as set up free5GC and UERANSIM properly.

  • free5GC v3.3.0
  • UERANSIM v3.1.0

Note

Namespace free5GC represents host network namespace. And enp0s5 is an ethernet interface connectting to external.

Each devices as follow\n| Device        | IP             |\n| ------------- | -------------  |\n| veth0         | 10.200.200.1   |\n| veth1         | 10.200.200.2   |\n| br-veth0      | none           |\n| br-veth1      | none           |\n| enp0s5        | 10.211.55.23   |\n\n\nUE information in UERANSIM as follow. Already \n| IMSI             | DNN           |\n| ---------------- | ------------- |\n| 208930000000003  | internet      |\n
"},{"location":"blog/20230705/1-free5gc-with-namespace/#configuration-file-of-free5gc-and-ueransim","title":"Configuration file of free5GC and UERANSIM","text":""},{"location":"blog/20230705/1-free5gc-with-namespace/#free5gc","title":"free5GC","text":"
  • free5gc/config/amfcfg.yaml

Replace ngapIpList IP from 127.0.0.18 to 10.200.200.2:

info:\n  version: 1.0.9\n  description: AMF initial local configuration\n\nconfiguration:\n  amfName: AMF # the name of this AMF\n  ngapIpList:  # the IP list of N2 interfaces on this AMF\n    - 10.200.200.2 # 127.0.0.18\n  ngapPort: 38412 # the SCTP port listened by NGAP\n  sbi: # Service-based interface information\n    scheme: http # the protocol for sbi (http or https)\n    registerIPv4: 127.0.0.18 # IP used to register to NRF\n    bindingIPv4: 127.0.0.18  # IP used to bind the service\n    port: 8000 # port used to bind the service\n    tls: # the local path of TLS key\n      pem: cert/amf.pem # AMF TLS Certificate\n      key: cert/amf.key # AMF TLS Private key\n  serviceNameList: # the SBI services provided by this AMF, refer to TS 29.518\n    - namf-comm # Namf_Communication service\n    - namf-evts # Namf_EventExposure service\n    - namf-mt   # Namf_MT service\n    - namf-loc  # Namf_Location service\n    - namf-oam  # OAM service\n  servedGuamiList: # Guami (Globally Unique AMF ID) list supported by this AMF\n    # <GUAMI> = <MCC><MNC><AMF ID>\n    - plmnId: # Public Land Mobile Network ID, <PLMN ID> = <MCC><MNC>\n        mcc: 208 # Mobile Country Code (3 digits string, digit: 0~9)\n        mnc: 93 # Mobile Network Code (2 or 3 digits string, digit: 0~9)\n      amfId: cafe00 # AMF identifier (3 bytes hex string, range: 000000~FFFFFF)\n  supportTaiList:  # the TAI (Tracking Area Identifier) list supported by this AMF\n    - plmnId: # Public Land Mobile Network ID, <PLMN ID> = <MCC><MNC>\n        mcc: 208 # Mobile Country Code (3 digits string, digit: 0~9)\n        mnc: 93 # Mobile Network Code (2 or 3 digits string, digit: 0~9)\n      tac: 000001 # Tracking Area Code (3 bytes hex string, range: 000000~FFFFFF)\n  plmnSupportList: # the PLMNs (Public land mobile network) list supported by this AMF\n    - plmnId: # Public Land Mobile Network ID, <PLMN ID> = <MCC><MNC>\n        mcc: 208 # Mobile Country Code (3 digits string, digit: 0~9)\n        mnc: 93 # Mobile Network Code (2 or 3 digits string, digit: 0~9)\n      snssaiList: # the S-NSSAI (Single Network Slice Selection Assistance Information) list supported by this AMF\n        - sst: 1 # Slice/Service Type (uinteger, range: 0~255)\n          sd: 010203 # Slice Differentiator (3 bytes hex string, range: 000000~FFFFFF)\n        - sst: 1 # Slice/Service Type (uinteger, range: 0~255)\n          sd: 112233 # Slice Differentiator (3 bytes hex string, range: 000000~FFFFFF)\n  supportDnnList:  # the DNN (Data Network Name) list supported by this AMF\n    - internet\n  nrfUri: http://127.0.0.10:8000 # a valid URI of NRF\n  security:  # NAS security parameters\n    integrityOrder: # the priority of integrity algorithms\n      - NIA2\n      # - NIA0\n    cipheringOrder: # the priority of ciphering algorithms\n      - NEA0\n      # - NEA2\n  networkName:  # the name of this core network\n    full: free5GC\n    short: free\n  ngapIE: # Optional NGAP IEs\n    mobilityRestrictionList: # Mobility Restriction List IE, refer to TS 38.413\n      enable: true # append this IE in related message or not\n    maskedIMEISV: # Masked IMEISV IE, refer to TS 38.413\n      enable: true # append this IE in related message or not\n    redirectionVoiceFallback: # Redirection Voice Fallback IE, refer to TS 38.413\n      enable: false # append this IE in related message or not\n  nasIE: # Optional NAS IEs\n    networkFeatureSupport5GS: # 5gs Network Feature Support IE, refer to TS 24.501\n      enable: true # append this IE in Registration accept or not\n      length: 1 # IE content length (uinteger, range: 1~3)\n      imsVoPS: 0 # IMS voice over PS session indicator (uinteger, range: 0~1)\n      emc: 0 # Emergency service support indicator for 3GPP access (uinteger, range: 0~3)\n      emf: 0 # Emergency service fallback indicator for 3GPP access (uinteger, range: 0~3)\n      iwkN26: 0 # Interworking without N26 interface indicator (uinteger, range: 0~1)\n      mpsi: 0 # MPS indicator (uinteger, range: 0~1)\n      emcN3: 0 # Emergency service support indicator for Non-3GPP access (uinteger, range: 0~1)\n      mcsi: 0 # MCS indicator (uinteger, range: 0~1)\n  t3502Value: 720  # timer value (seconds) at UE side\n  t3512Value: 3600 # timer value (seconds) at UE side\n  non3gppDeregTimerValue: 3240 # timer value (seconds) at UE side\n  # retransmission timer for paging message\n  t3513:\n    enable: true     # true or false\n    expireTime: 6s   # default is 6 seconds\n    maxRetryTimes: 4 # the max number of retransmission\n  # retransmission timer for NAS Deregistration Request message\n  t3522:\n    enable: true     # true or false\n    expireTime: 6s   # default is 6 seconds\n    maxRetryTimes: 4 # the max number of retransmission\n  # retransmission timer for NAS Registration Accept message\n  t3550:\n    enable: true     # true or false\n    expireTime: 6s   # default is 6 seconds\n    maxRetryTimes: 4 # the max number of retransmission\n  # retransmission timer for NAS Authentication Request/Security Mode Command message\n  t3560:\n    enable: true     # true or false\n    expireTime: 6s   # default is 6 seconds\n    maxRetryTimes: 4 # the max number of retransmission\n  # retransmission timer for NAS Notification message\n  t3565:\n    enable: true     # true or false\n    expireTime: 6s   # default is 6 seconds\n    maxRetryTimes: 4 # the max number of retransmission\n  # retransmission timer for NAS Identity Request message\n  t3570:\n    enable: true     # true or false\n    expireTime: 6s   # default is 6 seconds\n    maxRetryTimes: 4 # the max number of retransmission\n  locality: area1 # Name of the location where a set of AMF, SMF, PCF and UPFs are located\n  sctp: # set the sctp server setting <optinal>, once this field is set, please also add maxInputStream, maxOsStream, maxAttempts, maxInitTimeOut\n    numOstreams: 3 # the maximum out streams of each sctp connection\n    maxInstreams: 5 # the maximum in streams of each sctp connection\n    maxAttempts: 2 # the maximum attempts of each sctp connection\n    maxInitTimeout: 2 # the maximum init timeout of each sctp connection\n  defaultUECtxReq: false # the default value of UE Context Request to decide when triggering Initial Context Setup procedure\n\nlogger: # log output setting\n  enable: true # true or false\n  level: info # how detailed to output, value: trace, debug, info, warn, error, fatal, panic\n  reportCaller: false # enable the caller report or not, value: true or false\n
- free5gc/config/smfcfg.yaml

Replace userplaneInformation / upNodes / UPF / interfaces / endpoints from 127.0.0.8 to 10.200.200.2:

info:\n  version: 1.0.7\n  description: SMF initial local configuration\n\nconfiguration:\n  smfName: SMF # the name of this SMF\n  sbi: # Service-based interface information\n    scheme: http # the protocol for sbi (http or https)\n    registerIPv4: 127.0.0.2 # IP used to register to NRF\n    bindingIPv4: 127.0.0.2 # IP used to bind the service\n    port: 8000 # Port used to bind the service\n    tls: # the local path of TLS key\n      key: cert/smf.key # SMF TLS Certificate\n      pem: cert/smf.pem # SMF TLS Private key\n  serviceNameList: # the SBI services provided by this SMF, refer to TS 29.502\n    - nsmf-pdusession # Nsmf_PDUSession service\n    - nsmf-event-exposure # Nsmf_EventExposure service\n    - nsmf-oam # OAM service\n  snssaiInfos: # the S-NSSAI (Single Network Slice Selection Assistance Information) list supported by this AMF\n    - sNssai: # S-NSSAI (Single Network Slice Selection Assistance Information)\n        sst: 1 # Slice/Service Type (uinteger, range: 0~255)\n        sd: 010203 # Slice Differentiator (3 bytes hex string, range: 000000~FFFFFF)\n      dnnInfos: # DNN information list\n        - dnn: internet # Data Network Name\n          dns: # the IP address of DNS\n            ipv4: 8.8.8.8\n            ipv6: 2001:4860:4860::8888\n    - sNssai: # S-NSSAI (Single Network Slice Selection Assistance Information)\n        sst: 1 # Slice/Service Type (uinteger, range: 0~255)\n        sd: 112233 # Slice Differentiator (3 bytes hex string, range: 000000~FFFFFF)\n      dnnInfos: # DNN information list\n        - dnn: internet # Data Network Name\n          dns: # the IP address of DNS\n            ipv4: 8.8.8.8\n            ipv6: 2001:4860:4860::8888\n  plmnList: # the list of PLMN IDs that this SMF belongs to (optional, remove this key when unnecessary)\n    - mcc: 208 # Mobile Country Code (3 digits string, digit: 0~9)\n      mnc: 93 # Mobile Network Code (2 or 3 digits string, digit: 0~9)\n  locality: area1 # Name of the location where a set of AMF, SMF, PCF and UPFs are located\n  pfcp: # the IP address of N4 interface on this SMF (PFCP)\n    # addr config is deprecated in smf config v1.0.3, please use the following config\n    nodeID: 127.0.0.1 # the Node ID of this SMF\n    listenAddr: 127.0.0.1 # the IP/FQDN of N4 interface on this SMF (PFCP)\n    externalAddr: 127.0.0.1 # the IP/FQDN of N4 interface on this SMF (PFCP)\n  userplaneInformation: # list of userplane information\n    upNodes: # information of userplane node (AN or UPF)\n      gNB1: # the name of the node\n        type: AN # the type of the node (AN or UPF)\n      UPF: # the name of the node\n        type: UPF # the type of the node (AN or UPF)\n        nodeID: 127.0.0.8 # the Node ID of this UPF\n        addr: 127.0.0.8 # the IP/FQDN of N4 interface on this UPF (PFCP)\n        sNssaiUpfInfos: # S-NSSAI information list for this UPF\n          - sNssai: # S-NSSAI (Single Network Slice Selection Assistance Information)\n              sst: 1 # Slice/Service Type (uinteger, range: 0~255)\n              sd: 010203 # Slice Differentiator (3 bytes hex string, range: 000000~FFFFFF)\n            dnnUpfInfoList: # DNN information list for this S-NSSAI\n              - dnn: internet\n                pools:\n                  - cidr: 10.60.0.0/16\n                staticPools:\n                  - cidr: 10.60.100.0/24\n          - sNssai: # S-NSSAI (Single Network Slice Selection Assistance Information)\n              sst: 1 # Slice/Service Type (uinteger, range: 0~255)\n              sd: 112233 # Slice Differentiator (3 bytes hex string, range: 000000~FFFFFF)\n            dnnUpfInfoList: # DNN information list for this S-NSSAI\n              - dnn: internet\n                pools:\n                  - cidr: 10.61.0.0/16\n                staticPools:\n                  - cidr: 10.61.100.0/24\n        interfaces: # Interface list for this UPF\n          - interfaceType: N3 # the type of the interface (N3 or N9)\n            endpoints: # the IP address of this N3/N9 interface on this UPF\n              - 10.200.200.2 # 127.0.0.8\n            networkInstances:  # Data Network Name (DNN)\n              - internet\n    links: # the topology graph of userplane, A and B represent the two nodes of each link\n      - A: gNB1\n        B: UPF\n  # retransmission timer for pdu session modification command\n  t3591:\n    enable: true     # true or false\n    expireTime: 16s   # default is 6 seconds\n    maxRetryTimes: 3 # the max number of retransmission\n  # retransmission timer for pdu session release command\n  t3592:\n    enable: true     # true or false\n    expireTime: 16s   # default is 6 seconds\n    maxRetryTimes: 3 # the max number of retransmission\n  nrfUri: http://127.0.0.10:8000 # a valid URI of NRF\n  #urrPeriod: 10 # default usage report period in seconds\n  #urrThreshold: 1000 # default usage report threshold in bytes\n\nlogger: # log output setting\n  enable: true # true or false\n  level: info # how detailed to output, value: trace, debug, info, warn, error, fatal, panic\n  reportCaller: false # enable the caller report or not, value: true or false\n
- free5gc/config/upfcfg.yaml

Replace gtpu from 127.0.0.8to 10.200.200.2:

version: 1.0.3\ndescription: UPF initial local configuration\n\n# The listen IP and nodeID of the N4 interface on this UPF (Can't set to 0.0.0.0)\npfcp:\n  addr: 127.0.0.8   # IP addr for listening\n  nodeID: 127.0.0.8 # External IP or FQDN can be reached\n  retransTimeout: 1s # retransmission timeout\n  maxRetrans: 3 # the max number of retransmission\n\ngtpu:\n  forwarder: gtp5g\n  # The IP list of the N3/N9 interfaces on this UPF\n  # If there are multiple connection, set addr to 0.0.0.0 or list all the addresses\n  ifList:\n    - addr: 10.200.200.2 # 127.0.0.8\n      type: N3\n      # name: upf.5gc.nctu.me\n      # ifname: gtpif\n      # mtu: 1400\n\n# The DNN list supported by UPF\ndnnList:\n  - dnn: internet # Data Network Name\n    cidr: 10.60.0.0/24 # Classless Inter-Domain Routing for assigned IPv4 pool of UE\n    # natifname: eth0\n\nlogger: # log output setting\n  enable: true # true or false\n  level: info # how detailed to output, value: trace, debug, info, warn, error, fatal, panic\n  reportCaller: false # enable the caller report or not, value: true or false\n
"},{"location":"blog/20230705/1-free5gc-with-namespace/#ueransim","title":"UERANSIM","text":"
  • UERANSIM/config/free5gc-gnb.yaml

  • Replace ngapIp from 127.0.0.1to 10.200.200.1

  • Replace gtpIp from 127.0.0.1to 10.200.200.1

  • Replace amfConfigs / address from 127.0.0.1to 10.200.200.2

mcc: '208'          # Mobile Country Code value\nmnc: '93'           # Mobile Network Code value (2 or 3 digits)\n\nnci: '0x000000010'  # NR Cell Identity (36-bit)\nidLength: 32        # NR gNB ID length in bits [22...32]\ntac: 1              # Tracking Area Code\n\nlinkIp: 127.0.0.1   # gNB's local IP address for Radio Link Simulation (Usually same with local IP)\nngapIp: 10.200.200.1 # 127.0.0.1   # gNB's local IP address for N2 Interface (Usually same with local IP)\ngtpIp: 10.200.200.1 # 127.0.0.1    # gNB's local IP address for N3 Interface (Usually same with local IP)\n\n# List of AMF address information\namfConfigs:\n  - address: 10.200.200.2 # 127.0.0.1\n    port: 38412\n\n# List of supported S-NSSAIs by this gNB\nslices:\n  - sst: 0x1\n    sd: 0x010203\n\n# Indicates whether or not SCTP stream number errors should be ignored.\nignoreStreamIds: true\n
- UERANSIM/config/free5gc-ue.yaml
# IMSI number of the UE. IMSI = [MCC|MNC|MSISDN] (In total 15 or 16 digits)\nsupi: 'imsi-208930000000003'\n# Mobile Country Code value\nmcc: '208'\n# Mobile Network Code value (2 or 3 digits)\nmnc: '93'\n\n# Permanent subscription key\nkey: '8baf473f2f8fd09487cccbd7097c6862'\n# Operator code (OP or OPC) of the UE\nop: '8e27b6af0e692e750f32667a3b14605d'\n# This value specifies the OP type and it can be either 'OP' or 'OPC'\nopType: 'OP'\n# Authentication Management Field (AMF) value\namf: '8000'\n# IMEI number of the device. It is used if no SUPI is provided\nimei: '356938035643803'\n# IMEISV number of the device. It is used if no SUPI and IMEI is provided\nimeiSv: '4370816125816151'\n\n# List of gNB IP addresses for Radio Link Simulation\ngnbSearchList:\n  - 127.0.0.1\n\n# Initial PDU sessions to be established\nsessions:\n  - type: 'IPv4'\n    apn: 'internet'\n    slice:\n      sst: 0x01\n      sd: 0x010203\n\n# List of requested S-NSSAIs by this UE\nslices:\n  - sst: 0x01\n    sd: 0x010203\n\n# Supported encryption and integrity algorithms by this UE\nintegrity:\n  IA1: true\n  IA2: true\n  IA3: true\nciphering:\n  EA1: true\n  EA2: true\n  EA3: true\n

"},{"location":"blog/20230705/1-free5gc-with-namespace/#environment-set-up-of-free5gc-and-ueransim","title":"Environment set up of free5GC and UERANSIM","text":"

First, create a namespace:

Note

Assume that you are either running as root, or it behoves you to prepend sudo to commands as necessary.

ip netns add ueransim\n
Next, add the bridge:
ip link add free5gc-br type bridge\n
Add two pairs of veth:
ip link add veth0 type veth peer name br-veth0\nip link add veth1 type veth peer name br-veth1\n
Now, it could be like:
root@free5gc:~# ip a\n1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000\n    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\n    inet 127.0.0.1/8 scope host lo\n       valid_lft forever preferred_lft forever\n    inet6 ::1/128 scope host\n       valid_lft forever preferred_lft forever\n2: enp0s5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000\n    link/ether 00:1c:42:b1:ba:f4 brd ff:ff:ff:ff:ff:ff\n    inet 10.211.55.23/24 brd 10.211.55.255 scope global dynamic enp0s5\n       valid_lft 1714sec preferred_lft 1714sec\n    inet6 fdb2:2c26:f4e4:0:21c:42ff:feb1:baf4/64 scope global dynamic mngtmpaddr noprefixroute\n       valid_lft 2591750sec preferred_lft 604550sec\n    inet6 fe80::21c:42ff:feb1:baf4/64 scope link\n       valid_lft forever preferred_lft forever\n3: enp0s6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000\n    link/ether 00:1c:42:f1:11:c6 brd ff:ff:ff:ff:ff:ff\n    inet 10.37.129.20/24 brd 10.37.129.255 scope global enp0s6\n       valid_lft forever preferred_lft forever\n    inet6 fdb2:2c26:f4e4:1:21c:42ff:fef1:11c6/64 scope global dynamic mngtmpaddr noprefixroute\n       valid_lft 2591750sec preferred_lft 604550sec\n    inet6 fe80::21c:42ff:fef1:11c6/64 scope link\n       valid_lft forever preferred_lft forever\n4: free5gc-br: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000\n    link/ether 4e:f6:d7:9c:50:de brd ff:ff:ff:ff:ff:ff\n5: br-veth0@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000\n    link/ether c2:31:0c:5f:45:81 brd ff:ff:ff:ff:ff:ff\n6: veth0@br-veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000\n    link/ether 4a:0f:1e:80:9b:be brd ff:ff:ff:ff:ff:ff\n7: br-veth1@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000\n    link/ether 56:99:b0:82:78:0d brd ff:ff:ff:ff:ff:ff\n8: veth1@br-veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000\n    link/ether 12:5a:56:00:5b:be brd ff:ff:ff:ff:ff:ff\n

Next, assign interface to namespace:

ip link set dev veth0 netns ueransim\n
Set ip address:
ip netns exec ueransim ip a add 10.200.200.1/24 dev veth0\n
Enable both interface. Don't forget lo:
ip netns exec ueransim ip link set lo up\nip netns exec ueransim ip link set veth0 up\n
Check with ip a:
root@free5gc:~# ip netns exec ueransim ip a\n1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000\n    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\n    inet 127.0.0.1/8 scope host lo\n       valid_lft forever preferred_lft forever\n    inet6 ::1/128 scope host\n       valid_lft forever preferred_lft forever\n6: veth0@if5: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state LOWERLAYERDOWN group default qlen 1000\n    link/ether 4a:0f:1e:80:9b:be brd ff:ff:ff:ff:ff:ff link-netnsid 0\n    inet 10.200.200.1/24 scope global veth0\n       valid_lft forever preferred_lft forever\n
Set for veth1 as well:
ip a add 10.200.200.2/24 dev veth1\nip link set veth1 up\n
Let two interfaces attatch to bridge:
ip link set dev br-veth0 master free5gc-br\nip link set dev br-veth1 master free5gc-br\nip link set br-veth0 up\nip link set br-veth1 up\nip link set free5gc-br up\n
Using bridge link to check:
root@free5gc:~# bridge link\n5: br-veth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master free5gc-br state forwarding priority 32 cost 2\n7: br-veth1@veth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master free5gc-br state forwarding priority 32 cost 2\n
Now it looks like:
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000\n    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\n    inet 127.0.0.1/8 scope host lo\n       valid_lft forever preferred_lft forever\n    inet6 ::1/128 scope host\n       valid_lft forever preferred_lft forever\n2: enp0s5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000\n    link/ether 00:1c:42:b1:ba:f4 brd ff:ff:ff:ff:ff:ff\n    inet 10.211.55.23/24 brd 10.211.55.255 scope global dynamic enp0s5\n       valid_lft 1000sec preferred_lft 1000sec\n    inet6 fdb2:2c26:f4e4:0:21c:42ff:feb1:baf4/64 scope global dynamic mngtmpaddr noprefixroute\n       valid_lft 2591870sec preferred_lft 604670sec\n    inet6 fe80::21c:42ff:feb1:baf4/64 scope link\n       valid_lft forever preferred_lft forever\n3: enp0s6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000\n    link/ether 00:1c:42:f1:11:c6 brd ff:ff:ff:ff:ff:ff\n    inet 10.37.129.20/24 brd 10.37.129.255 scope global enp0s6\n       valid_lft forever preferred_lft forever\n    inet6 fdb2:2c26:f4e4:1:21c:42ff:fef1:11c6/64 scope global dynamic mngtmpaddr noprefixroute\n       valid_lft 2591870sec preferred_lft 604670sec\n    inet6 fe80::21c:42ff:fef1:11c6/64 scope link\n       valid_lft forever preferred_lft forever\n4: free5gc-br: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000\n    link/ether 56:99:b0:82:78:0d brd ff:ff:ff:ff:ff:ff\n    inet6 fe80::5499:b0ff:fe82:780d/64 scope link\n       valid_lft forever preferred_lft forever\n5: br-veth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master free5gc-br state UP group default qlen 1000\n    link/ether c2:31:0c:5f:45:81 brd ff:ff:ff:ff:ff:ff link-netns ueransim\n    inet6 fe80::c031:cff:fe5f:4581/64 scope link\n       valid_lft forever preferred_lft forever\n7: br-veth1@veth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master free5gc-br state UP group default qlen 1000\n    link/ether 56:99:b0:82:78:0d brd ff:ff:ff:ff:ff:ff\n    inet6 fe80::5499:b0ff:fe82:780d/64 scope link\n       valid_lft forever preferred_lft forever\n8: veth1@br-veth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000\n    link/ether 12:5a:56:00:5b:be brd ff:ff:ff:ff:ff:ff\n    inet 10.200.200.2/24 scope global veth1\n       valid_lft forever preferred_lft forever\n    inet6 fe80::105a:56ff:fe00:5bbe/64 scope link\n       valid_lft forever preferred_lft forever\n
Let's test it:

Note

You can perform ip netns exec ueransim /bin/bash --rcfile <(echo \"PS1=\\\"ueransim> \\\"\") to enter namespace and modify shell prefix.

root@free5gc:~# ip netns exec ueransim /bin/bash --rcfile <(echo \"PS1=\\\"ueransim> \\\"\")\nueransim> ping -c2 10.200.200.2\nPING 10.200.200.2 (10.200.200.2) 56(84) bytes of data.\n64 bytes from 10.200.200.2: icmp_seq=1 ttl=64 time=0.089 ms\n64 bytes from 10.200.200.2: icmp_seq=2 ttl=64 time=0.226 ms\n\n--- 10.200.200.2 ping statistics ---\n2 packets transmitted, 2 received, 0% packet loss, time 1020ms\nrtt min/avg/max/mdev = 0.089/0.157/0.226/0.068 ms\n
Insert default routing rule:
ueransim> ip route add default via 10.200.200.2\nueransim> netstat -rn\nKernel IP routing table\nDestination     Gateway         Genmask         Flags   MSS Window  irtt Iface\n0.0.0.0         10.200.200.2    0.0.0.0         UG        0 0          0 veth0\n10.200.200.0    0.0.0.0         255.255.255.0   U         0 0          0 veth0\n
Try to ping 8.8.8.8:
ueransim> ping -c2 8.8.8.8\nPING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.\n\n--- 8.8.8.8 ping statistics ---\n2 packets transmitted, 0 received, 100% packet loss, time 1028ms\n
It is because the main host must translate the source addresses. Besides, the main host need to forward packet:
root@free5gc:~# iptables -t nat -A POSTROUTING -o enp0s5 -j MASQUERADE\nroot@free5gc:~# sysctl -w net.ipv4.ip_forward=1\nroot@free5gc:~# sudo iptables -I FORWARD 1 -j ACCEPT\n
And then:
ueransim> ping -c2 8.8.8.8\nPING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.\n64 bytes from 8.8.8.8: icmp_seq=1 ttl=127 time=13.9 ms\n64 bytes from 8.8.8.8: icmp_seq=2 ttl=127 time=28.0 ms\n\n--- 8.8.8.8 ping statistics ---\n2 packets transmitted, 2 received, 0% packet loss, time 1002ms\nrtt min/avg/max/mdev = 13.866/20.939/28.012/7.073 ms\n

After free5GC execute run.sh, it's time for UERANSIM:

In terminal 1:

ueransim> build/nr-gnb -c config/free5gc-gnb.yaml\nUERANSIM v3.1.0\n[2023-07-05 19:58:26.368] [sctp] [info] Trying to establish SCTP connection... (10.200.200.2:38412)\n[2023-07-05 19:58:26.373] [sctp] [info] SCTP connection established (10.200.200.2:38412)\n[2023-07-05 19:58:26.374] [sctp] [debug] SCTP association setup ascId[3]\n[2023-07-05 19:58:26.375] [ngap] [debug] Sending NG Setup Request\n[2023-07-05 19:58:26.380] [ngap] [debug] NG Setup Response received\n[2023-07-05 19:58:26.380] [ngap] [info] NG Setup procedure is successful\n[2023-07-05 19:58:35.804] [mr] [info] New UE connected to gNB. Total number of UEs is now: 1\n[2023-07-05 19:58:35.806] [rrc] [debug] Sending RRC Setup for UE[3]\n[2023-07-05 19:58:35.807] [ngap] [debug] Initial NAS message received from UE 3\n[2023-07-05 19:58:35.869] [ngap] [debug] Initial Context Setup Request received\n[2023-07-05 19:58:36.108] [ngap] [info] PDU session resource is established for UE[3] count[1]\n
In terminal 2:
ueransim> sudo build/nr-ue -c config/free5gc-ue.yaml\nUERANSIM v3.1.0\n[2023-07-05 19:58:35.803] [nas] [debug] NAS layer started\n[2023-07-05 19:58:35.803] [rrc] [debug] RRC layer started\n[2023-07-05 19:58:35.804] [nas] [info] UE switches to state: MM-DEREGISTERED/PLMN-SEARCH\n[2023-07-05 19:58:35.804] [nas] [info] UE connected to gNB\n[2023-07-05 19:58:35.804] [nas] [info] UE switches to state: MM-DEREGISTERED/NORMAL-SERVICE\n[2023-07-05 19:58:35.804] [nas] [info] UE switches to state: MM-REGISTERED-INITIATED/NA\n[2023-07-05 19:58:35.805] [rrc] [debug] Sending RRC Setup Request\n[2023-07-05 19:58:35.806] [rrc] [info] RRC connection established\n[2023-07-05 19:58:35.806] [nas] [info] UE switches to state: CM-CONNECTED\n[2023-07-05 19:58:35.838] [nas] [debug] Received rand[61262F32A617D0BAD716603B1CBDA477] autn[44778026F4238000FC14B59D68855328]\n[2023-07-05 19:58:35.838] [nas] [debug] Calculated res[47759045F5ACEA59] ck[1C559301F29EF49572F5D150B3B99288] ik[D223317F752F233CE4C7AA253644D882] ak[528433D1FBE6] mac_a[FC14B59D68855328]\n[2023-07-05 19:58:35.838] [nas] [debug] Used snn[5G:mnc093.mcc208.3gppnetwork.org] sqn[16F3B3F70FC5]\n[2023-07-05 19:58:35.838] [nas] [debug] Derived kSeaf[7FC8B7FB1B141B6579B9C0FAEB9CCF1312FE9F9634868E234756DE49FD67C5F1] kAusf[FA0402A892E6046D52F4DECACA40B2A75B698FCEAD5EB320139FC69B77BD4C46] kAmf[3D4AD68E153B9642ACBECC67AD399015F7CB578F9DF4C88A35EED99C72C9B95B]\n[2023-07-05 19:58:35.843] [nas] [debug] Derived kNasEnc[1F829EB2BA238DD0226C3484E6A79D1F] kNasInt[251C0412B1BAD88A9DD0008F32D6F216]\n[2023-07-05 19:58:35.843] [nas] [debug] Selected integrity[2] ciphering[0]\n[2023-07-05 19:58:35.869] [nas] [debug] T3512 started with int[3600]\n[2023-07-05 19:58:35.869] [nas] [info] UE switches to state: MM-REGISTERED/NORMAL-SERVICE\n[2023-07-05 19:58:35.869] [nas] [info] Initial Registration is successful\n[2023-07-05 19:58:35.869] [nas] [info] Initial PDU sessions are establishing [1#]\n[2023-07-05 19:58:35.869] [nas] [debug] Sending PDU session establishment request\n[2023-07-05 19:58:36.108] [nas] [info] PDU Session establishment is successful PSI[1]\n[2023-07-05 19:58:36.113] [app] [info] Connection setup for PDU session[1] is successful, TUN interface[uesimtun0, 10.60.0.1] is up.\n
In terminal 3:
ueransim> ip a\n1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000\n    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\n    inet 127.0.0.1/8 scope host lo\n       valid_lft forever preferred_lft forever\n    inet6 ::1/128 scope host\n       valid_lft forever preferred_lft forever\n2: uesimtun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 500\n    link/none\n    inet 10.60.0.1/32 scope global uesimtun0\n       valid_lft forever preferred_lft forever\n    inet6 fe80::b5ef:5b4:e3f6:af64/64 scope link stable-privacy\n       valid_lft forever preferred_lft forever\n6: veth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000\n    link/ether 4a:0f:1e:80:9b:be brd ff:ff:ff:ff:ff:ff link-netnsid 0\n    inet 10.200.200.1/24 scope global veth0\n       valid_lft forever preferred_lft forever\n    inet6 fe80::480f:1eff:fe80:9bbe/64 scope link\n       valid_lft forever preferred_lft forever\nueransim> ping -c2 -I uesimtun0 8.8.8.8\nPING 8.8.8.8 (8.8.8.8) from 10.60.0.1 uesimtun0: 56(84) bytes of data.\n64 bytes from 8.8.8.8: icmp_seq=1 ttl=127 time=19.5 ms\n64 bytes from 8.8.8.8: icmp_seq=2 ttl=127 time=33.2 ms\n\n--- 8.8.8.8 ping statistics ---\n2 packets transmitted, 2 received, 0% packet loss, time 1006ms\nrtt min/avg/max/mdev = 19.478/26.348/33.219/6.870 ms\n
Also ping to google.com:
ueransim> ping -c2 -I uesimtun0 google.com\nPING google.com (172.217.160.110) from 10.60.0.1 uesimtun0: 56(84) bytes of data.\n64 bytes from tsa03s06-in-f14.1e100.net (172.217.160.110): icmp_seq=1 ttl=127 time=17.3 ms\n64 bytes from tsa03s06-in-f14.1e100.net (172.217.160.110): icmp_seq=2 ttl=127 time=29.5 ms\n\n--- google.com ping statistics ---\n2 packets transmitted, 2 received, 0% packet loss, time 1005ms\nrtt min/avg/max/mdev = 17.295/23.385/29.476/6.090 ms\n

"},{"location":"blog/20230705/1-free5gc-with-namespace/#what-if-two-ueransims-with-two-namespaces","title":"What if two UERANSIMs with two namespaces?","text":"

Same as before, you should create another namespace for UERANSIM, called it ueransim2:

root@free5gc:~# ip netns ls\nueransim2 (id: 1)\nueransim (id: 0)\n
And then:
ip link add veth2 type veth peer name br-veth2\nip link set dev veth2 netns ueransim2\nip link set br-veth2 master free5gc-br\nip link set br-veth2 up\nip netns exec ueransim2 ip a add 10.200.200.3/24 dev veth2\nip netns exec ueransim2 ip link set lo up\nip netns exec ueransim2 ip link set veth2 up\nip netns exec ueransim2 ip route add default via 10.200.200.2\n

Copy UERANSIM/config/free5gc-gnb.yaml and UERANSIM/config/free5gc-ue.yaml to free5gc-gnb2.yaml and free5gc-ue2.yaml, modify:

free5gc-gnb2.yaml

  • Replace ngapIp from 127.0.0.1 to 10.200.200.3
  • Replace gtpIp from 127.0.0.1 to 10.200.200.3

...\nngapIp: 10.200.200.3 # 127.0.0.1   # gNB's local IP address for N2 Interface (Usually same with local IP)\ngtpIp: 10.200.200.3 # 127.0.0.1    # gNB's local IP address for N3 Interface (Usually same with local IP)\n\n# List of AMF address information\namfConfigs:\n  - address: 10.200.200.2 # 127.0.0.1\n    port: 38412\n...\n
free5gc-ue2.yaml

supi change to imsi-208930000000004

...\n# IMSI number of the UE. IMSI = [MCC|MNC|MSISDN] (In total 15 or 16 digits)\nsupi: 'imsi-208930000000004'\n...\n

Note

Should register ue to webconsole first.

The result:

ueransim> ip a\n1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000\n    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\n    inet 127.0.0.1/8 scope host lo\n       valid_lft forever preferred_lft forever\n    inet6 ::1/128 scope host\n       valid_lft forever preferred_lft forever\n6: veth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000\n    link/ether 4a:0f:1e:80:9b:be brd ff:ff:ff:ff:ff:ff link-netnsid 0\n    inet 10.200.200.1/24 scope global veth0\n       valid_lft forever preferred_lft forever\n    inet6 fe80::480f:1eff:fe80:9bbe/64 scope link\n       valid_lft forever preferred_lft forever\n7: uesimtun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 500\n    link/none\n    inet 10.60.0.1/32 scope global uesimtun0\n       valid_lft forever preferred_lft forever\n    inet6 fe80::f6d7:dd81:fe7f:496a/64 scope link stable-privacy\n       valid_lft forever preferred_lft forever\nueransim> ping -c2 -I uesimtun0 google.com\nPING google.com (172.217.160.110) from 10.60.0.1 uesimtun0: 56(84) bytes of data.\n64 bytes from tsa03s06-in-f14.1e100.net (172.217.160.110): icmp_seq=1 ttl=127 time=17.2 ms\n64 bytes from tsa03s06-in-f14.1e100.net (172.217.160.110): icmp_seq=2 ttl=127 time=28.5 ms\n\n--- google.com ping statistics ---\n2 packets transmitted, 2 received, 0% packet loss, time 1003ms\nrtt min/avg/max/mdev = 17.200/22.863/28.527/5.663 ms\n

ueransim2> ip a\n1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000\n    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\n    inet 127.0.0.1/8 scope host lo\n       valid_lft forever preferred_lft forever\n    inet6 ::1/128 scope host \n       valid_lft forever preferred_lft forever\n5: uesimtun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 500\n    link/none \n    inet 10.60.0.2/32 scope global uesimtun0\n       valid_lft forever preferred_lft forever\n    inet6 fe80::16a4:523a:a86:bf83/64 scope link stable-privacy \n       valid_lft forever preferred_lft forever\n12: veth2@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000\n    link/ether fa:12:bb:9c:fa:40 brd ff:ff:ff:ff:ff:ff link-netnsid 0\n    inet 10.200.200.3/24 scope global veth2\n       valid_lft forever preferred_lft forever\n    inet6 fe80::f812:bbff:fe9c:fa40/64 scope link \n       valid_lft forever preferred_lft forever\nueransim2> ping -c2 -I uesimtun0 google.com\nPING google.com (172.217.160.110) from 10.60.0.2 uesimtun0: 56(84) bytes of data.\n64 bytes from tsa03s06-in-f14.1e100.net (172.217.160.110): icmp_seq=1 ttl=127 time=18.9 ms\n64 bytes from tsa03s06-in-f14.1e100.net (172.217.160.110): icmp_seq=2 ttl=127 time=15.8 ms\n\n--- google.com ping statistics ---\n2 packets transmitted, 2 received, 0% packet loss, time 1002ms\nrtt min/avg/max/mdev = 15.786/17.353/18.921/1.567 ms\n
"},{"location":"blog/20230705/1-free5gc-with-namespace/#about","title":"About","text":"

Hi, my name is Jimmy Chang. The current research topic is 5G LAN with a focus on the 5G Data Plane. Any questions or errors in the article are welcome for correction. Please feel free to send an email to provide feedback.

  • Graduate student major in 5GC Research
  • LinkedIn
"},{"location":"blog/20230705/1-free5gc-with-namespace/#reference","title":"Reference","text":"
  • https://github.com/s5uishida/free5gc_ueransim_ulcl_sample_config
  • https://github.com/free5gc/free5gc/blob/main/test_ulcl.sh
  • https://blog.scottlowe.org/2013/09/04/introducing-linux-network-namespaces/
  • https://man7.org/linux/man-pages/man7/namespaces.7.html
  • https://linux.die.net/man/8/iptables

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20230712/TSN/","title":"Time-Sensitive Networking over 5G system - Introduction (Rel-16)","text":"

Note

Author: Ya-shih Tseng Date: 2023/7/12

This blog focuses on the role of the 5G system in 3GPP Release 16 TSN (Time-Sensitive Networking).

"},{"location":"blog/20230712/TSN/#what-is-time-sensitive-network-tsn","title":"What is Time-Sensitive Network (TSN)","text":"

Traditional Ethernet technology can only achieve \"best-effort\" communication and cannot meet the high reliability and low latency requirements of industrial manufacturing applications. Therefore, in the context of industrial automation, there is a need to upgrade the traditional \"best-effort\" Ethernet to provide \"deterministic\" services.

Time Sensitive Networking (TSN) brings determinism and real-time communication to standard Ethernet through mechanisms and protocols defined by the IEEE 802.1 standard, which is used by Audio Video Bridging (AVB) and TSN. It offers reliable message delivery, minimized jitter, and guaranteed delivery through central management, time scheduling, and other key features. The introduction of TSN technology holds great potential and benefits for real-time applications in industrial control, automation, and other fields.

TSN_OSI_layer

"},{"location":"blog/20230712/TSN/#tsn-standard","title":"TSN Standard","text":"

There are a lot of standards that TSN task group has completed or ongoing projects. Here are some base standards.

Standard Title IEEE 1588 V2 Precision Clock Synchronization Protocol for Networked Measurement and Control Systems IEEE 802.1Q-2022 Bridges and Bridged Networks IEEE 802.1AB-2016 Station and Media Access Control Connectivity Discovery (specifies the Link Layer Discovery Protocol (LLDP)) IEEE 802.1AS-2020 Timing and Synchronization for Time-Sensitive Applications IEEE 802.1AX-2020 Link Aggregation IEEE 802.1CB-2017 Frame Replication and Elimination for Reliability IEEE 802.1CS-2020 Link-local Registration Protocol"},{"location":"blog/20230712/TSN/#the-role-of-5g-system-in-tsn","title":"The role of 5G system in TSN","text":"

With the increasing demands for wireless control in applications such as industrial automation, remote surgical operations, smart grid distribution automation, transportation safety, autonomous driving, and more, there is a growing need to meet the low-latency requirements of these applications while achieving management, scheduling, and traffic planning. Time synchronization becomes a critical aspect. The following will explain how the interaction between TSN and 5G systems enables time synchronization.

"},{"location":"blog/20230712/TSN/#time-synchronization","title":"Time Synchronization","text":"

To achieve time synchronization between TSN and 5G systems, TSN utilizes the time synchronization method defined in IEEE 802.1AS, which is the generalized Precision Time Protocol (gPTP). gPTP supports time synchronization for Time-aware end stations and Time-aware Bridges in Layer 2. In the 3GPP TS23.501 release 16 specification, the 5G system plays the role of a \"Time-aware system\" as defined in IEEE 802.1AS and is designated as a Logical bridge, connecting TSN system end stations.

Note

gPTP is an extended version of PTP (Precision Time Protocol) that primarily expands support for second-layer network devices.

How can we synchronize the time of two end stations into the same time domain?

First, the time synchronization architecture includes Master clocks and Slave clocks. The Master regularly sends sync messages to allow the Slave to obtain the Master's time. The Slave, in turn, periodically sends peer delay requests to exchange messages with the Master, obtaining the delay time between the two devices for time correction. Additionally, the resident time, which is the message propagation delay introduced by bridges, should also be taken into account. By considering all these factors, the time synchronization of both sides can be achieved within the TSN time domain.

Time Synchronization process of gPTP

Check the link for more detail about how PTP works.

"},{"location":"blog/20230712/TSN/#intergration-of-tsn-and-5g","title":"Intergration of TSN and 5G","text":"

By now, I believe you have gained an understanding of the time synchronization mechanism in TSN. Let's briefly explain how the 5G system supports TSN as a logical TSN bridge. The 3GPP has defined new functionalities such as NW-TT, DS-TT, and TSN-AF, as well as TSN control nodes like CUC and CNC. Please check TS 23.501 Release 16 for more details.

System architecture of 5G support TSN

"},{"location":"blog/20230712/TSN/#support-ethernet-type-pdu-session","title":"Support Ethernet type PDU session","text":"

To archive the intergration, 5G system should support ingress port and egress port pair via an Ethernet Type PDU session between the corresponding UE and UPF. As mentioned above, gPTP supports layer 2 (Ethernet) only.

"},{"location":"blog/20230712/TSN/#ds-tt-and-nw-tt","title":"DS-TT and NW-TT","text":"

In the 5G system, DS-TT (Device-side TSN translator) and NW-TT (Network-side TSN translator) serve as TSN translators. DS-TT is responsible for connecting TSN Slave endpoints with the UE, while NW-TT connects TSN Master endpoints with the UPF.

When the sync message generated by the Master clock reaches the bridge, NW-TT captures its Ingress Timestamp and measures the delay between NW-TT and the Master clock. These timestamps are then embedded within the sync message and transmitted to the UE. Once the UE receives the sync message, DS-TT calculates the resident time by subtracting the Ingress Timestamp provided in the sync message, from the Egress Timestamp which represents the time of sync message reception. The resident time is added to the delay time mentioned in the sync message to determine the corrected time. Through the assistance of the TSN translators, the Slave endpoint receives the message and obtains information about time deviation and other relevant data for further adjustment.

Note

DS-TT and NW-TT enable the 5G system to function as a virtual bridge. The bridge is also called \"Transparent clock\" which is definded in IEEE 1588 and required in IEEE 802.1AS. You can say that Master and Slaver don't know the exist of the 5G TSN bridge, since it's logical transparent.

\"Transparent clocks are used to route timing messages within a network. Used when: Ethernet timing must pass through switches.\" - different type of clocks

"},{"location":"blog/20230712/TSN/#tsn-af","title":"TSN-AF","text":"

With TSN-AF, CNC can manage the 5G system functioning as a logical bridge and achieve the integration of the 5G TSN bridge with the TSN network in collaboration with NW-TT and DS-TT. Additionally, TSN-AF gathers information and capability lists of the 5G TSN Bridge and transmits them to CNC.

"},{"location":"blog/20230712/TSN/#tsn-control-nodes","title":"TSN control nodes","text":"

To meet the requirements of application services and control TSN, there are two key functions utilized in the TSN system. CNC (Centralized Network Controller), as the central controller in the TSN system, receives the information from CUC (Centralized User Configuration) and performs scheduling and planning tasks. It calculates the optimal transmission schedule for the TSN traffic based on factors such as bandwidth requirements, latency constraints, and network conditions. Once the transmission schedule is computed and confirmed, CNC proceeds to deploy the necessary network resource configuration on the TSN switches. This ensures that the TSN network operates efficiently and effectively in delivering the required QoS (Quality of Service) for the application services.

"},{"location":"blog/20230712/TSN/#reference","title":"Reference","text":"
  • IEEE Std 802.1AS-2020: \u201cIEEE Standard for Local and metropolitan area networks--Timing and Synchronization for Time-Sensitive Applications\u201d.
  • IEEE Std 1588: \u201cIEEE Standard for a Precision Clock Synchronization Protocol for Networked Measurement and Control Systems\u201d, Edition 2019.
  • 3GPP TS 23.501 Release 16
  • Time-Sensitive Networking - Wikipedia
  • Time-Sensitive Networking (TSN) Task Group | - IEEE 802.1
"},{"location":"blog/20230712/TSN/#about","title":"About","text":"

Hi, This is Ya-shih Tseng. I am currently researching the implementation of 5G TSN (Time-Sensitive Networking) as part of my master's studies. In the future, I will introduce more information about TSN. Hope you enjoy it.

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20230719/UDM_introduce/","title":"Network function UDM introduction","text":"

Note

Author: \u5f35\u54f2\u777f Date: 2023/7/19

"},{"location":"blog/20230719/UDM_introduce/#overview","title":"Overview","text":"

In this article, I will introduce UDM and its three services that will be used in the general UE registration procedure (Nudm_UECM service, Nudm_SubscriberDataManagement Service, and Nudm_UEAuthentication service) to let everyone understand UDM more clearly.

"},{"location":"blog/20230719/UDM_introduce/#udm","title":"UDM","text":"

Unified Data Management is responsible for managing information related to UE. When other NFs need to use the UE subscription information, they will obtain it from UDM through the SBI of UDM.

"},{"location":"blog/20230719/UDM_introduce/#nudm_ueauthentication-service","title":"Nudm_UEAuthentication Service","text":"

This service is used by AUSF to retrieve authentication-related information and, after authentication, confirm the result.

3GPP TS33.501 v15.2.0

In Authentication, AUSF uses the GET operation to retrieve authentication information for the UE. The request contains the UE\u2019s identity (supi or suci) and the serving network name. The serving network name is used in the derivation of the anchor key, which is used by subsensual authentication. UE\u2019s identity will be contained in the URI, and the serving network name will be contained in the request body.

Upon reception of the Nudm_UEAuthentication_Get Request, the UDM shall de-conceal SUCI to gain SUPI if SUCI is received. At this time, UDM will query the authentication subscription data from UDR. Then, UDM shall select the authentication method based on SUPI, and if required (e.g., 5G-AKA), UDM will calculate the authentication vector and pass it to AUSF.

  • SUPI: A globally unique 5G Subscription Permanent Identifier, used to identify UE.
  • SUCI: Subscription concealed identifier, obtained by encrypting supi through the Home Network Public Key so that supi will not be obtained by a third party on the network.
logger.UeauLog.Traceln(\"In GenerateAuthDataProcedure\")\n\nresponse = &models.AuthenticationInfoResult{}\nrand.Seed(time.Now().UnixNano())\nsupi, err := suci.ToSupi(supiOrSuci, udm_context.Getself().SuciProfiles)\nif err != nil {\n    problemDetails = &models.ProblemDetails{\n        Status: http.StatusForbidden,\n        Cause:  authenticationRejected,\n        Detail: err.Error(),\n    }\n\n    logger.UeauLog.Errorln(\"suciToSupi error: \", err.Error())\n    return nil, problemDetails\n}\n\nlogger.UeauLog.Tracef(\"supi conversion => [%s]\", supi)\n\nclient, err := createUDMClientToUDR(supi)\nif err != nil {\n    return nil, openapi.ProblemDetailsSystemFailure(err.Error())\n}\nauthSubs, res, err := client.AuthenticationDataDocumentApi.QueryAuthSubsData(context.Background(), supi, nil)\n\n//in the udm/internal/sbi/producer/generate_auth_data.go, GenerateAuthDataProcedure function.\n

From the code, we can see UDM first de-conceal SUCI (line 5), then use QueryAuthSubsData to get authSub from UDR. After that, UDM uses this information to create the authentication vector.

Then we record the packet sent in the registration process and find the packet according to the URI specified by the specification. We can find the packet corresponding to this service.

Open the response packet, and we can see the response body matches the AuthenticationInfoResult data type.

3GPP TS29.503 v15.2.1

After AUSF authenticates the UE, it will confirm the result with UDM. These details will be used in linking authentication confirmation to the Nudm_UECM_Registration procedure from AMF.

func communicateWithUDM(ue *context.AmfUe, accessType models.AccessType) error {\n    ue.GmmLog.Debugln(\"communicateWithUDM\")\n    amfSelf := context.GetSelf()\n\n    // UDM selection described in TS 23.501 6.3.8\n    // TODO: consider udm group id, Routing ID part of SUCI, GPSI or External Group ID (e.g., by the NEF)\n    param := Nnrf_NFDiscovery.SearchNFInstancesParamOpts{\n        Supi: optional.NewString(ue.Supi),\n    }\n    resp, err := consumer.SendSearchNFInstances(amfSelf.NrfUri, models.NfType_UDM, models.NfType_AMF, &param)\n    if err != nil {\n        return errors.Errorf(\"AMF can not select an UDM by NRF: SendSearchNFInstances failed\")\n    }\n\n    var uecmUri, sdmUri string\n    for _, nfProfile := range resp.NfInstances {\n        ue.UdmId = nfProfile.NfInstanceId\n        uecmUri = util.SearchNFServiceUri(nfProfile, models.ServiceName_NUDM_UECM, models.NfServiceStatus_REGISTERED)\n        sdmUri = util.SearchNFServiceUri(nfProfile, models.ServiceName_NUDM_SDM, models.NfServiceStatus_REGISTERED)\n        if uecmUri != \"\" && sdmUri != \"\" {\n            break\n        }\n    }\n    ue.NudmUECMUri = uecmUri\n    ue.NudmSDMUri = sdmUri\n    if ue.NudmUECMUri == \"\" || ue.NudmSDMUri == \"\" {\n        return errors.Errorf(\"AMF can not select an UDM by NRF: SearchNFServiceUri failed\")\n    }\n\n    problemDetails, err := consumer.UeCmRegistration(ue, accessType, true)\n    if problemDetails != nil {\n        return errors.Errorf(problemDetails.Cause)\n    } else if err != nil {\n        return errors.Wrap(err, \"UECM_Registration Error\")\n    }\n\n    // TS 23.502 4.2.2.2.1 14a-c.\n    // \"After a successful response is received, the AMF subscribes to be notified\n    //      using Nudm_SDM_Subscribe when the data requested is modified\"\n    problemDetails, err = consumer.SDMGetAmData(ue)\n    if problemDetails != nil {\n        return errors.Errorf(problemDetails.Cause)\n    } else if err != nil {\n        return errors.Wrap(err, \"SDM_Get AmData Error\")\n    }\n\n    problemDetails, err = consumer.SDMGetSmfSelectData(ue)\n    if problemDetails != nil {\n        return errors.Errorf(problemDetails.Cause)\n    } else if err != nil {\n        return errors.Wrap(err, \"SDM_Get SmfSelectData Error\")\n    }\n\n    problemDetails, err = consumer.SDMGetUeContextInSmfData(ue)\n    if problemDetails != nil {\n        return errors.Errorf(problemDetails.Cause)\n    } else if err != nil {\n        return errors.Wrap(err, \"SDM_Get UeContextInSmfData Error\")\n    }\n\n    problemDetails, err = consumer.SDMSubscribe(ue)\n    if problemDetails != nil {\n        return errors.Errorf(problemDetails.Cause)\n    } else if err != nil {\n        return errors.Wrap(err, \"SDM Subscribe Error\")\n    }\n    ue.ContextValid = true\n    return nil\n}\n\n\n//in the amf/internal/gmm/handler.go.\n

Next, let's take a look at this function. It is called in HandleInitialRegistration, which handles UE's initial registration. UeCmRegistration will use the Nudm_UECM (UECM) service to store related UE Context Management information in UDM. In lines 40, 47, and 54, AMF uses the Nudm_SubscriberDataManagement (SDM) Service to get some subscribe data.

"},{"location":"blog/20230719/UDM_introduce/#nudm_uecontextmanagement-service","title":"Nudm_UEContextManagement Service","text":"

In the UeCmRegistration function, AMF registers as UE's serving NF on UDM and stores related UE Context Management information in UDM. Looking at the packet, you can see that the request body contains amfInstanceId and guami, representing the amf identity, and ratType, representing the radio access technology type used by UE.

// TS 29.503 5.3.2.2.2\nfunc RegistrationAmf3gppAccessProcedure(registerRequest models.Amf3GppAccessRegistration, ueID string) (\n    header http.Header, response *models.Amf3GppAccessRegistration, problemDetails *models.ProblemDetails,\n) {\n    // TODO: EPS interworking with N26 is not supported yet in this stage\n    var oldAmf3GppAccessRegContext *models.Amf3GppAccessRegistration\n    if udm_context.Getself().UdmAmf3gppRegContextExists(ueID) {\n        ue, _ := udm_context.Getself().UdmUeFindBySupi(ueID)\n        oldAmf3GppAccessRegContext = ue.Amf3GppAccessRegistration\n    }\n\n    udm_context.Getself().CreateAmf3gppRegContext(ueID, registerRequest)\n\n    clientAPI, err := createUDMClientToUDR(ueID)\n    if err != nil {\n        return nil, nil, openapi.ProblemDetailsSystemFailure(err.Error())\n    }\n\n    var createAmfContext3gppParamOpts Nudr_DataRepository.CreateAmfContext3gppParamOpts\n    optInterface := optional.NewInterface(registerRequest)\n    createAmfContext3gppParamOpts.Amf3GppAccessRegistration = optInterface\n    resp, err := clientAPI.AMF3GPPAccessRegistrationDocumentApi.CreateAmfContext3gpp(context.Background(),\n        ueID, &createAmfContext3gppParamOpts)\n    if err != nil {\n        logger.UecmLog.Errorln(\"CreateAmfContext3gpp error : \", err)\n        problemDetails = &models.ProblemDetails{\n            Status: int32(resp.StatusCode),\n            Cause:  err.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails).Cause,\n            Detail: err.Error(),\n        }\n        return nil, nil, problemDetails\n    }\n    defer func() {\n        if rspCloseErr := resp.Body.Close(); rspCloseErr != nil {\n            logger.UecmLog.Errorf(\"CreateAmfContext3gpp response body cannot close: %+v\", rspCloseErr)\n        }\n    }()\n\n    // TS 23.502 4.2.2.2.2 14d: UDM initiate a Nudm_UECM_DeregistrationNotification to the old AMF\n    // corresponding to the same (e.g. 3GPP) access, if one exists\n    if oldAmf3GppAccessRegContext != nil {\n        deregistData := models.DeregistrationData{\n            DeregReason: models.DeregistrationReason_SUBSCRIPTION_WITHDRAWN,\n            AccessType:  models.AccessType__3_GPP_ACCESS,\n        }\n        callback.SendOnDeregistrationNotification(ueID, oldAmf3GppAccessRegContext.DeregCallbackUri,\n            deregistData) // Deregistration Notify Triggered\n\n        return nil, nil, nil\n    } else {\n        header = make(http.Header)\n        udmUe, _ := udm_context.Getself().UdmUeFindBySupi(ueID)\n        header.Set(\"Location\", udmUe.GetLocationURI(udm_context.LocationUriAmf3GppAccessRegistration))\n        return header, &registerRequest, nil\n    }\n}\n\n//in the udm/internal/sbi/producer/ue_context_management.go\n

In the RegistrationAmf3gppAccessProcedure function, UDM first checks whether the context has been established for that UE; if UDM has such a context, it initiates a Nudm_UECM_DeregistrationNotification to the old AMF later. UDM used the received information to create context and stored it in UDR.

"},{"location":"blog/20230719/UDM_introduce/#nudm_subscriberdatamanagement-sdm-service","title":"Nudm_SubscriberDataManagement (SDM) Service","text":"

The SDM service is used to retrieve the UE's individual subscription data relevant to the consumer's NF from the UDM. In the SDMGetAmData function, AMF gets subscription data used in registration and mobility management. In the response packet, AMF got gpsis, subscribedUeAmbr, and nssai.

The GPSI (Generic Public Subscription Identifier) is used to address a 3GPP subscription in data networks outside the realms of a 3GPP system. It contains either an External ID or an MSISDN \uff08Mobile Subscriber ISDN Number\uff09.The subscribedUeAmbr is The Maximum Aggregated uplink and downlink MBRs (max. bit rate) to be shared across all Non-GBR (non-guaranteed Bit Rate) QoS Flows according to the subscription of the user.

In the SDMGetSmfSelectData function, AMF gets subscribed S-NSSAIs (Single Network Slice Selection Assistance Information) and Data Network Names for these S-NSSAIs. AMF will use this information to select an SMF that manages the PDU Session.

func HandleInitialRegistration(ue *context.AmfUe, anType models.AccessType) error {\n    ue.GmmLog.Infoln(\"Handle InitialRegistration\")\n\n    amfSelf := context.GetSelf()\n\n    // update Kgnb/Kn3iwf\n    ue.UpdateSecurityContext(anType)\n\n    // Registration with AMF re-allocation (TS 23.502 4.2.2.2.3)\n    if len(ue.SubscribedNssai) == 0 {\n        getSubscribedNssai(ue)\n    }\n\n    if err := handleRequestedNssai(ue, anType); err != nil {\n        return err\n    }\n\n//in the amf/internal/gmm/handler.go.\n

In the initialization of HandleInitialRegistration, AMF sends a request to the UDM to receive the UE's NSSAI (Network Slice Selection Assistance Information). After receiving subscribed NSSAI, AMF will compare it to UE's requested NSSAI. If there is a S-NSSAI that has not been subscribed before, AMF will request NSSF for Allowed NSSAI.

func handleRequestedNssai(ue *context.AmfUe, anType models.AccessType) error {\n    amfSelf := context.GetSelf()\n\n    if ue.RegistrationRequest.RequestedNSSAI != nil {\n        requestedNssai, err := nasConvert.RequestedNssaiToModels(ue.RegistrationRequest.RequestedNSSAI)\n        if err != nil {\n            return fmt.Errorf(\"Decode failed at RequestedNSSAI[%s]\", err)\n        }\n\n        needSliceSelection := false\n        for _, requestedSnssai := range requestedNssai {\n            ue.GmmLog.Infof(\"RequestedNssai - ServingSnssai: %+v, HomeSnssai: %+v\",\n                requestedSnssai.ServingSnssai, requestedSnssai.HomeSnssai)\n            if ue.InSubscribedNssai(*requestedSnssai.ServingSnssai) {\n                allowedSnssai := models.AllowedSnssai{\n                    AllowedSnssai: &models.Snssai{\n                        Sst: requestedSnssai.ServingSnssai.Sst,\n                        Sd:  requestedSnssai.ServingSnssai.Sd,\n                    },\n                    MappedHomeSnssai: requestedSnssai.HomeSnssai,\n                }\n                if !ue.InAllowedNssai(*allowedSnssai.AllowedSnssai, anType) {\n                    ue.AllowedNssai[anType] = append(ue.AllowedNssai[anType], allowedSnssai)\n                }\n            } else {\n                needSliceSelection = true\n                break\n            }\n        }\n\n        if needSliceSelection {\n            if ue.NssfUri == \"\" {\n                for {\n                    err := consumer.SearchNssfNSSelectionInstance(ue, amfSelf.NrfUri, models.NfType_NSSF, models.NfType_AMF, nil)\n                    if err != nil {\n                        ue.GmmLog.Errorf(\"AMF can not select an NSSF Instance by NRF[Error: %+v]\", err)\n                        time.Sleep(2 * time.Second)\n                    } else {\n                        break\n                    }\n                }\n            }\n\n            // Step 4\n            problemDetails, err := consumer.NSSelectionGetForRegistration(ue, requestedNssai)\n            if problemDetails != nil {\n                ue.GmmLog.Errorf(\"NSSelection Get Failed Problem[%+v]\", problemDetails)\n                gmm_message.SendRegistrationReject(ue.RanUe[anType], nasMessage.Cause5GMMProtocolErrorUnspecified, \"\")\n                return fmt.Errorf(\"Handle Requested Nssai of UE failed\")\n            } else if err != nil {\n                ue.GmmLog.Errorf(\"NSSelection Get Error[%+v]\", err)\n                gmm_message.SendRegistrationReject(ue.RanUe[anType], nasMessage.Cause5GMMProtocolErrorUnspecified, \"\")\n                return fmt.Errorf(\"Handle Requested Nssai of UE failed\")\n            }\n\n//in the amf/internal/gmm/handler.go.\n
if param.SliceInfoRequestForRegistration.RequestedNssai != nil &&\n    len(param.SliceInfoRequestForRegistration.RequestedNssai) != 0 {\n    // Requested NSSAI is provided\n    // Verify which S-NSSAI(s) in the Requested NSSAI are permitted based on comparing the Subscribed S-NSSAI(s)\n    if param.Tai != nil &&\n        !util.CheckSupportedNssaiInPlmn(param.SliceInfoRequestForRegistration.RequestedNssai, *param.Tai.PlmnId) {\n        // Return ProblemDetails indicating S-NSSAI is not supported\n        // TODO: Based on TS 23.501 V15.2.0, if the Requested NSSAI includes an S-NSSAI that is not valid in the\n        //       Serving PLMN, the NSSF may derive the Configured NSSAI for Serving PLMN\n        *problemDetails = models.ProblemDetails{\n            Title:  util.UNSUPPORTED_RESOURCE,\n            Status: http.StatusForbidden,\n            Detail: \"S-NSSAI in Requested NSSAI is not supported in PLMN\",\n            Cause:  \"SNSSAI_NOT_SUPPORTED\",\n        }\n\n        status = http.StatusForbidden\n        return status\n    }\n\n    // Check if any Requested S-NSSAIs is present in Subscribed S-NSSAIs\n    checkIfRequestAllowed := false\n\n    for _, requestedSnssai := range param.SliceInfoRequestForRegistration.RequestedNssai {\n        if param.Tai != nil && !util.CheckSupportedSnssaiInTa(requestedSnssai, *param.Tai) {\n            // Requested S-NSSAI does not supported in UE's current TA\n            // Add it to Rejected NSSAI in TA\n            authorizedNetworkSliceInfo.RejectedNssaiInTa = append(\n                authorizedNetworkSliceInfo.RejectedNssaiInTa,\n                requestedSnssai)\n            continue\n        }\n\n        var mappingOfRequestedSnssai models.Snssai\n        // TODO: Compared with Restricted S-NSSAI list in configuration under roaming scenario\n        if param.HomePlmnId != nil && !util.CheckStandardSnssai(requestedSnssai) {\n            // Standard S-NSSAIs are supported to be commonly decided by all roaming partners\n            // Only non-standard S-NSSAIs are required to find mappings\n            targetMapping, found := util.FindMappingWithServingSnssai(requestedSnssai,\n                param.SliceInfoRequestForRegistration.MappingOfNssai)\n\n            if !found {\n                // No mapping of Requested S-NSSAI to HPLMN S-NSSAI is provided by UE\n                // TODO: Search for local configuration if there is no provided mapping from UE, and update UE's\n                //       Configured NSSAI\n                checkInvalidRequestedNssai = true\n                authorizedNetworkSliceInfo.RejectedNssaiInPlmn = append(\n                    authorizedNetworkSliceInfo.RejectedNssaiInPlmn,\n                    requestedSnssai)\n                continue\n            } else {\n                // TODO: Check if mappings of S-NSSAIs are correct\n                //       If not, update UE's Configured NSSAI\n                mappingOfRequestedSnssai = *targetMapping.HomeSnssai\n            }\n        } else {\n            mappingOfRequestedSnssai = requestedSnssai\n        }\n\n        hitSubscription := false\n        for _, subscribedSnssai := range param.SliceInfoRequestForRegistration.SubscribedNssai {\n            if mappingOfRequestedSnssai == *subscribedSnssai.SubscribedSnssai {\n                // Requested S-NSSAI matches one of Subscribed S-NSSAI\n                // Add it to Allowed NSSAI list\n                hitSubscription = true\n\n                var allowedSnssaiElement models.AllowedSnssai\n                allowedSnssaiElement.AllowedSnssai = new(models.Snssai)\n                *allowedSnssaiElement.AllowedSnssai = requestedSnssai\n                nsiInformationList := util.GetNsiInformationListFromConfig(requestedSnssai)\n                if nsiInformationList != nil {\n                    // TODO: `NsiInformationList` should be slice in `AllowedSnssai` instead of pointer of slice\n                    allowedSnssaiElement.NsiInformationList = append(\n                        allowedSnssaiElement.NsiInformationList,\n                        nsiInformationList...)\n                }\n                if param.HomePlmnId != nil && !util.CheckStandardSnssai(requestedSnssai) {\n                    allowedSnssaiElement.MappedHomeSnssai = new(models.Snssai)\n                    *allowedSnssaiElement.MappedHomeSnssai = *subscribedSnssai.SubscribedSnssai\n                }\n\n                // Default Access Type is set to 3GPP Access if no TAI is provided\n                // TODO: Depend on operator implementation, it may also return S-NSSAIs in all valid Access Type if\n                //       UE's Access Type could not be identified\n                var accessType models.AccessType = models.AccessType__3_GPP_ACCESS\n                if param.Tai != nil {\n                    accessType = util.GetAccessTypeFromConfig(*param.Tai)\n                }\n\n                util.AddAllowedSnssai(allowedSnssaiElement, accessType, authorizedNetworkSliceInfo)\n\n                checkIfRequestAllowed = true\n                break\n            }\n        }\n\n        if !hitSubscription {\n            // Requested S-NSSAI does not match any Subscribed S-NSSAI\n            // Add it to Rejected NSSAI in PLMN\n            checkInvalidRequestedNssai = true\n            authorizedNetworkSliceInfo.RejectedNssaiInPlmn = append(\n                authorizedNetworkSliceInfo.RejectedNssaiInPlmn,\n                requestedSnssai)\n        }\n    }\n\n    if !checkIfRequestAllowed {\n        // No S-NSSAI from Requested NSSAI is present in Subscribed S-NSSAIs\n        // Subscribed S-NSSAIs marked as default are used\n        useDefaultSubscribedSnssai(param, authorizedNetworkSliceInfo)\n    }\n} else {\n    // No Requested NSSAI is provided\n    // Subscribed S-NSSAIs marked as default are used\n    checkInvalidRequestedNssai = true\n    useDefaultSubscribedSnssai(param, authorizedNetworkSliceInfo)\n}\n\n//in the nssf/internal/sbi/producer/nsselection_for_registration.go, nsselectionForRegistration funcion.\n

If NSSF needs to select S-NSSAI, it first finds the mapping of requested NSSAI to configured NSSAI for the HPLMN and converts requested S-NSSAI to S-NSSAI in configured NSSAI for the HPLMN. Then compare these S-NSSAIs with Subscribed S-NSSAIs; if NSSF find one match, set it as AllowedSnssai. If NSSF can't find such a mapping or no S-NSSAI in the mapping matches subscribed S-NSSAIs, it will use default subscribed S-NSSAIs.

"},{"location":"blog/20230719/UDM_introduce/#reference","title":"Reference","text":"
  • 3GPP TS29.503 v15.2.1
  • 3GPP TS23.502 v15.2.0
  • 3GPP TS23.501 v15.2.0
  • 3GPP TS33.501 v15.2.0
  • free5GC v3.3.0
"},{"location":"blog/20230719/UDM_introduce/#about","title":"About","text":"

Hello! My name is \u5f35\u54f2\u777f, and my current research topic is ATSSS (Access Traffic Steering, Switching and Splitting), I will continue to write articles related to 5G networks in the future. If you find any mistakes in my articles or have any topics you want to know about, please contact me.

  • Linkedln

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20230726/network_slice/","title":"How to deploy a free5GC network slice on OpenStack","text":"

Note

Author: Daniel Hsieh Date: 2023/7/26

"},{"location":"blog/20230726/network_slice/#whats-network-slicing","title":"What's Network Slicing","text":"

Network slicing allows for the creation of multiple logical, isolated, and independent virtual networks that can coexist within a shared physical infrastructure. Each network slice provides dedicated and customized network resources to meet the specific requirements of different services The main elements of a network slice include:

  • Virtualized Network Functions (VNFs): Each network slice can include a set of virtualized network functions that provide specific network capabilities and services. These VNFs can include functions like routing, switching, firewalling, load balancing, or any other network service required by the slice.

  • Isolation and Resource Allocation: Network slicing ensures the isolation of resources between slices, preventing interference and conflicts. It allows for the allocation of dedicated and optimized resources such as bandwidth, processing power, and storage to each slice based on its specific needs.

  • Orchestration and Management: Network slice orchestration involves the creation, provisioning, and management of network slices. It involves configuring the appropriate VNFs, assigning resources, and establishing connectivity between the different components of a slice.

NFV Enabling Network Slicing for 5G

Take Figure 1 as an example. The first slice is designed for mobile devices such as smartphones. Such slice requires a huge diversity of VNFs, and virtual links with high speed and low latency to support the broadband service of smartphones. In 5G network, Those slices are referred to as eMBB (enhanced mobile broadband) slices.

The second slice is designed for autonomous driving. In such scenario, extremely low latency and high reliability are paramount to ensure the vehicles' operability, smoothness and safety. To achieve low latency, some of the NFs should be deployed close to the access node,i.e. on edge cloud. To achieve high reliability, a NF should have multiple instances on available physical resources to make the slice more fault tolerant. Such slice is referred to as URLLC (Ultra-Reliable Low-Latency Communications) slice.

The third slice is designed for massive IoT. IoT devices are expected to not move and send very small amount of data intermittently. Due to the nature of such devices, functions that handle mobiltiy and always-on connections are not needed. Such slices are referred to as mIoT (massive IoT) slices.

"},{"location":"blog/20230726/network_slice/#mano-architecture","title":"MANO Architecture","text":"

In this article, we utilize MANO network function virtualization (NFV) architecture to deploy virtual network function (VNF). It plays the role of creating, deploying, and managing VNFs. MANO consists of three main functional components: NFV Orchestrator (NFVO), Virtualized Infrastructure Manager (VIM), and Virtual Network Function Manager (VNFM).

NFV MANO Architecture

  • NFVO manages the underlying resource by coordinating VIM and VNFM. It handles tasks such as receiving requests, service instantiation, scaling, termination, and monitoring.

  • VNFM manages the lifecycle of VNF instances. It interacts with the VIM to instantiate, configure, monitor, and terminate VNF instances.

  • VIM is responsible for managing the underlying virtualized infrastructure that hosts the VNFs. It abstracts the physical resources, such as compute, storage, and networking, and provides a unified view to the NFVO. The VIM handles tasks like resource allocation, performance monitoring, fault management, and virtualization management.

For VIM, we use OpenStack, an open-source software that provides IaaS, to utilize the physical resources. For VNFM and NFVO, we use Tacker, a service component of OpenStack, to manage VNFs.

"},{"location":"blog/20230726/network_slice/#openstack","title":"OpenStack","text":"

OpenStack is an open-source cloud computing platform that provides a set of software tools for building and managing customized clouds. OpenStack offers a infrastructure-as-a-service (IaaS) solution, enabling organizations to create and manage virtualized resources in a cloud environment. It is designed to be modular and consists of various components that work together to deliver a comprehensive cloud computing platform. Some of the key components include:

  • Nova: Nova is the computing component of OpenStack and serves as the main compute engine. It manages the creation, scheduling, and management of virtual machines (VMs) and provides APIs for controlling and interacting with the compute resources.

  • Cinder: Cinder is the block storage component of OpenStack. It provides persistent storage for virtual machines. With Cinder, users can create and manage volumes that can be attached to instances, allowing for flexible and scalable storage options.

  • Neutron: Neutron is the networking component of OpenStack. It provides a networking-as-a-service (NaaS) solution, allowing users to define and manage network resources. Neutron supports virtual LANs, software-defined networking (SDN), and network function virtualization (NFV), etc.

  • Keystone: Keystone is the identity service component of OpenStack. It provides authentication and authorization services, enabling users to securely access and manage resources within the cloud. Keystone supports multiple authentication mechanisms, including username/password, token-based, and external identity providers.

  • Horizon: Horizon is the web-based dashboard for OpenStack. It provides a user-friendly interface for managing and monitoring the cloud infrastructure. With Horizon, users can perform various tasks, such as launching instances, managing storage resources, and configuring networking options.

OpenStack Architecture

OpenStack is highly flexible and customizable, allowing organizations to tailor the cloud infrastructure to their specific needs. It supports multiple hypervisors, including KVM, VMware, and Hyper-V.

"},{"location":"blog/20230726/network_slice/#tacker","title":"Tacker","text":"

To enable NFV, we need another service component of OpenStack called Tacker. Tacker is designed to simplify the deployment and lifecycle management of VNFs and network service functions (NSFs) in a cloud infrastructure. It leverages OpenStack's existing components, such as Nova, Neutron, and Heat, to provide a comprehensive solution for network service orchestration. Tacker provides several key features and functionalities:

  • Service Templates: Tacker uses service templates to define the composition and behavior of network services. These templates describe the VNFs and NSFs involved, their interconnections, resource requirements, etc. Service templates are written using the TOSCA (Topology and Orchestration Specification for Cloud Applications) standard.

  • Lifecycle Management: Tacker automates the entire lifecycle of network services, including provisioning, scaling, healing, and termination. It leverages Heat, OpenStack's orchestration service, to manage the underlying infrastructure resources required by the services and handle dynamic scaling of VNFs based on traffic demands.

  • VNF Manager: Tacker includes a VNF Manager component responsible for managing the lifecycle of VNFs. It interacts with OpenStack's compute and networking services, to instantiate and manage VNF instances.

  • Multi-VIM Support: Tacker supports multiple virtual infrastructure managers to accommodate different cloud platforms and environments. It can interact with OpenStack, VMware vSphere and Kubernetes and so on, enabling operators to deploy network services across heterogeneous infrastructure environments.

Tacker Architecture

"},{"location":"blog/20230726/network_slice/#deploy-a-free5gc-network-slice","title":"Deploy a free5GC Network Slice","text":"
  1. In our implementation, we install OpenStack and Tacker on two different virtual machines for resource utilization reasons, but in fact, they can be installed on the same virtual machine.

  2. we need to install OpenStack on a virtual machine. Specific details and corresponding compatibility can be found on OpenStack official website. Using devstack scripts for installation enables operators to customize the environment based on their needs, such as extra plugins (softwares that extends the functionality of OpenStack environment) and overcommit (allows deploying NFs that require more resource than existing physical resourcce) functionality. Upon completion, a web UI enabled by Horizon can be used to access and operate on your own personalized OpenStack cloud.

  3. Install Tacker on another virtual machine, which requires four OpenStack service components, Keystone, Mistral, Barbican and Horizon. Once the installation is completed, we can register our OpenStack VIM on Tacker using openstack vim registercommand.

  4. Create two instances that will be used as images (one for control plane VNFs, one for UPF) for the VNFs that we will create. Then, ssh into those instances to set up the configurations for the VNFs, such as, installing required packages (go language, mongodb, libtool, etc.) and git clone free5GC source code. Once all the configurations are done, use OpenStack dashboard to take snapshots of these instances, which will be used as the images for VNFs.

  5. Import all the VNF descriptors (VNFD) of the VNFs we need by using openstack vnf descriptor create command. VNFDs should be written in accordance with TOSCA format. TOSCA format allows you to define the virtual links (a virtual network VNFs will be running in) and virtual deployment unit (operation unit of a VNF). Below is an example of UPF VNFD:

    tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0\ndescription: description\nnode_types:\n  tosca.nodes.nfv.VNF11:\n    requirements:\n    - virtualLink1:\n        type: tosca.nodes.nfv.VL\n        required: true\nmetadata:\n  template_name: free5GCSetup\ntopology_template:\n  substitution_mappings:\n    node_type: tosca.nodes.nfv.VNF11\n  node_templates:\n    VDU1:\n      type: tosca.nodes.nfv.VDU.Tacker\n      properties:\n        name: free5gc-upf1-VNF\n        image: stage3-up\n        flavor: free5gc\n        availability_zone: nova\n        mgmt_driver: noop\n        key_name: free5gc\n        user_data_format: RAW\n        user_data: |\n          #!/bin/sh\n          cd /home/ubuntu/free5gc/src/upf/build \n          cat > config/upfcfg.yaml <<- EOM\n          info:\n            version: 1.0.0\n            description: UPF configuration\n\n          configuration:\n            # debugLevel: panic|fatal|error|warn|info|debug|trace\n            debugLevel: info\n\n            pfcp:\n              - addr: 192.168.2.111\n\n            gtpu:\n              - addr: 192.168.2.111\n              # [optional] gtpu.name\n              # - name: upf.5gc.nctu.me\n              # [optional] gtpu.ifname\n              # - ifname: gtpif\n\n            apn_list:\n              - apn: internet\n                cidr: 60.60.0.0/24\n                # [optional] apn_list[*].natifname\n                # natifname: eth0\n          EOM\n          #sudo ./bin/free5gc-upfd -f config/upfcfg.yaml\n\n    CP1:\n      type: tosca.nodes.nfv.CP.Tacker\n      properties:\n        ip_address: 192.168.2.111\n        management: true\n      requirements:\n      - virtualLink:\n          node: VL1\n      - virtualBinding:\n          node: VDU1\n    VL1:\n      type: tosca.nodes.nfv.VL\n      properties:\n        network_name: 5GC\n        vendor: Tacker\n    FIP1:\n      type: tosca.nodes.network.FloatingIP\n      properties:\n        floating_network: public\n        floating_ip_address: 172.24.4.111\n      requirements:\n      - link:\n          node: CP1\n

  6. Import the network service descriptor (NSD) using openstack ns descriptor create command. The NSD should also be written in accordance with TOSCA format. Once all the VNFDs and NSD are all successfully imported, we can use openstack ns create to deploy the network slice. The VNFs specified in the NSD will also be instantiated along with the network slice. Their instances can be viewed on OpenStack dashboard enabled by Horizon or just use openstack vnf list to check the status of the VNFs. Below is an example of NSD
    tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0\ndescription: Import Common Slice VNFDs (already on-boarded)\nimports:\n  - mongo\n  - nrf\n  - amf\n  - smf\n  - udr\n  - pcf\n  - udm\n  - nssf\n  - ausf\ntopology_template:\n  node_templates:\n    VNF0:\n      type: tosca.nodes.nfv.VNF0\n    VNF1:\n      type: tosca.nodes.nfv.VNF1\n    VNF2:\n      type: tosca.nodes.nfv.VNF2\n    VNF3:\n      type: tosca.nodes.nfv.VNF3\n    VNF4:\n      type: tosca.nodes.nfv.VNF4\n    VNF5:\n      type: tosca.nodes.nfv.VNF5\n    VNF6:\n      type: tosca.nodes.nfv.VNF6\n    VNF7:\n      type: tosca.nodes.nfv.VNF7\n    VNF8:\n      type: tosca.nodes.nfv.VNF8\n
  7. ssh into the VNF instances to make the necessary configuration for each VNF and start the free5GC VNF.
  8. Voila! Now we have a fully functional free5GC network slice.

There are many other ways to set up a network slice. For example, we can deploy VNFs of the same network slice on different VIMs, or we can deploy all the network slices on the same VIM, as long as it is specified in the VNFDs.

"},{"location":"blog/20230726/network_slice/#about","title":"About","text":"

Hi, my name is Daniel Hsieh. I am a CS major graduate student. My research field is network slicing. If there are any questions about the article, please feel free to contact.

  • email: e657shai@gmail.com
"},{"location":"blog/20230726/network_slice/#reference","title":"Reference","text":"
  • https://www.acecloudhosting.com/blog/openstack-the-catalyst-of-the-public-cloud-market/

  • https://telcocloudbridge.com/blog/a-beginners-guide-to-nfv-management-orchestration-mano/

  • https://wiki.openstack.org/wiki/Tacker

  • B. Chatras, U. S. Tsang Kwong and N. Bihannic, \"NFV enabling network slicing for 5G,\" 2017 20th Conference on Innovations in Clouds, Internet and Networks (ICIN), Paris, France, 2017, pp. 219-225, doi: 10.1109/ICIN.2017.7899415.

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20230802/20230802/","title":"Authentication Mechanism in NRF: What Is OAuth?","text":"

Note

Author: Wilson Date: 2023/8/2

"},{"location":"blog/20230802/20230802/#abstract","title":"Abstract","text":"

Regarding the theme this time, I will briefly introduce OAuth. OAuth 2.0 defines four types of authorization flows. I choose the Client Credentials Flow to explain because the authentication mechanism in NRF is closely related to the Client Credentials Flow.

Next, I will explain how to apply the concept of the Client Credentials Flow to NRF and introduce Nnrf_AccessToken Service, because Nnrf_AccessToken Service is closely related to the Client Credentials Flow.

Finally, I will make a simple experiment of the authentication mechanism in NRF and share the environment settings and methods of operation.

"},{"location":"blog/20230802/20230802/#oauth","title":"OAuth","text":"

Before explaining the authentication mechanism in NRF, I will introduce OAuth. Regarding the OAuth flow, we can log in to the account through the platform before accessing an application. After logging in, we agree that an application can limitedly obtain the information of the user on the platform. The application can be LinkedIn, YouTube, etc. The platform can be Google, Facebook, etc.

The full English name of OAuth is Open standard Authorization. OAuth is an open standard, and it's used to deal with authorization-related behaviors. OAuth 2.0 defines four types of authorization flows. The four types of the authorization flows are:

  • Authorization Code
  • Implicit
  • Resource Owner Password Credentials
  • Client Credentials

This article explains the entire authorization of the Client Credentials Flow only, because the authentication mechanism in NRF adopts the Client Credentials.

If you're interested in how authorization mechanism works, please refer to this article for more details.

"},{"location":"blog/20230802/20230802/#client-credentials-flow","title":"Client Credentials Flow","text":"

Figure 1. Client Credentials Flow

Referring to the Figure 1, the Client Credentials Flow is mainly composed of:

  • Client
  • Authorization Server
  • Resource Server

The entire authorization of the Client Credentials Flow can be devided into 3 steps:

  • First, the Client provdies Client ID, Client Secret Token, Scope, and Grant Type to the Authorization Server.
  • Second, the Authorization Server verifies the information provided by the Client. After the Authorization Server verifies the information, it signs with the private key and sends the Access Token to the Client.
  • Third, the Client obtains resources from the Resource Server with the Access Token.

In addition, the Client and the Authorization Server have their own Scope list. The Scope list records a series of the actions. The Client or the Authorization Server is permitted to do the actions for obtaining the user\u2019s name, deleting posts, etc.

Below I will explain how to apply the Client Credentials Flow to NRF after talking about the Client Credentials Flow.

"},{"location":"blog/20230802/20230802/#client-credentials-flow-in-nrf","title":"Client Credentials Flow in NRF","text":"

The Figure 2 and the Figure 3 originate from the Figure 13.4.1.1-1 and the Figure 13.4.1.1-2 of the TS 33.501.

Figure 2. NF Service Consumer Obtaining Access Token before NF Service Access

The entire flow in Figure 2 is the same as Step 1 and Step 2 in the Figure 1. The role of the Client is played by the NF Service Consumer, and the role of the Authorization Server is played by the NRF.

First, the NF Service Consumer registers with NRF. Then the NF Service Consumer sends the Nnrf_AccessToken_Get Request to NRF. The Nnrf_AccessToken_Get Request includes:

  • Consumer NF Type
  • Expected NF Type
  • Expected NF Service Name
  • Client ID

The NF Type can be AMF, SMF, etc. , and the NF Service Name can be namf-comm, nsmf-pdusession, etc.

NRF verifies the information provided by the NF Service Consumer after it receives the Nnrf_AccessToken_Get Request. NRF generates an Access Token and uses the NRF private key to sign on the Access Token after the verification is successful.

Finally, NRF returns the Nnrf_AccessToken_Get Response to the NF Service Consumer. The NF Service Consumer stores the Access Token within the validity period after it gets the Access Token. The services provided by the NF Service Producer are in the Expected NF Service Name. The NF Service Consumer doesn\u2019t need to verify again when it wants to use the services provided by the NF Service Producer.

Figure 3. NF Service Consumer Requesting Service Access with an Access Token

The entire flow in Figure 3 is the same as Step 3 in the Figure 1. The role of the Client is played by the NF Service Consumer, and the role of the Resource Server is played by the NF Service Producer.

First, the NF Service Consumer sends the NF Service Request to the NF Service Producer with the Access Token. Simply put, the NF Service Consumer wants to consume the service provided by the NF Service Producer.

The NF Service Producer uses the NRF public key to verify the signed Access Token after it receives the NF Service Request. If the verification is successful, the NF Service Producer will send the NF Service Response to the NF Service Consumer.

Now I will talk about the Nnrf_AccessToken Service after explaining how to apply the Client Credentials Flow to NRF.

"},{"location":"blog/20230802/20230802/#nnrf_accesstoken-service","title":"Nnrf_AccessToken Service","text":"

Figure 4. Access Token Request

The Figure 4 originates from the Figure 5.4.2.2.1-1 of the TS 29.510.

First, the NF Service Consumer sends the POST /oauth2/token to NRF, and the data is stored in the AccessTokenReq. The attribute name, the data type, and the formulation rule of the AccessTokenReq are shown in the Table 1. The Table 1 originates from the Table 6.3.5.2.2-1 of the TS 29.510.

Table 1. Definition of Type AccessTokenReq

Definition of type AccessTokenReq:

  • grant_type: The value must be set to the client_credentials, and it is checked in the Snippet 1.
  • nfInstanceId: The value stores the ID of the NF Service Consumer.
  • targetNfInstanceId: The value stores the ID of the NF Service Producer.
  • nfType: The value stores the network function name of the NF Service Consumer. The network function name can be the AMF, SMF, etc.
  • targetNfType: The value stores the network function name of the NF Service Producer.
  • scope: It stores the services. The services can be the namf-comm, nsmf-pdusession, etc. When the NF Service Consumer requests the services. The services will be provided by the NF Service Producer.
  • requesterPlmn: It is mainly used in the roaming.
  • targetPlmn: It is mainly used in the roaming.

if reqGrantType != \"client_credentials\" {\n    return &models.AccessTokenErr{\n        Error: \"unsupported_grant_type\",\n    }\n}\n
Snippet 1. Grant Type Value Checking

NRF sends AccessTokenRsp to the NF Service Consumer in the Step 2a of the Figure 4. The attribute name, the data type, and the formulation rule of the AccessTokenRsp are shown in the Table 2. The Table 2 originates from the Table 6.3.5.2.3-1 of the TS 29.510.

Table 2. Definition of Type AccessTokenRsp

The AccessTokenRsp contains four attribute names. The four attribute names are:

  • access_token: It stores all the attribute names and values of the AccessTokenClaims in the Table 3. The Table 3 originates form the Table 6.3.5.2.4-1 of the TS 29.510.
  • token_type: It must be set to the Bearer and can be seen in the Snippet 2.
  • expires_in: It stores information related to the expiration date.
  • scope: The NF Service Consumer and the NF Service Producer have their own scope list. The scope in the AccessTokenRsp has a series of these services, and the NF Service Producer is permitted to consume these services.

Table 3. Definition of Type AccessTokenClaims

Definition of Type AccessTokenClaims:

  • iss: It is called issuer, and the content usually stores the ID of NRF.
  • sub: It is called subject, and the content stores the ID of the NF Service Consumer.
  • aud: It is called audience, and the content stores the ID of the NF Service Producer.
  • scope: The scope in the AccessTokenClaims has a series of these services, and the NF Service Consumer is authorized by the NF Service Producer and permitted to consume these services.
  • exp: It stores information related to the validity period.

func AccessTokenProcedure(request models.AccessTokenReq) (\n    *models.AccessTokenRsp, *models.AccessTokenErr,\n) {\n    logger.AccTokenLog.Infoln(\"In AccessTokenProcedure\")\n\n    var expiration int32 = 1000\n    scope := request.Scope\n    tokenType := \"Bearer\"\n    now := int32(time.Now().Unix())\n\n    errResponse := AccessTokenScopeCheck(request)\n    if errResponse != nil {\n        return nil, errResponse\n    }\n\n    // Create AccessToken\n    nrfCtx := nrf_context.GetSelf()\n    accessTokenClaims := models.AccessTokenClaims{\n        Iss:            nrfCtx.Nrf_NfInstanceID,    // NF instance id of the NRF\n        Sub:            request.NfInstanceId,       // nfInstanceId of service consumer\n        Aud:            request.TargetNfInstanceId, // nfInstanceId of service producer\n        Scope:          request.Scope,              // TODO: the name of the NF services for which the\n        Exp:            now + expiration,           // access_token is authorized for use\n        StandardClaims: jwt.StandardClaims{},\n    }\n    accessTokenClaims.IssuedAt = int64(now)\n\n    // Use NRF private key to sign AccessToken\n    token := jwt.NewWithClaims(jwt.GetSigningMethod(\"RS512\"), accessTokenClaims)\n    accessToken, err := token.SignedString(nrfCtx.NrfPrivKey)\n    if err != nil {\n        logger.AccTokenLog.Warnln(\"Signed string error: \", err)\n        return nil, &models.AccessTokenErr{\n            Error: \"invalid_request\",\n        }\n    }\n\n    response := &models.AccessTokenRsp{\n        AccessToken: accessToken,\n        TokenType:   tokenType,\n        ExpiresIn:   expiration,\n        Scope:       scope,\n    }\n    return response, nil\n}\n
Snippet 2. AccessTokenProcedure Function

The Snippet 2 is the AccessTokenProcedure() function. The function is executed in NRF.

The function mainly processes:

  1. The NRF receives the AccessTokenReq sent by the NF Service Consumer.
  2. The function calls the AccessTokenScopeCheck() function. The AccessTokenScopeCheck() function checks whether the content of the attribute name in the AccessTokenReq complies with the requirements of the TS 29.510. If not, the AccessTokenProcedure() function immediately returns the AccessTokenErr to the NF Service Consumer.
  3. The function starts to create the Access Token. The Access Token is stored in the AccessTokenRsp. The AccessTokenRsp is sent back to the NF Service Consumer. The Iss in the AccessToken obtains its own ID in NRF. The Sub and Aud are obtained from the NfInstancedId and the TargetNfInstanceId in the AccessTokenReq respectively. The Scope is obtained from the scope in the AccessTokenReq. The expiration is set to the 1000 in the Snippet 2. Therefore, the value of the exp is the current time + 1000.
  4. After the Access Token is created, the function uses the NRF private key to sign on the Access Token. After signing, the function checks whether there is an error. If so, the function immediately sends the AccessTokenErr to the NF Service Consumer.
  5. The function puts the signed Access Token into the AccessTokenRsp. The value of the TokenType is set to the Bearer by the function. The function sets the ExprieIn and the Scope in the Snippet 2.

Finally, I make a simple experiment about the Access Token and share the environment setting and method of operation with you.

"},{"location":"blog/20230802/20230802/#experiment","title":"Experiment","text":"

The Table 4 is my environment setting. I provide the Table 4 for you. You can refer it.

Table 4. Environment

Remove the part of the tls and add the content of the cert, rootcert and oauth under sbi in the nrfcfg.yaml before implementing about the Access Token.

info:\n  version: 1.0.2\n  description: NRF initial local configuration\n\nconfiguration:\n  MongoDBName: free5gc # database name in MongoDB\n  MongoDBUrl: mongodb://127.0.0.1:27017 # a valid URL of the mongodb\n  sbi: # Service-based interface information\n    scheme: http # the protocol for sbi (http or https)\n    registerIPv4: 127.0.0.10 # IP used to serve NFs or register to another NRF\n    bindingIPv4: 127.0.0.10  # IP used to bind the service\n    port: 8000 # port used to bind the service\n    cert:\n      pem: cert/nrf.pem\n      key: cert/nrf.key\n    rootcert:\n      pem: cert/nrf.pem\n      key: cert/nrf.key\n    oauth: true\n  DefaultPlmnId:\n    mcc: 208 # Mobile Country Code (3 digits string, digit: 0~9)\n    mnc: 93 # Mobile Network Code (2 or 3 digits string, digit: 0~9)\n  serviceNameList: # the SBI services provided by this NRF, refer to TS 29.510\n    - nnrf-nfm # Nnrf_NFManagement service\n    - nnrf-disc # Nnrf_NFDiscovery service\n\nlogger: # log output setting\n  enable: true # true or false\n  level: info # how detailed to output, value: trace, debug, info, warn, error, fatal, panic\n  reportCaller: false # enable the caller report or not, value: true or false\n
nrfcfg.yaml

Find the http://127.0.0.10:8000/nnrf-nfm/v1/nf-instances/8f7891b4-b127-4f59-9ec2-b5e6aade5531 in the NRF log, and you will get the 8f7891b4-b127-4f59-9ec2-b5e6aade5531. The 8f7891b4-b127-4f59-9ec2-b5e6aade5531 is the nfInstanceID.

2023-08-02T20:07:43.300826205Z [INFO][NRF][NFM] Handle NFRegisterRequest\n2023-08-02T20:07:43.308259291Z [INFO][NRF][NFM] urilist create\n2023-08-02T20:07:43.311674255Z [INFO][NRF][NFM] Create NF Profile\n2023-08-02T20:07:43.318192771Z [INFO][NRF][NFM] Location header:  http://127.0.0.10:8000/nnrf-nfm/v1/nf-instances/8f7891b4-b127-4f59-9ec2-b5e6aade5531\n2023-08-02T20:07:43.325073275Z [INFO][NRF][GIN] | 201 |       127.0.0.1 | PUT     | /nnrf-nfm/v1/nf-instances/8f7891b4-b127-4f59-9ec2-b5e6aade5531 |\n

Execute $ curl -X GET {apiRoot}/nnrf-nfm/v1/nf-instances/{nfInstanceID}, and you will obtain the detail information about the nfInstanceID. You can see the nfType of the nfInstanceID is NSSF, and the information about the nfInstanceID is used when you implement the Access Token.

ubuntu@free5GC:~/free5gc/NFs/nrf$ curl -X GET http://127.0.0.10:8000/nnrf-nfm/v1/nf-instances/8f7891b4-b127-4f59-9ec2-b5e6aade5531\n{\"ipv4Addresses\":[\"127.0.0.31\"],\"nfInstanceId\":\"8f7891b4-b127-4f59-9ec2-b5e6aade5531\",\"nfServices\":[{\"apiPrefix\":\"http://127.0.0.31:8000\",\"ipEndPoints\":[{\"ipv4Address\":\"127.0.0.31\",\"port\":8000,\"transport\":\"TCP\"}],\"nfServiceStatus\":\"REGISTERED\",\"scheme\":\"http\",\"serviceInstanceId\":\"0\",\"serviceName\":\"nnssf-nsselection\",\"versions\":[{\"apiFullVersion\":\"1.0.2\",\"apiVersionInUri\":\"v1\"}]},{\"apiPrefix\":\"http://127.0.0.31:8000\",\"ipEndPoints\":[{\"ipv4Address\":\"127.0.0.31\",\"port\":8000,\"transport\":\"TCP\"}],\"nfServiceStatus\":\"REGISTERED\",\"scheme\":\"http\",\"serviceInstanceId\":\"1\",\"serviceName\":\"nnssf-nssaiavailability\",\"versions\":[{\"apiFullVersion\":\"1.0.2\",\"apiVersionInUri\":\"v1\"}]}],\"nfStatus\":\"REGISTERED\",\"nfType\":\"NSSF\",\"plmnList\":[{\"mcc\":\"208\",\"mnc\":\"93\"}]}\n

Then you execute this command, see below.

$ curl -X POST -H \"Content-Type: application/json\" -d '{\"nfInstanceId\": {nfInstanceID}, \"grant_type\": \"client_credentials\", \"nfType\": {nfType}, \"targetNfType\": \"UDR\", \"scope\": \"nudr-dr\"}' {apiRoot}/oauth2/token\n
You will get the long symbols. The long symbols is that the Access Token is encrypted by the private key of NRF and stored in the AccessTokenRsp.

ubuntu@free5GC:~/free5gc/NFs/nrf$ curl -X POST -H \"Content-Type: application/json\" -d '{\"nfInstanceId\": \"8f7891b4-b127-4f59-9ec2-b5e6aade5531\", \"grant_type\": \"client_credentials\", \"nfType\": \"NSSF\", \"targetNfType\": \"UDR\", \"scope\": \"nudr-dr\"}' http://127.0.0.10:8000/oauth2/token\n\"eyJhY2Nlc3NfdG9rZW4iOiJleUpoYkdjaU9pSlNVelV4TWlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKcGMzTWlPaUlpTENKemRXSWlPaUk0WmpjNE9URmlOQzFpTVRJM0xUUm1OVGt0T1dWak1pMWlOV1UyWVdGa1pUVTFNekVpTENKaGRXUWlPaUlpTENKelkyOXdaU0k2SW01MVpISXRaSElpTENKbGVIQWlPakUyT1RFd01EZzBPRGdzSW1saGRDSTZNVFk1TVRBd056UTRPSDAuY3VHSkkwTndfV280S2lQbS1fZEZVdnVTQWM1WVEwMmRKYk5PTUhmMV9IOHdIZ2JKWFhUam9xU1Y2OTNYSmFKemkweGIxdC1DMW14TWhVZkZjbXpNMC1Nd2oxTXZYaWhyTTktdDFRUFItSFcxQlBlN0tHMUxBV3d5MEJfcXpIalltRlR6eGhONVlyNkpURDhBbkMxaFJFeEh4WHBjV1NqbV9vZnV0NVhfUFRFRkZtaHZrbmtVbU8waWFrTmdRWElRVTc1NnlvZ29ZTlFDRnJvSmRWamJMdnpFdkJLYTVFN0hQeXc3RkRDRHpTZU5WT2t2WTlobU11eldYZ3dOVmRIT3c1c2lNbmppbTlmTVZ0RTFxS1hjWDlScXlUdXlsWjM2ZlJ1QjdVZ2hkLU15Q19xd2VJRE41ZFdYOWZqdnA3VUNZZ01mVHhSLUI2M3d5OWFjQ183eThRIiwidG9rZW5fdHlwZSI6IkJlYXJlciIsImV4cGlyZXNfaW4iOjEwMDAsInNjb3BlIjoibnVkci1kciJ9\"\n

NSSF sends the AccessTokenReq to NRF after you execute the above command. In the AccessTokenReq, the nfInstanceId is set to 8f7891b4-b127-4f59-9ec2-b5e6aade5531. The grant_type is set to the client_credentials. The nfType is set to NSSF. The targetNfType is set to UDR. The scope is set to nudr-dr. The information is shown in the NRF log. The value of the targetNfInstanceId, requesterPlmn, and targetPlmn is empty because they are not set.

2023-08-02T20:18:08.127557565Z [INFO][NRF][Token] In AccessTokenProcedure\n2023-08-02T20:18:08.127586736Z [INFO][NRF][Token] Access Token Request\n2023-08-02T20:18:08.127611885Z [INFO][NRF][Token] Grant Type: client_credentials\n2023-08-02T20:18:08.127637480Z [INFO][NRF][Token] NF Instance ID: 8f7891b4-b127-4f59-9ec2-b5e6aade5531\n2023-08-02T20:18:08.127664415Z [INFO][NRF][Token] Target NF Instance ID:\n2023-08-02T20:18:08.127689792Z [INFO][NRF][Token] NF Type: NSSF\n2023-08-02T20:18:08.127712916Z [INFO][NRF][Token] Target NF Type: UDR\n2023-08-02T20:18:08.127734827Z [INFO][NRF][Token] Scope: nudr-dr\n2023-08-02T20:18:08.127758317Z [INFO][NRF][Token] Requester PLMN: <nil>\n2023-08-02T20:18:08.127781052Z [INFO][NRF][Token] Target PLMN: <nil>\n

Next, you can see the Access Token in the NRF log. The value of the Sub is 8f7891b4-b127-4f59-9ec2-b5e6aade5531. The Sub represents NSSF, and NSSF belongs to the NF Service Consumer. The value of the Scope is the nudr-dr. The value of the Exp is 1691008488.

2023-08-02T20:18:08.134096785Z [INFO][NRF][Token] Access Token Claims\n2023-08-02T20:18:08.138100978Z [INFO][NRF][Token] Iss:\n2023-08-02T20:18:08.138185972Z [INFO][NRF][Token] Sub: 8f7891b4-b127-4f59-9ec2-b5e6aade5531\n2023-08-02T20:18:08.138228925Z [INFO][NRF][Token] Aud:\n2023-08-02T20:18:08.138264519Z [INFO][NRF][Token] Scope: nudr-dr\n2023-08-02T20:18:08.138298628Z [INFO][NRF][Token] Exp: 1691008488\n

Next, you can see the AccessTokenRsp. You can see that the Access Token has become the long symbols. The value of the Token Type is set to the Bearer. The value of the ExpiresIn is set to 1000. The value of the Scope is set to nudr-dr.

2023-08-02T20:18:08.149587382Z [INFO][NRF][Token] Access Token Response\n2023-08-02T20:18:08.150006665Z [INFO][NRF][Token] Access Token: eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIiLCJzdWIiOiI4Zjc4OTFiNC1iMTI3LTRmNTktOWVjMi1iNWU2YWFkZTU1MzEiLCJhdWQiOiIiLCJzY29wZSI6Im51ZHItZHIiLCJleHAiOjE2OTEwMDg0ODgsImlhdCI6MTY5MTAwNzQ4OH0.cuGJI0Nw_Wo4KiPm-_dFUvuSAc5YQ02dJbNOMHf1_H8wHgbJXXTjoqSV693XJaJzi0xb1t-C1mxMhUfFcmzM0-Mwj1MvXihrM9-t1QPR-HW1BPe7KG1LAWwy0B_qzHjYmFTzxhN5Yr6JTD8AnC1hRExHxXpcWSjm_ofut5X_PTEFFmhvknkUmO0iakNgQXIQU756yogoYNQCFroJdVjbLvzEvBKa5E7HPyw7FDCDzSeNVOkvY9hmMuzWXgwNVdHOw5siMnjim9fMVtE1qKXcX9RqyTuylZ36fRuB7Ughd-MyC_qweIDN5dWX9fjvp7UCYgMfTxR-B63wy9acC_7y8Q\n2023-08-02T20:18:08.150094277Z [INFO][NRF][Token] Token Type: Bearer\n2023-08-02T20:18:08.150133189Z [INFO][NRF][Token] Expires In: 1000\n2023-08-02T20:18:08.150167371Z [INFO][NRF][Token] Scope: nudr-dr\n

Finally, you can see 200. 200 means that AUSF sends the AccessTokenReq to NRF. NRF successfully sends to AUSF after verification.

2023-08-02T20:18:08.150302345Z [INFO][NRF][GIN] | 200 |       127.0.0.1 | POST    | /oauth2/token |\n
"},{"location":"blog/20230802/20230802/#reference","title":"Reference","text":"
  • TS 29.510
  • TS 33.501
  • [Notes] Understanding OAuth 2.0: Understand the differences of each role and each type of process at one time
  • https://blog.techbridge.cc/2019/02/01/linux-curl-command-tutorial/
  • https://github.com/free5gc/free5gc/issues/434
"},{"location":"blog/20230802/20230802/#about","title":"About","text":"

Hi, my name is Wilson. I am a master\u2019s student. My main area of research is network slicing. In the future, I will introduce more information about 5G. Hope you enjoy it.

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20230809/main/","title":"Fuzz Testing in Go: Discovering Vulnerabilities and Analyzing a Real Case (CVE-2022-43677)","text":"

Note

Author: Yu-Sheng Liu Date: 2023/8/9

"},{"location":"blog/20230809/main/#overview","title":"Overview","text":"

In this article, we begin by introducing the concept of fuzz testing and its significance in software testing. Subsequently, we present Go Fuzzing as an illustrative example to demonstrate how to implement fuzz testing in Go. Lastly, we showcase a practical case, CVE-2022-43677, to exemplify how we conduct fuzz testing on the free5GC system.

"},{"location":"blog/20230809/main/#fuzz-testing","title":"Fuzz Testing","text":""},{"location":"blog/20230809/main/#what-is-fuzz-testing-fuzzing","title":"What is Fuzz Testing (Fuzzing)?","text":"

Fuzz testing, commonly known as fuzzing, is an automated software testing technique used to uncover vulnerabilities, defects, and unexpected behavior in computer systems, applications, and networks. The primary objective of fuzzing is to identify security flaws, crashes, or abnormal program behavior caused by invalid or unexpected inputs.

"},{"location":"blog/20230809/main/#how-fuzz-testing-works","title":"How Fuzz Testing Works","text":"

Fuzz testing involves subjecting the target software or system to a large number of inputs, including random or malformed data, to see how it handles them. The idea is to explore edge cases and input combinations that may not have been adequately tested during traditional software testing. Here's how the fuzzing process typically works:

  1. Test Input Generation:

    • Fuzzing tools generate test inputs based on various techniques, which can be broadly categorized as:
      • Random Fuzzing: The simplest approach where random data is generated as input.
      • Mutation-Based Fuzzing: Starting with valid inputs, the tool applies mutations to create variations of the original data.
      • Grammar-Based Fuzzing: Using a predefined grammar or structure to generate valid and invalid inputs.
      • Seed Corpus: Using existing inputs (e.g., files, network packets) as the starting set for mutation.
  2. Test Execution:

    • The generated inputs are provided as input to the target application, component, or system.
    • The application is executed with each input, and its behavior is monitored during runtime.
  3. Monitoring and Analysis:

    • The fuzzer observes the application's behavior, including any crashes, exceptions, hangs, or memory-related issues.
    • Code coverage analysis is often performed to determine which parts of the code were exercised during the testing.
  4. Feedback and Iteration:

    • Fuzzing tools use feedback mechanisms to prioritize and select inputs that lead to new code paths or unique behavior.
    • The process is iterated with refined inputs to continue exploring deeper into the application.
"},{"location":"blog/20230809/main/#types-of-fuzz-testing","title":"Types of Fuzz Testing","text":"
  1. Black Box Fuzzing:

    • The tester has no access to the application's internal code or design.
    • Random or mutated inputs are provided to the application to observe its behavior.
    • Suitable for testing closed-source software or systems where the tester has limited knowledge.
  2. White Box Fuzzing:

    • The tester has access to the application's source code and can leverage this knowledge for targeted testing.
    • Inputs can be intelligently crafted to explore specific code paths and functions.
    • Provides more in-depth coverage but requires access to the application's code.
  3. Grey Box Fuzzing:

    • A combination of black box and white box approaches.
    • The tester has partial knowledge of the application, such as certain APIs or protocols, but not complete access to the source code.
    • Offers a balance between targeted testing and exploration of unknown behaviors.
"},{"location":"blog/20230809/main/#advantages-of-fuzz-testing","title":"Advantages of Fuzz Testing","text":"
  1. Bug and Vulnerability Discovery:

    • Fuzzing can uncover previously unknown security vulnerabilities and software defects, including memory corruption errors and input validation issues.
  2. Automation and Efficiency:

    • Fuzzing is an automated testing process, which allows it to scale and test large codebases quickly and efficiently.
  3. Diverse Test Inputs:

    • Fuzzing generates a wide range of test inputs, exploring various edge cases that might not be covered by manual testing.
  4. Early Vulnerability Detection:

    • Fuzzing can be integrated into the development process, enabling early detection and mitigation of vulnerabilities before deployment.
"},{"location":"blog/20230809/main/#conclusion-for-fuzz-testing","title":"Conclusion for fuzz testing","text":"

Fuzz testing, or fuzzing, is a powerful and essential technique in the realm of software security testing. By providing a diverse set of inputs and exploring uncharted code paths, fuzz testing uncovers vulnerabilities and defects that might otherwise remain hidden.

Next, we will use Go fuzzing as an example to introduce how to develop a fuzzing in Go.

"},{"location":"blog/20230809/main/#go-fuzzing","title":"Go Fuzzing","text":"

Go officially supports fuzzing starting from Go 1.18, and its official figure provides a brief and clear summary of the fuzzing function components.

Similar to Go's unit test functions, the fuzzing function in Go must follow the naming convention FuzzXxx and take an argument of type *testing.F. This argument has two main functions, Add and Fuzz.

  1. Add Function:

    • You can use the Add function to add your own test data to the seed corpus for fuzz testing. The seed corpus is the initial set of inputs that go-fuzz will use to start the fuzzing process.
  2. Fuzz Function:

    • The Fuzz function will be the target function that you want to test using fuzzing. It must have *testing.T as its first argument, similar to regular unit tests.
    • Additionally, the Fuzz function supports variadic arguments with the following basic data types:
      • string, []byte
      • int, int8, int16, int32/rune, int64
      • uint, uint8/byte, uint16, uint32, uint64
      • float32, float64
      • bool

These data types represent the different kinds of input data that can be passed to the Fuzz function during the fuzzing process. The fuzzer will generate and mutate inputs of these types to explore different code paths and uncover bugs or unexpected behavior in the target function.

In summary, when writing fuzzing functions in Go, remember to use the FuzzXxx naming pattern, accept *testing.F as an argument, utilize the Add function to customize the seed corpus, and use the Fuzz function with supported basic data types to perform fuzz testing on your target functions.

You can use the command to execute the fuzz testing:

go test -fuzz=<regex> -fuzztime=<duration or times>\n\n# Execute the fuzz testing until it crashs or finding some errors\ngo test -fuzz=Fuzz\n\n# Execute the fuzz testing ten iterations\ngo test -fuzz=Fuzz -fuzztime=10x\n\n# Execute the fuzz testing twenty seconds\ngo test -fuzz=Fuzz -fuzztime=20s\n

"},{"location":"blog/20230809/main/#simple-example-division","title":"Simple Example - Division","text":"

We have developed a very simple function called Division that accepts two arguments, dividend and divisor, and then returns two results: quotient and remainder.

func Division(dividend, divisor int32) (\n    quotient, remainder int32,\n) {\n    quotient = dividend / divisor\n    remainder = dividend % divisor\n\n    return\n}\n

In the FuzzDivision function, we utilize the data generated by the Go fuzzer to test our Division function.

func FuzzDivision(f *testing.F) {\n    f.Fuzz(func(t *testing.T,\n        n1, n2 int32,\n    ) {\n        q, r := Division(n1, n2)\n\n        require.Equal(t, n1, n2*q+r)\n    })\n}\n

We expected to see:

n1 / n2 = q ... r\nn1 = n2 * q + r\n

There should not be any problems with this implementation.

Then we can use the following command to start the fuzz testing.

go test -fuzz=^FuzzDivision$\n

The fuzz testing reports the error \"integer divide by zero\".

As a normal user, we understand that the divisor cannot be zero. However, the input data may not always be as expected. This is precisely why we use fuzz testing\u2014to help us find edge cases and uncover unexpected behavior.

Go stores the data that caused the fuzz testing to fail. You can check them using the following command. * Note: The file name, 29bf8459dc5d452f64d41eb8a253f6a672939b146b07fcced0b17e99729e9b91, may not be the same.

cat testdata/fuzz/FuzzDivision/29bf8459dc5d452f64d41eb8a253f6a672939b146b07fcced0b17e99729e9b91\n

The content of the file is as follows:

go test fuzz v1\nint32(-7)\nrune('\\x00')\n

The first line indicates the encoding version, and the subsequent lines represent the argument values that triggered the error during the fuzz testing.

Now we can modify our Division function to check the divisor if it is zero.

var ErrorDivideByZero = fmt.Errorf(\"integer divide by zero\")\n\nfunc Division(dividend, divisor int32) (\n    quotient, remainder int32, err error,\n) {\n    if divisor == 0 {\n        err = ErrorDivideByZero\n        return\n    }\n\n    quotient = dividend / divisor\n    remainder = dividend % divisor\n\n    return\n}\n

Similarly, the FuzzDivision fuzzing function now checks for the presence of the ErrorDivideByZero error.

func FuzzDivision(f *testing.F) {\n    f.Add(int32(67), int32(3))\n\n    f.Fuzz(func(t *testing.T,\n        n1, n2 int32,\n    ) {\n        if q, r, err := Division(n1, n2); err != ErrorDivideByZero {\n            require.Equal(t, n1, n2*q+r)\n        }\n    })\n}\n

Now, we can use the following command to re-test the failing case.

go test -run=FuzzDivision/29bf8459dc5d452f64d41eb8a253f6a672939b146b07fcced0b17e99729e9b91\n
"},{"location":"blog/20230809/main/#conclusion-for-go-fuzzing","title":"Conclusion for Go Fuzzing","text":"

We have used a simple example to describe how to develop a fuzzing function in Go and how to leverage the Go command-line tool to execute fuzz testing.

Next, we will examine a real case, CVE-2022-43677, and demonstrate the process of developing a fuzzing function to identify edge cases.

"},{"location":"blog/20230809/main/#cve-2022-43677","title":"CVE-2022-43677","text":"

Accroding to the descriptoin:

In free5GC 3.2.1, a malformed NGAP message can crash the AMF and NGAP decoders via an index-out-of-range panic in aper.GetBitString.

In response to this vulnerability, we have developed a fuzzing function to test the NGAP decoder. The function utilizes two approaches: modifying the NGAP message's content under a valid template or adjusting its format by changing the Information Elements (IEs) with variable lengths.

// Put the code under the free5gc/test\nfunc FuzzNgapDecode(f *testing.F) {\n    f.Fuzz(func(t *testing.T,\n        modifyWhat uint8,\n        changeIe0, changeIe1, changeIe2, changeIe3, changeIe4 bool,\n        valueIe0A uint32,\n        valueIe2ACellId uint64, valueIe2ATac uint32,\n        valueIe3A uint64,\n        valueIe4A uint64,\n        valueIePlmn uint32,\n    ) {\n        var idx, n int\n        var sendMsg []byte\n        var registrationRequest []byte\n        var bs []byte\n        var err error\n        var ngapPdu ngapType.NGAPPDU\n        var mobileIdentity5GS nasType.MobileIdentity5GS\n        var ue *test.RanUeContext\n\n        // New UE\n        ue = test.NewRanUeContext(\"imsi-2089300007487\", 1, security.AlgCiphering128NEA0, security.AlgIntegrity128NIA2,\n            models.AccessType__3_GPP_ACCESS)\n        ue.AmfUeNgapId = 1\n        ue.AuthenticationSubs = test.GetAuthSubscription(TestGenAuthData.MilenageTestSet19.K,\n            TestGenAuthData.MilenageTestSet19.OPC,\n            TestGenAuthData.MilenageTestSet19.OP)\n\n        mobileIdentity5GS = nasType.MobileIdentity5GS{\n            Len:    12, // suci\n            Buffer: []uint8{0x01, 0x02, 0xf8, 0x39, 0xf0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x47, 0x78},\n        }\n\n        if modifyWhat%2 == DoModifyContent {\n            if changeIe0 {\n                // RAN UE NGAP ID\n                ue.RanUeNgapId = int64(valueIe0A)\n            }\n\n            registrationRequest = nasTestpacket.GetRegistrationRequest(\n                nasMessage.RegistrationType5GSInitialRegistration, mobileIdentity5GS, nil, ue.GetUESecurityCapability(), nil, nil, nil)\n            ngapPdu = ngapTestpacket.BuildInitialUEMessage(ue.RanUeNgapId, registrationRequest, \"\")\n\n            if changeIe2 {\n                // User Location Information\n                for _, ie := range ngapPdu.InitiatingMessage.Value.InitialUEMessage.ProtocolIEs.List {\n                    if ie.Id.Value == ngapType.ProtocolIEIDUserLocationInformation {\n                        bs = make([]byte, 4)\n                        valueIePlmn &= uint32(PlmnMask)\n                        binary.LittleEndian.PutUint32(bs, valueIePlmn)\n\n                        NgRan := ie.Value.UserLocationInformation.UserLocationInformationNR\n                        NgRan.NRCGI.PLMNIdentity.Value = bs[:PlmnByteLen]\n                        NgRan.TAI.PLMNIdentity.Value = bs[:PlmnByteLen]\n\n                        bs = make([]byte, 8)\n                        valueIe2ACellId &= uint64(CellIdMask)\n                        binary.LittleEndian.PutUint64(bs, valueIe2ACellId)\n                        NgRan.NRCGI.NRCellIdentity.Value.Bytes = bs[:CellIdByteLen]\n\n                        bs = make([]byte, 4)\n                        valueIe2ATac &= uint32(TacMask)\n                        binary.LittleEndian.PutUint32(bs, valueIe2ATac)\n                        NgRan.TAI.TAC.Value = bs[:TacByteLen]\n                    }\n                }\n            }\n            if changeIe3 {\n                // RRC Establishment Cause\n                for _, ie := range ngapPdu.InitiatingMessage.Value.InitialUEMessage.ProtocolIEs.List {\n                    if ie.Id.Value == ngapType.ProtocolIEIDRRCEstablishmentCause {\n                        ie.Value.RRCEstablishmentCause.Value = aper.Enumerated(valueIe3A)\n                    }\n                }\n            }\n            if changeIe4 {\n                // UE Context Request\n                for _, ie := range ngapPdu.InitiatingMessage.Value.InitialUEMessage.ProtocolIEs.List {\n                    if ie.Id.Value == ngapType.ProtocolIEIDUEContextRequest {\n                        ie.Value.UEContextRequest.Value = aper.Enumerated(valueIe4A)\n                    }\n                }\n            }\n            sendMsg, err = ngap.Encoder(ngapPdu)\n        } else if modifyWhat%2 == DoModifyFormat {\n            registrationRequest = nasTestpacket.GetRegistrationRequest(\n                nasMessage.RegistrationType5GSInitialRegistration, mobileIdentity5GS, nil, ue.GetUESecurityCapability(), nil, nil, nil)\n\n            if changeIe1 {\n                registrationRequest[3] += 1\n                registrationRequest = append(registrationRequest, registrationRequest[len(registrationRequest)-1])\n            } else {\n                registrationRequest[3] -= 1\n                registrationRequest = registrationRequest[:len(registrationRequest)-1]\n            }\n\n            ngapPdu = ngapTestpacket.BuildInitialUEMessage(ue.RanUeNgapId, registrationRequest, \"\")\n            sendMsg, err = ngap.Encoder(ngapPdu)\n            require.Nil(t, err, \"Error: %v\", err)\n            require.Equal(t, int(sendMsg[3]), len(sendMsg[4:]), \"%v\", sendMsg)\n\n            idx = bytes.Index(sendMsg, []byte(\"\\x00\\x70\\x40\"))\n            assert.NotEqual(t, idx, -1, \"Can not find UE context Request\")\n            if idx != -1 {\n                if valueIe4A%8 == 0 || valueIe4A%8 == 1 {\n                    n = 2\n                } else {\n                    n = int(valueIe4A % 8)\n                }\n                sendMsg[idx+3] = uint8(n)\n                sendMsg = sendMsg[:idx+4]\n                bs = make([]byte, 8)\n                binary.LittleEndian.PutUint64(bs, valueIe4A)\n\n                for i := 0; i < n; i++ {\n                    sendMsg = append(sendMsg, bs[i])\n                }\n\n                sendMsg[3] += uint8(n - 1) // total length\n            }\n        }\n        require.Equal(t, int(sendMsg[3]), len(sendMsg[4:]), \"%v\", sendMsg)\n\n        _, err = ngap.Decoder(sendMsg)\n    })\n}\n

We can use the following command to execute the fuzz testing.

go test -fuzz=^FuzzNgapDecode$ -run=^FuzzNgapDecode$\n

The test resulted in a crash, which confirms the presence of the vulnerability as described in CVE-2022-43677.

The bug was found in the package aper at version v1.0.4. Fortunately, the latest version of the package has already fixed this issue. To verify the fix, we can update the aper package to the latest commit using the following commands:

# Update package aper to the latest commit\ngo get github.com/free5gc/aper@main\n

After updating the aper package, we can test it again with the fuzzing function:

go test -fuzz=^FuzzNgapDecode$ -run=^FuzzNgapDecode$\n

# Alternatively, re-testing the failing case\ngo test -run=FuzzNgapDecode/87af855bbc381c8d510af5ce897fcdd7f9154574e61c0413223f7e31769c2767\n

"},{"location":"blog/20230809/main/#conclusion","title":"Conclusion","text":"

Fuzz testing is a powerful technique for improving the security and reliability of software systems. By subjecting programs to a wide range of inputs, fuzzing can uncover vulnerabilities and defects that might not be found through traditional testing methods. It automates the testing process, making it efficient and scalable for large codebases.

In the context of Go programming, Go fuzzing is well-supported and integrates seamlessly with the standard testing framework. Developers can create fuzzing functions to target specific functions and uncover potential issues using random or mutated inputs.

To demonstrate the effectiveness of fuzz testing, we presented a real case, CVE-2022-43677, which affected free5GC version 3.2.1. By developing a fuzzing function for the NGAP decoder, we were able to identify a vulnerability that caused a crash.

In conclusion, fuzz testing is a critical practice in software development, enabling developers to proactively discover and resolve bugs and vulnerabilities. It empowers them to deliver more secure and robust software systems, providing users with a higher level of confidence in the applications they use. By incorporating fuzz testing as part of the software development lifecycle, developers can significantly enhance the quality and security of their software products.

"},{"location":"blog/20230809/main/#reference","title":"Reference","text":"
  • Go Fuzzing
  • CVE-2022-43677
"},{"location":"blog/20230809/main/#about","title":"About","text":"

I'm Yu-Sheng Liu, a master's student at National Yang Ming Chiao Tung University. My research topic focuses on improving the performance of the 5G core network, such as reducing the latency of message propagation in SBI. If you have any questions, please don't hesitate to contact me!

  • LinkedIn

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20230816/main/","title":"Introduce Kubernetes and Deploy free5GC on Kubernetes with helm","text":"

Note

Author: Elisa Lee Date: 2023/8/16

"},{"location":"blog/20230816/main/#abstract","title":"Abstract","text":"

In the initial section of the article, I will provide an introduction to Kubernetes. Moving on to the subsequent part, I will delve into the utilization of Kubernetes for facilitating the deployment of free5GC. Lastly, in the final segment of the article, I will elaborate on the effective utilization of Kubernetes for monitoring services.

"},{"location":"blog/20230816/main/#introduce-kubernetes","title":"Introduce Kubernetes","text":"

Do you know why Kubernetes is called k8s?

It's due to a shorthand notation that uses the first letter \"k,\" followed by the number \"8,\" and ending with the last letter \"s\" to represent the full name. Kubernetes,stands as an open-source container orchestration platform that bestows organizations with the capacity to adeptly govern, deploy, and expand containerized applications. Initially conceived by Google and presently overseen by the Cloud Native Computing Foundation (CNCF), Kubernetes has ascended to become a pivotal technological underpinning within the realm of contemporary cloud-native computing.

For those who find themselves unacquainted with the intricacies of Kubernetes, let us embark on an exploration of its architectural framework.

I understand that the image presented above might appear intricate at first glance. However, there's no need for concern. Allow me to guide you through each component step by step.

"},{"location":"blog/20230816/main/#pods","title":"Pods","text":"

A \"Pod\" stands as the most diminutive executable entity within the Kubernetes ecosystem. It possesses the capability to encompass either an individual container or a collective assembly of containers. The subsequent enumeration outlines several salient distinctions that set it apart from the act of directly launching a standalone container.

Notably, a Pod possesses its dedicated network interface, affording all enclosed containers the ability to intercommunicate seamlessly by interfacing with the \"localhost.\" Furthermore, connectivity to other Pods is conveniently established through direct usage of their respective IP addresses within the Kubernetes environment.

The Pod's inherent duplicability and capacity for effortless restarts, even from the point of its most recent execution, distinguish it. Additionally, the versatility of including a functioning container within its initial state further characterizes its nature.

Collectively, these attributes contribute to the refinement and streamlined nature of networking within the Kubernetes ecosystem. Such qualities not only facilitate smoother scalability but also offer enhanced capabilities for restarting services with utmost ease.

"},{"location":"blog/20230816/main/#nodes","title":"Nodes","text":"

Consider a \"node\" as a tangible computing entity akin to a physical machine. Analogous to our personal machines, these nodes possess the capacity to concurrently execute multiple tasks, akin to the pods referenced earlier. The orchestration of nodes is overseen by a pivotal component known as the Kubernetes control plane, which, in an automated fashion, allocates pods across the available nodes. Within each node, a minimum of two services operate in tandem.

  1. Kubelet: This crucial service undertakes the responsibility of facilitating seamless communication between the Kubernetes control plane and the individual node. It serves as the intermediary that relays instructions and status updates, ensuring synchronization and cooperation.

  2. Container Runtime: Operating in tandem with Kubelet, the container runtime undertakes pivotal functions. These encompass retrieving container images from registries, the unpacking of containers, and the actual execution of applications. A prime example of such a container runtime is Docker, renowned for its role in enabling containerization.

In essence, this intricate interplay of nodes, services, and orchestration elements underscores the dynamism and efficiency inherent to the Kubernetes ecosystem. Through these interlocking mechanisms, the platform optimizes resource utilization, ensures effective communication, and enables the seamless execution of applications across a distributed infrastructure.

"},{"location":"blog/20230816/main/#master-nodes","title":"Master Nodes","text":"

We've now explored all the scalable components and the task runner. Undoubtedly, to orchestrate and oversee everything, a central command hub is necessary \u2013 this is referred to as the master node. Although direct intervention within these nodes isn't typically required to ensure the seamless operation of the entire system, it's still beneficial for you to grasp a basic understanding of its functionality.

"},{"location":"blog/20230816/main/#api-server","title":"API server","text":"

It determines which interface among all nodes can be externally accessed. Any subsequent commands you execute will be channeled through this service to the designated node or pod. Furthermore, essential cluster information can also be obtained from this service.

"},{"location":"blog/20230816/main/#scheduler","title":"Scheduler","text":"

Similar to an airport's control tower, its function is akin to orchestrating the deployment of pods on specific nodes based on the rules you've established and the data obtained from the API server. The effectiveness of these rules is pivotal, as they often determine the system's overall efficiency.

"},{"location":"blog/20230816/main/#controller-manager","title":"Controller Manager","text":"

When the need arises to enact concrete modifications on a pod, such as terminating or pausing its operation, a fundamental prerequisite is pinpointing the pod's process location and establishing the means to interact with it. This is precisely the role fulfilled by the controller manager. Additionally, this manager oversees vital components, including accounts, services, and more.

"},{"location":"blog/20230816/main/#etcd","title":"Etcd","text":"

For a simplified understanding, we can view this as essentially a comprehensive backup of the entire cluster.

"},{"location":"blog/20230816/main/#service","title":"Service","text":"

In Kubernetes, a \"service\" is an abstraction that enables communication between different sets of pods, usually to provide a stable network endpoint for accessing a specific group of pods. Pods in Kubernetes are ephemeral and can be created, terminated, or scaled dynamically, which makes their IP addresses and lifecycles unpredictable. Services provide a way to decouple the frontend of an application from the backend pods, making it easier for other components or users to access the application without having to know the exact locations or IP addresses of the pods.

A service can be defined in Kubernetes using a YAML or JSON configuration file, and it is associated with a set of pods based on a label selector. The service acts as a load balancer, distributing incoming network traffic among the pods that match the specified selector. This distribution ensures that even if pods are scaled up or down, the service remains available and reachable.

There are different types of services in Kubernetes:

  1. ClusterIP: This is the default service type, and it exposes the service on a cluster-internal IP address. It is accessible only within the cluster.

  2. NodePort: This type exposes the service on each node's IP address at a static port. It allows external access to the service using the node's IP and the specified static port.

  3. LoadBalancer: This type automatically provisions a cloud provider load balancer to expose the service externally. It works in environments that support external load balancers.

  4. ExternalName: This type provides an alias for an external service by returning a CNAME record with the configured DNS name.

Services are a fundamental concept in Kubernetes and play a crucial role in enabling communication and load balancing between pods and external clients. They provide a stable and abstracted network endpoint that allows applications to scale and be more resilient without disrupting access from users or other components.

In the upcoming section, I will delve into the process of deploying free5GC on Kubernetes.

"},{"location":"blog/20230816/main/#deploying-5g-core-network-with-free5gc","title":"Deploying 5G core network with free5GC","text":"

Now I'm going to introduce how to implement free5GC on Kubernetes with helm

"},{"location":"blog/20230816/main/#install-require-packages","title":"Install require packages","text":"
sudo apt update -y\nsudo apt upgrade -y\n
"},{"location":"blog/20230816/main/#install-apt-transport-https","title":"Install apt-transport-https","text":"

\"apt-transport-https\" is a crucial package that equips your system with the essential tools and libraries required to seamlessly integrate the HTTPS protocol. This integration ensures secure and encrypted communication when connecting to package repositories while utilizing the Advanced Package Tool (APT) for effective package management.

sudo apt install -y curl wget apt-transport-https\n

"},{"location":"blog/20230816/main/#install-gtp5g","title":"Install gtp5g","text":"

\"gtp5g\" refers to a customized Linux kernel module specifically designed to handle packets by PFCP (Packet Forwarding Control Protocol) Information Elements (IEs) such as PDR (Packet Detection Rule) and FAR (Forwarding Action Rule). For comprehensive insights, you can delve into the 3GPP specifications TS 29.281 and TS 29.244. To employ the UPF (User Plane Function) component effectively, it's imperative to operate on either the 5.0.0-23-generic or 5.4.x version of the Linux kernel. This ensures optimal compatibility and seamless integration with the necessary functionalities.

sudo apt install gcc\nsudo apt install make\ngit clone -b v0.8.1 https://github.com/free5gc/gtp5g.git\ncd gtp5g\nmake\nsudo make install\n

"},{"location":"blog/20230816/main/#install-docker","title":"Install docker","text":"

\"docker\" is a platform that enables developers to build, package, and distribute applications as containers. Containers are lightweight, portable, and self-sufficient units that encapsulate everything an application needs to run, including the code, runtime, system tools, system libraries, and settings. Docker provides a consistent environment across different development and deployment stages, from local development to testing and production.

for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do sudo apt-get remove $pkg; done\nsudo apt-get update\nsudo apt-get install ca-certificates curl gnupg\nsudo install -m 0755 -d /etc/apt/keyrings\ncurl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg\nsudo chmod a+r /etc/apt/keyrings/docker.gpg\necho \\\n  \"deb [arch=\"$(dpkg --print-architecture)\" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \\\n  \"$(. /etc/os-release && echo \"$VERSION_CODENAME\")\" stable\" | \\\n  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null\n  sudo apt-get update\n  sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin\n

"},{"location":"blog/20230816/main/#install-minikube","title":"Install minikube","text":"

\"minikube\" is an open-source tool that enables developers to set up and run a single-node Kubernetes cluster locally on their own computer. It's particularly useful for learning, development, and testing purposes. Minikube provides an easy way to experience Kubernetes without needing access to a full-scale cluster, making it a great tool for getting familiar with Kubernetes concepts and features.

wget https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64\nsudo cp minikube-linux-amd64 /usr/local/bin/minikube\nsudo chmod +x /usr/local/bin/minikube\n

"},{"location":"blog/20230816/main/#install-kubectl","title":"Install kubectl","text":"

\"kubectl\" is the command-line tool used to interact with and manage Kubernetes clusters. It is an essential component for working with Kubernetes, allowing users to perform various tasks and operations on Kubernetes clusters directly from the terminal.

curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl\nchmod +x kubectl\nsudo mv kubectl /usr/local/bin/\n

"},{"location":"blog/20230816/main/#install-helm","title":"Install helm","text":"

\"helm\" is a package manager for Kubernetes that simplifies the deployment and management of applications and services on a Kubernetes cluster. It allows you to define, install, and upgrade complex applications using pre-configured templates called \"charts.\" These charts encapsulate all the necessary resources, configurations, and dependencies required to run an application.

curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3\nchmod 700 get_helm.sh\n./get_helm.sh\nhelm list -A\n

"},{"location":"blog/20230816/main/#install-multus-cni","title":"Install multus-cni","text":"

\"multus-cni\" is a project that provides a Kubernetes network plugin, specifically a \"Container Network Interface\" (CNI) plugin, which enables the attachment of multiple network interfaces to pods in a Kubernetes cluster.

git clone https://github.com/k8snetworkplumbingwg/multus-cni.git \n

"},{"location":"blog/20230816/main/#deploy-free5gc","title":"Deploy free5GC","text":""},{"location":"blog/20230816/main/#useful-kubectl-command","title":"Useful kubectl command","text":"

Now, I will proceed to introduce a selection of kubectl command that can be employed during the deployment of free5GC.

\"kubectl get pods\" retrieves a list of running pods in the current namespace along with their names, statuses, and other relevant information.

kubectl get pods \n
\"kubectl describe pod\" is used to get detailed information about a specific pod, including its status, events, labels, and more.

kubectl describe pod [pod-name]\n
\"kubectl logs\" fetches the logs of a specific pod, helping you troubleshoot issues and monitor application output.

kubectl logs [pod-name]\n
\"kubectl exec -it\" allows you to execute a command inside a running pod. The -it flag enables interactive terminal access.
kubectl exec -it [pod-name] -- [command]\n
\"kubectl apply -f \" deploys resources defined in a YAML file, such as pods, services, or deployments, to your cluster.
kubectl apply -f [yaml-file]\n
\"kubectl delete\" deletes a specific resource by specifying its type and name, freeing up resources and cleaning the cluster.
kubectl delete [resource-type] [resource-name]\n
\"kubectl expose deployment\" creates a new service, typically of type LoadBalancer, to expose a deployment's pods to external network traffic.
kubectl expose deployment [deployment-name] --type=LoadBalancer --port=[port]\n

\"kubectl get services\" lists all services running in the current namespace along with their details, including ClusterIP, external IP (if applicable), and ports.

kubectl get services\n
\"kubectl get nodes\" retrieves information about the worker nodes in the cluster, displaying their statuses, roles, and other essential data.
kubectl get nodes\n
\"kubectl describe node\" provides detailed information about a specific node, including its capacity, allocated resources, and conditions.
kubectl describe node [node-name]\n
\"kubectl get namespaces\" displays all available namespaces in the cluster, which are used to isolate resources and manage multi-tenancy.
kubectl get namespaces\n
\"kubectl create namespace\" creates a new namespace, allowing you to logically separate and organize resources.
kubectl create namespace [namespace-name]\n
\"kubectl port-forward\" enables you to create a network tunnel between your local machine and a specific pod running within a Kubernetes cluster. This allows you to access services or applications running inside the pod as if they were running on your local machine. The command forwards traffic from a specified local port to a port on the selected pod.
kubectl port-forward [pod-name] [local-port]:[remote-port]\n

"},{"location":"blog/20230816/main/#start-minikube","title":"Start minikube","text":"

Use flannel as cni plugin to start minikue. Flannel is a popular \"Container Network Interface\" (CNI) plugin used for networking in Kubernetes and other container orchestration platforms. It provides a simple and lightweight network fabric designed to facilitate communication between containers and pods in a distributed environment, such as a Kubernetes cluster.

sudo usermod -aG docker $USER && newgrp docker\nminikube start --driver=docker --cpus=4 --memory=8g --disk-size=20g --cni=flannel\n## verify minikube installation\nminikube status \n

"},{"location":"blog/20230816/main/#enable-multus-cni-plugin","title":"Enable Multus-CNI Plugin","text":"
cd multus-cni\ncat ./deployments/multus-daemonset.yml | kubectl apply -f -\n
"},{"location":"blog/20230816/main/#install-free5gc-and-ueransim","title":"Install free5GC and UERANSIM","text":"

If you have only one interface on each Kubernetes node and its name is toto. Then you have to set these parameters to toto: global.n2network.masterIf global.n3network.masterIf global.n4network.masterIf global.n6network.masterIf global.n9network.masterIf

kubectl create ns free5gc\ngit clone https://github.com/Orange-OpenSource/towards5gs-helm.git\ncd towards5gs-helm/charts/\nhelm -n free5gc install free5gc-v1 ./free5gc/\nhelm -n free5gc install ueransim-v1 ./ueransim/\nwatch kubectl get pods -n free5gc\n

"},{"location":"blog/20230816/main/#start-webconsole","title":"Start WebConsole","text":"

free5GC offers a user-friendly web tool called WebConsole, designed to facilitate the creation and management of User Equipment (UE) registrations. This tool serves as a valuable resource for multiple 5G network functions (NFs), streamlining the process of handling UE registrations and associated tasks.

kubectl port-forward --namespace free5gc svc/webui-service 5000:5000\n
Execute the following command in your local machine's terminal, and subsequently, you will be able to access the WebConsole via localhost:5000. You can login with username admin and password free5gc.
ssh -L localhost:5000:localhost:5000 ubuntu@[VM ip]\n

"},{"location":"blog/20230816/main/#service-monitoring","title":"Service Monitoring","text":""},{"location":"blog/20230816/main/#install-prometheusgrafana-services","title":"install Prometheus/Grafana services","text":"

For monitoring Kubernetes, I utilized Prometheus and Grafana. The installation of Prometheus and Grafana services is facilitated through the Helm chart provided by the prometheus-community.

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts\nhelm repo update\nkubectl create namespace prometheus\nhelm install prometheus prometheus-community/kube-prometheus-stack -n prometheus\nwatch kubectl get pods -n prometheus\n

kubectl port-forward -n prometheus svc/prometheus-grafana 8080:80\n
Execute the following command in your local machine's terminal, and subsequently, you will be able to access the WebConsole via localhost:8080.
ssh -L localhost:8080:localhost:8080 ubuntu@[VM ip]\n
A variety of dashboards are available, offering different perspectives for those who are interested. Below is a snapshot of one such dashboard option.

"},{"location":"blog/20230816/main/#reference","title":"Reference","text":"

https://free5gc.org/

https://medium.com/rahasak/deploying-5g-core-network-with-free5gc-kubernets-and-helm-charts-29741cea3922

https://github.com/Orange-OpenSource/towards5gs-helm

https://github.com/k8snetworkplumbingwg/multus-cni

"},{"location":"blog/20230816/main/#about","title":"About","text":"

Hello, I am Elisa Lee. My ongoing research revolves around VoNR (Voice over New Radio). I encourage any inquiries or identification of errors within the article, as they are welcomed for correction. Your feedback is invaluable, so please don't hesitate to reach out via email to share your insights.

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20230823/20230823/","title":"Web security: CSRF vulnerability in webconsole","text":"

Note

Author: Brian Chen (\u9673\u715c\u76db) Date: 2023/8/23

"},{"location":"blog/20230823/20230823/#abstract","title":"Abstract","text":"

This article is intended for individuals who possess an interest in free5gc/webconsole and hold concerns regarding security matters. It aims to provide a concise introduction to the webconsole, followed by an exposition of a significant security concern along with our corresponding solution. Within webconsole v1.2.0, aligning with the most recent iteration of free5gc v3.3.0, certain vulnerabilities have been identified that could potentially lead to the exposure of subscriber data. It is my responsibility to address and rectify these vulnerabilities, enhancing the webconsole's resilience against cyber attacks.

"},{"location":"blog/20230823/20230823/#webconsole-overview","title":"Webconsole Overview","text":"

The Webconsole serves as a web-based tool designed to manage User Equipment (UE) subscription data. It plays a crucial role in aiding the free5GC Core Network manager by facilitating the configuration of UEs and providing the ability to monitor the status of activated UEs.

"},{"location":"blog/20230823/20230823/#environment","title":"Environment","text":"
  • Frontend
    • React v17.0.2
    • node.js v20.2.0
    • yarn v1.22.19
  • Backend
    • Golang v1.17
    • Gin v1.9.0
    • MongoDB v3.6.8
"},{"location":"blog/20230823/20230823/#install-run-webconsole","title":"Install & Run webconsole","text":"

Prior to building webconsole, install nodejs and yarn package first:

sudo apt remove cmdtest\nsudo apt remove yarn\ncurl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -\necho \"deb https://dl.yarnpkg.com/debian/ stable main\" | sudo tee /etc/apt/sources.list.d/yarn.list\nsudo apt-get update\nsudo apt-get install -y nodejs yarn\n

To run free5GC webconsole server. The following steps are to be considered.

git clone https://github.com/free5gc/webconsole.git\ncd frontend\nyarn install\nyarn build\nrm -rf ../public\ncp -R build ../public\ncd ..\ngo run server.go\n

Default account and password is admin/free5gc

"},{"location":"blog/20230823/20230823/#pages","title":"Pages","text":""},{"location":"blog/20230823/20230823/#subscribers","title":"SUBSCRIBERS","text":"

Creation/deletion/editing the subscriber's data:

A Subscriber data contains these informations:

  • PLMN ID
  • SUPI (UE ID)
  • AKA parameters
  • S-NSSAI Configurations
    • Sst/Sd
    • DNN
      • Name
      • AMBR
      • Flow Rules
        • IP Filter
        • Precedence
        • 5QI
        • GBR
        • MBR

"},{"location":"blog/20230823/20230823/#tenant-and-user","title":"TENANT AND USER","text":"

The Webconsole also allows for the creation, deletion, and editing of tenants. A tenant functions as an access control group, delineating specific permissions and boundaries. In this setup, if you do not possess admin privileges, you are unable to access subscriber data generated by other tenants, ensuring data privacy and security.

Furthermore, the capability to incorporate users within a tenant is available. To illustrate, by selecting the brian1 tenant and clicking on the New User option, it becomes possible to introduce a new user. As an example, a user with the email address aaabbb@gmail.com can be added through this process.

The data within MongoDB can be accessed and reviewed using the MongoDBCompass tool.

"},{"location":"blog/20230823/20230823/#csrf-cross-site-request-forgery-vulnerability","title":"CSRF (Cross-Site Request Forgery) Vulnerability","text":"

The vulnerability was discovered by INCIBE, and they promptly notified the free5GC team via email.

The corresponding issue related to this vulnerability is also documented in the free5gc repository. Despite the typical deployment of the webconsole within LAN or Docker environments, it's essential to exercise caution regarding users who operate this service on a public IP or within an insecure network environment.

In a nutshell, an attacker can gain unauthorized access to the database by merely setting the token to the term 'admin'.

$ curl '<webconsole's IP>:5000/api/subscriber' -H 'User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0' -H 'Accept: application/json' -H 'Accept-Language: en-US,en;q=0.5' -H 'Accept-Encoding: gzip, deflate' -H 'Referer: http://<webconsole's IP>:5000/' -H 'Connection: keep-alive' -H 'X-Requested-With: XMLHttpRequest' -H 'Token: admin' -H 'Pragma: no-cache' -H 'Cache-Control: no-cache'\n

Subsequently, they can directly retrieve subscriber information from the server's MongoDB.

[{\"plmnID\":\"20893\",\"ueId\":\"imsi-208930000000003\"}]\n

Undoubtedly, this vulnerability is of significant concern since the intended safeguard, allowing access solely to admin, has been compromised, thereby enabling easy access for anyone.

"},{"location":"blog/20230823/20230823/#trace-code","title":"Trace Code","text":""},{"location":"blog/20230823/20230823/#frontend","title":"Frontend","text":"

In webconsole/frontend/src/util/AuthHelper.js

  • In scenarios where the default username and password (admin/free5gc) are employed, the ApiHelper.login() function remains untouched. This practice might expedite agile development, but it comes at the cost of compromising security.

"},{"location":"blog/20230823/20230823/#backend","title":"Backend","text":"

In webconsole/frontend/WebUI/api_webui.go - In situations where a webconsole client configures the tokenStr as 'admin', the backend process will omit the execution of ParseJWT().

"},{"location":"blog/20230823/20230823/#json-web-token-jwt","title":"JSON Web Token (JWT)","text":"

The Webconsole relies on JSON Web Token (JWT) as its authentication mechanism, a specification outlined in RFC 7519.

JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.

When a client employs a web browser to initiate a login via the HTTP(s) protocol, the Web server is expected to furnish the client with a JWT token in response. Subsequently, the client employs this JWT token to interact with resources by sending requests through the RESTful API (such as GET, POST, PUT, etc.).

A JSON Web Token (JWT) consists of three distinct parts. In the context of the webconsole backend, the following fields are relevant:

  • Header: This section contains information about the type of token and the signing algorithm used. It often includes the \"alg\" (algorithm) and \"typ\" (type) fields.
  • Payload: The payload holds the actual claims or data that are being conveyed by the token. For the webconsole backend, specific fields within this section could include:
    • Claim (JSON object)
      • sub: identifies the principal that is the subject of the JWT.
      • iat: identifies the time at which the JWT was issued.
      • exp: identifies the expiration time onor after which the JWT MUST NOT be accepted for processing.
      • email
      • tenantId
      • ...(you can design the attribute yourself)
  • Signature: This component is created by combining the encoded header and payload with a secret key (or a public/private key pair) to ensure the token's integrity and authenticity. The signature allows the recipient to verify that the token hasn't been tampered with.

The JWT Claims Set represents a JSON object whose members are the claims conveyed by the JWT. The Claim Names within a JWT Claims Set MUST be unique; JWT parsers MUST either reject JWTs with duplicate Claim Names or use a JSON parser that returns only the lexically last duplicate member name

The image depicted below illustrates the process of using jwt.io to both encode and decode JWT tokens. These tokens are segmented into distinct sections denoted by the red, purple and blue divisions, separated by periods dots.

Given that the Payload can be decoded using the algorithm specified in the Header, it's essential to refrain from including sensitive details like passwords or credit card numbers within it. Instead, the Payload typically holds claims and application-specific metadata.

To maintain the security of the process, the server retains a confidential key used to validate the signature. In situations where a client endeavors to access a resource using a JWT token that possesses an incorrect Verify Signature, an error response will be generated. This stringent signature verification mechanism ensures that the authenticity and integrity of both the token and its enclosed data are upheld.

"},{"location":"blog/20230823/20230823/#trace-code-cont","title":"Trace Code (Cont.)","text":"

In webconsole/frontend/WebUI/api_webui.go, we can find the implementation of JWT.

  • JWT() is for encoding:
    func JWT(email, userId, tenantId string) string {\n    token := jwt.New(jwt.SigningMethodHS256)\n\n    claims := token.Claims.(jwt.MapClaims)\n    claims[\"sub\"] = userId\n    claims[\"iat\"] = time.Now()\n    claims[\"exp\"] = time.Now().Add(time.Hour * 24).Unix()\n    claims[\"email\"] = email\n    claims[\"tenantId\"] = tenantId\n\n    if jwtKey == \"\" {\n        return \"\"\n    }\n\n    tokenString, err := token.SignedString([]byte(jwtKey))\n    if err != nil {\n        logger.ProcLog.Errorf(\"JWT err: %+v\", err)\n        return \"\"\n    }\n\n    return tokenString\n}\n
  • ParseJWT() is for decoding:
    func ParseJWT(tokenStr string) (jwt.MapClaims, error) {\n    token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {\n        return []byte(jwtKey), nil\n    })\n    if err != nil {\n        return nil, errors.Wrap(err, \"ParseJWT error\")\n    }\n\n    claims, _ := token.Claims.(jwt.MapClaims)\n\n    return claims, nil\n}\n
  • The function CheckAuth() serves the purpose of determining whether a user possesses the authorization to access a particular resource.
func CheckAuth(c *gin.Context) bool {\n    tokenStr := c.GetHeader(\"Token\")\n    if tokenStr == \"admin\" {\n        return true\n    } else {\n        return false\n    }\n}\n

Warning

  • The secret key utilized for signature verification is obtained through os.Getenv(\"SIGNINGKEY\"). However, there's a possibility that SIGNINGKEY might not be exported as an environment variable, leading to a potential return of an empty value. Under such circumstances, an implication arises: an admin in Webconsole A could potentially gain access to subscriber data within Webconsole B.

  • Within the CheckAuth() function, if the client sets the JWT token to 'admin', the function will evaluate to true, effectively allowing the check to be passed.

"},{"location":"blog/20230823/20230823/#implementation","title":"Implementation","text":""},{"location":"blog/20230823/20230823/#check-authentication","title":"Check Authentication","text":"

Initially, I have revised the design of the CheckAuth() function to ensure the mandatory execution of ParseJWT().

func CheckAuth(c *gin.Context) bool {\n    tokenStr := c.GetHeader(\"Token\")\n    claims, err := ParseJWT(tokenStr)\n\n    if err == nil && claims[\"email\"] == \"admin\" {\n        return true\n    } else {\n        return false\n    }\n}\n
Furthermore, I've implemented a second change where, considering that the webconsole v1.2.0 doesn't inherently establish a tenant named 'admin' or a user named 'admin', I propose a more effective approach. During the initialization of the webconsole backend, it is recommended to generate an 'admin' tenant and user. This means that executing go run server.go within the webconsole/ directory should consistently generate an admin user, thereby fulfilling the initial login requirement.

Certainly, in the backend/WebUI/api_webui.go file, I propose the addition of a function named SetAdmin(). To streamline the process and maintain consistency with the rest of the free5GC project, it is recommended to leverage the mongoapi module established within the free5gc/util repository. Given the project's heavy reliance on MongoDB, employing mongoapi over frequent calls to mongo-driver is essential to ensure efficiency and coherence.

func SetAdmin() {\n    err := mongoapi.RestfulAPIDeleteOne(\"tenantData\", bson.M{\"tenantName\": \"admin\"})\n    if err != nil {\n        logger.InitLog.Errorf(\"RestfulAPIDeleteOne err: %+v\", err)\n    }\n    err = mongoapi.RestfulAPIDeleteOne(\"userData\", bson.M{\"email\": \"admin\"})\n    if err != nil {\n        logger.InitLog.Errorf(\"RestfulAPIDeleteOne err: %+v\", err)\n    }\n\n    // Create Admin tenant\n    logger.InitLog.Infoln(\"Create tenant: admin\")\n\n    adminTenantData := bson.M{\n        \"tenantId\":   uuid.Must(uuid.NewRandom()).String(),\n        \"tenantName\": \"admin\",\n    }\n\n    _, err = mongoapi.RestfulAPIPutOne(\"tenantData\", bson.M{\"tenantName\": \"admin\"}, adminTenantData)\n    if err != nil {\n        logger.InitLog.Errorf(\"RestfulAPIPutOne err: %+v\", err)\n    }\n\n    AmdinTenant, err := mongoapi.RestfulAPIGetOne(\"tenantData\", bson.M{\"tenantName\": \"admin\"})\n    if err != nil {\n        logger.InitLog.Errorf(\"RestfulAPIGetOne err: %+v\", err)\n    }\n\n    // Create Admin user\n    logger.InitLog.Infoln(\"Create user: admin\")\n\n    hash, err := bcrypt.GenerateFromPassword([]byte(\"free5gc\"), 12)\n    if err != nil {\n        logger.InitLog.Errorf(\"GenerateFromPassword err: %+v\", err)\n    }\n\n    adminUserData := bson.M{\n        \"userId\":            uuid.Must(uuid.NewRandom()).String(),\n        \"tenantId\":          AmdinTenant[\"tenantId\"],\n        \"email\":             \"admin\",\n        \"encryptedPassword\": string(hash),\n    }\n\n    _, err = mongoapi.RestfulAPIPutOne(\"userData\", bson.M{\"email\": \"admin\"}, adminUserData)\n    if err != nil {\n        logger.InitLog.Errorf(\"RestfulAPIPutOne err: %+v\", err)\n    }\n}\n
"},{"location":"blog/20230823/20230823/#jwt-verify-signature","title":"JWT Verify Signature","text":"

Certainly, within the backend/WebUI/api_webui.go file, I recommend introducing a string variable named jwtKey to serve as the private key for JWT Verify Signature. Although the length of jwtKey is specified as 256 bytes, it's worth noting that the distinction between 256 bytes and 256 bits is inconsequential in this context. The jwt module will adeptly transform the key to a 256-bit form. For further insights, you can refer to issue 28.

var jwtKey = \"\" // for generating JWT\n\n/* ... */\n\nfunc InitJwtKey() error {\n    randomBytes := make([]byte, 256)\n    _, err := rand.Read(randomBytes)\n    if err != nil {\n        return errors.Wrap(err, \"Init JWT key error\")\n    } else {\n        jwtKey = string(randomBytes)\n    }\n    return nil\n}\n
"},{"location":"blog/20230823/20230823/#backend-initialization","title":"Backend Initialization","text":"

In backend/webui_service/webui_init.go:

func (a *WebuiApp) Start(tlsKeyLogPath string) {\n    /* ... */\n    WebUI.SetAdmin()\n    if err := WebUI.InitJwtKey(); err != nil {\n        logger.InitLog.Errorln(err)\n        return\n    }\n    /* ... */\n}\n
"},{"location":"blog/20230823/20230823/#frontend-login","title":"Frontend Login","text":"

Certainly, in the frontend/src/util/AuthHelper.js file, it is advised to remove the section of code that could be considered a \"cheating snippet.\" To ensure a robust authentication process, all users should be required to successfully pass through the ApiHelper.login function and receive a response code of 200. This approach ensures a consistent and legitimate authentication mechanism.

static async login(username, password) {\n    let response = await ApiHelper.login({username: username, password: password});\n\n    if (response !== undefined && response.status === 200) {\n      var user = null\n      if (username == \"admin\") {\n        user = new User(username, \"System Administrator\", response.data.access_token);\n      } else {\n        user = new User(username, \"User\", response.data.access_token);\n      }\n      LocalStorageHelper.setUserInfo(user);\n      store.dispatch(authActions.setUser(user));\n      return true;\n    } else {\n      return false;\n    }\n  }\n
"},{"location":"blog/20230823/20230823/#conclusion","title":"Conclusion","text":"

In this endeavor, we've successfully addressed the CSRF vulnerability issue, as highlighted in issue #387 and acknowledged by INCIBE. Furthermore, I've introduced the concept of JWT tokens in this article, detailing their implementation and the corresponding adjustments made within the webconsole. You can locate the detailed implementation in the merged PR #44 of the webconsole repository. I'd like to extend my gratitude to the contributors kishiguro and LaumiH for their significant role in refactoring the webconsole. As a result of their efforts, the webconsole UI has been notably enhanced. Our upcoming focus involves the integration of the charging function, which we are actively pursuing.

"},{"location":"blog/20230823/20230823/#reference","title":"Reference","text":"

free5gc/webconsole merged PR #44 free5gc issue #387 free5gc issue #28 JSON Token RFC 7519 MongoDBCompass

"},{"location":"blog/20230823/20230823/#about","title":"About","text":"

Hello everyone,

I'm Brian Chen (\u9673\u715c\u76db), and I've been immersed in the realm of 5G Core Network technologies. Over the course of seven months, I've had the privilege of serving as an intern at Saviah. In this role, my responsibilities encompass a spectrum of tasks including maintenance, development, and rigorous testing of the free5GC project.

Should any inquiries, questions, or bug reports regarding free5GC arise, I encourage you to reach out by creating an issue in the free5gc repository or by participating in discussions on the forum. I'm here to assist and collaborate with the community as we navigate the intricacies of this project.

Warm regards, Brian Chen (\u9673\u715c\u76db)

  • Github
  • LinkedIn

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20230830/20230830/","title":"Article Sharing: eBPF: A New Approach to Cloud-Native Observability, Networking and Security for Current (5G) and Future Mobile Networks (6G and Beyond)","text":"

Note

Author: Lin Poyi Date: 2023/8/30

Original Paper: eBPF: A New Approach to Cloud-Native Observability, Networking and Security for Current (5G) and Future Mobile Networks (6G and Beyond)

"},{"location":"blog/20230830/20230830/#overview","title":"Overview","text":"

This article shows why extended Berkeley Packet Filter (eBPF) is a powerful tool to use on cloud-native platforms. Using specific eBPF programs to improve network observability and runtime security. Also, present a platform named Sauron to demonstrate how eBPF allows us to write custom code and dynamically load eBPF programs into the kernel. These programs can be used to derive performance counters and gauges for transport networks, 5G applications, and non-access stratum protocols.

"},{"location":"blog/20230830/20230830/#layout-of-ebpf","title":"Layout of eBPF","text":"
  • eBPF programs: run in the kernel to react to events.
  • User space programs: load eBPF programs to the kernel and interact with them.
  • eBPF Maps: allow data storage and information sharing between eBPF/User space programs.
    • There are different types of eBPF Maps, for example: Hash, Array, etc. A special type of eBPF Maps is called per-CPU map, since it is only accessed by one CPU and the eBPF process cannot be preempted, there's no lock needed. As a result, the performance is better.

Development phase:

  1. A program is compiled into an object Executable and Linkable Format (ELF) file. Using compiler suites like clang/LLVM to compile it into eBPF bytecode.
  2. The ELF file is loaded into the kernel using a monolithic approach enabled by bpf system calls.
  3. Maps are created in the kernel space. The eBPF programs can utilize eBPF maps to share data and preserve the state.

Runtime phase:

  1. The eBPF program is loaded into the kernel to be verified by eBPF verifier and to be compiled by JIT Compiler into native instructions.
  2. The eBPF program is attached to selected events (injected in the desired kernel hooks). The eBPF program is executed once the event has occurred.

Higher-level networking constructs can be created by combining the hooks below:

  • Express Data Path (XDP): XDP BPF hook can be attached to the networking driver. The eBPF program is triggered when a packet arrives in the driver.
  • Traffic Control ingress/egress: hooking eBPF programs to the traffic control ingress/egress. The eBPF program is triggered after the initial packet processing.
  • Socket operations
"},{"location":"blog/20230830/20230830/#ebpfs-for-kubernetes","title":"eBPF's for Kubernetes","text":""},{"location":"blog/20230830/20230830/#container-networking","title":"Container Networking","text":"

In K8s, the kernel typically runs a networking stack for each pod. The data path is convoluted, as shown in a). eBPF can simplify the networking stack in the kernel, and connect pods as endpoints.

This is an example of load balancing between four replicated pods on two worker nodes. Using eBPF service (svc) Map to replace the iptables rules. This allows operators to transport data directly from inbound sockets to outbound sockets, which enables super-fast service load balancing with eBPF.

"},{"location":"blog/20230830/20230830/#service-mesh","title":"Service Mesh","text":"

A Service Mesh is a dedicated infrastructure layer that can be added to applications or Cloud-Native Network Function (CNF) micro-services. Provides connectivity between applications at the service level, and offers features such as observability, security, and traffic management.

The traditional approach (left) was to use a Service Mesh Library. It requires the Service Mesh Library to be written in the application's language framework.

Then Service Mesh was achieved by implementing a cloud-native Sidecar Model (middle). With the Service Mesh Sidecar run as a proxy outside of the application, there's no limit to the language being used. And there's no modification needed to be done in the application code.

Service Mesh which utilizes eBPF (right) has fewer resources used since duplicate copies of state configuration information within each pod are no longer needed. Also, there's no pod configuration to be modified. In addition, eBPF is aware of all activities carried out on that node, which can provide extra security.

"},{"location":"blog/20230830/20230830/#sauron-concept-and-architecture","title":"Sauron concept and architecture","text":"

The proposed eBPF platform, named Sauron, includes a Node Agent, a Controller to handle the Node Agents (NAs) deployed in the clusters, and three eBPF modules for observability, security, and networking use cases. There's no installation of any specific K8s plug-in needed.

"},{"location":"blog/20230830/20230830/#sauron-ebpf-module-for-transport","title":"Sauron eBPF module for transport","text":""},{"location":"blog/20230830/20230830/#ebpf-solutions-for-latency-calculation","title":"eBPF solutions for latency calculation","text":"

By leveraging the XDP hook, the programs can inspect or modify the content of the packets, but they can also decide whether to redirect or drop them. Moreover, through eBPF helpers, they can perform more advanced actions such as timestamping.

The eBPF program utilizes an XDP feature that allows it to redirect the synthetic packet through the same interface at which it was received. The program also adds a timestamp of each action (send or receive) to the packet payload. The packet will bounce between the receiving device and the original sender for a fixed number of times. After the final iteration, the original sender passes the packet to the Sauron Agent through the socket interface. The Sauron Agent then uses the collected timestamps to calculate both One-way and Two-way latency. Once the calculation is done, the data can be exported to a remote collector for processing and visualization.

"},{"location":"blog/20230830/20230830/#sauron-module-for-network-observability","title":"Sauron module for network observability","text":"

The Sauron Agent configures the necessary eBPF programs and collects the messages using a Ring buffer or throughout eBPF Maps. It also detects containers and pods of the 5GC network by querying the API server. The Agent can be configured to collect information from all parts of the network (radio access and core domains) with minimal overhead.

"},{"location":"blog/20230830/20230830/#sauron-ebpf-module-for-security","title":"Sauron eBPF module for security","text":"

eBPF Traffic Control Programs are used to implement network security. The primary job is to detect policy violations and enforce configured rules on unwanted traffic. Unwanted traffic could be:

  1. Traffic directed to or coming from entities that are not allowed to communicate with the object pod.
  2. Malicious traffic directed to or coming from sources that are allowed to communicate with the object pod but not authorized to perform certain tasks.
  3. Malicious traffic that insists on a disallowed communication path.

eBPF tracepoints programs are attached to static markers defined by kernel developers in the kernel code. Tracepoints can be used to track events related to multiple subsystems, including sched, netlink, and system call (only syscall is shown in the graph). Static markers implementing the hook points guarantee the Application Binary Interface for tracepoints to be more stable than the one available for kprobes.

Even though kprobes are not as effective or stable as tracepoints, they allow the implementation of an additional layer of defense. By tapping deeper into the system calls implementation, it is possible to solve the well-known Time-Of-Check Time-Of-Use vulnerability of tracepoints.

eBPF Linux Security Module (LSM) programs also add an additional layer of security to the kernel. They can detect malicious behaviors and deny the access and permission directly in the kernel. By doing this, they provide faster response than tracepoint and kprobe programs.

The agent in user space provides all rules for detecting malicious behavior to eBPF programs via eBPF maps. The eBPF programs in kernel space sent three kinds of data to the Agent: samples of normal events, suspicious events, and malicious events. Suspicious events will be classified in the agent. Then all collected data will be sent to a cluster-wide events collector, which can build an AI/ML model using the collected events to enforce rules in each agent or provide a plugin-based interface to a third-party system.

"},{"location":"blog/20230830/20230830/#ebpf-for-performance-monitoring-of-5g-protocols","title":"eBPF for performance monitoring of 5G protocols","text":""},{"location":"blog/20230830/20230830/#ngap-and-nas-performance-monitoring","title":"NGAP and NAS performance monitoring","text":"

NAS messages are encapsulated into NGAP messages and NGAP relies on the SCTP of the 5G transport network layer. To extract information from NGAP messages, we need to parse SCTP packets containing multiple data chunks that can be fragmented. Since XDP and TC hooks are at the packet level, they're not suitable for handling NGAP messages. To have direct access to messages, using tracepoint hooks is the way to go.

Examples of NGAP and NAS metrics:

  • NGAP Initial Context Setup Counters
  • NGAP Setup Success Ratio
  • NGAP Setup Time (ms)
  • NGAP Procedure Duration (ms)
  • NGAP Failed Procedures Cause Code
  • PDU Session Establishment Attempts
"},{"location":"blog/20230830/20230830/#packet-capturing-and-forwarding","title":"Packet capturing and forwarding","text":"
  1. A libpcap probe is installed on the interface.
  2. Packets pass through the BPF filter installed on the interface.
  3. Matching packets flow to the agent.
  4. The agent encodes incoming packets in PCAPNG format.
  5. Send them to the processing probe.
"},{"location":"blog/20230830/20230830/#ebpf-for-estimating-energy-consumption","title":"eBPF for estimating energy consumption","text":""},{"location":"blog/20230830/20230830/#conclusion","title":"Conclusion","text":"

Using eBPF is hard. The way of implementation also changes based on what version of kernel is used. There's no easy packaging solution. Developers need to be familiar with not only the eBPF library but also the kernel they're working with.

Although the development of eBPF might be challenging, it can be used to provide great observability, security, and networking in a cloud-native environment. Allow developers to dynamically program the kernel in a safe, performant, and scalable way. By leveraging the benefit of low overhead monitoring and close-to-source data processing, eBPF will be handy for real-time monitoring 5GC, or even boost the performance of K8s deployments.

"},{"location":"blog/20230830/20230830/#about","title":"About","text":"

Poyi Lin

  • Graduate student majoring in 5GC Research
"},{"location":"blog/20230830/20230830/#reference","title":"Reference","text":"
  • eBPF: A New Approach to Cloud-Native Observability, Networking and Security for Current (5G) and Future Mobile Networks (6G and Beyond)

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20230906/0905-2023blog/","title":"The role of VNFD and NSD in 5G Network Slicing","text":"

Note

Author: Leon Sawada(\u6fa4\u7530\u535a\u4e45) Date: 2023/09/05

"},{"location":"blog/20230906/0905-2023blog/#overview","title":"Overview","text":"

Before you read this blog article, I highly recommend you read this blog article first: How to deploy a free5GC network slice on OpenStack. This blog article was written by my friend Daniel Hsieh, and in his article, he went through the entire process of how to deploy a free5GC network slice on OpenStack. He also introduced some architectures we need to know when we are deploying the free5GC network slice. However, there are still some details remaining, so in this blog article, I will explore more information and what role these components play in the overall experiment. Daniel used 8 steps to explain how to [deploy a free5GC Network Slice].In this article, I am going to focus on VNFD and NSD, which appear in steps No.5 and No.6, these two most important concepts while we are creating the network slice. By this article, you will understand more about the 5G network slicing.

"},{"location":"blog/20230906/0905-2023blog/#vnfd","title":"VNFD","text":"

VNFD, You may have seen this word a few times in Daniel\u2019s blog posts already, but what exactly is VNFD? Before I tell you the answer directly, there is something I want you to know first.

We know that network slicing is a technology that allows network operators to divide physical network infrastructure into multiple customized virtual network slices, and each of the slices can meet specific requirements for specific use cases. One of the most important functions of 5G network slicing is the virtual network function (VNF). VNF is a virtualized network service running on an open computing platform. In this free5GC network slicing experiment, the VNF in our experiment is our NF in free5GC, such as UPF, NSSF, UDM... etc. When you load all your VNFs into OpenStack, it should be like this:

So what is VNFD? What is the connection between it and VNF? VNFD's full name is Virtual Network Function Descriptor. A VNFD is a deployment template that describes a VNF in terms of deployment and operational behavior requirements. It also contains connectivity, interface, and virtualized resource requirements. The VNFD conforms to the GS NFV-SOL 001 specifications and standards specified by ETSI. The Virtual Network Function Descriptor (VNFD) file describes the instantiation parameters and operational behaviors of the VNFs. It contains KPIs and other key requirements that can be used in the process of onboarding and managing the lifecycle of a VNF. Each VNFD template has the following fields:

In the deployment of free5GC network slicing experiment, if you onboard all your VNFs successfully, then your VNFD should also be like this:

As you can see, every VNFD is matched with its represented VNF and its IP address, VNFD acts as a blueprint or template for VNF. Also be aware that The VNFD is a static description file, not a dynamic configuration file. The metadata description in the VNFD is not changed during the whole VNF lifecycle. Some VNF parameters described in the VNFD can be declared to be configurable during the VNF design phase, and further be configured by the VNFM during or after VNF instantiation.

"},{"location":"blog/20230906/0905-2023blog/#vnfd-architecture-and-details","title":"VNFD Architecture And Details","text":"

The UML representation of the VNFD high-level structure is shown in the figure below.

The ETSI released a specification that defines the requirements for the structure and format of a VNFD. As you can see, this graph illustrates the high\u2010level structure of a VNFD. The VNFD is composed of one or many virtual deployment units (VDUs) that describe the deployment resources and operation behavior of a VNF component (VNFC). Virtual Deployment Unit(VDU) is a basic part of VNF. It is the VM that hosts the network function. They are virtual machines that host the VNF or parts of it. Each part of the VNF is a VNFC and can be deployed on one or more VDUs. Each VDU is characterized, among other elements, by the software image loaded on it and the resources needed to deploy it. That is the flavor of Nova in OpenStack, which can set Disk, Memory, CPU, etc. An NS(Network Service) might contain different VNF. A VNF might also contain different VDU. Each level puts constraints on the subsequent levels, information in a lower level does not appear in a higher level. This is a complex and flexible architecture, very beautiful, very powerful.

The following graph shows the composition of the virtual deployment unit in a VNFD:

This graph illustrates a VDU deployment view. A VDU describes mainly the virtual compute (VC), virtual storage (VS), and virtual memory (VM) resources that are necessary for deploying a VNFC, and it could be linked via connection points (CPD) to other VDUs or to external VDUs that belong to other VNFs via external CPD. Virtual links in the VNFD indicate how the VDUs are connected and via which CPD. This means that different VDUs can be connected to each other through the CPD. However, in free5GC network slicing, things might be different. For 5G's NS (network slice), since there are already many VNFs in 5G, under the 5G network slice architecture, an NS contains many VNFs, but a VNF always contains one and only VDU. We use AMF as an example, (AMF is an NF in free5GC) AMF is an important and complete function and we didn't break it into more pieces. Therefore, there are not many examples in our 5G of a VNF having many VDUs. Each VNF contains one VDU only, and the via connection points (CPD) function will no longer be used in 5G network slicing, although it is a powerful and convenient function.

"},{"location":"blog/20230906/0905-2023blog/#nsd","title":"NSD","text":"

NSD is a template file, whose parameters follow the ETSI MANO specification, used by the NFV Orchestrator (NFVO) for deploying network services (as a combination of multiple VNFs). Which consists of information used by the NFV Orchestrator (NFVO) for the life cycle management of an NS. Just like VNFD, NSD is also a static configuration file.

An NS is a composition of Network Functions (NF) arranged as a set of functions with unspecified connectivity between them or according to one or more forwarding graphs. As the following figure shows, the description of an NS as used by the NFV Management and Orchestration (MANO) functions to deploy an NS instance includes or references the descriptors of its constituent objects:

As the specification mentioned in ETSI GS NFV-IFA 014 V3.3.1, an NSD references at least one VNFD or one nested NSD, just like this graph has shown. Here is an example code if today we want to instantiate VNF1 and VNF2 in the experiment.

tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0\nimports:\n  - VNFD1\n  - VNFD2\ntopology_template:\n  node_templates:\n    VNF1:\n      type: tosca.nodes.nfv.VNF1\n      requirements:\n        - virtualLink1: VL1\n        - virtualLink2: VL2\n    VNF2:\n      type: tosca.nodes.nfv.VNF2\n    VL1:\n      type: tosca.nodes.nfv.VL\n      properties:\n      network_name: net0\n      vendor: tacker\n    VL2:\n      type: tosca.nodes.nfv.VL\n      properties:\n          network_name: net_mgmt\n          vendor: tacker\n

In the above NSD template, VL1 and VL2 are substituting the virtual links of VNF1. If we want to apply the NFs we have in free5GC, just import these NFs into the VNF to which they belong.

To onboard the above NSD:

tacker nsd-create \u2013nsd-file <nsd file> <nsd name>\n

This is also how we can use NSD to import all the VNFs we want to create network slices. It is very easy and convenient to create free5GC network slice that we want, the step was mentioned in Dainel's blog article.

"},{"location":"blog/20230906/0905-2023blog/#deployment-flavor","title":"Deployment flavor","text":"

Please go back to the VNFD high-level structure, On the right side we can see the DnfDf, which stands for VNF deployment flavor. Deployment flavor is a very important concept in NSD and VNFD, therefore it needs to be explained separately. Since VNFD and NSD are not dynamic configuration files but static configuration files. Even static configuration file has their own advantages, however, they are irreversible and lack of flexibility. To overcome this problem, we have a concept called deployment flavor. While we are using these static configuration files, we can have different deployment flavors, therefore when we are deploying these static configuration files, we can have different ways to deploy them. For example, when we deploy them, we use the 10 MB memory, and we use 100 MB memory to deploy another one. In this example, while we are deploying our deployment parameters are different, then we can define it into deployment flavor. Deployment flavor helps NSD and VNFD to increase their flexibility as a static configuration file. Make sure it's not 100% unchangeable. We can deploy different types of Deployment_Flavor that case needs, like Deployment_Flavor#2, Deployment_Flavor#3, and Deployment_Flavor#4. Therefore, we can deploy different NS and VNF for different specific needs. Add some deployment flexibility as much as possible under the limitation that they are static configuration files.

That's it, I hope this blog article can help you learn more about 5G network slicing, even if you go back and read this previous blog post How \u200b\u200bto deploy free5GC network slicing on OpenStack now, I think you will have a clearer idea of \u200b\u200bhow the whole experiment works. Learning the theory behind the scenes will always help you further your studies.

"},{"location":"blog/20230906/0905-2023blog/#reference","title":"Reference","text":"

https://docs.openstack.org/tacker/latest/contributor/vnfd_template_description.html

https://docs.openstack.org/tacker/ocata/devref/nsd_usage_guide.html

Atoui, Wassim & Assy, Nour & Gaaloul, Walid & Grida Ben Yahia, Imen. (2020). Configurable Deployment Descriptor Model in NFV. Journal of Network and Systems Management. 28. 10.1007/s10922-020-09531-2.

Automated Network Service Scaling in NFV: Concepts, Mechanisms and Scaling Workflow

ETSI GS NFV-IFA 011 V2.4.1

ETSI GS NFV-IFA 014 V3.3.1

"},{"location":"blog/20230906/0905-2023blog/#about","title":"About","text":"

Greetings everyone, my name is Leon Sawada, I am a 2nd-year master's student in NYCU Wireless Internet Research and Engineering (WIRE) Laboratory. My research field is network slicing, and I will be very grateful if this blog article helps you understand more about these components in network slicing. Best wishes.

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20230913/20230913/","title":"CHarging Function(CHF) Overview","text":"

Note

Author: Benson Hsu Date: 2023/9/13

The charging function is a crucial component of the 5G core network responsible for tasks such as traffic calculation and quota management. For network providers and administrators, it is one of the most critical network elements. This article will explain the fundamental concepts and mechanisms of charging, as well as highlight the differences between 4G and 5G charging. Finally, we will include information about the CHF implementation developed by the free5GC team.

"},{"location":"blog/20230913/20230913/#charging-mechanisms","title":"Charging Mechanisms","text":"

The charging system can be divided into three main types: offline charging, online charging, and converged charging:

  • Offline Charging:

    • After network resource usage, charging information is transmitted from the network to the Billing Domain (BD).
    • The Billing Domain is responsible for performing cost calculations and data statistics.
  • Online Charging:

    • Before network resource usage, the network sends an authentication request to the Online Charging System (OCS).
    • The OCS queries the subscriber's account information to determine whether the usage of these resources is allowed.
    • After receiving authentication from the OCS, resource usage is monitored, and related account information is stored in the OCS.
  • Converged Charging:

    • Converged charging is a mechanism that combines both online and offline charging.
    • For example, the CHF (Converged Charging Function) architecture in the 5G core system is an example of converged charging.
    • In other service systems, if the charging system includes both offline and online charging functionalities, it can also be referred to as converged charging.

Typical triggers for charging related to network resource usage include:

  • A voice call of a certain duration.
  • The transport of a certain volume of data.
  • The submission of a multimedia message (MM) of a certain size.

Resource usage requests can be triggered by the UE or by the core network. Furthermore, for the same chargeable event, both offline and online charging can occur simultaneously and independently.

"},{"location":"blog/20230913/20230913/#high-level-common-architecture","title":"High Level Common Architecture","text":"

The diagram below is from 3GPP TS32.240-V15.5.0, which illustrates the architectural differences among the three charging systems and indicates through which reference points data is transmitted for each Network Element (NE).

"},{"location":"blog/20230913/20230913/#offline-charging-function","title":"Offline Charging Function","text":"

The Offline Charging Function primarily consists of three components: CTF (Charging Trigger Function), CDF (Charging Data Function), and CGF (Charging Gateway Function). Here's an overview of the roles of each function and how data flows between them:

  1. CTF (Charging Trigger Function):
  2. Role: CTF is responsible for generating charging events by observing network resource usage.
  3. Data Flow: It collects information about trigger conditions, information elements to be collected, and which service events, signaling, or user traffic to monitor.
  4. Data Transfer: CTF forwards generated charging events to CDF.

  5. CDF (Charging Data Function):

  6. Role: CDF receives charging events from CTF and processes them.
  7. Data Flow: It processes and stores charging data, potentially performing reformatting to comply with specific formats.
  8. Data Transfer: CDF sends the processed Charging Data Records (CDRs) to CGF.

  9. CGF (Charging Gateway Function):

  10. Role: CGF is responsible for persistent CDR storage, preparing CDR files, and transferring them to the Billing Domain (BD).
  11. Data Flow: It provides storage for CDRs and manages the routing of CDR files.
  12. Data Transfer: CGF transfers CDR files to the Billing Domain via the Bx reference point.

These functions work together to collect, process, and store charging data in the offline charging system.

"},{"location":"blog/20230913/20230913/#online-charging-functionsocf","title":"Online Charging Functions(OCF)","text":"

The Online Charging Function primarily consists of four components: CTF (Charging Trigger Function), OCF (Online Charging Function), ABMF (Account Balance Management Function), and RF (Rating Function). Here's an overview of the roles of each function and how data flows between them:

1. CTF (Charging Trigger Function): * Role: CTF observes network resource usage to generate charging events. * Data Flow: It collects information about trigger conditions, information elements to be collected, and the monitoring of service events, signaling, or user traffic. * Data Transfer: CTF forwards generated charging events to the OCF.

2. OCF (Online Charging Function): * Role: OCF is responsible for real-time charging and authorization of resource usage. * Data Flow: It evaluates charging events, determines the value of requested resource usage, and authorizes it. * Data Transfer: OCF sends authorization information to the network element, which then executes the resource usage.

3. ABMF (Account Balance Management Function): * Role: ABMF manages subscriber account balances and credit control. * Data Flow: It keeps track of subscriber account balances and interacts with OCF for authorization and debiting. * Data Transfer: ABMF communicates with OCF to manage subscriber balances during resource usage.

4. RF (Rating Function): * Role: RF determines the value or amount of actual network resource usage for OCF (according to OCF from charging event) * Data Flow: The OCF provides the RF with essential data extracted from the charging event. Then RF returns the rating results, which can be in the form of monetary or non-monetary units. * Data Transfer: OCF send charging event to RF, and RF return rating results in specific unit

These functions collaborate to enable real-time online charging, authorization, and account management for subscribers in the online charging system.

"},{"location":"blog/20230913/20230913/#converged-charging-functions","title":"Converged Charging Functions","text":"

The Converged Charging Functions consist of various components, each with specific roles and data flow within the system. Here's an overview of the roles of each function and how data flows between them:

1. CTF (Charging Trigger Function): * Role: CTF is mostly integrated into network functions like PCF, SMSF, and SMF. These network functions monitor charging information and transform it into charging events, which are then forwarded to the CHF for further processing. * Data Flow: It collects information on trigger conditions, information elements to be captured, and monitors service events, signaling, or user traffic. * Data Transfer: The CTF forwards the generated charging events to the CHF via an SBI (Service-Based Interface) located within the core network's control plane.

2. CHF (CHarging Function): * Role: CHF is responsible for facilitating communication among all functions within the Converged Charging System(CCS). Its role includes receiving charging events and generating CDRs (Charging Data Records). Importantly, it can simultaneously handle both online and offline charging functions. * Data Flow: It evaluates charging events, determines the value of requested resource usage, authorizes it, and manages subscriber account balances. * Data Transfer: CHF and its associated functions engage in data exchanges involving various types of information. These data exchanges include charging events, user count balances, CDRs (Charging Data Records), and rating information.

The remaining functions mentioned earlier in the descriptions of offline and online charging serve the same purposes and functions, so we won't go into further detail here.

"},{"location":"blog/20230913/20230913/#the-differences-between-4g-and-5g-charging","title":"The Differences between 4G and 5G Charging","text":"

In the 4G era, various concepts and functions for online and offline charging were already established. When defining the 5G standards, it was recognized that there was a need to support various charging methods. Therefore, a new function called CHF (Charging Function) was introduced, and other functions such as ABMF, CGF, and RF from different systems were integrated into the Converged Charging System. This addition expanded the capabilities of 5G Core (5GC) charging.

The differences between 4G and 5G charging can be summarized as follows:

1. Network Architecture: * 4G: In 4G networks, charging functions are typically less integrated and may involve multiple separate components. * 5G: 5G introduces a more integrated and converged charging system where various charging functions are unified within the Converged Charging System (CHF), allowing for more flexibility and efficiency.

2. Charging Flexibility: * 4G: 4G networks may have limitations in terms of charging flexibility, especially when it comes to handling different charging scenarios and services. * 5G: 5G charging is designed to be more versatile and adaptable, capable of handling various charging models, both online and offline, for a wide range of services and use cases.

3. Service Support: * 4G: 4G charging systems are primarily designed for traditional mobile services like voice and data. * 5G: 5G charging systems are built to support a broader range of services, including enhanced mobile broadband (eMBB), massive machine-type communications (mMTC), and ultra-reliable low-latency communications (URLLC), making them more suitable for diverse 5G applications.

4. Convergence: * 4G: Converged charging in 4G networks is often limited, and different charging models may not be as seamlessly integrated. * 5G: 5G emphasizes the convergence of charging functions, allowing for greater consistency and interoperability across various charging scenarios and services.

5. Charging Efficiency: * 4G: Charging efficiency in 4G networks may vary depending on the specific implementation and the level of integration between charging components. * 5G: 5G charging systems aim to improve efficiency by streamlining charging processes and reducing redundancy, thanks to the unified CHF.

Overall, the key difference between 4G and 5G charging lies in the level of integration, flexibility, and support for a broader range of services and use cases in 5G networks. The introduction of the CHF in 5G brings greater convergence and efficiency to charging operations.

"},{"location":"blog/20230913/20230913/#chf-implementation","title":"CHF Implementation","text":"

Hello everyone, we are the free5gc development team. We have recently added CHF (Charging Function) to enhance the functionality of 5G Core (5GC). We invite anyone interested in CHF to check out how we have implemented the charging function. Below is the relevant link, and thank you for your interest.

  • Ref: https://github.com/free5gc/chf
"},{"location":"blog/20230913/20230913/#reference","title":"Reference","text":"
  • 3GPP TS32.240 v15 spec(offline)
  • 3GPP TS32.240 v15 spec(online)
  • https://devopedia.org/5g-service-based-architecture
  • https://free5gc.org/

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20230920/Introduction_of_gtp5g_and_some_kernel_concepts/","title":"Introduction of gtp5g and some kernel concepts","text":"

Note

Author: Jimmy Date: 2023/9/20

"},{"location":"blog/20230920/Introduction_of_gtp5g_and_some_kernel_concepts/#overview","title":"Overview","text":"

GTP (General Packet Radio System Tunneling Protocol) is a group of IP-based communication protocols used to transport General Packet Radio Services (GPRS) within LTE, 5G NR, and other networks. GTP can be broken down into several components: GTP-C, GTP-U, and GTP prime. GTP-C and GTP-U are responsible for the control plane and user plane, respectively, while GTP prime is utilized for transmitting charging data through the Ga interface as defined in the 3GPP GPRS Core Network.

In the context of free5GC, the UPF network function combines the GTP-C control part to correctly instruct the routing path for any packet passing through the core network. GTP-U, on the other hand, is managed by gtp5g, which transports packets using kernel modules generated by gtp5g. This article will introduce how gtp5g assists free5GC in handling packets and some kernel-related concepts.

Let's start the journey!

Additional information:

  • Linux kernel version is 5.4.0-159-generic in article. According to other versions, some of content would be different, but the main concept is the same.
  • Gtp5g version is v0.8.2
  • UPF version is v1.2.0
"},{"location":"blog/20230920/Introduction_of_gtp5g_and_some_kernel_concepts/#netlink-generic-netlink-and-rtnetlink","title":"Netlink, Generic Netlink and Rtnetlink","text":""},{"location":"blog/20230920/Introduction_of_gtp5g_and_some_kernel_concepts/#netlink","title":"Netlink","text":"

Before we continue, I need to introduce Netlink first. What is Netlink? Netlink is an IPC (Inter Process Communication) protocol which can connect kernel space and user space processes by socket. Traditionally, it used three methods: Ioctl, sysfs, or procfs, to facilitate communication between the kernel and user space. However, it can only be initiated from user space, not from kernel space. Netlink can support not only initiated from kernel and user space but also:

  • Bidirectional transmission, asynchronous communication.
  • Standard socket API used in user space.
  • Specialized API used in kernel space.
  • Support for multicast.
  • Support for 32 protocol types.

There are servel usages define in include/uapi/linux/netlink.h

#define NETLINK_ROUTE       0   /* Routing/device hook              */\n#define NETLINK_UNUSED      1   /* Unused number                */\n#define NETLINK_USERSOCK    2   /* Reserved for user mode socket protocols  */\n#define NETLINK_FIREWALL    3   /* Unused number, formerly ip_queue     */\n#define NETLINK_SOCK_DIAG   4   /* socket monitoring                */\n#define NETLINK_NFLOG       5   /* netfilter/iptables ULOG */\n#define NETLINK_XFRM        6   /* ipsec */\n#define NETLINK_SELINUX     7   /* SELinux event notifications */\n#define NETLINK_ISCSI       8   /* Open-iSCSI */\n#define NETLINK_AUDIT       9   /* auditing */\n#define NETLINK_FIB_LOOKUP  10  \n#define NETLINK_CONNECTOR   11\n#define NETLINK_NETFILTER   12  /* netfilter subsystem */\n#define NETLINK_IP6_FW      13\n#define NETLINK_DNRTMSG     14  /* DECnet routing messages */\n#define NETLINK_KOBJECT_UEVENT  15  /* Kernel messages to userspace */\n#define NETLINK_GENERIC     16\n/* leave room for NETLINK_DM (DM Events) */\n#define NETLINK_SCSITRANSPORT   18  /* SCSI Transports */\n#define NETLINK_ECRYPTFS    19\n#define NETLINK_RDMA        20\n#define NETLINK_CRYPTO      21  /* Crypto layer */\n#define NETLINK_SMC     22  /* SMC monitoring */\n\n#define NETLINK_INET_DIAG   NETLINK_SOCK_DIAG\n\n#define MAX_LINKS 32\n

These are Linux system pre-defined Netlink protocols. Therefore, if users want to define their own Netlink protocol, they would need to modify the Linux kernel files to meet their requirements. However, the kernel must be protected from modification. Additionally, the maximum protocol number allowed is 32, can't exceed it.

"},{"location":"blog/20230920/Introduction_of_gtp5g_and_some_kernel_concepts/#generic-netlink","title":"Generic Netlink","text":"

Due to the shortage of protocol numbers and the need to prevent kernel modification, kernel developers extended Netlink and introduced Generic Netlink. Generic Netlink supports 1023 protocols, addressing the protocol number limitation, and it allocates protocol IDs automatically.

The following figure is Generic Netlink structure:

graph TD\nA1[Application_1] --- B[Kernel_Socket_API] \nA2[Application_2] --- B[Kernel_Socket_API]\n\nB[Kernel_Socket_API] --- C[Netlink_Subsystem]\nB[Kernel_Socket_API] --- C[Netlink_Subsystem]\n\nC[Netlink_Subsystem] --- D[Generic_Netlink_Bus]\n\nD[Generic_Netlink_Bus] --- E1[Controller]\nD[Generic_Netlink_Bus] --- E2[Kernel_User_1]\nD[Generic_Netlink_Bus] --- E3[Kernel_User_2]\n
  • The Generic Netlink users application_1 and application_2 could communicate both user space and kernel space endpoint through Kernel_socket_API.
  • The Netlink subsystem which serves as the underlying transport layer for all of the Generic Netlink communications.
  • The Generic Netlink bus which is implemented inside the kernel, but which is available to userspace through the socket API and inside the kernel via the normal Netlink and Generic Netlink APIs.
  • The Generic Netlink users who communicate with each other over the Generic Netlink bus; users can exist both in kernel and user space.
  • The Generic Netlink controller which is part of the kernel and is responsible for dynamically allocating Generic Netlink communication channels and other management tasks. The Generic Netlink controller is implemented as a standard Generic Netlink kernel user, however, it listens on a special, pre-allocated Generic Netlink channel.
  • The kernel socket API. Generic Netlink sockets are created with the PF_NETLINK domain and the NETLINK_GENERIC protocol values.
"},{"location":"blog/20230920/Introduction_of_gtp5g_and_some_kernel_concepts/#rtnetlink","title":"RtNetlink","text":"

The last one is rtnetlink, it also known as Netlink protocol type NETLINK_ROUTE, user space program could read and alter kernel's routing table or create new network device.

"},{"location":"blog/20230920/Introduction_of_gtp5g_and_some_kernel_concepts/#free5gc-upf","title":"free5GC UPF","text":"

Since gtp5g is part of UPF logically, article also covers part of UPF.

The Driver provides functions to communicate with gtp5g (the functions are one-to-one match to gtp5g_genl_ops[] in genl.c). So, when UPF receives a PFCP message, it parses the content and then uses various functions of the Driver to instruct gtp5g to take regarding rules.

// internel/forwarder/driver.go\ntype Driver interface {\n    Close()\n\n    CreatePDR(uint64, *ie.IE) error\n    UpdatePDR(uint64, *ie.IE) error\n    RemovePDR(uint64, *ie.IE) error\n\n    CreateFAR(uint64, *ie.IE) error\n    UpdateFAR(uint64, *ie.IE) error\n    RemoveFAR(uint64, *ie.IE) error\n\n    CreateQER(uint64, *ie.IE) error\n    UpdateQER(uint64, *ie.IE) error\n    RemoveQER(uint64, *ie.IE) error\n\n    CreateURR(uint64, *ie.IE) error\n    UpdateURR(uint64, *ie.IE) ([]report.USAReport, error)\n    RemoveURR(uint64, *ie.IE) ([]report.USAReport, error)\n    QueryURR(uint64, uint32) ([]report.USAReport, error)\n\n    CreateBAR(uint64, *ie.IE) error\n    UpdateBAR(uint64, *ie.IE) error\n    RemoveBAR(uint64, *ie.IE) error\n\n    HandleReport(report.Handler)\n}\n

UPF use rtnl to create device (interface) named upfgtp. User can observe it while executing run.sh.

func OpenGtp5gLink(mux *nl.Mux, addr string, mtu uint32, log *logrus.Entry) (*Gtp5gLink, error) {\n    g := &Gtp5gLink{\n        log: log,\n    }\n\n    g.mux = mux\n\n    rtconn, err := nl.Open(syscall.NETLINK_ROUTE)\n    if err != nil {\n        return nil, errors.Wrap(err, \"open\")\n    }\n    g.rtconn = rtconn\n    g.client = nl.NewClient(rtconn, mux)\n\n    laddr, err := net.ResolveUDPAddr(\"udp4\", addr)\n    if err != nil {\n        g.Close()\n        return nil, errors.Wrap(err, \"resolve addr\")\n    }\n    conn, err := net.ListenUDP(\"udp4\", laddr)\n    if err != nil {\n        g.Close()\n        return nil, errors.Wrap(err, \"listen\")\n    }\n    g.conn = conn\n\n    // TODO: Duplicate fd\n    f, err := conn.File()\n    if err != nil {\n        g.Close()\n        return nil, errors.Wrap(err, \"file\")\n    }\n    g.f = f\n\n    linkinfo := &nl.Attr{\n        Type: syscall.IFLA_LINKINFO,\n        Value: nl.AttrList{\n            {\n                Type:  rtnllink.IFLA_INFO_KIND,\n                Value: nl.AttrString(\"gtp5g\"),\n            },\n            {\n                Type: rtnllink.IFLA_INFO_DATA,\n                Value: nl.AttrList{\n                    {\n                        Type:  gtp5gnl.IFLA_FD1,\n                        Value: nl.AttrU32(f.Fd()),\n                    },\n                    {\n                        Type:  gtp5gnl.IFLA_HASHSIZE,\n                        Value: nl.AttrU32(131072),\n                    },\n                },\n            },\n        },\n    }\n    attrs := []*nl.Attr{linkinfo}\n\n    if mtu != 0 {\n        attrs = append(attrs, &nl.Attr{\n            Type:  syscall.IFLA_MTU,\n            Value: nl.AttrU32(mtu),\n        })\n    }\n\n    err = rtnllink.Create(g.client, \"upfgtp\", attrs...)\n    if err != nil {\n        g.Close()\n        return nil, errors.Wrap(err, \"create\")\n    }\n    err = rtnllink.Up(g.client, \"upfgtp\")\n    if err != nil {\n        g.Close()\n        return nil, errors.Wrap(err, \"up\")\n    }\n    link, err := gtp5gnl.GetLink(\"upfgtp\")\n    if err != nil {\n        g.Close()\n        return nil, errors.Wrap(err, \"get link\")\n    }\n    g.link = link\n    return g, nil\n}\n

Connect UPF Driver functions and gtp5g_genl_ops.

// internl/forwarder/buffnetlink/server.go\nfunc OpenServer(wg *sync.WaitGroup, client *nl.Client, mux *nl.Mux) (*Server, error) {\n    s := &Server{\n        client: client,\n        mux:    mux,\n    }\n\n    f, err := genl.GetFamily(s.client, \"gtp5g\")\n    if err != nil {\n        return nil, errors.Wrap(err, \"get family\")\n    }\n\n    s.conn, err = nl.Open(syscall.NETLINK_GENERIC, int(f.Groups[gtp5gnl.GENL_MCGRP].ID))\n    if err != nil {\n        return nil, errors.Wrap(err, \"open netlink\")\n    }\n\n    err = s.mux.PushHandler(s.conn, s)\n    if err != nil {\n        return nil, errors.Wrap(err, \"push handler\")\n    }\n\n    logger.BuffLog.Infof(\"buff netlink server started\")\n\n    // wg.Add(1)\n    return s, nil\n}\n

"},{"location":"blog/20230920/Introduction_of_gtp5g_and_some_kernel_concepts/#gtp5g","title":"GTP5G","text":"
  • Gtp5g utilizes a Linux kernel module to manage packet traffic. A Linux kernel module can be thought of as a small piece of code that is inserted into the Linux kernel, allowing users to customize the program according to the current hardware device
  • In gtp5g, the primary function is gtp5g_init in gtp5g.c; it exposes most of the components and techniques provided by gtp5g. This article will choose the following concepts to investigate further:
  • Network device -> net_device_ops
  • Rtnetlink -> gtp5g_link_ops
  • Generic Netlink -> gtp5g_genl_family
  • Additionally, the article will present two functions in detail:
  • rtnl_link_register()
  • genl_register_family()
// src/gtp5g.c\nstatic int __init gtp5g_init(void)\n{\n    int err;\n\n    GTP5G_LOG(NULL, \"Gtp5g Module initialization Ver: %s\\n\", DRV_VERSION);\n\n    init_proc_gtp5g_dev_list();\n\n    // set hash initial value\n    get_random_bytes(&gtp5g_h_initval, sizeof(gtp5g_h_initval));\n\n    err = rtnl_link_register(&gtp5g_link_ops);\n    if (err < 0) {\n        GTP5G_ERR(NULL, \"Failed to register rtnl\\n\");\n        goto error_out;\n    }\n\n    err = genl_register_family(&gtp5g_genl_family);\n    if (err < 0) {\n        GTP5G_ERR(NULL, \"Failed to register generic\\n\");\n        goto unreg_rtnl_link;\n    }\n\n    err = register_pernet_subsys(&gtp5g_net_ops);\n    if (err < 0) {\n        GTP5G_ERR(NULL, \"Failed to register namespace\\n\");\n        goto unreg_genl_family;\n    }\n\n    err = create_proc();\n    if (err < 0) {\n        goto unreg_pernet;\n    }\n    GTP5G_LOG(NULL, \"5G GTP module loaded\\n\");\n\n    return 0;\n    ...\n}\n
"},{"location":"blog/20230920/Introduction_of_gtp5g_and_some_kernel_concepts/#net_device_ops","title":"net_device_ops","text":"

It is defined in dev.h and referenced in dev.c. The structure net_device_ops encompasses all operations related to network device, and free5GC inherits some of these operations to implement self-made netdev ops.

// include/dev.h\nextern const struct net_device_ops gtp5g_netdev_ops;\n

// src/gtpu/dev.c\nconst struct net_device_ops gtp5g_netdev_ops = {\n    .ndo_init           = gtp5g_dev_init,\n    .ndo_uninit         = gtp5g_dev_uninit,\n    .ndo_start_xmit     = gtp5g_dev_xmit,\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)\n    .ndo_get_stats64    = dev_get_tstats64,\n#else\n    .ndo_get_stats64    = ip_tunnel_get_stats64,\n#endif\n};\n
According to /include/linux/netdevice.h, you can find the definition of hooks of net_device_ops:

  • .ndo_init: This function is called once when a network device is registered. The network device can use this for any late stage initialization or semantic validation. It can fail with an error code which will be propagated back to register_netdev.
  • .ndo_uninit: This function is called when device is unregistered or when registration fails. It is not called if init fails.
  • .ndo_start_xmit: Called when a packet needs to be transmitted. Returns NETDEV_TX_OK. Can return NETDEV_TX_BUSY, but you should stop the queue before that can happen; it's for obsolete devices and weird corner cases, but the stack really does a non-trivial amount of useless work if you return NETDEV_TX_BUSY. Required; cannot be NULL.
    struct net_device_ops {\n  int           (*ndo_init)(struct net_device *dev);\n  void          (*ndo_uninit)(struct net_device *dev);\n  netdev_tx_t       (*ndo_start_xmit)(struct sk_buff *skb,\n                      struct net_device *dev);\n...\n}\n
    Gtp5g self-made structure:
    // include/dev.h\nstruct gtp5g_dev {\n    struct list_head list;\n    struct sock *sk1u; // UDP socket from user space\n    struct net_device *dev;\n    unsigned int role;\n    unsigned int hash_size;\n    struct hlist_head *pdr_id_hash;\n    struct hlist_head *far_id_hash;\n    struct hlist_head *qer_id_hash;\n    struct hlist_head *bar_id_hash;\n    struct hlist_head *urr_id_hash;\n\n    struct hlist_head *i_teid_hash; // Used for GTP-U packet detect\n    struct hlist_head *addr_hash;   // Used for IPv4 packet detect\n\n    /* IEs list related to PDR */\n    struct hlist_head *related_far_hash; // PDR list waiting the FAR to handle\n    struct hlist_head *related_qer_hash; // PDR list waiting the QER to handle\n    struct hlist_head *related_bar_hash;\n    struct hlist_head *related_urr_hash;\n\n    /* Used by proc interface */\n    struct list_head proc_list;\n};\n

It would find the private data address in network device by netdev_priv() and allocate the device statistics space for each CPU by netdev_alloc_pcpu_stats():

// src/gtpu/dev.c\nstatic int gtp5g_dev_init(struct net_device *dev)\n{\n    struct gtp5g_dev *gtp = netdev_priv(dev);\n\n    gtp->dev = dev;\n\n    dev->tstats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);\n    if (!dev->tstats) {\n        return -ENOMEM;\n    }\n\n    return 0;\n}\n

From /include/linux/netdevice.h, the return value would be times of 32:

static inline void *netdev_priv(const struct net_device *dev)\n{\n    return (char *)dev + ALIGN(sizeof(struct net_device), NETDEV_ALIGN);\n}\n

Close the udp socket (sk1u) is used to receive uplink (N3) packet:

// src/gtpu/dev.c\nstatic void gtp5g_dev_uninit(struct net_device *dev)\n{\n    struct gtp5g_dev *gtp = netdev_priv(dev);\n\n    gtp5g_encap_disable(gtp->sk1u);\n    free_percpu(dev->tstats);\n}\n

Utilized for the reception of downlink (N6) packets by a network device:

// src/gtpu/dev.c\nstatic netdev_tx_t gtp5g_dev_xmit(struct sk_buff *skb, struct net_device *dev)\n{\n    unsigned int proto = ntohs(skb->protocol);\n    struct gtp5g_pktinfo pktinfo;\n    int ret = 0;\n\n    /* Ensure there is sufficient headroom */\n    if (skb_cow_head(skb, dev->needed_headroom)) {\n        goto tx_err;\n    }\n\n    skb_reset_inner_headers(skb);\n\n    /* PDR lookups in gtp5g_build_skb_*() need rcu read-side lock. \n     * */\n    rcu_read_lock();\n    switch (proto) {\n    case ETH_P_IP:\n        ret = gtp5g_handle_skb_ipv4(skb, dev, &pktinfo);\n        break;\n    default:\n        ret = -EOPNOTSUPP;\n    }\n    rcu_read_unlock();\n\n    if (ret < 0)\n        goto tx_err;\n\n    if (ret == FAR_ACTION_FORW)\n        gtp5g_xmit_skb_ipv4(skb, &pktinfo);\n\n    return NETDEV_TX_OK;\n    ...\n}\n

"},{"location":"blog/20230920/Introduction_of_gtp5g_and_some_kernel_concepts/#gtp5g_link_ops","title":"gtp5g_link_ops","text":"

Ignore headder file here, structure defines Rtnetlink operations.

// src/gtpu/link.c\nstruct rtnl_link_ops gtp5g_link_ops __read_mostly = {\n    .kind         = \"gtp5g\",\n    .maxtype      = IFLA_GTP5G_MAX,\n    .policy       = gtp5g_policy,\n    .priv_size    = sizeof(struct gtp5g_dev),\n    .setup        = gtp5g_link_setup,\n    .validate     = gtp5g_validate,\n    .newlink      = gtp5g_newlink,\n    .dellink      = gtp5g_dellink,\n    .get_size     = gtp5g_get_size,\n    .fill_info    = gtp5g_fill_info,\n};\n

Definition is in /include/net/rtnetlink.h: - .kind: Identifier - .maxtype: Highest device specific netlink attribute number - .policy: Netlink policy for device specific attribute validation - .priv_size: sizeof net_device private space - .setup: net_device setup function - .validate: Optional validation function for netlink/changelink parameters - .newlink: Function for configuring and registering a new device - .dellink: Function to remove a device - .get_size: Function to calculate required room for dumping device specific netlink attributes - .fill_info: Function to dump device specific netlink attributes

struct rtnl_link_ops {\n    struct list_head    list;\n\n    const char      *kind;\n\n    size_t          priv_size;\n    void            (*setup)(struct net_device *dev);\n\n    unsigned int        maxtype;\n    const struct nla_policy *policy;\n    int         (*validate)(struct nlattr *tb[],\n                        struct nlattr *data[],\n                        struct netlink_ext_ack *extack);\n\n    int         (*newlink)(struct net *src_net,\n                       struct net_device *dev,\n                       struct nlattr *tb[],\n                       struct nlattr *data[],\n                       struct netlink_ext_ack *extack);\n    int         (*changelink)(struct net_device *dev,\n                          struct nlattr *tb[],\n                          struct nlattr *data[],\n                          struct netlink_ext_ack *extack);\n    void            (*dellink)(struct net_device *dev,\n                       struct list_head *head);\n\n    size_t          (*get_size)(const struct net_device *dev);\n    int         (*fill_info)(struct sk_buff *skb,\n                         const struct net_device *dev);\n\n    size_t          (*get_xstats_size)(const struct net_device *dev);\n    int         (*fill_xstats)(struct sk_buff *skb,\n                           const struct net_device *dev);\n    unsigned int        (*get_num_tx_queues)(void);\n    unsigned int        (*get_num_rx_queues)(void);\n\n    unsigned int        slave_maxtype;\n    const struct nla_policy *slave_policy;\n    int         (*slave_changelink)(struct net_device *dev,\n                            struct net_device *slave_dev,\n                            struct nlattr *tb[],\n                            struct nlattr *data[],\n                            struct netlink_ext_ack *extack);\n    size_t          (*get_slave_size)(const struct net_device *dev,\n                          const struct net_device *slave_dev);\n    int         (*fill_slave_info)(struct sk_buff *skb,\n                           const struct net_device *dev,\n                           const struct net_device *slave_dev);\n    struct net      *(*get_link_net)(const struct net_device *dev);\n    size_t          (*get_linkxstats_size)(const struct net_device *dev,\n                               int attr);\n    int         (*fill_linkxstats)(struct sk_buff *skb,\n                           const struct net_device *dev,\n                           int *prividx, int attr);\n};\n

Once rtnl link setting up, gtp5g would assign net_device_ops to device.

static void gtp5g_link_setup(struct net_device *dev)\n{\n    dev->netdev_ops = &gtp5g_netdev_ops;   <---- network device assignment\n    dev->needs_free_netdev = true;\n\n    dev->hard_header_len = 0;\n    dev->addr_len = 0;\n    dev->mtu = ETH_DATA_LEN -\n        (sizeof(struct iphdr) +\n         sizeof(struct udphdr) +\n         sizeof(struct gtpv1_hdr));\n\n    /* Zero header length. */\n    dev->type = ARPHRD_NONE;\n    dev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;\n\n    dev->priv_flags |= IFF_NO_QUEUE;\n    dev->features |= NETIF_F_LLTX;\n    netif_keep_dst(dev);\n\n    /* TODO: Modify the headroom size based on\n     * what are the extension header going to support\n     * */\n    dev->needed_headroom = LL_MAX_HEADER +\n        sizeof(struct iphdr) +\n        sizeof(struct udphdr) +\n        sizeof(struct gtpv1_hdr);\n}\n

"},{"location":"blog/20230920/Introduction_of_gtp5g_and_some_kernel_concepts/#rtnl_link_register","title":"rtnl_link_register()","text":"
  • Definition is in /net/core/rtnetlink.c
  • This function should be used by drivers that create devices during module initialization. It must be called before registering the devices
  • Using the kind property of rtnl_link_ops to search for the existence of ops in link_ops. If ops do not exist, then inserted it at the end of link_ops
  • Once register sucess, UPF can create new network device (interface) using Rtnetlink socket
    int __rtnl_link_register(struct rtnl_link_ops *ops)\n{\n    if (rtnl_link_ops_get(ops->kind))\n        return -EEXIST;\n\n    /* The check for setup is here because if ops\n     * does not have that filled up, it is not possible\n     * to use the ops for creating device. So do not\n     * fill up dellink as well. That disables rtnl_dellink.\n     */\n    if (ops->setup && !ops->dellink)\n        ops->dellink = unregister_netdevice_queue;\n\n    list_add_tail(&ops->list, &link_ops);\n    return 0;\n}\nEXPORT_SYMBOL_GPL(__rtnl_link_register);\n
"},{"location":"blog/20230920/Introduction_of_gtp5g_and_some_kernel_concepts/#gtp5g_genl_family","title":"gtp5g_genl_family","text":"

Gtp5g defines the genl (Generic Netlink) interface to facilitate communication between user and kernel space after register 'family'. As mentioned earlier, there is a Generic Netlink Controller responsible for bus allocation and dynamically assigns tunnel based on genl family id (name).

// src/genl/genl.c\nstruct genl_family gtp5g_genl_family __ro_after_init = {\n    .name       = \"gtp5g\",\n    .version    = 0,\n    .hdrsize    = 0,\n    .maxattr    = GTP5G_ATTR_MAX,\n    .netnsok    = true,\n    .module     = THIS_MODULE,\n    .ops        = gtp5g_genl_ops,\n    .n_ops      = ARRAY_SIZE(gtp5g_genl_ops),\n    .mcgrps     = gtp5g_genl_mcgrps,\n    .n_mcgrps   = ARRAY_SIZE(gtp5g_genl_mcgrps),\n};\n
Definition is in /include/net/genetlink.h: - .name: name of family (exclusive) - .version: protocol version (usually is 1) - .hdrsize: length of user specific header in bytes - .maxattr: maximum number of attributes supported - .netnsok: set to true if the family can handle network namespaces and should be presented in all of them - .ops: the operations supported by this family - .n_ops: number of operations supported by this family - .mcgrps: multicast groups used by this family - .n_mcgrps: number of multicast groups

struct genl_family {\n    int         id;     /* private */\n    unsigned int        hdrsize;\n    char            name[GENL_NAMSIZ];\n    unsigned int        version;\n    unsigned int        maxattr;\n    bool            netnsok;\n    bool            parallel_ops;\n    const struct nla_policy *policy;\n    int         (*pre_doit)(const struct genl_ops *ops,\n                        struct sk_buff *skb,\n                        struct genl_info *info);\n    void            (*post_doit)(const struct genl_ops *ops,\n                         struct sk_buff *skb,\n                         struct genl_info *info);\n    struct nlattr **    attrbuf;    /* private */\n    const struct genl_ops * ops;\n    const struct genl_multicast_group *mcgrps;\n    unsigned int        n_ops;\n    unsigned int        n_mcgrps;\n    unsigned int        mcgrp_offset;   /* private */\n    struct module       *module;\n};\n

Gtp5g defines gtp5g_genl_ops, all operations are one-to-one match to Driver functions in UPF part:

// src/genl/genl.c\nstatic const struct genl_ops gtp5g_genl_ops[] = {\n    {\n        .cmd = GTP5G_CMD_ADD_PDR,\n        // .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,\n        .doit = gtp5g_genl_add_pdr,\n        // .policy = gtp5g_genl_pdr_policy,\n        .flags = GENL_ADMIN_PERM,\n    },\n    {\n        .cmd = GTP5G_CMD_DEL_PDR,\n        // .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,\n        .doit = gtp5g_genl_del_pdr,\n        // .policy = gtp5g_genl_pdr_policy,\n        .flags = GENL_ADMIN_PERM,\n    },\n    {\n        .cmd = GTP5G_CMD_GET_PDR,\n        // .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,\n        .doit = gtp5g_genl_get_pdr,\n        .dumpit = gtp5g_genl_dump_pdr,\n        // .policy = gtp5g_genl_pdr_policy,\n        .flags = GENL_ADMIN_PERM,\n    },\n    ...\n}\n

"},{"location":"blog/20230920/Introduction_of_gtp5g_and_some_kernel_concepts/#genl_register_family","title":"genl_register_family()","text":"
  • Definition is in /net/netlink/genetlink.c
  • Registers the specified family after validating it first. Only one family may be registered with the same family name or identifier. The family's ops, multicast groups and module pointer must already be assigned. Return 0 on success or a negative error code
  • Three functions within this method hold greater significance (all of them in /net/netlink/genetlink.c):
  • genl_validate_ops()
  • genl_family_find_byname()
  • genl_validate_assign_mc_groups()
int genl_register_family(struct genl_family *family)\n{\n    int err, i;\n    int start = GENL_START_ALLOC, end = GENL_MAX_ID;\n\n    err = genl_validate_ops(family);\n    if (err)\n        return err;\n\n    genl_lock_all();\n\n    if (genl_family_find_byname(family->name)) {\n        err = -EEXIST;\n        goto errout_locked;\n    }\n\n    if (family == &genl_ctrl) {\n        start = end = GENL_ID_CTRL;\n    } else if (strcmp(family->name, \"pmcraid\") == 0) {\n        start = end = GENL_ID_PMCRAID;\n    } else if (strcmp(family->name, \"VFS_DQUOT\") == 0) {\n        start = end = GENL_ID_VFS_DQUOT;\n    }\n\n    if (family->maxattr && !family->parallel_ops) {\n        family->attrbuf = kmalloc_array(family->maxattr + 1,\n                        sizeof(struct nlattr *),\n                        GFP_KERNEL);\n        if (family->attrbuf == NULL) {\n            err = -ENOMEM;\n            goto errout_locked;\n        }\n    } else\n        family->attrbuf = NULL;\n\n    family->id = idr_alloc_cyclic(&genl_fam_idr, family,\n                      start, end + 1, GFP_KERNEL);\n    if (family->id < 0) {\n        err = family->id;\n        goto errout_free;\n    }\n\n    err = genl_validate_assign_mc_groups(family);\n    if (err)\n        goto errout_remove;\n\n    genl_unlock_all();\n\n    /* send all events */\n    genl_ctrl_event(CTRL_CMD_NEWFAMILY, family, NULL, 0);\n    for (i = 0; i < family->n_mcgrps; i++)\n        genl_ctrl_event(CTRL_CMD_NEWMCAST_GRP, family,\n                &family->mcgrps[i], family->mcgrp_offset + i);\n\n    return 0;\n\nerrout_remove:\n    idr_remove(&genl_fam_idr, family->id);\nerrout_free:\n    kfree(family->attrbuf);\nerrout_locked:\n    genl_unlock_all();\n    return err;\n}\nEXPORT_SYMBOL(genl_register_family);\n
"},{"location":"blog/20230920/Introduction_of_gtp5g_and_some_kernel_concepts/#genl_validate_ops","title":"genl_validate_ops()","text":"

This function will verify if there is defined function for the operations and will also compare whether any operation is reused by a command. Using gtp5g as an example, command can be considered as action such as add, del, modify PDR rules.

static int genl_validate_ops(const struct genl_family *family)\n{\n    const struct genl_ops *ops = family->ops;\n    unsigned int n_ops = family->n_ops;\n    int i, j;\n\n    if (WARN_ON(n_ops && !ops))\n        return -EINVAL;\n\n    if (!n_ops)\n        return 0;\n\n    for (i = 0; i < n_ops; i++) {\n        if (ops[i].dumpit == NULL && ops[i].doit == NULL)\n            return -EINVAL;\n        for (j = i + 1; j < n_ops; j++)\n            if (ops[i].cmd == ops[j].cmd)\n                return -EINVAL;\n    }\n\n    return 0;\n}\n

"},{"location":"blog/20230920/Introduction_of_gtp5g_and_some_kernel_concepts/#genl_family_find_byname","title":"genl_family_find_byname()","text":"

Function would check every entry in genl_fam_idr whether exists the same family name.

static const struct genl_family *genl_family_find_byname(char *name)\n{\n    const struct genl_family *family;\n    unsigned int id;\n\n    idr_for_each_entry(&genl_fam_idr, family, id)\n        if (strcmp(family->name, name) == 0)\n            return family;\n\n    return NULL;\n}\n

Here is the macro of idr_for_each_entry():

#define idr_for_each_entry(idr, entry, id)          \\\n    for (id = 0; ((entry) = idr_get_next(idr, &(id))) != NULL; id += 1U)\n

"},{"location":"blog/20230920/Introduction_of_gtp5g_and_some_kernel_concepts/#genl_validate_assign_mc_groups","title":"genl_validate_assign_mc_groups()","text":"

This changes the number of multicast groups that are available when ntensok is ture:

static int genl_validate_assign_mc_groups(struct genl_family *family)\n{\n    int first_id;\n    int n_groups = family->n_mcgrps;\n    int err = 0, i;\n    bool groups_allocated = false;\n\n    if (!n_groups)\n        return 0;\n\n    for (i = 0; i < n_groups; i++) {\n        const struct genl_multicast_group *grp = &family->mcgrps[i];\n\n        if (WARN_ON(grp->name[0] == '\\0'))\n            return -EINVAL;\n        if (WARN_ON(memchr(grp->name, '\\0', GENL_NAMSIZ) == NULL))\n            return -EINVAL;\n    }\n\n    /* special-case our own group and hacks */\n    if (family == &genl_ctrl) {\n        first_id = GENL_ID_CTRL;\n        BUG_ON(n_groups != 1);\n    } else if (strcmp(family->name, \"NET_DM\") == 0) {\n        first_id = 1;\n        BUG_ON(n_groups != 1);\n    } else if (family->id == GENL_ID_VFS_DQUOT) {\n        first_id = GENL_ID_VFS_DQUOT;\n        BUG_ON(n_groups != 1);\n    } else if (family->id == GENL_ID_PMCRAID) {\n        first_id = GENL_ID_PMCRAID;\n        BUG_ON(n_groups != 1);\n    } else {\n        groups_allocated = true;\n        err = genl_allocate_reserve_groups(n_groups, &first_id);\n        if (err)\n            return err;\n    }\n\n    family->mcgrp_offset = first_id;\n\n    /* if still initializing, can't and don't need to to realloc bitmaps */\n    if (!init_net.genl_sock)\n        return 0;\n\n    if (family->netnsok) {\n        struct net *net;\n\n        netlink_table_grab();\n        rcu_read_lock();\n        for_each_net_rcu(net) {\n            err = __netlink_change_ngroups(net->genl_sock,\n                    mc_groups_longs * BITS_PER_LONG);\n            if (err) {\n                /*\n                 * No need to roll back, can only fail if\n                 * memory allocation fails and then the\n                 * number of _possible_ groups has been\n                 * increased on some sockets which is ok.\n                 */\n                break;\n            }\n        }\n        rcu_read_unlock();\n        netlink_table_ungrab();\n    } else {\n        err = netlink_change_ngroups(init_net.genl_sock,\n                         mc_groups_longs * BITS_PER_LONG);\n    }\n\n    if (groups_allocated && err) {\n        for (i = 0; i < family->n_mcgrps; i++)\n            clear_bit(family->mcgrp_offset + i, mc_groups);\n    }\n\n    return err;\n}\n
"},{"location":"blog/20230920/Introduction_of_gtp5g_and_some_kernel_concepts/#about","title":"About","text":"
  • Jimmy Chang
    • Graduate student majoring in 5GC Research
    • As I am a beginner in the Linux kernel, please feel free to send me an email if you find any errors.
    • mail
    • Linkin
"},{"location":"blog/20230920/Introduction_of_gtp5g_and_some_kernel_concepts/#reference","title":"Reference","text":"
  • https://bootlin.com
  • https://www.linuxjournal.com/article/8498
  • https://wiki.linuxfoundation.org/networking/generic_netlink_howto
  • https://www.kernel.org/doc/html/latest/driver-api/driver-model/overview.html
  • https://www.cnblogs.com/ssyfj/p/16230540.html
  • https://www.twblogs.net/a/5b81e5852b71772165aedd7a
  • https://www.cnblogs.com/ssyfj/p/16230540.html
  • IT blog by Ian Chen
  • IT blog by 0xff07

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20230927/Support_of_Time_Sensitive_Communication_and_Time_Synchronization_in_5G_system_-_Introduction_%28Rel-17%29/","title":"Support of Time Sensitive Communication and Time Synchronization in 5G system - Introduction (Rel-17)","text":"

Note

Author: Ya-shih Tseng Date: 2023/9/27

Before you read the article, I suggest you to read Time-Sensitive Networking over 5G system - Introduction (Rel-16).

"},{"location":"blog/20230927/Support_of_Time_Sensitive_Communication_and_Time_Synchronization_in_5G_system_-_Introduction_%28Rel-17%29/#overview","title":"Overview","text":"

This article will introduce TSN enhancements in 3GPP Release 17. We will start by discussing the new roles introduced in 5GS and their applicable scenarios. The architecture has been modified to accommodate a wider range of applications, and additional network functions have been added to offer a broader range of services.

Architcture to enable time sensitive communication and time synchronization

"},{"location":"blog/20230927/Support_of_Time_Sensitive_Communication_and_Time_Synchronization_in_5G_system_-_Introduction_%28Rel-17%29/#5gs-can-be-as-boundary-clock-and-transparent-clock","title":"5GS can be as Boundary Clock and Transparent Clock","text":"

Time Synchronization: describes how 5GS can operate as a PTP Relay (IEEE 802.1AS), as a Boundary Clock or as Transparent Clock (IEEE 1588) for PDU Session type Ethernet and IP.

According to 3GPP TS 23.501 Release 17, 5GS's role in TSN goes beyond just acting as a bridge. It now extends to UDP/IP applications. In addition to its original support for Ethernet through IEEE 802.1AS Time-Aware System, it has been updated to include four types of PTP instances described in IEEE 1588.

"},{"location":"blog/20230927/Support_of_Time_Sensitive_Communication_and_Time_Synchronization_in_5G_system_-_Introduction_%28Rel-17%29/#what-is-bc-and-tc","title":"What is BC and TC?","text":"

Difference types of PTP instances (clocks).

  • For the BC, you can have a GM as a time source, and it can also act as a time source itself, allowing other slave clocks to synchronize their time with it.
  • For the TC, it doesn't need to synchronize its time with other clocks. Its role is to receive time from one end, potentially adjust it, and then forward it to the next clock

For more detail about PTP instance / Clock types, please check IEEE 1588v2 PTP Support.

To support above applications, DS-TT and NW-TT have expanded their functionalities.

"},{"location":"blog/20230927/Support_of_Time_Sensitive_Communication_and_Time_Synchronization_in_5G_system_-_Introduction_%28Rel-17%29/#capability-of-ds-tt-and-nw-tt","title":"Capability of DS-TT and NW-TT","text":"

With release 17 now supporting for the scenarios when Master clock is behind the UE (uplink time sync, UE \u2013 UE time sync), behind the network (downlink time sync) and support for multiple (g)PTP domains.

Note

In release 16, the roles of 5GS, DS-TT, and NW-TT are fixed. The Data Network side follows the TSN master, while the UE side follows the slave. When a packet enters, the entry time is first recorded in NW-TT, and the exit time is recorded in DS-TT. The difference between these two times determines the packet's resident time.

In general case, when receiving PTP event messages from upstream PTP instances, the ingress TT (NW-TT or DS-TT) records an ingress timestamp (TSi) for each PTP event, such as Sync messages. Then, NW-TT determines whether to forward messages to DS-TT or discard it.

Subsequently, the egress TT creates an egress timestamp (TSe) for PTP events (i.e., Sync messages) in the external PTP network and modifies the payload of the PTP message to add/update the resident time.

  • As for 5GS operates as BC, NW-TT and DS-TT should support generate Sync, Follow_Up and Announce message. If DS-TT doesn't support it, NW-TT will take over.
"},{"location":"blog/20230927/Support_of_Time_Sensitive_Communication_and_Time_Synchronization_in_5G_system_-_Introduction_%28Rel-17%29/#ptp-transparent","title":"PTP transparent","text":"

TTs can support the following PTP transport modes described in IEEE 1588:

  • over UDP/IPv4
  • over UDP/IPv6
  • over IEEE 802.3 (Ethernet)
"},{"location":"blog/20230927/Support_of_Time_Sensitive_Communication_and_Time_Synchronization_in_5G_system_-_Introduction_%28Rel-17%29/#delay-measurements","title":"Delay measurements","text":"

Different PTP instances have different measurements of path delay due to hardware limitations.

TTs should support the following delay measurements as described in IEEE 1588:

  • Delay request-response mechanism
  • Peer-to-peer delay mechanism
  • which we talked about it in previous article.

Let's see what's the diference between them.

Difference measurements of path delay

  • For E2E (End-to-End) slaves send delay requests to the Master, passing through other devices in the network. Each TC (Transparent Clock) modifies the request to update the time it has spent locally. Finally, it reaches the Master, so the E2E measured delay represents the total time taken along the entire path.
  • For P2P (Point-to-Point) devices, they send requests to nearby neighbors to measure the delay time between each other.

By now, We introduce few supports for PTP instances. Then, How should these functionalities be managed?

"},{"location":"blog/20230927/Support_of_Time_Sensitive_Communication_and_Time_Synchronization_in_5G_system_-_Introduction_%28Rel-17%29/#time-sensitive-communication-and-time-synchronization-function","title":"Time Sensitive Communication and Time Synchronization Function","text":"

In release 16, TSN AF is used for exchanging messages to manage TSN bridges in conjunction with centralized network configuration (CNC). This means that TSN AF is responsible for managing the ports of DS-TT and NW-TT. Additionally, in release 17, TSCTSF is a new component introduced between PCF and NEF. To support AF requests related to time-sensitive communication.

TSCTSF can check the PTP functionalities supported by DS-TT and NW-TT by retrieving the following port management information or user plane node management information:

  • Supported PTP instance types;
  • Supported transport types;
  • Supported PTP delay mechanisms;
  • Grandmaster capability;
  • Supported PTP profiles;
  • Number of supported PTP instances.

AF can obtain the required services directly or indirectly through TSCTSF

  • e.g. AF can provide traffic pattern parameters to NEF. NEF will forward the received traffic pattern parameters to TSCTSF.
  • e.g. AF trusted by the operator can directly provide such traffic pattern parameters to TSCTSF.

TSCTSF may support AF to

  • Activate and deactivate the time syncgronization services
  • Control the time synchronization service for target UE.
  • Configure the TTs to operate on AF-selected method
  • Managing the DS-TT and NW-TT via exchange of PMIC and UMIC
  • Provide specific QoS traffic pattern
  • Creating the TSC Assistance Container based on individual traffic pattern parameters from the NEF/AF and providing it to the PCF.
"},{"location":"blog/20230927/Support_of_Time_Sensitive_Communication_and_Time_Synchronization_in_5G_system_-_Introduction_%28Rel-17%29/#abbreviation","title":"Abbreviation","text":"TSN Time-Sensitive network TT TSN Translator NW-TT Network-Side TSN Translator DS-TT Device-Side TSN Translator PTP Precision Time Protocol BC Boundary Clock E2E-TC End-to-End Transparent Clock P2P-TC Peer-to-Peer Transparent Clock GM Grand Master AF Application Function TSCTSF Time Sensitive Communication and Time Synchronization Function PMIC Port Management Information Container UMIC User Plane Node Management Information Container"},{"location":"blog/20230927/Support_of_Time_Sensitive_Communication_and_Time_Synchronization_in_5G_system_-_Introduction_%28Rel-17%29/#conclusion","title":"Conclusion","text":"

Release 17 expanded and improved the integration with IEEE TSN. This included enabling uplink synchronization through the 5G System (5GS), enhancing End-to-End Quality of Service (QoS) across multiple clock domains, and facilitating direct communication between UE within the 5GS network.

"},{"location":"blog/20230927/Support_of_Time_Sensitive_Communication_and_Time_Synchronization_in_5G_system_-_Introduction_%28Rel-17%29/#reference","title":"Reference","text":"
  • IEEE Std 802.1AS-2020: \u201cIEEE Standard for Local and metropolitan area networks--Timing and Synchronization for Time-Sensitive Applications\u201d.
  • IEEE Std 1588: \u201cIEEE Standard for a Precision Clock Synchronization Protocol for Networked Measurement and Control Systems\u201d, Edition 2019.
  • 3GPP TS 23.501 v16.6.0: \"System Architecture for the 5G System\"
  • 3GPP TS 23.501 v17.8.0: \"System Architecture for the 5G System\"
  • 3GPP TS 23.502 v17.4.0: \"Procedures for the 5G System\"
  • Time-Sensitive Networking (TSN) Task Group | - IEEE 802.1
"},{"location":"blog/20230927/Support_of_Time_Sensitive_Communication_and_Time_Synchronization_in_5G_system_-_Introduction_%28Rel-17%29/#about","title":"About","text":"

Hi, This is Ya-shih Tseng. I am currently researching the implementation of 5G TSN as part of my master's studies. Feel free to discuss with me.

  • Here is my linkedin profile

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20231004/20231004/","title":"Introduction of MPTCP","text":"

Note

Author: \u5f35\u54f2\u777f (Jerry) Date: 2023/10/04

MPTCP (multipath TCP) is a set of extensions to regular TCP that allows a connection to use many paths to transport data simultaneously. The simultaneous use of these multiple paths for a TCP/IP session would improve resource usage within the network, and if one path is blocked, it can use another path to transport data, improving resilience to network failure. A common use of mptcp is: Most mobile phones today have the ability to connect to wifi or mobile networks (4G/5G), but regular TCP can't use two network resources simultaneously, and if the network we were originally using (for example, wifi) is disconnected, it cannot automatically switch to using the mobile network. Therefore, the connection at the application layer will also be disconnected. In this article, I will introduce MPTCP, including how to establish an MPTCP connection, how to start a new subflow, the ack mechanism, and the congestion control algorithm it uses.

"},{"location":"blog/20231004/20231004/#initiating-an-mptcp-connection","title":"Initiating an MPTCP Connection","text":"

MPTCP implements multi-path transmission by creating multiple subflows; each subflow is similar to a general TCP connection. Use the 3-way handshaking to establish the connection. But instead of a separate connection, the subflow is bound into an existing MPTCP session. MPTCP uses SYN, SYN/ACK, and ACK packets to establish connections. But these sockets also carry the MP_CAPABLE option (in the optional field of the tcp header), which is used to verify whether the remote host supports MPTCP and exchange some information to authenticate the establishment of additional subflows.

RFC8684 3.1

The above figure shows the structure of MP_CAPABLE. First, the initiator sends only 4 octets. If the listener also supports MPTCP v1, it will respond SYN/ACK, and the listener's key will be included in MP_CAPABLE. Then the initiator replies ACK, which contains the sender's key and the receiver's key. These keys are used to authenticate the addition of future subflows to this connection; all future subflows will identify the connection using a 32-bit \"token\". This token is a cryptographic hash of this key. The use of flags in the fourth octets are as follow:

  • A: Indicate \"Checksum required\".
  • B: It is an extensibility flag, must be set to 0.
  • C: Indicate that the sender of this option will not accept additional MPTCP subflows to the source address and port.
  • D through H: The flags D through H, are used for crypto algorithm negotiation. In MPTCP v1, only the rightmost bit, labeled H, is assigned. Bit H indicates the use of HMAC-SHA256.
"},{"location":"blog/20231004/20231004/#starting-a-new-subflow","title":"Starting a New Subflow","text":"

Once an MPTCP connection has begun with the MP_CAPABLE exchange, further subflows can be added to the connection. A new subflow is started as a normal TCP SYN/ACK exchange. But these packets contain the Join Connection option.

RFC8684 3.2

This is the first Join Connection (MP_join) Option for the initial SYN. The token is a cryptographic hash of the receiver's key. The MP_join also contains an \"Address ID\", which is used to identify the source address of this packet. This mechanism allows addresses to be removed even if they are changed by NAT. The last bit of the third byte (flag B) indicates that the sender (1) wishes this subflow to be used as a backup path (B=1) in the event of failure of other paths or (2) wants the subflow to be used as part of the connection immediately. MP_JOIN also contains a random number to prevent replay attacks. If MP_JOIN contains a valid token, the recipient will respond with a SYN/ACK also containing an MP_JOIN option containing a random number and a truncated (leftmost 64-bit) HMAC. Transport HMAC is to have previously exchanged random data (in the first two SYN packets) that is used as the \"message\".

RFC8684 3.2

If the HMAC in SYN/ACK is valid, then the initiator will send its authentication in the third packet (ACK). This data needs to be sent reliably since it is the only time this HMAC is sent. Therefore, receipt of this packet triggers a regular TCP ACK in response, and the packet will be retransmitted if this ACK is not received.

RFC8684 3.2

RFC8684 3.2

This is total handshake procedure.

"},{"location":"blog/20231004/20231004/#mptcp-operation-and-data-transfer","title":"MPTCP Operation and Data Transfer","text":"

MPTCP takes one input data stream and splits it into subflows. It then uses a 64-bit data sequence number to number all the data and let the data be reassembled at the receiver. Each subflow has its own 32-bit sequence number space, utilizing the regular TCP sequence number header, and an MPTCP option (Data Sequence Signal optional) maps the subflow sequence space to the data sequence space. MPTCP uses the Data Sequence Signal optional to carry the Data Sequence Mapping, which consists of the subflow sequence number, data sequence number, and length for which this mapping is valid. The Data Sequence Number is the starting sequence number for the subflow, and the Subflow Sequence Number is the sequence number only relevant to the subflow. The Data Ack in the Data Sequence Signal optional is used to ACK for the connection, indicating how much data has been successfully received.

RFC8684 3.3

"},{"location":"blog/20231004/20231004/#coupled-congestion-control-algorithm","title":"Coupled Congestion Control Algorithm","text":"

There are two goals that MPTCP congestion control needs to meet: 1. A multipath flow should perform at least as well as a single-path flow would on the best of the paths available to it. 2. A multipath flow should take no more capacity on any path or collection of paths than if it were a single-path TCP flow using the best of those paths.

The second goal is to prevent MPTCP traffic from occupying too many resources and harming other TCP flows on the bottleneck link. So if we combine the first goal and the second goal, it means that the total capacity that MPTCP occupies should equal the capacity that the best path can transport. (In the situation where there is some competitive TCP flow, if there is no other competitive flow, then MPTCP will occupy all the capacity it can use.)

The algorithm rule: * Each ACK on subflow r, increase the window by . * Each loss on subflow r, decrease the window by .

is the current window size on path r, is equilibrium window size on path r, and similarly for the total window size.

And how derived? Here I write down some of my thoughts after reading the paper. There may be errors. If you find any, please tell me directly. First, we assume that , so if the transmission speed of subflow r is slower than tcp, then the increments of the window size r is . Because the increase and decrease of the window in the equilibrium state are equal to each other, so we have the fallowing equation.

is the loss rate of subflow r.Then we assume loss rate is small.

Now assume that there is tcp on this subflow, then we have:

p is the loss rate. Here, assume p is equal to .(But actually, they would not be equal if there was no other competitive flow. is smaller, so we measure a better tcp performence.) Substitute with p, then we have:

And we want the total capacity MPTCP occupies to equal the capacity that the best path can transport using TCP. So we have the equation:

Window size divide by RTT is the traffic send on path per second.

Finally, we can derive .

Because tcp performance on path r is measured by loss rate , if there is no competitive flow, mptcp can use more resources, and the increments of the window finally bounded by . So if we only run mptcp on many paths, it's like we have many TCP connecion, the total traffic is sum of every subflow's max transmission capability.

"},{"location":"blog/20231004/20231004/#how-to-use-mptcp","title":"How to use MPTCP","text":"

As for how to use mptcp under Linux system, you can refer to this article. It describes how to use mptcpize to change the original tcp connection to mptcp.

"},{"location":"blog/20231004/20231004/#about","title":"About","text":"

Hello! My name is \u5f35\u54f2\u777f, and my current research topic is ATSSS (Access Traffic Steering, Switching and Splitting). If you find any mistakes in my articles, please contact me.

  • Linkedln
"},{"location":"blog/20231004/20231004/#reference","title":"Reference","text":"
  • RFC 8684
  • RFC 6356
  • Coupled Congestion Control Algorithm
  • MPTCP congestion control
  • How to use MPTCP

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20231018/20231018/","title":"LTE Authentication Introduction: EPS-AKA'","text":"

Note

Author: Wilson Date: 2023/10/18

"},{"location":"blog/20231018/20231018/#abstract","title":"Abstract","text":"

In this article, I will delve into the topic of EPS-AKA', a subject closely related to 4G network security. The article is structured as follows:

  1. An introduction to EPS-AKA'.
  2. An explanation of the concept of authentication.
  3. An exploration of key hierarchy generation in the 4G network, emphasizing its relevance to the EPS-AKA' procedure.
  4. A detailed examination of the EPS-AKA' procedure.
  5. A comparison between 4G AKA and 5G AKA, highlighting both their similarities and differences.
"},{"location":"blog/20231018/20231018/#introduction","title":"Introduction","text":"

Every day, people use apps like Line to send messages, watch videos on YouTube, and send emails via Gmail. To do any these activities, a connection to the Internet must be established. However, once a connection is made, the data packets transmitted are at risk of being intercepted. To ensure the confidentiality of the data and to verify its integrity, the LTE Security Procedure is initiated whenever a mobile phone goes online or sends a message.

The LTE Security Procedure is categorized into three stages :

  • EPS-AKA'
  • NAS Security
  • AS Security

EPS-AKA' stands for \"Evolved Packet System-Authentication and Key Agreement\". As suggested by its full name, AKA primarily involves two key actions:

  • Authentication
  • Key Agreement

The focus of this article will be on EPS-AKA', which is further divided into two main steps:

  1. In the first step, the network generate an authentication vector. Some of the information within this vector is used for authentication.
  2. The second step involves mutual authentication between the user and the network.
"},{"location":"blog/20231018/20231018/#understanding-the-definition-of-authentication","title":"Understanding the Definition of Authentication","text":"

Most people have come across the term \"authentication\", and its definition varies depending on context and interpretation. For the purposes of this article, and based on my own understanding, I'll use an everyday analogy to explain the concept of authentication.

Imagine during summer of winter vacation, your family decides to travel abroad. Before you board the plane, you must clear customs. The customs officier determines if you can proceed by examining your passport. This procedure resembles the process of authentication. As illustrated in Figure 1, a traveler presents their passport to a customs officer who then compares the passport details with information on their computer. If the passport's contents match the computer's records, you are granted passage. At the heart of authentication is this act of verification.

In the context of the EPS-AKA' procedure, authentication occurs twice:

  1. Initially, the HSS provides the to the UE. The UE then compares the received whith its . If the mathes the , it indicates sucessful authentication. This step verifies the authenticity of the HSS and, by extension, the network.
  2. In the second instance, the UE forwards the RES to the MME. The MME then compares the provided RES with the XRES. If the RES aligns with the XRES, authentication is successful. This stage confirms the authenticity of the UE.

Figure 1. The Definition of Authentication

"},{"location":"blog/20231018/20231018/#key-hierarchy-generation-in-4g","title":"Key Hierarchy Generation in 4G","text":"

The Figure 2 is the key hierarchy generation in 4G. When an UE is generated, the IMSI and K value are stored in the USIM card, and the IMSI and K value are regarded as the permanent value. When an UE subscribes to the operator's network, the HSS will get the permanent value of the UE's IMSI and K.

The USIM and HSS use the K as the base to generate the CK and IK in the EPS-AKA' stage of the LTE Security Procedure. The HSS uses the CK and IK as the base to generate the and transmits the to the MME. The USIM transmits the CK and IK to the UE, and UE uses the CK and IK as the base to generate the .

The UE and MME use the as the base and generate separately three keys in the NAS Security stage of the LTE Security Procedure. Three keys are the , , and . The and are used in the NAS Security stage, and the is transmitted to the eNodeB.

The UE and eNodeB use the as the base and generate separately three keys in the AS Security stage of the LTE Security Procedure. Three keys are the , , and .

Figure 2. Key Hierarchy Generation in 4G

"},{"location":"blog/20231018/20231018/#eps-aka-procedure","title":"EPS-AKA' Procedure","text":"

Figure 3. EPS-AKA' Procedure

"},{"location":"blog/20231018/20231018/#the-ue-sends-the-attach-request-to-the-mme","title":"The UE sends the Attach Request to the MME","text":"

The Figure 3 shows the entire EPS-AKA' procedure. The UE sends the Attach Request to the MME. The Attach Request contains the IMSI, UE Network Capability, and . The IMSI is like the ID number of this UE. The UE Network Capability is what encryption and integrity protection algorithms the UE supports. The UE sends UE Network Capability to tell MME. The content of UE Network Capability is like EEA0=on, EEA1=on, EEA2=off, ..., EIA1=on, EIA2=on, ... The on and off symbols are represented by one bit. The represents the number corresponding to the . The is represented by 3 bits. The value of is 7, which means that the UE does not have the yet.

"},{"location":"blog/20231018/20231018/#the-mme-sends-the-authentication-information-request-to-the-hss","title":"The MME sends the Authentication Information Request to the HSS","text":"

The MME sends the Authentication Information Request to the HSS after receiving the Attach Request. The Authentication Information Request contains the IMSI, SN ID, n, and Network Type. The full English name of SN ID is Serving Network ID, which refers to the network that the user wants to connect to. The value of SN ID is usually composed of the MCC and MNC (PLMN ID). The n represents how many verifcation vectors are sent to the HSS. The Network Type is what kind of wireless network the user wants to connect to the Internet from my understanding.

"},{"location":"blog/20231018/20231018/#the-hss-sends-the-authentication-information-answer-to-the-mme","title":"The HSS sends the Authentication Information Answer to the MME","text":"

The HSS automatically generates the RAND and SQN after receiving the Authentication Information Request. The HSS inputs the K, SQN, and RAND into the Cryptographic Function. The Cryptographic Function uses the EPS AKA algorithm and outputs the XRES, , CK, and IK. The HSS inputs the CK, IK, SQN, and SN ID into the KDF (Key Derivation Function). The KDF outputs . Then, the RAND that is generated by the HSS itself, the and XRES that are generated by the Cryptographic Function, and the that is generated by KDF are combined into the AV. The Authentication Information Answer includes AV and is sent to the MME.

"},{"location":"blog/20231018/20231018/#the-mme-sends-the-authentication-request-to-the-ue","title":"The MME sends the Authentication Request to the UE","text":"

The MME selects an AV among multiple AVs and sends the Authentication Request to the UE after receiving the Authentication Information Answer. The Authentication Request contains the RAND, , and .

"},{"location":"blog/20231018/20231018/#the-ue-sends-the-authentication-response-to-the-mme","title":"The UE sends the Authentication Response to the MME","text":"

The UE receives the Authentication Request. Why do you see the SQN is generated by the HSS in the Figure 3? The carries the SQN. The UE can find the SQN in the . The USIM inputs the K, SQN, and RAND into the Cryptographic Function. The Cryptographic Function uses the EPS AKA algorithm and outputs the RES, , CK, and IK. When the UE gets , it compares the with the . If the authentication is successful, the UE will generate the RES. The Authentication Response contains the RES and is returned to the MME.

"},{"location":"blog/20231018/20231018/#the-mme-receives-the-authentication-response","title":"The MME receives the Authentication Response","text":"

The MME compares the XRES with RES after receiving the Authentication Response. If the content of the XRES and RES are the same, it means that the authentication is successful. The authentication stage has come to an end.

Finally, the UE inputs the CK, IK, SQN, and SN ID into the KDF. The KDF outputs . The UE and MME have the same . The doesn't be transmitted between the UE and MME. The is transmitted in the form of the to prevent the from being stolen by a third party.

"},{"location":"blog/20231018/20231018/#4g-vs-5g-aka-a-comparison","title":"4G vs. 5G AKA: A Comparison","text":"

Similarities:

  1. Both 4G and 5G UEs utilize the same Universal Subscriber Identity Module (USIM).
  2. Both systems rely on shared symmetric keys for authentication.

Differences:

  1. Authentication Mechanism:

    • 4G: EPS-AKA'
    • 5G: 5G-AKA and EAP-AKA'
  2. Serving Network:

    • 4G: MME
    • 5G: SEAF
  3. Home Network:

    • 4G: HSS
    • 5G: AUSF, UDM, ARPF, and SIDF
  4. Network Function for Authentication Vector Generation:

    • 4G: HSS
    • 5G: UDM and ARPF
  5. Network Function to Verify UE Identity:

    • 4G: MME
    • 5G
      • 5G-AKA: SEAF and AUSF
      • EAP-AKA': AUSF
  6. Key Hierarchy Generation:

    • 4G: K -> CK + IK ->
    • 5G
      • 5G-AKA: K -> CK + IK -> ->
      • EAP-AKA': K -> CK + IK -> CK' + IK' -> ->
"},{"location":"blog/20231018/20231018/#reference","title":"Reference","text":"
  • LTE Security I: Concept and Authentication
  • LTE Security II: NAS and AS Security
  • https://kknews.cc/zh-tw/code/65y6x9l.html
  • https://nccnews.com.tw/202010/ch4.html
"},{"location":"blog/20231018/20231018/#about","title":"About","text":"

Hi, my name is Wilson. I am a master\u2019s student. My main area of research is network slicing. In the future, I will introduce more information about 5G. Hope you enjoy it.

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20231108/20231108/","title":"Introduction of IP Multimedia Subsystem Part 1","text":"

Note

Author: Elisa Lee Date: 2023/11/08

The IMS is a framework for delivering multimedia and voice services over IP networks. It plays a crucial role in enabling communication services like voice, video, and messaging to be delivered in a converged, efficient, and scalable manner.

"},{"location":"blog/20231108/20231108/#definition-and-overview-of-ims","title":"Definition and Overview of IMS","text":""},{"location":"blog/20231108/20231108/#introduction-to-ims","title":"Introduction to IMS","text":"

A standardized, open architectural framework that allows multimedia services to be delivered over IP networks. It is defined by the 3GPP (3rd Generation Partnership Project) and widely used in mobile networks.

"},{"location":"blog/20231108/20231108/#purpose-and-significance-of-ims","title":"Purpose and Significance of IMS","text":""},{"location":"blog/20231108/20231108/#purpose-of-ims","title":"Purpose of IMS","text":""},{"location":"blog/20231108/20231108/#multimedia-services","title":"Multimedia Services","text":"

IMS is designed to support a wide range of multimedia services such as voice, video, messaging, and data services over IP networks.

"},{"location":"blog/20231108/20231108/#interoperability","title":"Interoperability","text":"

It provides a standardized architecture that allows different network elements and applications from various vendors to work together seamlessly. This promotes interoperability, which is vital in a diverse telecommunications ecosystem.

"},{"location":"blog/20231108/20231108/#service-convergence","title":"Service Convergence","text":"

IMS allows the convergence of various services like voice, video, and data onto a single IP network. This makes it possible for users to access multiple services through a unified interface.

"},{"location":"blog/20231108/20231108/#quality-of-service-qos","title":"Quality of Service (QoS)","text":"

IMS enables network operators to prioritize and manage different types of traffic, ensuring that services like voice and video receive the necessary bandwidth and low latency for a high-quality user experience.

"},{"location":"blog/20231108/20231108/#session-management","title":"Session Management","text":"

It manages sessions for different types of services, ensuring that sessions are established, maintained, and terminated appropriately.

"},{"location":"blog/20231108/20231108/#mobility-management","title":"Mobility Management","text":"

IMS allows for seamless mobility across different access networks (e.g., Wi-Fi, LTE, 5G), ensuring continuity of services as users move between different coverage areas.

"},{"location":"blog/20231108/20231108/#security","title":"Security","text":"

IMS incorporates security features and protocols to protect the confidentiality, integrity, and authenticity of communication sessions.

"},{"location":"blog/20231108/20231108/#billing-and-charging","title":"Billing and Charging","text":"

It supports real-time charging and billing capabilities, allowing operators to accurately charge users for the services they consume.

"},{"location":"blog/20231108/20231108/#significance-of-ims","title":"Significance of IMS","text":""},{"location":"blog/20231108/20231108/#transition-to-all-ip-networks","title":"Transition to All-IP Networks","text":"

IMS is a key enabler in the shift from traditional circuit-switched networks to all-IP networks. This transition is essential for supporting advanced multimedia services.

"},{"location":"blog/20231108/20231108/#facilitates-4g-and-5g-technologies","title":"Facilitates 4G and 5G Technologies","text":"

IMS is a critical component for the deployment and operation of 4G LTE and 5G networks, providing the infrastructure for delivering high-speed data services and advanced multimedia applications.

"},{"location":"blog/20231108/20231108/#enhances-user-experience","title":"Enhances User Experience","text":"

By enabling the delivery of high-quality multimedia services, IMS significantly enhances the user experience, particularly for applications like video calling, streaming, and real-time gaming.

"},{"location":"blog/20231108/20231108/#enables-next-generation-services","title":"Enables Next-Generation Services","text":"

IMS is foundational for emerging technologies like IoT (Internet of Things) and Industry 4.0, enabling a wide range of applications and services across various industries.

"},{"location":"blog/20231108/20231108/#promotes-innovation","title":"Promotes Innovation","text":"

IMS's standardized architecture encourages innovation by allowing developers to create new services and applications that can run on IMS-enabled networks.

"},{"location":"blog/20231108/20231108/#global-standardization","title":"Global Standardization","text":"

IMS is based on international standards defined by organizations like 3GPP and ETSI (European Telecommunications Standards Institute), ensuring global compatibility and uniformity in telecommunications networks.

"},{"location":"blog/20231108/20231108/#key-components-of-ims","title":"Key Components of IMS","text":""},{"location":"blog/20231108/20231108/#sip-session-initiation-protocol","title":"SIP (Session Initiation Protocol)","text":"

SIP is a signaling protocol used for initiating, maintaining, modifying, and terminating real-time sessions that involve video, voice, messaging, and other communications applications and services over the internet. It operates at the application layer of the OSI model and is widely used for VoIP (Voice over Internet Protocol) and other multimedia communication applications.

"},{"location":"blog/20231108/20231108/#role-of-sip-in-ims","title":"Role of SIP in IMS","text":"

In the context of IMS, SIP serves as the primary signaling protocol.

"},{"location":"blog/20231108/20231108/#session-establishment","title":"Session Establishment","text":"

SIP is used to initiate communication sessions between devices or applications. When a user wants to make a call, send a message, or establish a multimedia session, SIP is responsible for setting up the connection.

"},{"location":"blog/20231108/20231108/#user-location-and-registration","title":"User Location and Registration","text":"

SIP allows devices to register their location on the network. This information is crucial for routing calls and messages to the correct destination.

"},{"location":"blog/20231108/20231108/#call-control","title":"Call Control","text":"

SIP handles the signaling for call control functions such as call setup, call hold, call transfer, and call release. It defines how these operations should be communicated between endpoints.

"},{"location":"blog/20231108/20231108/#service-invocation","title":"Service Invocation","text":"

It enables the invocation of various services within IMS, like call forwarding, call waiting, and supplementary services.

"},{"location":"blog/20231108/20231108/#session-modification-and-termination","title":"Session Modification and Termination","text":"

SIP is also responsible for modifying existing sessions (e.g., adding participants to a call) and terminating sessions when the user ends the communication.

"},{"location":"blog/20231108/20231108/#media-negotiation","title":"Media Negotiation","text":"

While SIP is responsible for signaling, it doesn't handle the actual media (audio, video, etc.) transfer. It works in conjunction with protocols like RTP (Real-time Transport Protocol) for media transmission.

"},{"location":"blog/20231108/20231108/#signaling-for-establishing-modifying-and-terminating-sessions","title":"Signaling for establishing, modifying, and terminating sessions","text":""},{"location":"blog/20231108/20231108/#session-establishment_1","title":"Session Establishment:","text":"
  • Caller's Request: The caller's device sends a SIP INVITE message to the SIP server, indicating the intention to establish a session with the callee.

  • SIP Server Processing: The SIP server processes the INVITE message. It checks the user's location and forwards the request to the recipient's SIP server.

  • Recipient's Response: The recipient's SIP server delivers the INVITE message to the callee's device. If the recipient is available and agrees to the session, their device sends a SIP 200 OK response back.

  • Session Establishment: Once the caller's device receives the 200 OK response, it acknowledges the establishment of the session. The media streams (audio, video, etc.) can then begin.

"},{"location":"blog/20231108/20231108/#session-modification","title":"Session Modification","text":"

If a user wants to modify an ongoing session, such as adding another participant to a call, the SIP signaling is used to send an UPDATE request. This request is processed by the SIP servers and communicated to the affected parties.

"},{"location":"blog/20231108/20231108/#session-termination","title":"Session Termination","text":"

When a user decides to end a session, their device sends a SIP BYE message. This message is processed by the SIP servers, and the appropriate responses are sent to indicate the termination of the session. It's important to note that SIP is a text-based protocol, and it uses URLs similar to those used on the web to identify users and services. This makes it versatile and easy to integrate into existing internet infrastructure.

"},{"location":"blog/20231108/20231108/#sdp-session-description-protocol","title":"SDP (Session Description Protocol)","text":"

The SDP is a protocol used in multimedia communications to negotiate and describe sessions between participants. It provides a concise and human-readable way to convey information about the characteristics of a multimedia session.

"},{"location":"blog/20231108/20231108/#role-of-sdp-in-session-negotiation-and-description","title":"Role of SDP in session negotiation and description","text":""},{"location":"blog/20231108/20231108/#session-negotiation","title":"Session Negotiation","text":"
  • Media Types: This includes information about the type of media (e.g., audio, video, application data) that will be exchanged during the session.

  • Codecs and Formats: SDP specifies which codecs (compression/decompression algorithms) and formats will be used for media encoding and decoding.

  • Transport Protocols: It describes the transport protocol and ports that will be used for sending and receiving media packets.

  • Network Addresses: SDP provides information about the IP addresses and ports where media should be sent.

  • Timing and Synchronization: SDP can include information about timing aspects like session start and end times, as well as synchronization of different media streams.

"},{"location":"blog/20231108/20231108/#session-description","title":"Session Description","text":"

SDP accomplishes session description by providing a structured text-based format that includes information about the session parameters mentioned above. This description is typically conveyed in the form of an SDP message.

"},{"location":"blog/20231108/20231108/#negotiating-capabilities","title":"Negotiating Capabilities","text":"
  • Establishing Media Streams: SDP is crucial for establishing media streams. It specifies how media should be encoded, transported, and synchronized between participants. This ensures that audio, video, and other data are transmitted and received correctly.

  • Interoperability: SDP is a standardized format, allowing devices and applications from different vendors to communicate effectively. This promotes interoperability, which is crucial in diverse multimedia environments.

  • Dynamic Adaptation: SDP can be updated dynamically during a session. For example, if network conditions change, SDP can be used to renegotiate parameters like codecs or bitrates to ensure the best possible quality.

"},{"location":"blog/20231108/20231108/#hss-home-subscriber-server","title":"HSS (Home Subscriber Server)","text":"

The HSS is a crucial component within the IMS architecture. It serves as a central database that stores and manages subscriber-related information for users in a telecommunications network.

"},{"location":"blog/20231108/20231108/#definition-and-function-of-hss-in-ims","title":"Definition and function of HSS in IMS","text":""},{"location":"blog/20231108/20231108/#definition","title":"Definition","text":"

The HSS is a core network element in the IMS architecture. It is a centralized database that contains essential information about subscribers, including their identities, services, and authentication credentials. The HSS plays a pivotal role in facilitating various services and functions within IMS.

"},{"location":"blog/20231108/20231108/#function-of-hss-in-ims","title":"Function of HSS in IMS","text":"
  • Subscriber Profile Management: The HSS stores detailed information about each subscriber, including their IMS public user identity, contact information, allocated services, and preferences. This data is used for session setup, authorization, and routing decisions.

  • Authentication and Authorization: HSS is responsible for authenticating users when they attempt to access IMS services. It verifies the subscriber's credentials (such as username and password) before granting access. Additionally, it provides information about the services and capabilities available to each subscriber.

  • Subscriber Location: It keeps track of the location of subscribers within the network. This information is crucial for routing calls and messages to the appropriate serving network element.

  • Security Functions: The HSS plays a role in the security architecture of IMS. It stores security-related keys and information necessary for secure communication between the subscriber's device and the IMS network.

  • Policy and Charging Control (PCC): HSS may also be involved in policy control and charging decisions. It provides information about the subscriber's subscription and policy rules that dictate how services are delivered and charged.

"},{"location":"blog/20231108/20231108/#managing-subscriber-data-and-authentication","title":"Managing subscriber data and authentication","text":""},{"location":"blog/20231108/20231108/#managing-subscriber-data","title":"Managing Subscriber Data","text":"
  • Subscriber Registration: When a subscriber logs into the network, the HSS is involved in the registration process. It verifies the subscriber's identity and allocates necessary resources and services.

  • Profile Updates: The HSS is responsible for updating subscriber profiles. This includes adding or removing services, updating contact information, or changing authentication credentials.

  • Roaming Support: In the case of roaming, the HSS plays a key role in managing the subscriber's information, ensuring that services can be delivered seamlessly across different networks.

"},{"location":"blog/20231108/20231108/#authentication","title":"Authentication","text":"
  • Challenge-Response Mechanism: When a subscriber attempts to access IMS services, the HSS challenges the subscriber's device for authentication credentials.

  • Verifying Credentials: The HSS verifies the subscriber's credentials using authentication algorithms and keys. This ensures that only authorized users can access the network.

  • Generating Security Tokens: After successful authentication, the HSS generates security tokens that are used to secure the communication sessions between the subscriber's device and the network.

  • Subscriber Privacy and Security: The authentication process ensures that subscriber information and communication sessions are protected from unauthorized access and interception.

"},{"location":"blog/20231108/20231108/#cscf-call-session-control-function","title":"CSCF (Call Session Control Function)","text":"

The CSCF is a crucial element in the IMS architecture, responsible for handling call/session control and routing within the network. There are three types of CSCFs, each serving specific roles in the IMS framework.

"},{"location":"blog/20231108/20231108/#proxy-cscf-p-cscf","title":"Proxy CSCF (P-CSCF)","text":"

The P-CSCF is a vital component within the IMS architecture. It serves as the initial point of contact for UE (User Equipment) when establishing communication sessions. The P-CSCF behaves as a proxy, managing incoming requests by either processing them internally or forwarding them to appropriate destinations. It does not modify the Request URI in the SIP INVITE message. In certain scenarios, the P-CSCF may also act as a User Agent, independently generating SIP transactions. Additionally, the P-CSCF's address is discovered by UEs following PDP context activation, allowing for seamless initiation of sessions.

"},{"location":"blog/20231108/20231108/#key-functions","title":"Key Functions","text":"
  • Forwarding SIP register requests from the UE to the appropriate I-CSCF based on the UE's home domain.

  • Relaying SIP messages received from the UE to the designated SIP server (e.g., S-CSCF) obtained during the registration process.

  • Forwarding SIP requests or responses to the UE.

  • Detecting and handling emergency session establishment requests according to specified error handling procedures.

  • Generating Call Detail Records for transaction records.

  • Establishing and maintaining Security Associations with each UE.

  • Performing SIP message compression and decompression.

  • Authorizing bearer resources and managing QoS

"},{"location":"blog/20231108/20231108/#interrogating-cscf-i-cscf","title":"Interrogating CSCF (I-CSCF)","text":"

The I-CSCF functions as the initial entry point within an operator's network for all incoming connections directed to a user of that network operator or to a roaming user currently situated within the operator's service area. An operator's network may incorporate multiple I-CSCFs to manage these incoming connections effectively.

"},{"location":"blog/20231108/20231108/#key-functions_1","title":"Key Functions","text":"
  • Assigning a Serving-CSCF (S-CSCF) to a user during the SIP registration process.
  • Routing SIP requests received from other networks to the appropriate S-CSCF.
  • Retrieving the S-CSCF address from the HSS.
  • Forwarding SIP requests or responses to the determined S-CSCF.
  • Generating Call Detail Records to maintain transaction records.
"},{"location":"blog/20231108/20231108/#serving-cscf-s-cscf","title":"Serving CSCF (S-CSCF)","text":"

The S-CSCF is a pivotal component in the IMS architecture. Its primary role involves providing session control services for the UE. The S-CSCF maintains a session state essential for supporting various services as required by the network operator. It's worth noting that different S-CSCFs within an operator's network may have varying functionalities tailored to specific requirements.

"},{"location":"blog/20231108/20231108/#key-functions_2","title":"Key Functions","text":"
  • May act as a Registrar, accepting registration requests and making its information available through the location server (e.g., HSS).
  • Controls sessions for registered endpoints, including rejecting IMS communication to/from barred public user identities.
  • May behave as a Proxy Server, handling requests internally or forwarding them.
  • May behave as a User Agent, capable of terminating and independently generating SIP transactions.
  • Provides endpoints with service event-related information, such as notification of tones/announcements and location of additional media resources, along with billing notifications.
  • Obtains the Address of the I-CSCF for the network operator serving the destination user and forwards SIP requests/responses accordingly.
  • Routes SIP requests/responses based on the type of procedure.
"},{"location":"blog/20231108/20231108/#cscfs-role-in-callsession-control-and-routing","title":"CSCFs Role in Call/Session Control and Routing","text":""},{"location":"blog/20231108/20231108/#session-setup-and-control","title":"Session Setup and Control","text":"
  • P-CSCF: Acts as the first point of contact for UE and handles initial session signaling. It forwards requests to the appropriate destination, facilitating session establishment.

  • I-CSCF: Serves as the entry point within the operator's network for connections directed to a user. It assigns a S-CSCF during SIP registration and routes requests/responses accordingly.

  • S-CSCF: Manages session control services for the UE. It maintains a session state as required by the network operator and controls sessions for registered endpoints.

"},{"location":"blog/20231108/20231108/#authentication-and-authorization","title":"Authentication and Authorization","text":"
  • P-CSCF: May perform Registrar functions, accepting registration requests from the UE and making information available through the location server (e.g., HSS).

  • S-CSCF: Manages authentication and authorization for the UE, ensuring secure access to network services.

"},{"location":"blog/20231108/20231108/#routing-and-interrogation","title":"Routing and Interrogation","text":"
  • I-CSCF: Routes SIP requests received from other networks towards the appropriate S-CSCF. It retrieves the S-CSCF address from the HSS and forwards requests/responses accordingly.
"},{"location":"blog/20231108/20231108/#service-enforcement","title":"Service Enforcement","text":"
  • S-CSCF: Enforces services by providing endpoints with service event-related information, such as notification of tones/announcements, location of additional media resources, and billing notifications.
"},{"location":"blog/20231108/20231108/#interworking-with-other-networks","title":"Interworking with Other Networks","text":"
  • S-CSCF: Depending on operator policy, may forward SIP requests or responses to SIP servers located within an ISP domain outside of the IMS Core Network. It can also forward requests for call routing to the PSTN or CS Domain.
"},{"location":"blog/20231108/20231108/#architecture-and-interfaces-of-ims","title":"Architecture and Interfaces of IMS","text":""},{"location":"blog/20231108/20231108/#architecture-of-ims","title":"Architecture of IMS","text":""},{"location":"blog/20231108/20231108/#mgcfmedia-gateway-control-function","title":"MGCF(Media Gateway Control Function)","text":"

The MGCF is a crucial component within the IMS architecture. Its primary role is to facilitate the interconnection between the IMS network and circuit-switched networks, such as the Public Switched Telephone Network (PSTN) or the Circuit-Switched domain.

"},{"location":"blog/20231108/20231108/#asapplication-server","title":"AS(Application Server)","text":"

An AS in the context of the IMS is a critical component responsible for providing various value-added services to users within the IMS network. The AS hosts and executes applications that deliver a wide range of services, enriching the user experience with multimedia capabilities.

"},{"location":"blog/20231108/20231108/#tas-telephony-application-server","title":"TAS (Telephony Application Server)","text":"

The TAS is a critical component within the IMS architecture. It plays a key role in delivering advanced telephony services and features in a multimedia-rich environment. The TAS is responsible for the provisioning, execution, and control of telephony-related applications and services within the IMS network.

"},{"location":"blog/20231108/20231108/#p-cscf","title":"P-CSCF","text":"

As described above.

"},{"location":"blog/20231108/20231108/#i-cscf","title":"I-CSCF","text":"

As described above.

"},{"location":"blog/20231108/20231108/#s-cscf","title":"S-CSCF","text":"

As described above.

"},{"location":"blog/20231108/20231108/#hss","title":"HSS","text":"

As described above.

"},{"location":"blog/20231108/20231108/#interfaces-of-ims","title":"Interfaces of IMS","text":""},{"location":"blog/20231108/20231108/#gm-interface","title":"Gm Interface","text":"
  • Description: The Gm Interface plays a pivotal role in all user-based communications. It serves as the link between the UE and the P-CSCF, facilitating various functions such as registration, de-registration, handling incoming and outgoing calls, managing SIP signaling, and enabling any form of communication between the user and the IMS network. To ensure security, the Gm and Mw Interfaces are physically isolated. This interface is responsible for authentication, encryption, and comprehensive session control. Gm utilizes SIP and SDP protocols.
  • Function: Facilitates connectivity between the UE and the IMS network for tasks including registration, authentication, encryption, and session control.
"},{"location":"blog/20231108/20231108/#mw-interface","title":"Mw Interface","text":"
  • Description: The Mw Interface facilitates communication between CSCF nodes within the IMS core network. Usually, the Session Border Controller (SBC) hosts the P-CSCF, establishing a connection to the I/S-CSCF through the Mw Protocol. Mw employs SIP and SDP Protocols to enable this connectivity.
  • Function: Facilitates message exchange between CSCFs (e.g., from P-CSCF to S-CSCF)
"},{"location":"blog/20231108/20231108/#isc-interface","title":"ISC Interface","text":"
  • Description: The ISC interface establishes connectivity between the S-CSCF and TAS (Telephony Application Server). This interface engages with the MMTel supplementary services deployed on the TAS, utilizing the IMS Service Control Interface to request services. Additionally, it accommodates TAS-initiated requests to the IMS.
  • Function: The S-CSCF interacts with the MMTel supplementary services integrated into the TAS.
"},{"location":"blog/20231108/20231108/#sh-interface","title":"Sh Interface","text":"
  • Description: The Sh Interface is employed between the TAS and HSS to facilitate the exchange of User Profile information, encompassing service-related details, user location information, and charging function data. It enables the transmission of service and subscriber-related information from the HSS to the TAS. This operation is akin to a Location Update between the HSS and TAS. Sh operates using the Diameter protocol.
  • Function: The TAS retrieves user profiles from the HSS and activates services for the user.
"},{"location":"blog/20231108/20231108/#cx-interfaces","title":"Cx Interfaces","text":"
  • Description:The Cx Interface is utilized between the I/S-CSCF and HSS to download subscriber data from the HSS to the S-CSCF. It enables IMS registration and the transfer of subscriber data to the S-CSCF. Cx operates on the Diameter protocol.
  • Function: The I/S-CSCF communicates with the HSS to retrieve user profiles, assisting in user registration and authentication.
"},{"location":"blog/20231108/20231108/#rx-interface","title":"Rx Interface","text":"
  • Description: The Rx interface serves as a bridge between the IMS Network and the Packet Core. It operates between the P-CSCF and the PCRF, enabling the request of an appropriate policy for session establishment, including both incoming and outgoing voice calls. The Rx Interface plays a crucial role in controlling the setup of various call types, thereby authorizing and managing resource usage in the access networks. It operates on the Diameter protocol.
  • Function: Establishing a suitable policy for incoming or outgoing calls on the network through PCRF.
"},{"location":"blog/20231108/20231108/#mg-interface","title":"Mg Interface","text":"
  • Description: The Mg Interface facilitates the transmission of SIP messages between an S/I-CSCF and an MGCF for signaling purposes. It is utilized for the exchange of signaling between the CSCF and Media Gateway for calls coming and going towards circuit core networks.
  • Function: Facilitates interworking with the circuit-switched network for the CSCF.
"},{"location":"blog/20231108/20231108/#reference","title":"Reference","text":"

3GPP TS 29 163: \"Interworking between the IP Multimedia (IM) Core Network (CN) subsystem and Circuit Switched (CS) networks\"

3GPP TS 22.228: \"Service requirements for the Internet Protocol (IP) multimedia core network subsystem (IMS) - Stage 1\"

3GPP TS 23.218: \"IP Multimedia (IM) session handling; IM call model; Stage 2\"

3GPP TS 23.228: \"IP Multimedia Subsystem (IMS) - Stage 2\"

3GPP TS 24.229: \"IP multimedia call control protocol based on Session Initiation Protocol (SIP) and Session Description Protocol (SDP) - Stage 3\"

3GPP TS 29.228: \"IP Multimedia (IM) Subsystem Cx and Dx Interfaces; Signalling flows and message contents\"

"},{"location":"blog/20231108/20231108/#about","title":"About","text":"

Hello, I am Elisa Lee. My ongoing research revolves around VoNR (Voice over New Radio). I encourage any inquiries or identification of errors within the article, as they are welcomed for correction. Your feedback is invaluable, so please don't hesitate to reach out via email to share your insights.

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20231115/free5GC_OAuth2_Procedure/","title":"Introduction to free5GC OAuth2 Procedure","text":"

Note

Author: Andy Chen (CTFang) Date: 2023/11/15

Description

[0-0]. NF_Registration: See TS 29.510 Section5.2.2.2 NFRegister for more details.

[0-1]. When an NF registers with NRF using NFProfile, NRF adds CustomInfo.oauth2=true to NFProfile and replies to NF upon successful registration. (See TS29.510 for more detailed information about CustomInfo.)

[1]. The GetTokenCtx() function generates a context and inserts the access token into the request header.

[2]. If the token has expired, the NF would use SendAccTokenReq() to obtain a new token from NRF.

[3]. NRF would verify the request NFType and the requested service for authorization, and issue the token if authorized.

"},{"location":"blog/20231115/free5GC_OAuth2_Procedure/#note","title":"Note","text":"

The OAuth2 functions are under development in free5GC. They will be released after thorough testing. Furthermore, there may be a more detailed workflow article related to this topic.

"},{"location":"blog/20231115/free5GC_OAuth2_Procedure/#in-progress","title":"In Progress","text":"
  • free5gc/NRF PR#27
  • free5gc/openapi PR#17
  • There would be related Pull Request for each NF.
"},{"location":"blog/20231115/free5GC_OAuth2_Procedure/#future-work","title":"Future Work","text":"
  • allowedNfTypes: TS 29.510 Section6.1.6.2.2 Definition of type NFProfile

    When NRF verifies the scope during the AccessTokenRequest, it checks the target NF's NFProfile allowedNfTypes to determine whether the NF consumer is in the allowedNfType or not.

  • TLS Mutual Authentication: TS 33.501 Section13.3.1 Authentication & Authorization between NF and NRF

    Authentication and authorization between NF and NRF are completed if PLMN uses protection at the transport layer with mutual authentication.

"},{"location":"blog/20231115/free5GC_OAuth2_Procedure/#about","title":"About","text":"

Hello, I am Andy Chen. I have just started making contributions to the free5GC core network. This post is my first blog, so if there are any inquiries or identification of errors within, we welcome discussion and correction. Your feedback is invaluable, so please don't hesitate to reach out via email to share your insights.

"},{"location":"blog/20231115/free5GC_OAuth2_Procedure/#connect-with-me","title":"Connect with Me","text":"
  • Linkedin: https://www.linkedin.com/in/tsung-fang-chen-437a71191/

  • Github: https://github.com/andy89923

"},{"location":"blog/20231115/free5GC_OAuth2_Procedure/#appendix","title":"Appendix","text":"

Additionally, I have provided the graph with a dark background.

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20231122/20231122/","title":"Nephio: a Cloud Native Network Automation Linux Foundation Project","text":"

Note

Author: Brian Chen (\u9673\u715c\u76db) Date: 2023/11/08

As a free5GC maintainer, I'm excited to see a Linux Foundation project that utilizes our open-source software. Let's explore what Nephio can do. I will briefly introduce what is Nephio and then demonstrate how to install the free5gc-operator.

"},{"location":"blog/20231122/20231122/#nephios-goal","title":"Nephio's Goal","text":"

Nephio delivers carrier-grade, simple, open, Kubernetes-based cloud native intent automation and common automation templates that materially simplify the deployment and management of multi-vendor cloud infrastructure and network functions across large-scale edge deployments. It brings zero-touch provisioning of cloud infrastructure and network functions, which can offer increased network availability and resiliency while reducing maintenance downtime.

"},{"location":"blog/20231122/20231122/#what-problem-does-nephio-solve","title":"What Problem Does Nephio Solve?","text":"

Distributed cloud technologies facilitate edge access through on-demand, API-driven mechanisms. Regrettably, prevailing imperative, fragile, and fire-and-forget orchestration techniques face challenges in fully exploiting the dynamic capabilities inherent in these innovative infrastructure platforms. To achieve success in this domain, Nephio employs novel methodologies capable of navigating the intricacies associated with provisioning and overseeing a distributed, multi-vendor, multi-site deployment of interconnected network functions within on-demand distributed cloud environments.

The objective of the solution is to handle the initial provisioning of network functions and the underlying cloud infrastructure. Additionally, it incorporates Kubernetes-enabled reconciliation to ensure network resilience amid failures, scaling occurrences, and alterations within the distributed cloud.

"},{"location":"blog/20231122/20231122/#benefits-of-kubernetes-based-cloud-native-automation","title":"Benefits of Kubernetes Based Cloud Native Automation","text":"

It enables multi-vendor support, faster onboarding, easier lifecycle management, embedded control-loop, active reconciliation, and service assurance, reducing costs through efficiency and agility. A common cloud-based automation framework, based on well-proven Kubernetes technology, minimizes the need for custom automation solutions for each application. Multi-vendor integration is enabled, improving customer experience.

"},{"location":"blog/20231122/20231122/#kubernetes-as-a-uniform-automation-control-plane","title":"Kubernetes as a Uniform Automation Control Plane","text":"

We can broadly think of three layers in the stack:

  1. cloud infrastructure Nephio releases Custom Resource Definitions (CRDs) and operators built on Kubernetes for automation in both public and private cloud infrastructures, aligning with industry standards like the O-RAN O2 interface. These CRDs and operators have the capability to leverage established projects within the Kubernetes ecosystem as interchangeable southbound interfaces (such as Google Config Connector, AWS Controllers for Kubernetes, and Azure Service Operator). This establishes an open integration point, ensuring a more consistent approach to automation across various service providers.

  2. workload (network function) resources It covers configuring the provisioning of network function containers and the associated requirements for nodes and network fabric. This encompasses native Kubernetes primitives and industry extensions like multi-network Pods, SR-IOV, and similar technologies. Currently, efficient utilization of these features demands intricate Infrastructure-as-Code templates tailored to specific network functions. Nephio aims to simplify this process by adopting a Configuration-as-Data approach, utilizing Kubernetes CRDs and well-structured schemas to enable robust, standards-based automation for network function configuration.

  3. workload (network function) configuration. Nephio starts by offering tools and libraries to support vendors in merging their current Yang and other industry models with Nephio, complying with standards (e.g., 01, 3GPP interface specs). To completely grasp the advantages of cloud-native automation, these models must transition to Kubernetes CRDs since these configurations are closely linked to those elucidated in (2). Nephio furnishes identical tooling across each stratum, facilitating the automation of interconnected configurations amidst these layers.

Configuration Layers

"},{"location":"blog/20231122/20231122/#nephio-release-1","title":"Nephio Release 1","text":"

Project Nephio announced the availability of Release 1 (R1) on August, 2023.

Nephio R1 brings revolutionary advancements in deployment, life cycle management and scaling of telecom cloud infrastructure and network functions by using Kubernetes cloud native technologies and intent-based automation.

For more details, see Learning Nephio R1.

Release 1 highlights:

  • A framework to orchestrate cloud native network functions (CNF), infrastructure, and cross-domain lifecycle management
  • Core Nephio principles and Kubernetes integration with custom resource definitions (CRDs).
  • Enhanced user experience and sandbox environment.
"},{"location":"blog/20231122/20231122/#prove-of-concept-free5gc-operator","title":"Prove-of-Concept: free5gc-operator","text":"

You can refer to the PowerPoint presentations and YouTube videos they have posted to fully grasp the entire concept and architecture. Here, I attach some architectural diagrams of free5gc-operator below, but for more detailed information, I recommend watching the videos.

Nephio project utilizes KPT, a toolkit develop by Google, which help you manage, manipulate, customize, and apply Kubernetes Resource configuration data files. KPT supports management of Configuration as Data, which is an approach to management of configuration which

  • makes configuration data the source of truth, stored separately from the live state
  • uses a uniform, serializable data model to represent configuration
  • separates code that acts on the configuration from the data and from packages / bundles of the data
  • abstracts configuration file structure and storage from operations that act upon the configuration data; clients manipulating configuration data don't need to directly interact with storage (git, container images)

"},{"location":"blog/20231122/20231122/#what-does-nephio-free5gc-operator-do","title":"What does Nephio free5gc-operator do?","text":"
  1. Reconciles {AMF,SMF,UPF}Deployment CustomResources (CRs)
  2. For all three NFs, they each have a Deployment and a ConfigMap Kubernetes object, and for AMF and SMF, they also have a Service resource
    • Determines resource needs based on capacity input
    • Builds these K8s objects with XXXDeployment CRs fields as input
  3. Manages dependencies
    • Ensures that the corresponding NADs exist in namespace
    • Creates ConfigMap before applying Deployment
  4. Associates objects with CR
  5. Updates status for XXXDeployment
  6. Ensures ConfigMap updates will lead to Deployment update
"},{"location":"blog/20231122/20231122/#environment-setup","title":"Environment Setup","text":"

Before cloning and launching a free5gc-operator, you need to setup an environment which matches the official demonstration. The official recommendation includes two installation methods:

  • through the Google Compute Engine (GCE) to set up the environment with an account of Google Cloud Platform,
  • directly preparing a Pre-Provisioned VM to set up the environment required for free5gc-operator.

Warning

Due to the mandatory hardware resource requirements set by the official guidelines, which include an 8-core CPU, 16 GB of memory, 200 GB disk, the resources needed are quite >substantial. If you operate on the Google Cloud Native platform, you may incur significant charges on your credit card. Therefore, I recommend installing the environment on a virtual >machine (VM) on your local machine for follow-on exercises.

"},{"location":"blog/20231122/20231122/#installation-on-pre-provisioned-vm","title":"Installation on Pre-Provisioned VM","text":"

After completing the Ubuntu 20.04 focal installation, you need to get a sudo passwordless permissions. Edit /etc/sudoers:

$ sudo vim /etc/sudoers\n

Add 2 lines:

<linux username>   ALL=(ALL:ALL) NOPASSWD:ALL\nsudo    ALL=(ALL:ALL) NOPASSWD:ALL\n
"},{"location":"blog/20231122/20231122/#kick-off-an-installation-on-vm","title":"Kick Off an Installation on VM:","text":"

wget -O - https://raw.githubusercontent.com/nephio-project/test-infra/v1.0.1/e2e/provision/init.sh |  \\\nsudo NEPHIO_DEBUG=false   \\\n     NEPHIO_BRANCH=v1.0.1 \\\n     NEPHIO_USER=<linux username>   \\\n     bash\n
The next step is to wait for Ansible to automatically set up the environment. The entire process may take approximately 10 minutes, so please be patient. Once it is completed, ssh in and port forward the port to the UI (7007) and to Gitea's HTTP interface, if desired (3000):

ssh <user>@<vm-address> \\\n                -L 7007:localhost:7007 \\\n                -L 3000:172.18.0.200:3000 \\\n                kubectl port-forward --namespace=nephio-webui svc/nephio-webui 7007\n
You can now navigate to:

  • http://localhost:7007/config-as-data to browse the Nephio Web UI

  • http://localhost:3000/nephio to browse the Gitea UI

"},{"location":"blog/20231122/20231122/#free5gc-amfsmfupf-deployments-prerequisite-automatically-done-in-environment-setup","title":"free5GC AMF/SMF/UPF Deployment's Prerequisite (automatically done in environment setup)","text":"
  1. Install multus
  2. Install CNI binary (macvlan) on all hosts that can possibly run free5gc AMF, SMF, or UPF
  3. Install gtp5g on nodes that could run UPF
  4. Install sctp on nodes that could run AMF
  5. Connectivity: a. IP address allocation and reachability
  6. Network Attachment Definitions (NADs) for all the secondary interfaces
  7. 3GPP related configs (PLMN, S-NSSAI info)
  8. free5gc SMF config requires connected UPF information
  9. Client (such as UERANSIM) set up with corresponding IP addresses and 3GPP info
"},{"location":"blog/20231122/20231122/#quick-start-exercises","title":"Quick Start Exercises","text":"

You can follow the tutorial steps in this page. I won't show all detail in this article. I only list the action of all steps:

  • Step 1: Create the Regional cluster
  • Step 2: Check the Regional cluster installation
  • Step 3: Deploy two Edge clusters
  • Step 4: Deploy Free5GC Control Plane Functions
  • Step 5: Deploy Free5GC Operator in the Workload clusters
  • Step 6: Check Free5GC Operator Deployment
  • Step 7: Deploy the AMF, SMF and UPF NFs
  • Step 8: Deploy UERANSIM
  • Step 9: Change the Capacities of the UPF and SMF NFs
"},{"location":"blog/20231122/20231122/#reference","title":"Reference","text":"
  • https://nephio.org/
  • https://nephio.org/about/
  • https://github.com/nephio-project/docs/blob/main/install-guide/README.md#demonstration-environment-installation
  • https://github.com/nephio-project/docs/blob/main/user-guide/exercises.md
  • https://pkg.go.dev/github.com/GoogleContainerTools/kpt#section-readme
"},{"location":"blog/20231122/20231122/#about","title":"About","text":"

Hello everyone, I'm Brian Chen (\u9673\u715c\u76db). Currently, I am pursuing a master's degree and have been interning at Saviah for 10 months. My current role involves maintaining free5GC and reviewing issues and PR. Additionally, I am researching Kubernetes with the aim of improving the efficiency of deployment experiences for free5GC. There is still a lot of knowledge for me to acquire, so please feel free to contact me if you have any questions or would like to discuss anything.

  • My GitHub
  • LinkedIn

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20231129/20231129/","title":"Basic concept of RCU: Read, Copy, Update","text":"

Note

Author: Lin Poyi Date: 2023/11/29

"},{"location":"blog/20231129/20231129/#what-is-rcu","title":"What is RCU","text":"

RCU is a lock-free synchronization mechanism in Linux kernel, and we use it in gtp5g. RCU is a way of making changes to data structures that are mostly read by many threads but rarely updated by a few threads. RCU stands for \u201cRead, Copy, Update\u201d, which means that the updater thread first makes a copy of the data item, modifies the copy, and then replaces the original with the copy. It works very well and has great scalability under read-heavy use cases. Compared with Reader-Writer lock (RWlock) and other lock-based approaches, RCU is a lot faster but does not provide strong consistency over data.

In RCU, there's nothing blocking readers from entering the critical section. The reader can access the protected data at all times. Also, the updater can modify the data whenever it wants (assume only 1 updater) no matter if there are still readers using the data.

"},{"location":"blog/20231129/20231129/#drawbacks-of-reader-writer-lock-rwlock","title":"Drawbacks of Reader-Writer lock (RWlock)","text":"
  1. It is expensive and complex to implement, especially for fine-grained locking of individual data items.
  2. It can cause contention and scalability issues, especially when the number of readers is large or the lock is held for a long time.
  3. It can introduce deadlocks and livelocks, especially when the locking order is not well-defined or the lock is nested or reentrant.
"},{"location":"blog/20231129/20231129/#benefits-of-rcu","title":"benefits of RCU","text":"
  1. It is faster and more scalable for read-intensive workloads because the readers do not need to wait for the updaters or contend for shared resources.
  2. It is simpler and more reliable for avoiding deadlocks because the readers do not need to acquire any locks or follow any ordering rules.
  3. It is more flexible and adaptable for different scenarios because there are many variants of RCU that can handle different types of updates and readers.
"},{"location":"blog/20231129/20231129/#how-to-use-rcu","title":"How to use RCU","text":"

While using RCU, there are some rules to be followed: 1. You need to mark the sections of code where you access the RCU-protected data structures as \u201cRCU read-side critical sections\u201d, using the appropriate RCU primitives, such as rcu_read_lock() and rcu_read_unlock(). 2. You need to mark the sections of code where you modify the RCU-protected data structures as \u201cRCU update-side critical sections\u201d, using the appropriate RCU primitives, such as call_rcu() and synchronize_rcu(). 3. You need to ensure that the RCU read-side critical sections are short and do not block, switch to user mode, or enter the idle loop unless you use a special variant of RCU that allows blocking, such as SRCU or preemptible RCU. 4. You need to ensure that the RCU update-side critical sections wait for a \u201cgrace period\u201d to elapse before freeing or reusing the old versions of the data items and that the grace period is long enough that all the readers have finished accessing the old versions.

Note

Grace period is the time between the data being changed by the updater and the time when all readers are using the new data. During the grace period, some readers may be using the old data, some may be using the new data.

"},{"location":"blog/20231129/20231129/#main-rcu-apis","title":"main RCU APIs","text":"
  1. rcu_read_lock(): Inform others the reader is entering the critical section, does not actually block anyone from entering the critical section.
  2. rcu_read_unlock(): Inform others the reader has exit critical section.
  3. rcu_assign_pointer(): Using this API instead of directly assign pointer with \"=\" can avoid pointer been assigned before the update_data() been done due to CPU's out-of-order execution.
  4. syncronize_rcu(): Wait for readers who are already in the critical section and haven't left yet. Used after updating the data and before the old data has been released.
  5. call_rcu(): this function is the asyncronized version of syncronize_rcu(), it will return right after it scedules a handler (clean up the old data) to be performed after all previous readers left C.S.(critical section) We use this instead of the syncronized version in gtp5g.
"},{"location":"blog/20231129/20231129/#pseudo-code-for-reader-and-updater","title":"pseudo code for reader and updater","text":"

void Reader(){\n    rcu_read_lock();    //enter C.S. (critical section)\n    item *data = Item_A;\n    access_data(data);\n    rcu_read_unlock();  //leave C.S.\n    return;\n}\n
void Updater(){\n    item *old_data = Item_A;  //save the old location\n    item *new_data = new(item);    //make a new item\n    update_data(new_data);   //modify the data in new item\n    rcu_assign_pointer(Item_A, new_data);   //now the global pointer points to the new item\n    syncronize_rcu();   //wait for all readers using old_data to leave C.S.\n    kfree(old_data);  //now we can safely release the old_data\n    return;\n}\n

"},{"location":"blog/20231129/20231129/#another-version-of-updater-with-call_rcu","title":"another version of Updater with call_rcu()","text":"

void item_free(item *data){\n    kfree(data);\n    return;\n}\nvoid Updater(){\n    item *old_data = Item_A;  //save the old location\n    item *new_data = new(item);    //make a new item\n    update_data(new_data);   //modify the data in new item\n    rcu_assign_pointer(Item_A, new_data);   //now the global pointer points to the new item\n    call_rcu(old_data, item_free);   //schedule item_free(old_data) to be performed after all previous readers left C.S. and return immediately\n    return;\n}\n

When the updater tries to update the data, the data doesn't get replaced instantly. The pointer will point to new data, but the old data still exists in the memory. This method prevents readers who are still accessing the old data from accessing the released memory and also allows updates before all readers leave the critical section.

"},{"location":"blog/20231129/20231129/#comparison-between-rcu-and-rwlock","title":"Comparison between RCU and RWlock","text":"

scalability of RWlock and RCU

The graph above shows that RCU not only has better scalability with CPUs but also has better overall performance, which is not surprising when we look at the CPU utilization difference between RWlock and RCU in the graph shown below. But keep in mind that RCU does not guarantee that all readers running simultaneously have the same version of data, but RWlock does. Also if there's more than one updater, you might need some locks to prevent multiple updates been performed at the same time.

What it might look like in the CPUs while running these two methods

"},{"location":"blog/20231129/20231129/#reference","title":"Reference","text":"

https://www.kernel.org/doc/html/next/RCU/whatisRCU.html

https://hackmd.io/@cccccs100203/So-What-Has-RCU-Done-Lately

Linux kernel design: RCU synchronization mechanism

So What Has RCU Done Lately?

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20231206/20231206/","title":"Introduction of IP Multimedia Subsystem Part 2","text":"

Note

Author: TzuChieh Huang Date: 2023/12/06

In the rapidly evolving landscape of telecommunications, the IP Multimedia Subsystem (IMS) stands as a linchpin for delivering advanced multimedia and voice services over IP networks. As explored in the preceding introduction (IMS Part 1), IMS serves as a standardized and open architectural framework defined by the 3rd Generation Partnership Project (3GPP). Its role in facilitating seamless communication services, including voice, video, messaging, and data, highlights its pivotal position in modern network infrastructures. Building upon the foundational knowledge established in IMS Part 1, this section will delve into the IMS registration.

"},{"location":"blog/20231206/20231206/#interaction-and-flow-of-registration","title":"Interaction and flow of Registration","text":""},{"location":"blog/20231206/20231206/#procedures-related-to-proxy-cscf-discovery","title":"Procedures related to Proxy-CSCF discovery","text":"

P-CSCF(Proxy-Call Session Control Function) is the initial point of contact for UE when establishing communication sessions. Therefore, P-CSCF discovery is a crucial step in IMS architecture. It involves the process of determining the network address of P-CSCF that the UE(User Equipment) should contact during the initiation of the communication session.

The Proxy-CSCF discovery shall be performed using the following mechanisms:

  • During the establishment of connectivity to the IP-Connectivity Access Network, provided that the IP-Connectivity Access Network supports such procedures.
  • Alternatively, the P-CSCF discovery may take place after the IP connectivity has been established. To enable P-CSCF discovery after the establishment of IP connectivity, the IP-Connectivity Access Network shall provide the following P-CSCF discovery option to the UE:
    • Utilizing DHCP to furnish the UE with the domain name and/or IP address of a Proxy-CSCF, along with the address of a Domain Name Server (DNS) capable of resolving the Proxy-CSCF name
    • The UE may be configured (e.g., during initial provisioning or via a 3GPP IMS Management Object (MO), or in the ISIM) to be aware of the fully qualified domain name (FQDN) of the P-CSCF or its IP address. If the domain name is known, DNS resolution is employed to obtain the IP address.
"},{"location":"blog/20231206/20231206/#dhcpdns-procedure-for-p-cscf-discovery","title":"DHCP/DNS procedure for P-CSCF discovery","text":"

When DNS is employed to retrieve the IP address of the P-CSCF, the name-address resolution mechanism is permitted to take the load information of the P-CSCFs. This load information, obtained through network management procedures, is factored in when determining the address of the P-CSCF for the UE.

  1. Initiate the establishment of an IP-Connectivity Access Network bearer, utilizing the procedures available in the IP-Connectivity Access Network, if it is not already in place.

  2. The UE initiates a request to a DHCP server, seeking the domain name and/or IP address of the P-CSCF, along with the IP addresses of DNS servers. This may involve multiple DHCP Query/Response message exchanges to obtain the necessary information.

  3. The UE conducts a DNS query to retrieve a list of IP addresses for the P-CSCF(s), from which one is selected. If the response lacks IP addresses, an additional DNS query becomes necessary to resolve a FQDN to an IP address.

    • If the UE is aware of multiple P-CSCF addresses, the selection process is determined based on the home operator's configured policy for P-CSCF selection. The policy dictates the criteria or rules according to which one P-CSCF address is chosen over others in such scenarios.
    • Depending on the policy set by the home operator, the UE chooses the Home Proxy-CSCF by using a preconfigured Home P-CSCF FQDN. This selection can be made without P-CSCF discovery procedure.
"},{"location":"blog/20231206/20231206/#assigning-a-serving-cscf-for-a-user","title":"Assigning a Serving-CSCF for a user","text":"

When an UE attaches itself and becomes available for access to IMS services through explicit registration within the IMS, a S-CSCF (Serving-Call Session Control Function)shall be assigned to serve the UE. The S-CSCF plays a central role in call control and service execution for the registered UE within the IMS network.

The assignment of a S-CSCF is carried out within the I-CSCF (Interrogating-Call Session Control Function). The selection of the S-CSCF requires the following information:

  1. Required capabilities for user services:

    • Provided by the HSS (Home Subscriber Server), this information outlines the necessary capabilities for the user's services.
  2. Operator preference on a per-user basis:

    • Also provided by the HSS, this information reflects the operator's preferences specific to each user.
  3. Capabilities of individual S-CSCFs in the home network:

    • This internal information within the operator's network, pertaining to the capabilities of individual S-CSCFs, may influence the S-CSCF selection.
  4. Topological (i.e. P-CSCF) information of where the user is located

    • This internal information within the operator's network, concerning the topological details of the user's location, may be utilized in the S-CSCF selection process. The P-CSCF name is received in the registration request.
  5. Topological information of where the S-CSCF is located

    • Another internal aspect within the operator's network, this information regarding the topological details of the S-CSCF's location may play a role in S-CSCF selection.
  6. Availability of S-CSCFs

    • Internal to the operator's network, information about the availability of S-CSCFs may be considered in the S-CSCF selection process.
"},{"location":"blog/20231206/20231206/#registration-flow","title":"Registration Flow:","text":"
  1. UE to P-CSCF:

    • After establishing IP connectivity, the UE, such as a mobile device, initiates IM registration by sending a SIP (Session Initiation Protocol) REGISTER request to the P-CSCF. This flow includes essential details such as the Public User Identity, Private User Identity, home network domain name, UE IP address, Instance Identifier, and GRUU (Globally Routable User Agent URI) Support Indication.
    • Upon receiving the Register information flow, the P-CSCF examines the \"home domain name\" to identify the entry point to the home network, which is the I-CSCF.
  2. P-CSCF to I-CSCF:

    • The P-CSCF forwards the Register information flow to the I-CSCF, including details such as the P-CSCF address/name, Public User Identity, Private User Identity, P-CSCF network identifier, and UE IP address.
  3. I-CSCF to HSS:

    • The I-CSCF initiates the Cx-Query/Cx-Select-Pull information flow, sending relevant information to the HSS to perform user authentication and retrieve the user's location information. This flow includes the Public User Identity, Private User Identity, and P-CSCF network identifier.
      • The HSS verifies if the user is already registered and assesses whether the user is permitted to register in the specified P-CSCF network based on user subscription and operator limitations/restrictions.
    • Upon receiving Cx-Query Resp/Cx-Select-Pull Resp from the HSS, which includes the S-CSCF name or capabilities, the I-CSCF constructs a name from the capabilities, if necessary. The registration attempt is rejected if the HSS checking is unsuccessful.
  4. I-CSCF to S-CSCF:

    • The I-CSCF, using the S-CSCF name, determines the address of the S-CSCF through a name-address resolution mechanism. The load information of the S-CSCFs is considered in the resolution process. The I-CSCF also identifies the name of a suitable home network contact point, potentially based on information received from the HSS. The I-CSCF then sends the register information flow (P-CSCF address/name, Public User Identity, Private User Identity, P-CSCF network identifier, UE IP address) to the selected S-CSCF.

    • The S-CSCF rejects the registration if the number of registered contact addresses for a Public User Identity exceeds the configured limit. The S-CSCF stores the P-CSCF address/name supplied by the visited network, representing the address/name that the home network uses to forward subsequent terminating session signaling to the UE. The S-CSCF stores the P-CSCF Network ID information.

  5. S-CSCF to HSS:

    • The S-CSCF communicates with the HSS to retrieve user profiles and perform tasks like registration and authentication.
      • The S-CSCF sends Cx-Put/Cx-Pull (Public User Identity, Private User Identity, S-CSCF name) to the HSS.
      • The HSS stores the S-CSCF name for the user and returns the information flow Cx-Put Resp/Cx-Pull Resp (user information) to the S-CSCF. The user information includes one or more names/addresses that can be used to access the platforms used for service control while the user is registered. Security information may also be sent for use within the S-CSCF.
  6. S-CSCF to AS (Application Server):

    • The S-CSCF may interact with Application Servers for additional services.
  7. S-CSCF to TAS (Telephony Application Server):

    • The S-CSCF interfaces with the TAS for services related to MMTel (MultiMedia Telephony) supplementary services.
  8. S-CSCF to MGCF (Media Gateway Control Function):

    • The S-CSCF may communicate with the MGCF for signaling purposes.
"},{"location":"blog/20231206/20231206/#conclusion","title":"Conclusion","text":"

IMS registration is a fundamental process in the IMS that establishes connectivity for UE to access advanced multimedia and voice services over IP networks. The IMS operates as a standardized and open architectural framework defined by the 3GPP. Through standardized protocols, IMS ensures secure and seamless communication services, making it a crucial framework in modern telecommunications.

"},{"location":"blog/20231206/20231206/#reference","title":"Reference","text":"

3GPP TS 22.228: \"Service requirements for the Internet Protocol (IP) multimedia core network subsystem (IMS) - Stage 1\"

3GPP TS 23.218: \"IP Multimedia (IM) session handling; IM call model; Stage 2\"

3GPP TS 23.228: \"IP Multimedia Subsystem (IMS) - Stage 2\"

3GPP TS 29.228: \"IP Multimedia (IM) Subsystem Cx and Dx Interfaces; Signalling flows and message contents\"

"},{"location":"blog/20231206/20231206/#about","title":"About","text":"

Hello, I am TzuChieh Huang. My ongoing research focuses on VoNR (Voice over New Radio). Feel free to reach out and share any inquiries or point out errors in the article. I welcome corrections and value your feedback. Please don't hesitate to contact me via email to contribute your insights.

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20231213/20231213/","title":"Article Sharing: Evaluating Dedicated Slices of Different Configurations in 5G Core","text":"

Note

Author:Leon Sawada(\u6fa4\u7530\u535a\u4e45) Date: 2023/12/13

"},{"location":"blog/20231213/20231213/#overview","title":"Overview","text":"

Network slicing is a new concept born after the emergence of 5G. Network slicing and edge computing (Multi-access Edge) are both key technologies in 5G networks. Network slicing uses virtualization technology to cut the network into multiple virtual end-to-end networks, and the equipment, access, transmission, and core network between each virtual network are all independent. Dynamic demand, resource adjustment, and improved network flexibility can also reduce the construction cost of hardware resources and network construction time, and achieve hardware resource sharing. Since one of the characteristics of network slicing is its flexibility, this article verifies this very well. In this article, the author uses free5GC, ETSI NFV MANO framework, and other tools, and designs three different slices to show how different configurations of dedicated slices will affect the performance of the 5G core. Finally, discussed which slice should be used in different situations to provide the best service to users.

"},{"location":"blog/20231213/20231213/#nfv-mano","title":"NFV MANO","text":"

The management and scheduling of the core network mainly uses NFV MANO (Management and Orchestration) technology, which is an architecture proposed by the European Telecom Standards Institute (ETSI) and later adopted by 3GPP to manage 5G core network virtualization. Network function (Virtual Network Function, VNF) specification, NFV MANO allows the core network to adopt a fully virtualized VNF design and better schedule various network slicing functions. MANO of the 5G core network has the ability to manage and schedule VNFs belonging to different slices for different slice services. As shown in the figure, NFV MANO can manage and schedule VNFs of the 5G core network such as AMF, NSSF, NRF, AUSF, and PCF, SMF, UDM, UPF, etc. This will also be paired with the core network in free5GC that we will only use. As you can see: NFV Orchestrator (NFVO), VNF Management (VNFM), and Virtualized Infrastructure Managers (VIM) are three major components in the MANO architecture framework.

*NFV Orchestrator: Responsible for supporting new network services (NS) suites and virtual network functions (VNF). Nuclear submarine life cycle management. Global resource management. Review and approve requests for Network Functions Virtualization Infrastructure (NFVI) resources.

*VNF Manager: Controls the life cycle management of VNF instances. Performs the function of coordinating and coordinating configuration and event reporting between the NFV infrastructure (NFVI) and network element/network management systems.

*Virtualization Infrastructure Manager (VIM): Monitors and manages NFVI compute, storage, and network resources.

Figure 1. NFV MANO architecture

According to the definition of 3GPP, network slices are composed of multiple network slice sub-networks such as core network, access network, and transport network. NFV MANO plays an important role in mapping each network slice network to network services.

"},{"location":"blog/20231213/20231213/#free5gc","title":"free5GC","text":"

free5GC is an open-source project for fifth-generation (5G) mobile networks. The ultimate goal of this project is to implement the 5G Core Network (5GC) defined in 3GPP Release 15 (R15) and later.

As shown in the figure, NYCU free5GC provides all the functions of a 5G network and is divided into the control plane and the user plane. The control plan includes NSSF, NRF, UDM, PCF, NEF, AUSF, AMF, SMF, UDR, and AF. In the user plane, there is only one network function called UPF. The separation of the user plane and the control plane allows each aircraft to be deployed and evolved independently. NYCU free5GC has also modulated its functional design to make the deployment of network functions in an NFV environment more flexible and efficient.

Figure 2. free5GC architecture

In addition to free5GC, use MANO open source including OpenStack and Tacker. OpenStack is an open-source cloud operating system that virtualizes resources such as storage, computing, and networking. Most of these virtualized resources are deployed in the cloud as Infrastructure-as-a-service (IaaS). OpenStack provides a web-based interface dashboard. This dashboard allows us to efficiently configure, manage and monitor our virtualization resources. Tacker is an OpenStack project that provides NFVO and VNFM functionality to configure, deploy, manage, and schedule NS and VNFs on NFV infrastructure platforms such as OpenStack. The Tacker API can be used not only by NFV Orchestrator but also by OSS/BSS to deploy VNFs.

"},{"location":"blog/20231213/20231213/#three-different-types-of-slice","title":"Three different types of slice","text":""},{"location":"blog/20231213/20231213/#free5gc-stage-3-with-upf-dedicated-slices","title":"free5GC Stage 3 with UPF Dedicated Slices","text":"

The image below shows the architecture of free5GC Stage 3, each dedicated slice consisting of only UPF. It uses three dedicated slices to handle three different types of test traffic. Each dedicated slice is connected to a specific data network (DN) server. These UPFs in different dedicated slices share the same SMF in the common slice.

Figure 3. free5GC with different configurations of dedicated slices

Three traffic generators are provided to simulate the packets that will be sent to the 5GC by the 5G UEs and RAN. Each traffic generator will transmit two types of packets: UDP and ICMP (Internet Control Message Protocol). Each traffic generator Each traffic generator sends UDP packets at different rates: high, medium, and low. All traffic generators send both UDP packets and ICMP packets. This can estimate the throughput of UDP traffic and calculate the response time of ICMP packets. Each network cutter routes packets to a different data network (DN) server. (DN) servers so that can verify successful transmission through the selected UPF.

Firstly, the UE will be connected to the RAN within the traffic generator. Secondly, the traffic generator transmits the NGAP Initial UE message to the AMF. This message carries the registration request and the UE information (including UE address IP, SST (Slice/Service Type), and SD (Slice Differentiator)). Thirdly, AMF requests UE authentication from AUSF. If the UE is a valid user, the AUSF accepts the request and transmits the response to the AMF. After that, the AMF transmits the UE information to the NSSF, which then provides a list of available SMFs to the AMF based on the information received. Next, AMF will select a suitable SMF for UE and create a smContext for SMF to establish a new session. Finally, the SMF will select a suitable UPF and establish a PDU (Protocol Data Unit) session between the UE and the selected UPF.

Figure 4. Registration workflow of free5GC Stage 3

After the registration is complete, the UE can start transmitting packets to its DN server. The transmission workflow is shown in the following figure. First, the UE sends UDP packets and ICMP packets to the UPF through its PDU session. Second, the UPF will forward these packets to a specific DN. Third, the DN will calculate the throughput of the UDP packets received from the UPF. Fourth, when the DN receives an ICMP packet, it will send the ICMP response back to the UPF. Finally, the UPF will forward this ICMP response to the UE; this will allow the UE to calculate the ICMP response time.

Figure 5. Transmission workflow

"},{"location":"blog/20231213/20231213/#free5gc-stage-3-with-smfupf-dedicated-slices","title":"free5GC Stage 3 with SMF/UPF Dedicated Slices","text":"

The architecture of this slice is similar to the previous one, as shown in part b of the figure. Each dedicated slice is composed of SMF and UPF It also uses three dedicated slices to handle different data rate requirements, each connected to a specific DN server. All VNFs use the same resources as the previous VNF. Unlike the previous one, we move the SMF from the common slice to the dedicated slice. Each UPF connects to an SMF in the same dedicated slice and both need to share resources. The traffic generators and DN servers are the same as those used in the previous architecture. The registration workflow is also similar to Fig. 4; the only difference is that in the last step, the SMFs do not need to select the UPFs because the UPFs have already been assigned to each SMF. The SMFs only need to set up PDU sessions between the UEs and the UPFs. After the registration is completed, the transmission workflow is also the same as the previous architecture, as shown in the above figure.

Figure 3.free5GC with different configurations of dedicated slices

"},{"location":"blog/20231213/20231213/#free5gc-stage-2-with-upf-dedicated-slice","title":"free5GC Stage 2 with UPF Dedicated Slice","text":"

The architecture of this slice, with each dedicated shard consisting only of UPFs, is the same as that shown in Figure 3 part(a). The only difference is that the VNF version in this architecture is free5GC Stage 2. VNFs in Stage 2 are less optimized than in Stage 3 The traffic generator and DN server are also the same as in free5GC Stage 3. However, the registration workflow is very different from Stage 3. This is because, in free5GC Stage 2, the NSSF is not utilized to select a dedicated and exclusive slice to serve the requesting UE. It is assumed that the UE will deliver the traffic directly to the allocated slice as shown in Fig. 6. First, the UE will connect to the RAN within the traffic generator. Second, the traffic generator will send a registration request and assign the UPF. Third, the AMF requests the UE authentication from the AUSF. If the UE is a valid user, the AUSF accepts the request and transmits the response to the AMF. Next, the AMF transmits the UE message to the SMF. Finally, the SMF locates the UPF specified by the UE and establishes a PDU session between the UE and the UPF. The transmission workflow in Stage 2 is the same as that in Stage 3, as shown in Fig. 5.

Figure 6. Registration workflow of free5GC Stage 2

"},{"location":"blog/20231213/20231213/#experiment","title":"Experiment","text":"

There are two sumptions: 1st, all the dedicated slices are allocated with the same amount of resources, i.e., to use the same number of vCPU and the same amount of memory and storage. 2nd, all three systems under evaluation are allocated the same amount of resources. We use two identical rack servers, one for OpenStack and one for Tacker. Follow the ETSI MANO framework to build this slicing environment.

For the first assumption, the specification of VNFs is divided into two parts: common slice and dedicated slice. For the common slice, the specifications of all VNFs in the common slice are the same for different systems. As shown in Table 2. For the dedicated slices, we show the VNFs with different specifications in Table 3. Three traffic generators and three DN servers are configured with 2 vCPUs, 1 GB RAM, and 10 GB disks, and mirrored with Ubuntu 18.04.

Table 1. Specification of implementation environment

Table 2. Specification of VNF in a common slice for the first assumption

Table 3. Specification of VNF in the dedicated slice for the first assumption

For the second assumption, the specification of VNFs is also divided into two parts: common slices and dedicated slices. For common slices, the resource specifications of VNFs other than SMFs are the same as in Table 2. On the other hand, the resource specifications of SMFs in different configurations are shown in Table 4. Correspondingly, the resource specifications of UPFs in different configurations are shown in Table 5.

Table 4. Specification of SMF for second assumption

Table 5. Specification of UPF for second assumption

"},{"location":"blog/20231213/20231213/#result-and-conclusion","title":"Result and conclusion","text":"

Figures 7 and 8 show the average throughput results for three different systems at three UDP packet data rates: low (80 Mbps), medium (200 Mbps), and high (400 Mbps).

Figure 7. Average throughput under the first assumption

Figure 8. Average throughput under the second assumption

Based on the graph, we can see that under the first assumption, free5GC Stage 3 with UPF-specific cuts provides higher throughput than free5GC Stage 3 with SMF/UPF-specific cuts because the former has more vCPU resources than the latter. Compared with free5GC Stage 2, the free5GC Stage 3 system is more optimized.

Figures 9 and 10 show the average response time of the three different systems under the two assumptions. As the design of our proposed system is more complicated than that of the comparator system, the average response time becomes more modest. Under the second assumption, both systems have similar response times. However, under the first assumption, the response time of free5GC Stage 3 with UPF-specific slice is shorter because UPF has more vCPU resources.

Figure 9. Average response time under the first assumption

Figure 10. Average response time under the second assumption

Figures 11 and 12 show the average CPU utilization of the two free5GC Stage 3 systems at the three data rates. Under the second assumption, there is no difference in CPU usage between the two free5GC Stage 3 systems. However, under the first assumption, the CPU usage rate is almost the same even if the number of vCPUs used in the UPF is different. However, in both assumptions, if we send more packets from the traffic generator, the CPU usage rate will be higher. This is because CPU usage depends only on the number of packets transmitted. On the other hand, the CPU usage of free5GC Stage 3 with the UPF dedicated cutter is slightly higher than that of free5GC Stage 3 with the SMF/UPF dedicated cutter, because the use of more vCPU resources results in more competitive conditions among CPUs.

Figure 11. Average response time under the second assumption

Figure 12. Average CPU utilization under the first assumption

Figures 13 and Figures 14 show the average registration time in our proposed system. In free5GC Stage 3 with SMF/UPF dedicated slices, SMF does not require the choice of UPF, and thus the registration time is lower under both assumptions. Even though we provide more powerful SMF and UPF dedicated slices in free5GC Stage 3 under the second assumption, the registration process still takes longer. This is because SMF takes extra time to select UPF.

Figure 13. Average registration time under the first assumption

Figure 14. Average registration time under the second assumption

Based on these results lead to the following conclusions: Under the first assumption, moving the SMF from the common slice to the dedicated slice will shorten the registration time, but it will deteriorate the performance of the UPF because fewer resources are allocated to the UPF than before. However, under the second assumption, only the registration time is affected; the performance of the UPF is not affected. This is because the functions in the control plane are no longer involved in the operation when the transfer starts. Therefore, if a large number of connections are required in a short period, moving the SMF from the common slice to the dedicated slice will be a better choice because the registration time of the system is shorter; it can handle a large number of registrations more efficiently. In addition, if users want to get better throughput and shorter response time under the first assumption, it is recommended to keep the SMF in the common slice so that more resources can be allocated to the UPF for better performance. Therefore we know we need to use different network slices based on different scenarios, which is the best proof of the convenience of network slicing.

Network slicing is a crucial concept in 5G networks, allowing virtual network functions to form interconnected network services, with dedicated slices being evaluated to determine the best-performing configuration based on various factors. Fuchun Joseph Lin and Yu-Herng Chai described evaluating dedicated slices of different configurations in the 5G core. The text discusses the creation of a common slice and three dedicated slices in a network architecture. The performance of these systems is evaluated based on throughput, response time, CPU utilization, and registration time. The deployment of dedicated slices is experimented with using open-source projects. Moving the SMF from the common slice to the dedicated slice is recommended for handling a large number of connections efficiently. Different resource allocations to dedicated slices are also considered. The performance of the three systems is compared based on registration time, response time, throughput, resource cost, and CPU utilization. The testbed is built using the ETSI NFV MANO framework, free5GC, OpenStack, and Tacker. The goal is to evaluate the performance of different configurations of 5G core dedicated slices. The response time of free5GC Stage 3 with UPF dedicated slice is shorter under the first assumption, while a more powerful SMF in free5GC Stage 3 still takes longer during registration under the second assumption.

"},{"location":"blog/20231213/20231213/#about","title":"About","text":"

Leon Sawada

NYCU CS Graduate student, today I introduce an article written by my professor Fuchun Joseph Lin. I hope this article could help you understand more about the attributes of different network slices.

"},{"location":"blog/20231213/20231213/#reference","title":"Reference","text":"

What Is NFV MANO -SDxCentral Studios

free5GC Offical website

Li, X., et al. (2017) Network Slicing for 5G: Challenges and Opportunities. IEEE Internet Computing, 21, 20-27

V. K. Choyi, A. Abdel-Hamid, Y. Shah, S. Ferdi, and A. Brusilovsky, \"Network slice selection, assignment and routing within 5G Networks,\" 2016 IEEE Conference on Standards for Communications and Networking (CSCN), Berlin, Germany, 2016, pp. 1-7, doi: 10.1109/CSCN.2016.7784887.

Liao, C.W., Lin, F.J. and Sato, Y. (2020) Evaluating NFV-enabled Network Slicing for 5G Core. 2020 21st Asia-Pacific Network Operations and Management Symposium (APNOMS), Daegu, Korea (South), 22-25 September 2020, 401-404.

Chai, Y.-H. and Lin, F.J. (2021) Evaluating Dedicated Slices of Different Configurations in 5G Core. Journal of Computer and Communications, 9, 55-72

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"blog/20240103/20240103/","title":"UE-initiated PLR Measurement Procedure in PMFP Procedure","text":"

Note

Author: Samuel Lin Date: 2024/01/03

"},{"location":"blog/20240103/20240103/#introduction","title":"Introduction","text":"

Performance measurement function protocol (PMFP) procedures are performed between a performance measurement function (PMF) in a UE and a PMF in the UPF.

The PMF can measure Round-Trip Time (RTT) and Packet Loss Rate (PLR) by exchanging information through PMFP messages to perform these calculations.

In this document, we solely introduce the UE-initiated Packet Loss Rate (PLR) Measurement Procedure.

"},{"location":"blog/20240103/20240103/#description","title":"Description","text":"

Counting start:

  • [1-4] UE initiates measurement by sending a PMFP PLR Count Request message to the UPF. UE and UPF commence respective packet counting processes.

Reporting and Potential Restart:

  • [5, 6-2] UE sends a PMFP PLR Report Request message to request UPF's counted packets and may optionally request counting restart.
  • [7-2, 8] UPF responds with a PMFP PLR Report Response message containing counted packets and potentially restarts counting if requested and accepted.

PLR Calculation and Procedure Termination:

  • [6-1, 7-1] Procedure terminates if restart is not requested or not accepted.
  • [9] UE calculates the UL PLR (Uplink PLR) based on local and reported packet counts.
  • [10-14] Counting can be restarted multiple times upon mutual agreement between UE and UPF, enabling continuous measurement.
"},{"location":"blog/20240103/20240103/#key-considerations","title":"Key Considerations","text":"

Message Transport: All PMFP messages are transported over the same QoS flow on the same access.

PLR Calculation: UE derives the UL PLR using its own transmitted packet count and the UPF's reported received packet count.

Compliance: The procedure adheres to 3GPP specifications for packet loss management.

"},{"location":"blog/20240103/20240103/#additional-notes","title":"Additional Notes","text":"

QoS Flow Selection: The specific QoS flow for message transmission is contingent upon the measurement type (default or non-default QoS rule).

Continuous Measurement: The restart capability facilitates ongoing packet loss monitoring.

"},{"location":"blog/20240103/20240103/#conclusion","title":"Conclusion","text":"

This UE-initiated PLR measurement procedure provides a standardized and coordinated approach to assess UL packet loss within 5G networks. Its adherence to 3GPP specifications ensures interoperability and consistency across network implementations.

Note

The network-initiated PLR measurement procedure is to enable UPF to measure the PLR of DL traffic access to the UE through the MA PDU session. The process is the same as the UE-initiated PLR measurement, except that the objects are reversed.

"},{"location":"blog/20240103/20240103/#reference","title":"Reference","text":"
  • 3GPP TS 24.193: 5G System; Access Traffic Steering, Switching and Splitting (ATSSS); Stage 3
  • 3GPP TS 23.501: System architecture for the 5G System (5GS)
"},{"location":"blog/20240103/20240103/#about","title":"About","text":"

Hello, I am Samuel Lin. I have just started making contributions to the free5GC core network. This post is my first blog, so if there are any inquiries or identification of errors within, we welcome discussion and correction. Your feedback is invaluable, so please don't hesitate to reach out via email to share your insights.

"},{"location":"blog/20240103/20240103/#connect-with-me","title":"Connect with Me","text":"
  • Linkedin: https://www.linkedin.com/in/pin-fan-lin-464201290/
  • Github: https://github.com/pf-lin
"},{"location":"blog/20240103/20240103/#appendix","title":"Appendix","text":"

Additionally, I have provided the graph with the network-initiated PLR measurement procedure.

Note

If you are interested in supporting free5GC, we welcome your donation. Please visit this link for more details.

"},{"location":"guide/","title":"Index","text":""},{"location":"guide/#user-guide","title":"User Guide","text":""},{"location":"guide/#information","title":"Information","text":"
  • Hardware tested
  • Supported features
"},{"location":"guide/#roadmap","title":"Roadmap","text":"

Here are the features on the roadmap. These items are planned to be supported in the near future:

  • Charging Function (CHF)
  • Registration without authentication, due to RAN did the AMF RESELECTION
  • OAuth on SBA
  • Network Exposure Function (NEF)
"},{"location":"guide/#free5gc-installation-guide","title":"free5GC Installation Guide","text":"

Note

If you have to develop a new feature on free5GC, please check the links below to install the free5GC. Otherwise, for normal use/test purposes, we recommend you use the free5GC Compose to launch the free5GC without complicated configuration settings.

"},{"location":"guide/#recommended-free5gc-compose","title":"[Recommended] free5GC compose","text":"
  • Installing free5GC Core Network with free5GC-Compose
"},{"location":"guide/#advanced-build-free5gc-from-scratch","title":"[Advanced] Build free5GC from scratch","text":"

For people who are not familiar with virtual machines and Linux installation, here are some example demonstrations:

  • Creating a Ubuntu VM using VirtualBox
  • Creating and Configuring a free5GC VM
  • Build and Install free5GC from source code and Test free5GC
  • Installing a UE/RAN Simulator
  • free5GC Simple Apps
  • All of tutorial videos are available at our Youtube channel:
    • English
    • Chinese
    • Environment setup of multiple SMF, DNN, and UPF
"},{"location":"guide/#configuration","title":"Configuration","text":"
  • Environment
  • Basic
  • SMF
  • Webconsole
  • Select UPF based on S-NSSAI
  • Select nearby UPF according to the connected gNodeB
  • ULCL
  • Netns5g - A free5gc and UERANSIM deployment using Linux network namespaces
"},{"location":"guide/#deployment","title":"Deployment","text":"

For Container deployment:

  • free5GC Compose (Docker Compose)
  • Towards5gs-helm (Kubernetes)
"},{"location":"guide/#others","title":"Others","text":"
  • Release Note
  • Trouble Shooting
  • Appendix
"},{"location":"guide/0-compose/","title":"0 compose","text":"

free5GC compose is a docker compose version of free5GC for stage 3. It's inspired by free5gc-docker-compose and also reference to docker-free5gc.

"},{"location":"guide/0-compose/#prerequisites","title":"Prerequisites","text":"
  • GTP5G kernel module: needed to run the UPF
  • Docker Engine: needed to run the Free5GC containers
  • Docker Compose v2: needed to bootstrap the free5GC stack

Note: AVX for MongoDB: some HW does not support MongoDB releases above4.4 due to use of the new AVX instructions set. To verify if your CPU is compatible you can check CPU flags by running grep avx /proc/cpuinfo. A workaround is suggested here.

"},{"location":"guide/0-compose/#start-free5gc","title":"Start free5GC","text":"

Because we need to create tunnel interface, we need to use privileged container with root permission.

"},{"location":"guide/0-compose/#pull-docker-images-from-docker-hub","title":"Pull docker images from Docker Hub","text":"
docker compose pull\n
"},{"location":"guide/0-compose/#optional-build-docker-images-from-local-sources","title":"[Optional] Build docker images from local sources","text":"
# Clone the project\ngit clone https://github.com/free5gc/free5gc-compose.git\ncd free5gc-compose\n\n# clone free5gc sources\ncd base\ngit clone --recursive -j `nproc` https://github.com/free5gc/free5gc.git\ncd ..\n\n# Build the images\nmake all\ndocker compose -f docker-compose-build.yaml build\n\n# Alternatively you can build specific NF image e.g.:\nmake amf\ndocker compose -f docker-compose-build.yaml build free5gc-amf\n

Note:

Dangling images may be created during the build process. It is advised to remove them from time to time to free up disk space.

docker rmi $(docker images -f \"dangling=true\" -q)\n
"},{"location":"guide/0-compose/#run-free5gc","title":"Run free5GC","text":"

You can create free5GC containers based on local images or docker hub images:

# use local images\ndocker compose -f docker-compose-build.yaml up\n# use images from docker hub\ndocker compose up # add -d to run in background mode\n

Destroy the established container resource after testing:

# Remove established containers (local images)\ndocker compose -f docker-compose-build.yaml rm\n# Remove established containers (remote images)\ndocker compose rm\n
"},{"location":"guide/0-compose/#troubleshooting","title":"Troubleshooting","text":"

Sometimes, you need to drop data from DB:

docker exec -it mongodb mongo\n> use free5gc\n> db.dropDatabase()\n> exit # (Or Ctrl-D)\n

You can see logs for each service using docker logs command. For example, to access the logs of the SMF you can use:

docker logs smf\n

Please refer to the wiki for more troubleshooting information.

"},{"location":"guide/0-compose/#integration-with-external-gnbue-simulators","title":"Integration with external gNB/UE simulators","text":"

The integration with the UERANSIM eNB/UE simulator is documented here.

You can also refer to this issue to find out how you can configure the UPF to forward traffic between the UERANSIM to the DN (eg. internet) in a docker environment.

This issue provides detailed steps that might be useful.

"},{"location":"guide/0-compose/#integration-of-webui-with-nginx-reverse-proxy","title":"Integration of WebUI with Nginx reverse proxy","text":"

Here you can find helpful guidelines on the integration of Nginx reverse proxy to set it in front of the WebUI: https://github.com/free5gc/free5gc-compose/issues/55#issuecomment-1146648600

"},{"location":"guide/0-compose/#vagrant-box-option","title":"Vagrant Box Option","text":"

For Linux kernel version below 5.4 you can setup a working environment using a vagrant box: https://github.com/abousselmi/vagrant-free5gc Please refer to GTP5G kernel module for more information.

"},{"location":"guide/0-compose/#ulcl-configuration","title":"ULCL Configuration","text":"

You can check the following informations below:

  • ulcl-example branch, or
  • patch file
"},{"location":"guide/1-vm-en/","title":"1 vm en","text":""},{"location":"guide/1-vm-en/#ubuntu-virtual-machine-installation-demo","title":"Ubuntu Virtual Machine Installation Demo","text":"

In this demo, we will

  • Install VirtualBox
  • Create a Ubuntu Server VM using VirtualBox
  • Use SSH to connect to the Ubuntu VM to install free5GC stage 3
  • Update and upgrade Ubuntu
"},{"location":"guide/1-vm-en/#1-install-virtualbox","title":"1. Install VirtualBox","text":"

Search virtualbox download, or visit virtualbox.org to download and install VirtualBox (currently 7.0.12) for your operational system.

Once VirtualBox is installed, launch it and see if you have something like this:

"},{"location":"guide/1-vm-en/#2-download-ubuntu-server","title":"2. Download Ubuntu Server","text":"

Search ubuntu server download on the web and download the latest Ubuntu Server 20.04 LTS, or visit ubuntu.com, choose Manual Installation Option to download the .iso file (currently 20.04.6 LTS)

You should have downloaded a .iso image file with a name like ubuntu-20.04.6-live-server-amd64.iso, probably in your download directory.

"},{"location":"guide/1-vm-en/#3-create-a-ubuntu-server-vm","title":"3. Create a Ubuntu Server VM","text":"

Launch VirtualBox and create your first Ubuntu VM using the downloaded .iso image file. We use Ubuntu Server instead of Ubuntu Desktop because we only need a basic server machine without too many unnecessary functionalities. The resulting overhead to your host machine is smaller, and the VM starts up faster too.

Tips

  • Name the first VM using a generic name as ubuntu, ubuntu-server, or ubuntu-20.04.
  • You can pick 1 or 2 (or more) CPUs, and about 2048MB memory, although you can change them later.
  • In addition to the default NAT network interface, add also another \u201cHost-only\u201d network interface.

Refer to the videos Creating VM, Setting up VM.

"},{"location":"guide/1-vm-en/#31-start-installing-ubuntu","title":"3.1 Start Installing Ubuntu","text":"

Some notes about installing Ubuntu:

  • It's recommended that you choose short username and password for ease of typing later
  • Not choosing LVM will make it a little bit easier later if you want to extend your disk space
  • Choose to include SSH Server
  • Let the security update complete

Refer to videos Install Ubuntu 1, Install Ubuntu 2.

"},{"location":"guide/1-vm-en/#32-log-in-to-ubuntu","title":"3.2 Log in to Ubuntu","text":"

Reboot after Ubuntu installation is finished then wait a little bit for some initialization steps to complete. Then log in with your username and password.

First try the ifconfig command\uff1a

ubuntu@ubuntu:~$ ifconfig\nCommand 'ifconfig' not found, but can be installed with:\nsudo apt install net-tools\nubuntu@ubuntu:~$\n

If some messages like above show, it means ifconfig has not been installed yet. (ifconfig is no longer installed by defaults in newer Ubuntu, and is replaced by more versatile ip command, but we will use it here for simplicity).

Follow its suggestion and install ifconfig:

ubuntu@ubuntu:~$ sudo apt install net-tools\n
The image below shows the installation result:

Run ifconfig again to check the network interfaces:

Your display may look different, but take notes about the IP address of the Host-only interface card. The example above shows 192.168.56.101. You can SSH from your host machine into this Ubuntu VM using the IP later. (The other IP address, 10.0.2.15 is the IP address of the NAT interface card, which means that the apps in your host machine cannot access it).

Finally, check if the VM has internet access using:

ubuntu@ubuntu:~$ ping google.com\n

Refer to the first part of the video Ping, SSH, and Upgrade.

"},{"location":"guide/1-vm-en/#4-connect-to-the-ubuntu-vm-using-ssh","title":"4. Connect to the Ubuntu VM using SSH","text":"

Launch your favorite SSH client from the host machine. Some operational systems (Mac, Ubuntu, some Windows) have SSH clients preinstalled. If you are using Windows, you can also download third-party SSH clients, by for example, searching \u201cwindows ssh download\u201d on the web.

The benefit of using SSH is that you can easily copy and paste commands from your machine to Ubuntu VM for execution, and vice versa. You can also create multiple SSH connections with the Ubuntu VM for control and monitoring at the same time.

Below it's possible to see some examples on a Mac host machine. Suppose the Host-only network IP is 192.168.56.101, and the username is ubuntu:

ssh 192.168.56.101 -l ubuntu\n
The first time you connect to the VM, your SSH client may show some message asking you for confirmation. Enter yes:

Tips

If somehow SSH shows some warning messages telling you the machine has potential security risk, you may have to remove an entry in the file <your home directory>/.ssh/known_hosts related the the IP address.

If you log in successfully, you will enter a command line interface:

Repeat the basic commands such as ping, ifconfig to see if the VM is working properly. If so, we can access the Ubuntu VM \u201cremotely\u201d from now on.

"},{"location":"guide/1-vm-en/#5-update-and-upgrade-your-ubuntu","title":"5. Update and Upgrade your Ubuntu","text":"

Let also update and upgrade the Ubuntu VM right now to make sure it is up-to-date with proper security updates.

sudo apt update\nsudo apt upgrade\n

"},{"location":"guide/2-config-vm-en/","title":"2 config vm en","text":""},{"location":"guide/2-config-vm-en/#creating-a-free5gc-vm-and-setting-up-network","title":"Creating a free5GC VM and Setting up Network","text":"

In this demo we will exercise:

  • Cloning an existing VM, and install free5GC on it
  • Setting up the networking for the free5GC VM

Tips

Refer to video Clone VM and Change IP.

"},{"location":"guide/2-config-vm-en/#1-check-up-an-existing-vm-for-cloning","title":"1. Check up an existing VM for Cloning","text":"

Launch VirtualBox, and make sure the Ubuntu VM (ubuntu) we created before can boot up, then:

  • Log in into the VM using SSH from the host machine, and check if the VM has internet access
  • Make sure you have done sudo apt update and sudo apt upgrade (or you can do it again)
  • Shutdown the VM. You can:
  • use command sudo shutdown -P now, or
  • click the \u201cClose Window\u201d of the Ubuntu VM terminal and choose the middle option (better not force to turn off the machine power)
  • later if you just want to reboot, enter sudo shutdown -r now
"},{"location":"guide/2-config-vm-en/#2-create-a-free5gc-vm","title":"2. Create a free5GC VM","text":"

First let\u2019s clone a new VM:

  • Select an existing VM (ubuntu) and click the buttons on the right: / Snapshopts / Clone.
  • Name the new VM free5gc.
  • The MAC address rule: Create new MAC addresses for all network cards.
  • Choose the Link cloning option (or you can also choose to complete clone the VM if you like).

After the new VM is created:

  • Start up the new free5gc VM, and use the same username and password to log in.
  • In the Ubuntu terminal, issue ping and ifconfig again to make sure it has internet access, and also make note of the IP address of the Host-only network interface.
    • for example the IP could still be 192.168.56.101, and the interface name is enp0s8.
  • Log in into free5gc VM using SSH, and make sure all things working properly.
"},{"location":"guide/2-config-vm-en/#3-change-hostname","title":"3. Change hostname","text":"

The cloned free5gc VM still has host name ubuntu (or the name you gave it in the original VM). Let\u2019s rename the VM to free5gc. You can do this by editing the file /etc/hostname (using vi or nano):

sudo nano /etc/hostname\n# or \nsudo vi /etc/hostname\n
In the file, change ubuntu into free5gc\u3002If you are using nano \uff0cyou can press Ctrl-O to save the file, then Ctrl-X to exit.

Let\u2019s also change the file /etc/hosts by replacing the ubuntu inside into free5gc:

sudo nano /etc/hosts\n

New content of the file /etc/hosts looks like this:

127.0.0.1 localhost\n127.0.1.1 free5gc\n...\n

The changes will take effect after next reboot.

"},{"location":"guide/2-config-vm-en/#4-setting-static-ip-address","title":"4. Setting Static IP Address","text":"

The Host-only network interface, by default, gets its IP address through DHCP. The cloned free5gc VM seems to have trouble obtaining new IP address. We can change the host-only interface to use static IP address instead, which can save a lot of trouble later.

Here let\u2019s fix the static IP address as 192.168.56.101:

$ cd /etc/netplan\n$ ls\n00-installer-config.yaml\n$ cat 00-installer-config.yaml\n
The original content of the file 00-installer-config.yaml looks like:
# This is the network config written by 'subiquity'\nnetwork:\n  ethernets:\n    enp0s3:\n      dhcp4: true\n    enp0s8:\n      dhcp4: true\n  version: 2\n
meaning the VM has two network interfaces. Using ifconfig we know that enp0s8 is the name of the Host-only network interface. We can edit the file:
sudo nano 00-installer-config.yaml\n
and change it into:
# This is the network config written by 'subiquity'\nnetwork:\n  ethernets:\n    enp0s3:\n      dhcp4: true\n    enp0s8:\n      dhcp4: no\n      addresses: [192.168.56.101/24]\n  version: 2\n
First check if the new content is correct:
sudo netplan try\n
Press enter to exit, if successful. The apply tne new interface setting:
sudo netplan apply\n
Run ifconfig to see if the network setting has been changed correctly:
enp0s3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500\n        inet 10.0.2.15  netmask 255.255.255.0  broadcast 10.0.2.255\n        inet6 fe80::a00:27ff:fec4:254f  prefixlen 64  scopeid 0x20<link>\n        ether 08:00:27:c4:25:4f  txqueuelen 1000  (Ethernet)\n        RX packets 2  bytes 1180 (1.1 KB)\n        RX errors 0  dropped 0  overruns 0  frame 0\n        TX packets 18  bytes 1894 (1.8 KB)\n        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0\n\nenp0s8: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500\n        inet 192.168.56.101  netmask 255.255.255.0  broadcast 192.168.56.255\n        inet6 fe80::a00:27ff:fe7e:ada6  prefixlen 64  scopeid 0x20<link>\n        ether 08:00:27:7e:ad:a6  txqueuelen 1000  (Ethernet)\n        RX packets 8420  bytes 531867 (531.8 KB)\n        RX errors 0  dropped 0  overruns 0  frame 0\n        TX packets 10887  bytes 823487 (823.4 KB)\n        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0\n\nlo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536\n        inet 127.0.0.1  netmask 255.0.0.0\n        inet6 ::1  prefixlen 128  scopeid 0x10<host>\n        loop  txqueuelen 1000  (Local Loopback)\n        RX packets 6621  bytes 596035 (596.0 KB)\n        RX errors 0  dropped 0  overruns 0  frame 0\n        TX packets 6621  bytes 596035 (596.0 KB)\n        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0\n
We can also check the routing table, just to have a grasp of what is going on regarding the network setting:
$ route -n\nKernel IP routing table\nDestination     Gateway         Genmask         Flags Metric Ref    Use Iface\n0.0.0.0         10.0.2.2        0.0.0.0         UG    100    0        0 enp0s3\n10.0.2.0        0.0.0.0         255.255.255.0   U     0      0        0 enp0s3\n10.0.2.2        0.0.0.0         255.255.255.255 UH    100    0        0 enp0s3\n192.168.56.0    0.0.0.0         255.255.255.0   U     0      0        0 enp0s8\n

For the display above, we learn that the Host-only network 192.168.56.0/24 does not have internet access by itself (even though we can access it using SSH from the host machine). Internet access is through the NAT network 10.0.2.0/24, with the gateway being 10.0.2.2 (provided by VirtualBox). Now we can SSH into free5gc VM using 192.168.56.101:

ssh 192.168.56.101 -l ubuntu\n
This is also how we interact with free5gc VM from now on.

"},{"location":"guide/3-install-free5gc/","title":"3 install free5gc","text":""},{"location":"guide/3-install-free5gc/#installation","title":"Installation","text":""},{"location":"guide/3-install-free5gc/#a-prerequisites","title":"A. Prerequisites","text":"

1. Linux Kernel Version

  • In order to use the UPF element, you must use the 5.0.0-23-generic or 5.4.x version of the Linux kernel. free5gc uses the gtp5g kernel module, which has been tested and compiled against that kernel versions only. If you installed Ubuntu 20.04, the version should be like 5.4.x. To determine the version of the Linux kernel you are using:
$ uname -r\n5.4.0-65-generic\n

You will not be able to run most of the tests in Test page unless you deploy a UPF.

2. Golang Version

  • As noted above, free5gc is built and tested with Go 1.18.10
  • To check the version of Go on your system, from a command prompt:

    go version\n

  • If another version of Go is installed, remove the existing version and install Go 1.18.10:

# this assumes your current version of Go is in the default location:\nsudo rm -rf /usr/local/go\nwget https://dl.google.com/go/go1.18.10.linux-amd64.tar.gz\nsudo tar -C /usr/local -zxvf go1.18.10.linux-amd64.tar.gz\n
  • If Go is not installed on your system:
wget https://dl.google.com/go/go1.18.10.linux-amd64.tar.gz\nsudo tar -C /usr/local -zxvf go1.18.10.linux-amd64.tar.gz\nmkdir -p ~/go/{bin,pkg,src}\n# The following assume that your shell is bash:\necho 'export GOPATH=$HOME/go' >> ~/.bashrc\necho 'export GOROOT=/usr/local/go' >> ~/.bashrc\necho 'export PATH=$PATH:$GOPATH/bin:$GOROOT/bin' >> ~/.bashrc\necho 'export GO111MODULE=auto' >> ~/.bashrc\nsource ~/.bashrc\n
  • Further information and installation instructions for golang are available at the official golang site.

3. Control-plane Supporting Packages

sudo apt -y update\nsudo apt -y install mongodb wget git\nsudo systemctl start mongodb\n
  • WARNING: MongoDB 5.0+ requires a CPU with AVX support. Or downgrade your MongoDB to 4.4
  • see this post on MongoDB's official forum
  • see also docker-library/mongo#485 (comment)

Note: If you are using Ubuntu 22.04.x and you face issues, please, check this appendix section

4. User-plane Supporting Packages

sudo apt -y update\nsudo apt -y install git gcc g++ cmake autoconf libtool pkg-config libmnl-dev libyaml-dev\n

5. Linux Host Network Settings

sudo sysctl -w net.ipv4.ip_forward=1\nsudo iptables -t nat -A POSTROUTING -o <dn_interface> -j MASQUERADE\nsudo iptables -A FORWARD -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1400\nsudo systemctl stop ufw\nsudo systemctl disable ufw # prevents the firewall to wake up after a OS reboot\n

Note: In Ubuntu Server 20.04 and 22.04 the dn_interface may be called enp0s3 or enp0s4 by default. Use the command ip a to help to figure it out

"},{"location":"guide/3-install-free5gc/#b-install-control-plane-elements","title":"B. Install Control Plane Elements","text":"
  1. Clone the free5GC repository

    • To install the latest stable build (v3.3.0):

      cd ~\ngit clone --recursive -b v3.3.0 -j `nproc` https://github.com/free5gc/free5gc.git\ncd free5gc\n
    • (Alternatively) to install the latest nightly build:

      cd ~/free5gc\ngit checkout main\ngit submodule sync\ngit submodule update --init --jobs `nproc`\ngit submodule foreach git checkout main\ngit submodule foreach git pull --jobs `nproc`\n
  2. Compile network function services in free5gc

    • To do so individually (e.g., AMF only):

      cd ~/free5gc\nmake amf\n
    • To build all network functions:

      cd ~/free5gc\nmake\n
"},{"location":"guide/3-install-free5gc/#c-install-user-plane-function-upf","title":"C. Install User Plane Function (UPF)","text":"
  1. As noted above, the GTP kernel module used by the UPF requires that you use Linux kernel version 5.0.0-23-generic or 5.4.x. To verify your version:

    uname -r\n
  2. Retrieve the 5G GTP-U kernel module using git and build it

    git clone -b v0.8.3 https://github.com/free5gc/gtp5g.git\ncd gtp5g\nmake\nsudo make install\n
  3. Build the UPF (you may skip this step if you built all network functions above):

    • to build using make:

      cd ~/free5gc\nmake upf\n
  4. Customize the UPF as desired. The UPF configuration file in run.sh is free5gc/config/upfcfg.yaml.

"},{"location":"guide/3-install-free5gc/#d-install-webconsole","title":"D. Install WebConsole","text":"
  1. Before building WebConsole, install nodejs and yarn packages first:

    sudo apt remove cmdtest\nsudo apt remove yarn\ncurl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -\necho \"deb https://dl.yarnpkg.com/debian/ stable main\" | sudo tee /etc/apt/sources.list.d/yarn.list\nsudo apt update\nsudo apt install -y nodejs yarn\n
  2. Build WebConsole

    • to build using make:

      cd ~/free5gc\nmake webconsole\n
    • (Alternatively) to build manually:

      cd ~/free5gc/webconsole/frontend\nyarn install\nyarn build\nrm -rf ../public\ncp -R build ../public\ncd ..\ngo build -o bin/webconsole server.go\n

Note: 2GB or more of OS memory is recommended. WebConsole may be failed to build if memory is less then 1GB.

"},{"location":"guide/4-test-free5gc/","title":"4 test free5gc","text":""},{"location":"guide/4-test-free5gc/#test-free5gc","title":"Test free5GC","text":"

Start a Wireshark capture on any core-connected interface, applying the filter 'pfcp||icmp||gtp'.

In order to run the tests, first do this:

cd ~/free5gc\nmake upf\nchmod +x ./test.sh\n

The tests are all run from within ~/free5gc.

a. TestRegistration

./test.sh TestRegistration\n

b. TestGUTIRegistration

./test.sh TestGUTIRegistration\n

c. TestServiceRequest

./test.sh TestServiceRequest\n

d. TestXnHandover

./test.sh TestXnHandover\n

e. TestDeregistration

./test.sh TestDeregistration\n

f. TestPDUSessionReleaseRequest

./test.sh TestPDUSessionReleaseRequest\n

g. TestPaging

./test.sh TestPaging\n

h. TestN2Handover

./test.sh TestN2Handover\n

i. TestNon3GPP

./test.sh TestNon3GPP\n

j. TestReSynchronization

./test.sh TestReSynchronization\n

k. TestULCL

./test_ulcl.sh TestRequestTwoPDUSessions\n
"},{"location":"guide/5-install-ueransim/","title":"5 install ueransim","text":""},{"location":"guide/5-install-ueransim/#installing-ueransim-a-ueran-simulator","title":"Installing UERANSIM - a UE/RAN Simulator","text":"

In this demo we will practice:

  • Installing UERANSIM
  • Configuring free5GC and UERANSIM
  • Running UERANSIM against free5GC
"},{"location":"guide/5-install-ueransim/#1-install-ueramsim-vm","title":"1. Install ueramsim VM","text":"

Repeat the steps of cloning free5gc VM from the base VM, create a new VM for the UERANSIM simulator:

  • Name the VM ueransim, and create new MAC addresses for all network cards.
  • Make sure the VM has internet access and can log in using SSH.
  • Change the hostname to ueransim.
  • Make the Host-only network interface have static IP address 192.168.56.102.
  • Reboot the ueransim VM, as well as the free5gc VM.
  • You can ping 192.168.56.101 from the ueransim VM, and also ping 192.168.56.102 from the free5gc VM.
"},{"location":"guide/5-install-ueransim/#2-install-ueransim","title":"2. Install UERANSIM","text":"

Search \u201cueransim\u201d on the web, and get the web site. On the web site, review what the UERANSIM open-source project is about, then browse into the installation page.

To download UERANSIM:

cd ~\ngit clone https://github.com/aligungr/UERANSIM\ncd UERANSIM\ngit checkout 3a96298\n

Update and upgrade ueransim VM first:

sudo apt update\nsudo apt upgrade\n

Install required tools:

sudo apt install make\nsudo apt install g++\nsudo apt install libsctp-dev lksctp-tools\nsudo apt install iproute2\nsudo snap install cmake --classic\n

Build UERANSIM:

cd ~/UERANSIM\nmake\n

"},{"location":"guide/5-install-ueransim/#3-install-free5gc-webconsole","title":"3. Install free5GC WebConsole","text":"

free5GC provides a simple web tool WebConsole to help creating and managing UE registrations to be used by various 5G network functions (NF). To build WebConsole we need Node.js and Yarn.

First SSH into free5gc (192.168.56.101)\uff0cand remove obsolete tools that may exists:

sudo apt remove cmdtest\nsudo apt remove yarn\n

Then install Node.js and Yarn:

curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -\necho \"deb https://dl.yarnpkg.com/debian/ stable main\" | sudo tee /etc/apt/sources.list.d/yarn.list\nsudo apt-get update\nsudo apt-get install -y nodejs yarn\n

To build WebConsole:

cd ~/free5gc\nmake webconsole\n

"},{"location":"guide/5-install-ueransim/#4-use-webconsole-to-add-an-ue","title":"4. Use WebConsole to Add an UE","text":"

First start up the WebConsole server:

cd ~/free5gc/webconsole\n./bin/webconsole\n

The screen shows the port number :5000 at the end. Open your web browser from your host machine, and enter the URL http://192.168.56.101:5000

  • On the login page, enter username admin and password free5gc.
  • Once logged in, widen the page until you see \u201cSubscribers\u201d on the left-hand side column.
  • Click on the Subscribers tab and then on the New Subscriber button
    • Scroll down to Operator Code Type and change it from \"OPc\" to \"OP\".
    • Leave the other fields unchanged. This registration data is used for ease of testing and actual use later.
    • Scroll all the way down and click on Submit.
  • Once the data shows up on the \"Subscribers\" table, you can press Ctrl-C on the terminal to kill the WebConsole process on the free5gc VM
"},{"location":"guide/5-install-ueransim/#5-setting-free5gc-and-ueransim-parameters","title":"5. Setting free5GC and UERANSIM Parameters","text":"

In free5gc VM, we need to edit three files:

  • ~/free5gc/config/amfcfg.yaml
  • ~/free5gc/config/smfcfg.yaml
  • ~/free5gc/config/upfcfg.yaml

First SSH into free5gc VM, and change ~/free5gc/config/amfcfg.yaml:

cd ~/free5gc\nnano config/amfcfg.yaml\n

Replace ngapIpList IP from 127.0.0.1 to 192.168.56.101, namely from:

...\n  ngapIpList:  # the IP list of N2 interfaces on this AMF\n  - 127.0.0.1\n
into:
...\n  ngapIpList:  # the IP list of N2 interfaces on this AMF\n  - 192.168.56.101  # 127.0.0.1\n

Next edit ~/free5gc/config/smfcfg.yaml:

nano config/smfcfg.yaml\n
and in the entry inside userplaneInformation / upNodes / UPF / interfaces / endpoints, change the IP from 127.0.0.8 to 192.168.56.101, namely from:
...\n  interfaces: # Interface list for this UPF\n   - interfaceType: N3 # the type of the interface (N3 or N9)\n     endpoints: # the IP address of this N3/N9 interface on this UPF\n       - 127.0.0.8\n
into:
...\n  interfaces: # Interface list for this UPF\n   - interfaceType: N3 # the type of the interface (N3 or N9)\n     endpoints: # the IP address of this N3/N9 interface on this UPF\n       - 192.168.56.101  # 127.0.0.8\n
Finally, edit ~/free5gc/config/upfcfg.yaml\uff0cand chage gtpu IP from 127.0.0.8 into 192.168.56.101, namely from:
...\n  gtpu:\n    forwarder: gtp5g\n    # The IP list of the N3/N9 interfaces on this UPF\n    # If there are multiple connection, set addr to 0.0.0.0 or list all the addresses\n    ifList:\n      - addr: 127.0.0.8\n        type: N3\n
into:
...\n  gtpu:\n    forwarder: gtp5g\n    # The IP list of the N3/N9 interfaces on this UPF\n    # If there are multiple connection, set addr to 0.0.0.0 or list all the addresses\n    ifList:\n      - addr: 192.168.56.101  # 127.0.0.8\n        type: N3\n

"},{"location":"guide/5-install-ueransim/#6-setting-ueransim","title":"6. Setting UERANSIM","text":"

In the ueransim VM, there are two files related to free5GC\uff1a

  • ~/UERANSIM/config/free5gc-gnb.yaml
  • ~/UERANSIM/config/free5gc-ue.yaml

The second file is for UE, which we don\u2019t have to change if the data inside is consistent with the (default) registration data we set using WebConsole previously.

First SSH into ueransim, and edit the file ~/UERANSIM/config/free5gc-gnb.yaml, and change the ngapIp IP, as well as the gtpIp IP, from 127.0.0.1 to 192.168.56.102\uff0cand also change the IP in amfConfigs into 192.168.56.101, that is, from:

...\n  ngapIp: 127.0.0.1   # gNB's local IP address for N2 Interface (Usually same with local IP)\n  gtpIp: 127.0.0.1    # gNB's local IP address for N3 Interface (Usually same with local IP)\n\n  # List of AMF address information\n  amfConfigs:\n    - address: 127.0.0.1\n
into:
...\n  ngapIp: 192.168.56.102  # 127.0.0.1   # gNB's local IP address for N2 Interface (Usually same with local IP)\n  gtpIp: 192.168.56.102  # 127.0.0.1    # gNB's local IP address for N3 Interface (Usually same with local IP)\n\n  # List of AMF address information\n  amfConfigs:\n    - address: 192.168.56.101  # 127.0.0.1\n
Next we examine the file ~/UERANSIM/config/free5gc-ue.yaml\uff0cand see if the settings is consistent with those in free5GC (via WebConsole), for example:
# IMSI number of the UE. IMSI = [MCC|MNC|MSISDN] (In total 15 or 16 digits)\nsupi: 'imsi-208930000000003'\n# Mobile Country Code value\nmcc: '208'\n# Mobile Network Code value (2 or 3 digits)\nmnc: '93'\n\n# Permanent subscription key\nkey: '8baf473f2f8fd09487cccbd7097c6862'\n# Operator code (OP or OPC) of the UE\nop: '8e27b6af0e692e750f32667a3b14605d'\n# This value specifies the OP type and it can be either 'OP' or 'OPC'\nopType: 'OP'\n\n...\n\n# Initial PDU sessions to be established\nsessions:\n  - type: 'IPv4'\n    apn: 'internet'\n    slice:\n      sst: 0x01\n      sd: 0x010203\n\n# List of requested S-NSSAIs by this UE\nslices:\n  - sst: 0x01\n    sd: 0x010203\n\n...\n
The data appear to be the same as what we set in WebConsole.

"},{"location":"guide/5-install-ueransim/#7-testing-ueransim-against-free5gc","title":"7. Testing UERANSIM against free5GC","text":"

SSH into free5gc. If you have rebooted free5gc, remember to run:

sudo sysctl -w net.ipv4.ip_forward=1\nsudo iptables -t nat -A POSTROUTING -o <dn_interface> -j MASQUERADE\nsudo systemctl stop ufw\n

Note: In Ubuntu Server 20.04 and 22.04 the dn_interface may be called enp0s3 or enp0s4 by default. Use the command ip a to help to figure it out

In addition, execute the following command:

sudo iptables -I FORWARD 1 -j ACCEPT\n

Tip: As per the information on the appendix page, it's possible to use a script to reload the config above automatically after reboot

Also, make sure you have make proper changes to the free5GC configuration files, then run ./run.sh:

cd ~/free5gc\n./run.sh\n

At this time free5GC has been started.

Next, prepare three additional SSH terminals from your host machine (if you know how to use tmux, you can use just one).

In terminal 1: SSH into ueransim, make sure UERANSIM is built, and configuration files have been changed correctly, then execute nr-gnb:

cd ~/UERANSIM\nbuild/nr-gnb -c config/free5gc-gnb.yaml\n

In terminal 2, SSH into ueransim, and execute nr-ue with admin right:

cd ~/UERANSIM\nsudo build/nr-ue -c config/free5gc-ue.yaml # for multiple-UEs, use -n and -t for number and delay\n

In terminal 3, SSH into ueransim, and ping 192.168.56.101 to see free5gc is alive. Then, use ifconfig to see if the tunnel uesimtun0 has been created (by nr-ue):

$ ifconfig\n\nenp0s3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500\n        inet 10.0.2.15  netmask 255.255.255.0  broadcast 10.0.2.255\n        inet6 fe80::a00:27ff:fe65:1472  prefixlen 64  scopeid 0x20<link>\n        ether 08:00:27:65:14:72  txqueuelen 1000  (Ethernet)\n        RX packets 80  bytes 32423 (32.4 KB)\n        RX errors 0  dropped 0  overruns 0  frame 0\n        TX packets 90  bytes 12860 (12.8 KB)\n        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0\n\nenp0s8: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500\n        inet 192.168.56.102  netmask 255.255.255.0  broadcast 192.168.56.255\n        inet6 fe80::a00:27ff:fe5e:be64  prefixlen 64  scopeid 0x20<link>\n        ether 08:00:27:5e:be:64  txqueuelen 1000  (Ethernet)\n        RX packets 1515  bytes 130490 (130.4 KB)\n        RX errors 0  dropped 0  overruns 0  frame 0\n        TX packets 1010  bytes 206670 (206.6 KB)\n        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0\n\nlo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536\n        inet 127.0.0.1  netmask 255.0.0.0\n        inet6 ::1  prefixlen 128  scopeid 0x10<host>\n        loop  txqueuelen 1000  (Local Loopback)\n        RX packets 3445  bytes 174416 (174.4 KB)\n        RX errors 0  dropped 0  overruns 0  frame 0\n        TX packets 3445  bytes 174416 (174.4 KB)\n        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0\n\nuesimtun0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST>  mtu 1500\n        inet 60.60.0.1  netmask 255.255.255.255  destination 60.60.0.1\n        inet6 fe80::2034:d00:a76:84b7  prefixlen 64  scopeid 0x20<link>\n        unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 500  (UNSPEC)\n        RX packets 3  bytes 252 (252.0 B)\n        RX errors 0  dropped 0  overruns 0  frame 0\n        TX packets 13  bytes 732 (732.0 B)\n        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0\n

Now use ping:

ping -I uesimtun0 google.com\n
If ping gets replies, then free5GC is running properly. Congratulations!

"},{"location":"guide/6-simple-app/","title":"6 simple app","text":""},{"location":"guide/6-simple-app/#free5gc-simple-apps","title":"free5GC Simple Apps","text":"

In this demo we will use free5GC together with UERANSIM to exercise on some simple network applications:

  • ping + tcpdump
  • wget and curl
"},{"location":"guide/6-simple-app/#ping-tcpdump","title":"ping + tcpdump","text":"

First start up free5GC and ueransim VMs. This requires one SSH terminal for free5gc, and two for ueransim.

Open another SSH terminal and log in into ueransim:

ssh 192.168.56.102 -l ubuntu\n
Use ifconfig to check if uesimtun0 tunnel has been created, and use ping to check if we can ping through it\uff1a
$ ping google.com\nPING google.com (172.217.27.142) 56(84) bytes of data.\n64 bytes from tsa03s02-in-f14.1e100.net (172.217.27.142): icmp_seq=1 ttl=63 time=3.98 ms\n64 bytes from tsa03s02-in-f14.1e100.net (172.217.27.142): icmp_seq=2 ttl=63 time=3.87 ms\n64 bytes from tsa03s02-in-f14.1e100.net (172.217.27.142): icmp_seq=3 ttl=63 time=4.06 ms\n^C\n--- google.com ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 2003ms\nrtt min/avg/max/mdev = 3.872/3.970/4.060/0.076 ms\n

$ ping -I uesimtun0 google.com\nPING google.com (172.217.27.142) from 60.60.0.1 uesimtun0: 56(84) bytes of data.\n64 bytes from tsa03s02-in-f14.1e100.net (172.217.27.142): icmp_seq=1 ttl=61 time=5.85 ms\n64 bytes from tsa03s02-in-f14.1e100.net (172.217.27.142): icmp_seq=2 ttl=61 time=4.87 ms\n64 bytes from tsa03s02-in-f14.1e100.net (172.217.27.142): icmp_seq=3 ttl=61 time=4.76 ms\n^C\n--- google.com ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 2004ms\nrtt min/avg/max/mdev = 4.760/5.160/5.847/0.487 ms\n

Also use route -n to observe if current routing table shows some routing rules regarding the two network interfaces enp0s3 and enp0s8:

$ route -n\nKernel IP routing table\nDestination     Gateway         Genmask         Flags Metric Ref    Use Iface\n0.0.0.0         10.0.2.2        0.0.0.0         UG    100    0        0 enp0s3\n10.0.2.0        0.0.0.0         255.255.255.0   U     0      0        0 enp0s3\n10.0.2.2        0.0.0.0         255.255.255.255 UH    100    0        0 enp0s3\n192.168.56.0    0.0.0.0         255.255.255.0   U     0      0        0 enp0s8\n

The network 10.0.2.0/24 and its enp0s3 interface are related to VirtualBox NAT network card. We can bring down this interface:

$ sudo ifconfig enp0s3 down\n$ route -n\nKernel IP routing table\nDestination     Gateway         Genmask         Flags Metric Ref    Use Iface\n192.168.56.0    0.0.0.0         255.255.255.0   U     0      0        0 enp0s8\n
As shown aboe we have only Host-only network 192.168.56.0/24 left. Run ping again:
$ ping 8.8.8.8\nping: connect: Network is unreachable\n

And see that it can not ping through, but runing:

$ ping -I uesimtun0 8.8.8.8\nPING 8.8.8.8 (8.8.8.8) from 60.60.0.1 uesimtun0: 56(84) bytes of data.\n64 bytes from 8.8.8.8: icmp_seq=1 ttl=61 time=7.17 ms\n64 bytes from 8.8.8.8: icmp_seq=2 ttl=61 time=5.41 ms\n64 bytes from 8.8.8.8: icmp_seq=3 ttl=61 time=5.15 ms\n^C\n--- 8.8.8.8 ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 2005ms\nrtt min/avg/max/mdev = 5.150/5.907/7.165/0.895 ms\n

shows some responses, since we ask ping to go through the free5GC core network. To make ping 8.8.8.8 in addition to ping -I uesimtun0 8.8.8.8 work, we can set the uesimtun0 interface (IP 60.60.0.1) as the new default gateway:

$ sudo ip r add default dev uesimtun0\n$ route -n\nKernel IP routing table\nDestination     Gateway         Genmask         Flags Metric Ref    Use Iface\n0.0.0.0         0.0.0.0         0.0.0.0         U     0      0        0 uesimtun0\n192.168.56.0    0.0.0.0         255.255.255.0   U     0      0        0 enp0s8\n
Now traffic not for the 192.168.56.0/24 network will go to uesimtun0, and ping 8.8.8.8 works this time:
$ ping 8.8.8.8\nPING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.\n64 bytes from 8.8.8.8: icmp_seq=1 ttl=61 time=5.02 ms\n64 bytes from 8.8.8.8: icmp_seq=2 ttl=61 time=6.31 ms\n64 bytes from 8.8.8.8: icmp_seq=3 ttl=61 time=5.41 ms\n^C\n--- 8.8.8.8 ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 2004ms\nrtt min/avg/max/mdev = 5.017/5.581/6.312/0.541 ms\n...\n

Note that normally we are using ueransim to simulate \u201cterminal\u201d UE device, not as a network device or proxy, therefore the above two routing rules suffice.

Now if we still want to run:

$ ping google.com\nping: google.com: Temporary failure in name resolution\n

we will get unresolved domain name. To solve this, we can modify the file /etc/resolv.conf:

sudo nano /etc/resolv.conf\n

and change the nameserver IP to 8.8.8.8:

nameserver 8.8.8.8\n

After the change, we can see ping getting responses:

$ ping google.com\nPING google.com (216.58.200.46) 56(84) bytes of data.\n64 bytes from tsa01s08-in-f46.1e100.net (216.58.200.46): icmp_seq=1 ttl=61 time=5.19 ms\n64 bytes from tsa01s08-in-f46.1e100.net (216.58.200.46): icmp_seq=2 ttl=61 time=50.4 ms\n64 bytes from tsa01s08-in-f46.1e100.net (216.58.200.46): icmp_seq=3 ttl=61 time=5.66 ms\n^C\n--- google.com ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 2004ms\nrtt min/avg/max/mdev = 5.191/20.423/50.414/21.207 ms\n

We can also examine the network traffic happening underneath in the scenario above. First we open another SSH terminal into ueransim, and run the following command:

$ sudo tcpdump -n -i any host 60.60.0.1 or 192.168.56.101\ntcpdump: verbose output suppressed, use -v or -vv for full protocol decode\nlistening on any, link-type LINUX_SLL (Linux cooked v1), capture size 262144 bytes\n

then run ping 8.8.8.8 again, wait for a couple seconds, then Ctrl-C to exit. We see the data packets actually going in and out uesimtun0.

$ sudo tcpdump -n -i any host 60.60.0.1 or 192.168.56.101\ntcpdump: verbose output suppressed, use -v or -vv for full protocol decode\nlistening on any, link-type LINUX_SLL (Linux cooked v1), capture size 262144 bytes\n10:24:56.138729 IP 192.168.56.101.38412 > 192.168.56.102.38740: sctp (1) [HB REQ]\n10:24:56.138783 IP 192.168.56.102.38740 > 192.168.56.101.38412: sctp (1) [HB ACK]\n10:24:58.456532 IP 60.60.0.1 > 8.8.8.8: ICMP echo request, id 33, seq 1, length 64\n10:24:58.457416 IP 192.168.56.102.2152 > 192.168.56.101.2152: UDP, length 100\n10:24:58.462136 IP 192.168.56.101.2152 > 192.168.56.102.2152: UDP, length 92\n10:24:58.462324 IP 8.8.8.8 > 60.60.0.1: ICMP echo reply, id 33, seq 1, length 64\n10:24:59.458823 IP 60.60.0.1 > 8.8.8.8: ICMP echo request, id 33, seq 2, length 64\n10:24:59.459031 IP 192.168.56.102.2152 > 192.168.56.101.2152: UDP, length 100\n10:24:59.464214 IP 192.168.56.101.2152 > 192.168.56.102.2152: UDP, length 92\n10:24:59.464396 IP 8.8.8.8 > 60.60.0.1: ICMP echo reply, id 33, seq 2, length 64\n10:25:00.461293 IP 60.60.0.1 > 8.8.8.8: ICMP echo request, id 33, seq 3, length 64\n10:25:00.462178 IP 192.168.56.102.2152 > 192.168.56.101.2152: UDP, length 100\n10:25:00.474941 IP 192.168.56.101.2152 > 192.168.56.102.2152: UDP, length 92\n10:25:00.475561 IP 8.8.8.8 > 60.60.0.1: ICMP echo reply, id 33, seq 3, length 64\n10:25:01.463946 IP 60.60.0.1 > 8.8.8.8: ICMP echo request, id 33, seq 4, length 64\n10:25:01.464523 IP 192.168.56.102.2152 > 192.168.56.101.2152: UDP, length 100\n10:25:01.469297 IP 192.168.56.101.2152 > 192.168.56.102.2152: UDP, length 92\n10:25:01.470314 IP 8.8.8.8 > 60.60.0.1: ICMP echo reply, id 33, seq 4, length 64\n

"},{"location":"guide/6-simple-app/#wget","title":"wget","text":"

Simply look for any web page for file download on the web. For example, if we choose Golang web site as an example, we may find the URL:

https://golang.org/dl/go1.15.8.darwin-amd64.pkg\n
Using the same network settings is the previous exercise, just
wget https://golang.org/dl/go1.15.8.darwin-amd64.pkg\n
And see if you can download a Golang 1.15.8 install file.

"},{"location":"guide/6-simple-app/#ptt-ssh-bbsupttcc","title":"ptt (ssh bbsu@ptt.cc)","text":"

You can actually use SSH in the ueransim VM to access remote site. For example, you can SSH to a well-known terminal-based BBS site in Taiwan:

ssh bbsu@ppt.cc\n

"},{"location":"guide/6-simple-app/#youtube","title":"Youtube","text":"

You can also use Youtube as an example app. To achieve this goal, you can install a desktop VM with graphical UI, such as Ubuntu Desktop, and follow the same procedure to install and start up UERANSIM, then access Youtube through uesimtun0 and free5GC.

To reduce resource consumption on your host machine, you may install Lubuntu (at https://lubuntu.me), a more light-weight Ubuntu desktop distro instead. But since viewing free5GC YouTube Channel requires quite sime CPU consumption, you may have to set at least 2 CPUs and 2048 MB memory for the VM.

Refer to videos Access Youtube on Lubuntu (1, 2, 3, 4 and 5).

"},{"location":"guide/Appendix/","title":"Appendix","text":""},{"location":"guide/Appendix/#appendix","title":"Appendix","text":""},{"location":"guide/Appendix/#appendix-a-oam","title":"Appendix A: OAM","text":"
  1. Run the OAM server
    cd webconsole\ngo run server.go\n
  2. Access the OAM by
    URL: http://localhost:5000\nUsername: admin\nPassword: free5gc\n
  3. Now you can see the information of currently registered UEs (e.g. Supi, connected state, etc.) in the core network at the tab \"DASHBOARD\" of free5GC webconsole

Note: You can add the subscribers here too

"},{"location":"guide/Appendix/#appendix-b-orchestrator","title":"Appendix B: Orchestrator","text":"

Please refer to free5gmano

"},{"location":"guide/Appendix/#appendix-c-iptv","title":"Appendix C: IPTV","text":"

Please refer to free5GC/IPTV

"},{"location":"guide/Appendix/#appendix-d-system-environment-cleaning","title":"Appendix D: System Environment Cleaning","text":"

The below commands may be helpful for development purposes.

  1. Remove POSIX message queues
    • ls /dev/mqueue/
    • rm /dev/mqueue/*
  2. Remove gtp5g tunnels (using tools in libgtp5gnl)
    • cd ./src/upf/lib/libgtp5gnl/tools
    • ./gtp5g-tunnel list pdr
    • ./gtp5g-tunnel list far
  3. Remove gtp5g devices (using tools in libgtp5gnl)
    • cd ./src/upf/lib/libgtp5gnl/tools
    • sudo ./gtp5g-link del {Dev-Name}
"},{"location":"guide/Appendix/#appendix-e-change-kernel-version","title":"Appendix E: Change Kernel Version","text":"
  1. Check the previous kernel version: uname -r
  2. Search specific kernel version and install, take 5.0.0-23-generic for example
    sudo apt search 'linux-image-5.0.0-23-generic'\nsudo apt install 'linux-image-5.0.0-23-generic'\nsudo apt install 'linux-headers-5.0.0-23-generic'\n
  3. Update initramfs and grub
    sudo update-initramfs -u -k all\nsudo update-grub\n
  4. Reboot, enter grub and choose kernel version 5.0.0-23-generic
    sudo reboot\n
"},{"location":"guide/Appendix/#optional-remove-kernel-image","title":"Optional: Remove Kernel Image","text":"
sudo apt remove 'linux-image-5.0.0-23-generic'\nsudo apt remove 'linux-headers-5.0.0-23-generic'\n
"},{"location":"guide/Appendix/#appendix-f-program-the-sim-card","title":"Appendix F: Program the SIM Card","text":"

Install packages:

sudo apt-get install pcscd pcsc-tools libccid python-dev swig python-setuptools python-pip libpcsclite-dev\nsudo pip install pycrypto\n

Download PySIM

git clone git://git.osmocom.org/pysim.git\n

Change to pyscard folder and install

cd <pyscard-path>\nsudo /usr/bin/python setup.py build_ext install\n

Verify your reader is ready

sudo pcsc_scan\n

Check whether your reader can read the SIM card

cd <pysim-path>\n./pySim-read.py \u2013p 0\n

Program your SIM card information

./pySim-prog.py -p 0 -x 208 -y 93 -t sysmoUSIM-SJS1 -i 208930000000003 --op=8e27b6af0e692e750f32667a3b14605d -k 8baf473f2f8fd09487cccbd7097c6862 -s 8988211000000088313 -a 23605945\n

You can get your SIM card from sysmocom.

"},{"location":"guide/Appendix/#appendix-g-install-mongodb-70x-on-ubuntu-server-220403","title":"Appendix G: Install MongoDB 7.0.x on Ubuntu Server 22.04.03","text":"

Check that the system CPU supports AVX instructions as it's required since MongoDB 5.0. If not (i.e. the command below returns empty output), use MongoDB 4.4.x (see step 3 from installation prerequisites instructions)

grep --color avx /proc/cpuinfo\n

Before you begin the installation, update the package manager database and make sure MongoDB prerequisites are installed

sudo apt update\nsudo apt install gnupg curl\n
Add MongoDB public GPG key
curl -fsSL https://pgp.mongodb.com/server-7.0.asc | \\\n   sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg --dearmor\n
Note: if you are installing a version other than 7.0, remember, change it on the command above

Create the APT list entry file using the command below

echo \"deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse\" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list\n

Refresh the package database then install MongoDB

sudo apt update\nsudo apt install -y mongodb-org\n

For detailed instructions on how to freeze the installed version or install a specific version of MongoDB, please, check the reference below or follow this direct URL

Don't forget to load the DB service using

sudo systemctl start mongod\n

Reference: MongoDB official website

"},{"location":"guide/Appendix/#appendix-h-using-the-reload_host_configsh-script","title":"Appendix H: Using the reload_host_config.sh script","text":"

The script was designed to help reapplying the configurations after a VM reboot

Its usage is fairly simple, just run

./reload_host_config.sh <dn_interface>\n

For example, if your DN interface (e.g. free5GC's VM LAN interface) is named enp0s4, the command above will be

./reload_host_config.sh enp0s4\n

If you are unsure regarding the name of the interface, run ip a (see the image below) to help to figure it out

An example of the expected output is depicted above

"},{"location":"guide/Configuration/","title":"Configuration","text":""},{"location":"guide/Configuration/#configuration","title":"Configuration","text":""},{"location":"guide/Configuration/#sbi-configuration","title":"SBI Configuration","text":"

There are registerIP and bindingIP design on every NF's sbi interface.

This is due to some orchestration, such as Kubernetes or OpenStack, has the design of service IP mapping.

Use Kubernetes as an example. K8s has the service type that enable users to define the service IP outside the pod. But the service IP may be different from the IP assigned inside the pod. Therefore, if we register the binding IP inside the pod to NRF, NRF cannot know which service IP outside the pod has attached. As the result, we need to separate registerIP from bindingIP in this scenario.

If you are not sure what IP you should set, just configure it as the same IP address.

"},{"location":"guide/Configuration/#sample-configuration","title":"Sample configuration","text":"

We provide a sample config to connect to outer ran under /sample/ran_attach_config/. The architecture is as following.

As the result, user's RAN IP must set to 192.168.0.0/24 subnet or let the routing route to this subnet.

Notice: If user wants to use the setting, aware to set 192.168.0.1 to your host as well.

"},{"location":"guide/Configuration/#smf-configuration","title":"SMF Configuration","text":""},{"location":"guide/Configuration/#a-configure-smf-with-s-nssai","title":"A. Configure SMF with S-NSSAI","text":"
  1. Configure NF Registration SMF S-NSSAI in smfcfg.yaml
"},{"location":"guide/Configuration/#b-configure-uplink-classifier-ulcl-information-in-smf","title":"B. Configure Uplink Classifier (ULCL) information in SMF","text":"
  1. Configure UE routing path in uerouting.yaml
  • DestinationIP and DestinationPort will be the packet destination.
  • UPF field will be the packet datapath when it match the destination above.

For more detail of SMF config, please refer to here.

"},{"location":"guide/Environment/","title":"Environment","text":""},{"location":"guide/Environment/#recommended-environment","title":"Recommended Environment","text":"

free5gc has been tested against the following environment:

  • Software
    • OS: Ubuntu 20.04.6 LTS
    • gcc 9.4.0
    • go 1.18.10 linux/amd64
    • kernel version 5.4.0-169-generic

Note: The listed kernel version is required for the UPF element.

  • Minimum Hardware

    • CPU: Intel i5 processor
    • RAM: 4GB
    • Hard drive: 160GB
    • NIC: Any 1Gbps Ethernet card supported in the Linux kernel
  • Recommended Hardware

    • CPU: Intel i7 processor
    • RAM: 8GB
    • Hard drive: 160GB
    • NIC: Any 10Gbps Ethernet card supported in the Linux kernel

This guide assumes that you will run all 5GC elements on a single machine.

"},{"location":"guide/SMF-Config/","title":"SMF Config","text":""},{"location":"guide/SMF-Config/#smf-config-ulcl-config","title":"SMF Config / ULCL Config","text":"

This document explains the detail of SMF config. Also provide some examples about conversion between config file and real User Plane topology

ULCL limitation: The branching UPF now can't connect to the Internet. It only serves as a Intranet in the UPF topology. (Please refers to the topology of example 2)

"},{"location":"guide/SMF-Config/#sbi","title":"SBI","text":"Field meaning scheme The protocol for SBI registerIPv4 IP used to register to NRF bindingIPv4 IP used to bind the service port SMF bind the SBI service to this port"},{"location":"guide/SMF-Config/#pfcp","title":"PFCP","text":"Field meaning addr The IP address of N4 interface on the SMF (PFCP)"},{"location":"guide/SMF-Config/#userplane-information","title":"Userplane Information","text":"Field meaning userplane_information Includes topology and information of RAN and UPFs which are controlled by this SMF up_nodes The node in the user plane topology. Includes gNodeB, I-UPF and A-UPF links The edge in the user plane topology type Indicate it is RAN or specific kind of UPF node_id The PFCP IPv4 address for UPF

Note: up_resource_ip serves as default user plane IP for the UPF. In this version, UPF will determine its user plane IP by itself. So setting up_resource_ip in SMF config won't affect real config in user plane.

"},{"location":"guide/SMF-Config/#amf-config","title":"AMF Config","text":"

To understand whole PDU session config, we must take a step forward to understand the AMF config.

Field meaning NGAPIPList The IP list of N2 interfaces on the AMF SBI Same meaning with SMF/SBI."},{"location":"guide/SMF-Config/#example-1","title":"Example 1","text":""},{"location":"guide/SMF-Config/#smf-config","title":"SMF Config","text":"
  • sbi:
    • scheme: http
    • registerIPv4: 127.0.0.2
    • bindingIPv4: 127.0.0.2
    • port: 8000
  • pfcp:
    • addr: 10.200.200.1
  • userplane_information:
    • up_nodes:
      • gNB1:
        • type: AN
      • UPF:
        • type: UPF
        • node_id: 10.200.200.102
    • links:
      • A: gNB1
      • B: UPF
"},{"location":"guide/SMF-Config/#amf-config_1","title":"AMF Config","text":"
  • ngapIpList:
    • 127.0.0.1
  • sbi:
    • scheme: http
    • registerIPv4: 127.0.0.18
    • bindingIPv4: 127.0.0.18
    • port: 8000
"},{"location":"guide/SMF-Config/#representing-topology","title":"Representing Topology","text":""},{"location":"guide/SMF-Config/#example-2","title":"Example 2","text":""},{"location":"guide/SMF-Config/#smf-config_1","title":"SMF Config","text":"
  • sbi:
    • scheme: https
    • registerIPv4: 127.0.0.2
    • bindingIPv4: 127.0.0.2
    • port: 29502
  • pfcp:
    • addr: 10.200.200.1
  • userplane_information:
    • up_nodes:
      • gNB1:
        • type: AN
      • BranchingUPF:
        • type: UPF
        • node_id: 10.200.200.102
      • AnchorUPF1:
        • type: UPF
        • node_id: 10.200.200.101
      • AnchorUPF2:
        • type: UPF
        • node_id: 10.200.200.103
      • links:
      • A: gNB1 B: BranchingUPF
      • A: BranchingUPF B: AnchorUPF1
      • A: BranchingUPF B: AnchorUPF2
"},{"location":"guide/SMF-Config/#amf-config_2","title":"AMF Config","text":"
  • ngapIpList:
    • 127.0.0.1
  • sbi:
    • scheme: https
    • registerIPv4: 127.0.0.18
    • bindingIPv4: 127.0.0.18
    • port: 8000
"},{"location":"guide/SMF-Config/#representing-topology_1","title":"Representing Topology","text":""},{"location":"guide/Trouble_Shooting/","title":"Trouble Shooting","text":""},{"location":"guide/Trouble_Shooting/#trouble-shooting","title":"Trouble Shooting","text":""},{"location":"guide/Trouble_Shooting/#1-error-sctp-failed-to-connect-given-amf-n3iwfngap","title":"1. ERROR: [SCTP] Failed to connect given AMF N3IWF=NGAP","text":"

This error occured when N3IWF was started before AMF finishing initialization. This error usually appears when you run the TestNon3GPP in the first time.

Rerun the test should be fine. If it still not be solved, larger the sleeping time in line 110 of test.sh.

"},{"location":"guide/Trouble_Shooting/#2-testnon3gpp","title":"2. TestNon3GPP","text":"

TestNon3GPP will modify the config/amfcfg.conf. So, if you had killed the TestNon3GPP test before it finished, you might need to copy config/amfcfg.conf.bak back to config/amfcfg.conf to let other tests pass.

cp config/amfcfg.conf.bak config/amfcfg.conf

"},{"location":"guide/Trouble_Shooting/#3-db-on-tls-to-h2c","title":"3. DB on TLS to H2C","text":"

If you meet any problems about https or mogodb, it maybe couse our new version from v3.0.1 to v3.0.2 has change http to H2C verion. Try the command below.

mongo --eval \"db.NfProfile.drop()\" free5gc

"},{"location":"guide/Trouble_Shooting/#4-mqcreate-error-creating-message-queue-too-many-open-files-upfutil-upf","title":"4. MQCreate() Error creating message queue: Too many open files UPF=Util (UPF)","text":"

Remove POSIX message queues

ls /dev/mqueue/\nrm /dev/mqueue/*\n
"},{"location":"guide/Trouble_Shooting/#5-remove-gtp-devices-using-tools-in-libgtp5gnl-upf","title":"5. Remove gtp devices (using tools in libgtp5gnl) (UPF)","text":"
cd lib/libgtp5gnl/tools\nsudo ./gtp5g-link del {Dev-Name}\n
"},{"location":"guide/Trouble_Shooting/#6-upf-cli-run-error-open-gtp5g-open-link-create-file-exists","title":"6. UPF Cli Run Error: open Gtp5g: open link: create: file exists","text":"
sudo ip link del upfgtp\n
"},{"location":"guide/Trouble_Shooting/#7-smf-cannot-communicate-with-upf-upf-cannot-start-after-a-reboot-or-crash","title":"7. SMF cannot communicate with UPF / UPF cannot start after a reboot or crash","text":"

The message below shows up on the logs:

[WARN][SMF][Main] Failed to setup an association with UPF[127.0.0.8], error:Request Transaction [1]: retry-out\n

Verify on the logs if you got this message:

[ERRO][UPF][Main] UPF Cli Run Error: open Gtp5g: open link: create: operation not supported\n

If yes, then try to load the GTP module on the system:

modprobe gtp5g\n

If this outputs an error like this:

modprobe: FATAL: Module gtp5g not found in directory /lib/modules/5.4.0-xxx-generic\n

Reinstall the GTP-U kernel module using:

cd ~\ngit clone -b v0.8.3 https://github.com/free5gc/gtp5g.git\ncd ~/gtp5g\nsudo make\nsudo make install\n

Then, once running the core run.sh script, you should obtain the message on the logs:

[INFO][SMF][Main] Received PFCP Association Setup Accepted Response from UPF[127.0.0.8]\n

and it should work as normal

After that, if it's required to reload the module, just run modprobe gtp5g again

Note: The symptoms described above may happen if the host machine updated it's kernel version recently

References: Free5GC Forum and Install Guide

"},{"location":"guide/Trouble_Shooting/#8-decode-http2-packet-in-wireshark","title":"8. Decode HTTP/2 packet in Wireshark","text":"
  1. Run Network Function

    Check has XXFsslkey.log

  2. Edit >> Preference >> Protocols >> SSL (TLS)

  3. Add keylog

  4. Filter http2

"},{"location":"guide/Trouble_Shooting/#9-decode-h2c-http2-clear-text-without-tls","title":"9. Decode H2C (HTTP2 clear text without TLS)","text":"

The similar reason as NEA0 NAS message. Althrough H2C is clear text, wirshark still considers these packets as the normal TCP packets and does not decode them by HTTP2.

To see the details of H2C packets, do the following configuration.

  1. Analyze \u2192 Decode As\u2026

  2. click Add button to add the decode rules

    Decode the packets from the TCP ports listened by each NF as HTTP2 packets.

"},{"location":"guide/Trouble_Shooting/#10-to-clear-all-iptables-rules","title":"10. To clear all iptables rules","text":"

If something went wrong, it's possible to reset iptables' rules back using:

sudo iptables -P INPUT ACCEPT\nsudo iptables -P FORWARD ACCEPT\nsudo iptables -P OUTPUT ACCEPT\nsudo iptables -t nat -F\nsudo iptables -t mangle -F\nsudo iptables -F\nsudo iptables -X\n

Then remember to add back the rules required by the free5GC.

"},{"location":"guide/features/","title":"Features","text":""},{"location":"guide/features/#free5gc-specification","title":"free5GC Specification","text":"
  • 3GPP TS 23.501/23.502-Rel-15
  • 5G Standalone (SA) supported
  • Serviced-Based Interface (SBI) supported
    • Namf, Nsmf, Nausf, Nudm, Nudr, Nnssf, Nnrf, Npcf
  • N1, N2, N3, N4, N6, N9, interfaces supported
  • 5G SA Network Functions supported:
    • AMF: Access and Mobility Management Function
      • Registration Management, Connection Management, Reachability Management, Mobility Management, and Authentication
    • SMF: Session Management Function
      • Session Management, IP Assigning/Management
    • UPF: User Plane Function
      • Support multiple UPF and ULCL (uplink classifier)
      • Session and Service Continuity (SSC) mode 1
      • Packet Routing/Forwarding
    • CHF: Charging Function (will be supported in release v3.3.1)
      • Online/Offline Charging
      • Webconsole acts as BD (Billing Domain)
      • Flow-Based Charging on PDU Session
    • AUSF: Authentication Server Function
    • NRF: NF Repository Function
    • UDM: Unified Data Management
    • UDR: Unified Data Repository
    • PCF: Policy and Charging Function
    • NSSF: Network Slice Selection Function
    • N3IWF: Non-3GPP Interworking Function
"},{"location":"guide/features/#supported-features","title":"Supported features","text":"
  • Registration
    • Initial Registration
    • Periodic Registration
    • Mobility Registration
  • Authentication
    • 5G-AKA
    • EAP-AKA'
  • NAS Security
    • Ciphering: NEA0, NEA1, NEA2, NEA3
    • Integrity: NIA0, NIA1, NIA2, NIA3
  • Deregistration:
    • UE-initiated Deregistration
  • Service Request:
    • UE triggered Service Request
    • Network Triggered Service Request
  • AN Release
  • PDU Session Establishment
  • PDU Session Modification (v3.3.0)
  • Converged Charging (CHF has been published, all of the functions will be enabled in the incoming release)
    • Billing Domain (webconsole)
    • ABMF, RF, and CGF built-in CHF
    • Flow-based Charging (FBC)
  • PDU Session Release
  • Handover
    • N2 Handover (Indirect mode not supported)
    • Xn Handover
  • QoS
    • Control Plane only:
      1. 5QI, ARP, GBR, MBR of QoS Flow (v3.3.0)
      2. Session-AMBR supported
  • Collection and reporting of usage data over N4 interface
    • Volume measurement periodically
  • UP Security
  • Multiple UPFs and ULCL (Uplink Classifier)
  • Multiple Slice and DNN
  • Dynamic/Static IPv4 address allocation
"},{"location":"guide/hardware/","title":"Hardware","text":""},{"location":"guide/hardware/#hardware-tested","title":"Hardware Tested","text":"

Some 5G UE and gNodeB hardware have been tested with free5GC by partners or community members:

  • 5G UE (Support 5G SA):

    • APAL 5G Dongle
    • APAL 5G MiFi
    • Samsung S21 5G
    • Huawei P40 5G (forum link)
    • Huawei Mate30 5G (forum link)
  • gNodeB:

    • Alpha gNodeB
    • Compal gNodeB
    • FII gNodeB
    • ITRI gNodeB
    • Lions gNodeB
    • Amarisoft gNodeB (forum link)
    • Nokia gNodeB (forum link)
    • Nokia (AMIA AirScale Indoor Subrack 473098A)
    • OpenAirInterface
    • Open Source RAN Project - srsRAN

Reports of tested hardware not listed above on Github issue or free5GC forum are welcome.

PS: if you don't have any hardware available, we suggest to use UERANSIM to simulate.

(Refer to Advanced environment setup section)

"},{"location":"guide/Webconsole/Create-Subscriber-via-webconsole/","title":"Create Subscriber via webconsole","text":""},{"location":"guide/Webconsole/Create-Subscriber-via-webconsole/#create-subscriber-via-webconsole","title":"Create Subscriber via Webconsole","text":""},{"location":"guide/Webconsole/Create-Subscriber-via-webconsole/#1-install-webconsole","title":"1. Install Webconsole","text":"

If Webconsole isn't installed yet, please, follow the instructions from GitHub page.

"},{"location":"guide/Webconsole/Create-Subscriber-via-webconsole/#2-optional-delete-mongodb-database","title":"2. (Optional) Delete MongoDB database","text":"

If another version of free5GC was ran before, you have to delete MongoDB.

$ mongo --eval \"db.dropDatabase()\" free5gc\n

"},{"location":"guide/Webconsole/Create-Subscriber-via-webconsole/#3-run-webconsole-server","title":"3. Run Webconsole server","text":"
$ cd ~/free5gc/webconsole\n$ go run server.go\n
"},{"location":"guide/Webconsole/Create-Subscriber-via-webconsole/#4-open-webconsole","title":"4. Open Webconsole","text":"

Enter URL: <Webconsole server's IP>:5000 in browser

Default credential:

Username: admin\nPassword: free5gc\n

"},{"location":"guide/Webconsole/Create-Subscriber-via-webconsole/#5-add-new-subscriber","title":"5. Add new subscriber","text":"

Click SUBSCRIBERS -> CREATE

Edit the Subscriber's data and click CREATE, here you can configure the

  • Network Slicing configuration
    • SST/SD
    • DNN configuration
      • AMBR
      • Default 5QI
      • Flow configuration
        • IP Filter
        • Precedence
        • 5QI
        • Uplink GBR/MBR
        • Downlink GBR/MBR
        • Flow-Based Charging Config

Check that the new subscriber was added successfully

"},{"location":"membership/","title":"Index","text":""},{"location":"membership/#sponsorship-info","title":"Sponsorship Info","text":""},{"location":"membership/#sponsorship-tiers","title":"Sponsorship Tiers","text":"

free5GC is a nonprofit organization dedicated to developing innovative and next-generation features for open-source code of the 5G Core (5GC) Network under Apache 2.0 license. We are tirelessly working on the development of R16, R17, and so on, towards 6G. Your generous support and sponsorship will sustain our technology development and the operation of the community. Your company/organization logo will be displayed on the free5GC website and listed as the sponsorship you participate. Here are the sponsorship tiers we offer.

  • Sponsor \u2013 donation under US $17,000
  • Bronze Sponsor \u2013 donation from US 34,000
  • Silver Sponsor \u2013 donation from US 68,000
  • Gold Sponsor \u2013 donation from US 102,000
  • Platinum Sponsor \u2013 donation for more than US $102,000

You can now support free5GC with your credit card. Simply follow the two-step instructions provided here and use the link. (Download instruction PDF). Donations made in the US can be tax-deductible.

Sponsors who can read Chinese can use this link to donate.

Your generosity is appreciated.

Tips

  • You don\u2019t need to donate to use free5GC. You can download free5GC from https://github.com/free5gc/free5gc.
  • The license of free5GC follows Apache 2.0. That is, anyone can use free5GC for commercial purposes for free. We will not charge any license fee.
  • If you have any questions or problems regarding donation and sponsorship, please email us at free5GC.org@gmail.com.
"},{"location":"support/","title":"Index","text":""},{"location":"support/#technical-support","title":"Technical Support","text":"

If you encounter the usage problem on free5GC, please join our official forum forum.free5gc.org and initiate a new discussion.

Otherwise, you can raise the issue on our GitHub repository for reporting the bugs/suggestions (related to vulnerability/functionality/deployment/testing), or create the pull request for contributing to our community!

Tips

If your problem can not be solved via the platforms listed above, please send an email directly to free5GC.org@gmail.com. Thanks.

"}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 00000000..0f8724ef --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..c996f344cae61da18e29db6817b33586c12069ec GIT binary patch literal 127 zcmV-_0D%7=iwFp}CzfRb|8r?{Wo=<_E_iKh04<9_3V)_WXo8&M?ytk3HC}0~zlG)Vu + + + + + + + + + + + + + + + + + Index - free5GC + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +

Technical Support

+

If you encounter the usage problem on free5GC, please join our official forum forum.free5gc.org and initiate a new discussion.

+

Otherwise, you can raise the issue on our GitHub repository for reporting the bugs/suggestions (related to vulnerability/functionality/deployment/testing), or create the pull request for contributing to our community!

+
+

Tips

+

If your problem can not be solved via the platforms listed above, please send an email directly to free5GC.org@gmail.com.
+Thanks.

+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/videos/index.html b/videos/index.html new file mode 100644 index 00000000..7aaca925 --- /dev/null +++ b/videos/index.html @@ -0,0 +1,859 @@ + + + + + + + + + + + + + + + + + + + + + Other Videos - free5GC + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +

Other videos showing free5GC

+
    +
  1. +

    Akraino Blueprints: Integrated Cloud Native Private Wireless, The Linux Foundation, October 11, 2021

    +
  2. +
  3. +

    SD Core Techinar July 7 2021, Open Networking Foundation, July 13, 2021

    +
  4. +
  5. +

    Aarna Networks MWC 2021 Demo, Aarna Networks Channel, June 27, 2021

    +
  6. +
  7. +

    OpenStack Tacker Demo, Open Infrastructure Foundation, April 26, 2021

    +
  8. +
  9. +

    OpenNess Tungsten Fabric free5GC demo, Aarna Networks Channel, February 16, 2021

    +
  10. +
  11. +

    5G Core on Diamanti, Diamanti, Inc., February 3, 2021

    +
  12. +
  13. +

    free5GC (5G Core) Orchestration on Kubernetes with Tungsten Fabric CNI and Testing, Aarna Networks Channel, December 2, 2020

    +
  14. +
  15. +

    IoT LoRa (sensors and gateway in hardware), RAN in hardware (SDR) and software, and the free5GC, LABORA Research Group, July 3, 2020

    +
  16. +
  17. +

    UE and eNodeB in Hardware (conventional cell phone + SDR) and free5GC: a pratical approach in 5G, LABORA Research Group, July 3, 2020

    +
  18. +
  19. +

    OpenAirInterface and free5GC: a pratical approach in 5G networks, LABORA Research Group, June 29, 2020

    +
  20. +
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file

Currently, the major contributors are from National Yang Ming Chiao Tung University (NYCU). Please refer to our roadmap for the features of each release.