free5GCソースコード理解に向けた基礎知識

# What is free5GC

free5GCは、第5世代（5G）モバイルコアネットワーク向けのオープンソースプロジェクトです。
最終的な目標は、3GPPリリース15（R15）以降で定義されている5Gコアネットワーク（5GC）を実装することだそうです。

https://github.com/free5gc/free5gc

# そもそも、5GCoreの特徴は？

5GCoreを構成する機能群NF(Network Function)は、以下の図のようになっています。
そして、free5GCでは、RAN、UEを除く基本ソフトウェアモジュールを提供するものだそうです。

![5G system architecture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/c257ed39-213d-62f8-74f4-8caedb6e4ae2.png)

そして、5Gモバイル端末が、RANを経由して、5GCoreに登録する手順は、こんな感じのシーケンスになるようです。
![3gpp_ts_23_501.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/aeca40eb-c772-ad87-8972-9bc75b2007af.png)

# 5GCore動作を理解するには？
もちろん、実際に、free5GCが動作する環境を自前で準備して、色々と手元で動作を確認するのが早道だと思います。
そして、次のステップとしては、「どのようなfree5GCがメカニズムで動作しているのか？」を学習するのが王道でしょうか。
幸い、free5GCは、golangで実装されているので、ソースコードリーディングも行いやすいと思います。（UPFを除く）

# free5GCのソースコードを理解するための事前知識
ここでは、UPFの話は、対象外とさせて頂きます。
free5GC動作については、基本的に、Webアプリ基盤をベースとした、マイクロサービス的なアプローチで各NF(Network Function)を構成しています。従って、「どのような条件で、NF(Network Function)が動作するのか？」を理解するのが、free5GCのソースコード理解の早道になるわけです。

## free5GCの特徴１「OpenAPI活用」
5GCoreでのAPI仕様を、OpenAPIで定義されたドキュメントをベースとして、Webアプリ基盤をベースのコード実装を行なっています。
ちなみに、UDMモジュールが提供するサービスは、下表のように規定されています。
![udm.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/3313f487-2f09-1621-7664-05c3c7e486e1.png)


例えば、5GCoreでのAPI仕様をYAMLファイルで定義したOpenAPIは、こちらのWebサイトで確認できます。

https://www.opensourceagenda.com/projects/5gc-apis

そして、Nudm_UECMサービスに関わるインタフェースは、こんな感じで確認できます。
![swagger editor.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/78061f33-d118-856b-3508-bf77855b2338.png)

free5GCのWebアプリ基盤では、OpenAPIとして定義されたYAMLファイルを活用したOpenAPI Generatorツールを活用しています。
従って、OpenAPI Generatorでのコード自動生成ツールを習熟しておくことが、free5GCのWebアプリ基盤の理解の助けになるはずです。

ちなみに、OpenAPI Generatorツールの使い方のサンプルは、こちらのリポジトリが参考になると思います。

https://github.com/ttsubo/sample-openapi


## free5GCの特徴２「独自FSMなステート管理」

5GCoreでのRegistration Procedureをもう一度再掲します。
![3gpp_ts_23_501.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/64229b04-818a-3336-7835-9751122853f9.png)

5GCoreでのRegistration Procedureでの、技術サイト"RAN〜AMF間でのNASメッセージのやりとりについては5G NR SA Registration/Attach Call Flow"が参考になりました。

https://www.techplayon.com/5g-nr-sa-registration-attach-call-flow

着目すべきポイントは、「AMFが、RANからNASメッセージを受信した時に、如何に適切な処理を実装するか？」になります。
free5GCでは、「独自FSMなステート管理」を実装することによって対応しているようです。
![5G-Call-Flow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/680eceb9-3030-3f9a-5cbb-d2a82abd62c5.png)


free5GCのfsmリポジトリがその役割を担うわけですが、その活用方法については、READMEファイル含め、何の情報も公開されていないので、free5GCのソースコードリーディングに向けた乗り越えるべき最大の課題だと思います。

https://github.com/free5gc/fsm

# free5GCでのfsm機構を理解する

ちなみに、free5GCでのfsm機構のスペックとしては、
- STATE情報の状態遷移を定義して、SendEvent関数の発動条件と紐付けることができる
- 状態遷移の保持状態に応じた、適切な挙動を定義できる
- STATE情報の状態遷移には、SendEvent関数を使用する
- RANから、NASメッセージを受信した時には、SendEvent関数を起動することによって、fsm機構を動作させる


free5GCの特徴２「独自FSMなステート管理」を理解するために、サンプルアプリを別途、準備して挙動を確認しました。

https://github.com/ttsubo/free5gc-fsm-sample

まず、やりたいことは、5GCoreでのRegistration Procedureにて、AMFが、以下の4つのNASメッセージを受信した場合に、AMF内部で保持しているSTATE情報に応じて、適切な挙動を行う仕組みを理解したいという点です。

- [NAS-PDU: Registration request]
- [NAS-PDU: Authentication Response]
- [NAS-PDU: Security mode complete]
- [NAS-PDU: Registration complete]

## （１）STATE情報の状態遷移を定義して、SendEvent関数の発動条件と紐付ける


```go:free5gc-fsm-sample/gmm/init.go
package gmm

import (
	"fmt"
	"github.com/free5gc/fsm"
	"github.com/ttsubo/free5gc-fsm-sample/context"
)

const (
	GmmMessageEvent          fsm.EventType = "Gmm Message"
	StartAuthEvent           fsm.EventType = "Start Authentication"
	AuthSuccessEvent         fsm.EventType = "Authentication Success"
	AuthRestartEvent         fsm.EventType = "Authentication Restart"
	SecurityModeSuccessEvent fsm.EventType = "SecurityMode Success"
	ContextSetupSuccessEvent fsm.EventType = "ContextSetup Success"
)

var transitions = fsm.Transitions{
	{Event: GmmMessageEvent, From: context.Deregistered, To: context.Deregistered},
	{Event: GmmMessageEvent, From: context.Authentication, To: context.Authentication},
	{Event: GmmMessageEvent, From: context.SecurityMode, To: context.SecurityMode},
	{Event: GmmMessageEvent, From: context.ContextSetup, To: context.ContextSetup},
	{Event: GmmMessageEvent, From: context.Registered, To: context.Registered},
	{Event: StartAuthEvent, From: context.Deregistered, To: context.Authentication},
	{Event: AuthSuccessEvent, From: context.Authentication, To: context.SecurityMode},
	{Event: SecurityModeSuccessEvent, From: context.SecurityMode, To: context.ContextSetup},
	{Event: ContextSetupSuccessEvent, From: context.ContextSetup, To: context.Registered},
}

var callbacks = fsm.Callbacks{
	context.Deregistered:   DeRegistered,
	context.Authentication: Authentication,
	context.SecurityMode:   SecurityMode,
	context.ContextSetup:   ContextSetup,
	context.Registered:     Registered,
}

var GmmFSM *fsm.FSM

func init() {
	if f, err := fsm.NewFSM(transitions, callbacks); err != nil {
		fmt.Printf("Initialize Gmm FSM Error: %+v", err)
	} else {
		GmmFSM = f
	}
}
```

## （２）状態遷移の保持状態に応じた、適切な挙動を定義する

```go:free5gc-fsm-sample/blob/main/gmm/sm.go
package gmm

import (
	"fmt"
	"github.com/free5gc/fsm"
)

func DeRegistered(state *fsm.State, event fsm.EventType, args fsm.ArgsType) {
	switch event {
	case fsm.EntryEvent:
		fmt.Printf("# Receiving Event: [%+v] on DeRegistered\n", event)
	case GmmMessageEvent:
		fmt.Printf("# Receiving Event: [%+v] on DeRegistered\n", event)
		fmt.Println("... Send Event: StartAuthEvent")
		GmmFSM.SendEvent(state, StartAuthEvent, fsm.ArgsType{})
	case StartAuthEvent:
		fmt.Printf("# Receiving Event: [%+v] on DeRegistered\n", event)
	case fsm.ExitEvent:
		fmt.Printf("# Receiving Event: [%+v] on DeRegistered\n", event)
	default:
		fmt.Println("Unknown event [%+v]", event)
	}
}

func Authentication(state *fsm.State, event fsm.EventType, args fsm.ArgsType) {
	switch event {
	case fsm.EntryEvent:
		fmt.Printf("## Receiving Event: [%+v] on Authentication\n", event)
		fallthrough
	case AuthRestartEvent:
		fmt.Printf("## (Receiving Event: AuthRestartEvent on Authentication)\n")
	case GmmMessageEvent:
		fmt.Printf("## Receiving Event: [%+v] on Authentication\n", event)
		fmt.Println("... Send Event: AuthSuccessEvent")
		GmmFSM.SendEvent(state, AuthSuccessEvent, fsm.ArgsType{})
	case AuthSuccessEvent:
		fmt.Printf("## Receiving Event: [%+v] on Authentication\n", event)
	case fsm.ExitEvent:
		fmt.Printf("## Receiving Event: [%+v] on Authentication\n", event)
	default:
		fmt.Println("Unknown event [%+v]", event)
	}
}

func SecurityMode(state *fsm.State, event fsm.EventType, args fsm.ArgsType) {
	switch event {
	case fsm.EntryEvent:
		fmt.Printf("### Receiving Event: [%+v] on SecurityMode\n", event)
	case GmmMessageEvent:
		fmt.Printf("### Receiving Event: [%+v] on SecurityMode\n", event)
		fmt.Println("... Send Event: SecurityModeSuccessEvent")
		GmmFSM.SendEvent(state, SecurityModeSuccessEvent, fsm.ArgsType{})
	case SecurityModeSuccessEvent:
		fmt.Printf("### Receiving Event: [%+v] on SecurityMode\n", event)
	case fsm.ExitEvent:
		fmt.Printf("### Receiving Event: [%+v] on SecurityMode\n", event)
	default:
		fmt.Println("Unknown event [%+v]", event)
	}
}

func ContextSetup(state *fsm.State, event fsm.EventType, args fsm.ArgsType) {
	switch event {
	case fsm.EntryEvent:
		fmt.Printf("#### Receiving Event: [%+v] on ContextSetup\n", event)
	case GmmMessageEvent:
		fmt.Printf("#### Receiving Event: [%+v] on ContextSetup\n", event)
		fmt.Println("... Send Event: ContextSetupSuccessEvent")
		GmmFSM.SendEvent(state, ContextSetupSuccessEvent, fsm.ArgsType{})
	case ContextSetupSuccessEvent:
		fmt.Printf("#### Receiving Event: [%+v] on ContextSetup\n", event)
	case fsm.ExitEvent:
		fmt.Printf("#### Receiving Event: [%+v] on ContextSetup\n", event)
	default:
		fmt.Printf("Unknown event [%+v]", event)
	}
}

func Registered(state *fsm.State, event fsm.EventType, args fsm.ArgsType) {
	switch event {
	case fsm.EntryEvent:
		fmt.Printf("##### Receiving Event: [%+v] on Registered\n", event)
	default:
		fmt.Printf("Unknown event [%+v]", event)
	}
}
```

## （３）RANから、NASメッセージを受信した時には、SendEvent関数を起動することによって、fsm機構を動作させる

```go:free5gc-fsm-sample/main.go
package main

import (
	"fmt"
	"github.com/free5gc/fsm"
	"github.com/ttsubo/free5gc-fsm-sample/context"
	"github.com/ttsubo/free5gc-fsm-sample/gmm"
	"time"
)

func main() {
	ue := context.NewDummyAmfUe()

	time.Sleep(2 * time.Second)
	fmt.Println("... Send Event: [GmmMessageEvent], after receiving [NAS-PDU: Registration request]")
	gmm.GmmFSM.SendEvent(ue.State, gmm.GmmMessageEvent, fsm.ArgsType{})
	fmt.Println("")

	time.Sleep(5 * time.Second)
	fmt.Println("... Send Event: [GmmMessageEvent], after receiving [NAS-PDU: Authentication Response]")
	gmm.GmmFSM.SendEvent(ue.State, gmm.GmmMessageEvent, fsm.ArgsType{})
	fmt.Println("")

	time.Sleep(5 * time.Second)
	fmt.Println("... Send Event: [GmmMessageEvent], after receiving [NAS-PDU: Security mode complete]")
	gmm.GmmFSM.SendEvent(ue.State, gmm.GmmMessageEvent, fsm.ArgsType{})
	fmt.Println("")

	time.Sleep(5 * time.Second)
	fmt.Println("... Send Event: [GmmMessageEvent], after receiving [NAS-PDU: Registration complete]")
	gmm.GmmFSM.SendEvent(ue.State, gmm.GmmMessageEvent, fsm.ArgsType{})
}
```

あと、状態管理を担うコンテキストも定義します

```go:free5gc-fsm-sample/context/context.go
package context

import (
	"github.com/free5gc/fsm"
)

const (
	Deregistered            fsm.StateType = "Deregistered"
	DeregistrationInitiated fsm.StateType = "DeregistrationInitiated"
	Authentication          fsm.StateType = "Authentication"
	SecurityMode            fsm.StateType = "SecurityMode"
	ContextSetup            fsm.StateType = "ContextSetup"
	Registered              fsm.StateType = "Registered"
)

type DummyAmfUe struct {
	State *fsm.State
}

func (ue *DummyAmfUe) init() {
	ue.State = fsm.NewState(Deregistered)
}

func NewDummyAmfUe() *DummyAmfUe {
	ue := DummyAmfUe{}
	ue.init()
	return &ue
}
```

## 実際に、動かしてみる

コマンドの実行結果に基づき、サンプルアプリ実装をトレースすれば、fsm機構の習熟も可能だと思います。
```
% go run main.go
... Send Event: [GmmMessageEvent], after receiving [NAS-PDU: Registration request]
Receiving Event: [Gmm Message] on DeRegistered
... Send Event: StartAuthEvent
Receiving Event: [Exit event] on DeRegistered
Receiving Event: [Start Authentication] on DeRegistered
Receiving Event: [Entry event] on Authentication
(Receiving Event: AuthRestartEvent on Authentication)

... Send Event: [GmmMessageEvent], after receiving [NAS-PDU: Authentication Response]
Receiving Event: [Gmm Message] on Authentication
... Send Event: AuthSuccessEvent
Receiving Event: [Exit event] on Authentication
Receiving Event: [Authentication Success] on Authentication
Receiving Event: [Entry event] on SecurityMode

... Send Event: [GmmMessageEvent], after receiving [NAS-PDU: Security mode complete]
Receiving Event: [Gmm Message] on SecurityMode
... Send Event: SecurityModeSuccessEvent
Receiving Event: [Exit event] on SecurityMode
Receiving Event: [SecurityMode Success] on SecurityMode
Receiving Event: [Entry event] on ContextSetup

... Send Event: [GmmMessageEvent], after receiving [NAS-PDU: Registration complete]
Receiving Event: [Gmm Message] on ContextSetup
... Send Event: ContextSetupSuccessEvent
Receiving Event: [Exit event] on ContextSetup
Receiving Event: [ContextSetup Success] on ContextSetup
Receiving Event: [Entry event] on Registered
```

以上、free5GCのソースコードを理解するため、「どのようなfree5GCがメカニズムで動作しているのか？」の必要最低限なトピックを基礎知識としてまとめてみました。