From 391cb791070d5ee497396d0db041b222521f7de4 Mon Sep 17 00:00:00 2001
From: Louis Royer <infos.louis.royer@gmail.com>
Date: Mon, 16 Dec 2024 16:00:47 +0100
Subject: [PATCH 1/2] Rename RadioPeerMsg.go to radio_peer_msg.go

---
 jsonapi/n1n2/{RadioPeerMsg.go => radio_peer_msg.go} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename jsonapi/n1n2/{RadioPeerMsg.go => radio_peer_msg.go} (100%)

diff --git a/jsonapi/n1n2/RadioPeerMsg.go b/jsonapi/n1n2/radio_peer_msg.go
similarity index 100%
rename from jsonapi/n1n2/RadioPeerMsg.go
rename to jsonapi/n1n2/radio_peer_msg.go

From a36ba0a453aea35d99a171aa06125ed2e6543342 Mon Sep 17 00:00:00 2001
From: Louis Royer <infos.louis.royer@gmail.com>
Date: Tue, 17 Dec 2024 18:15:59 +0100
Subject: [PATCH 2/2] Add n1n2 api for Handover; close #23

---
 jsonapi/control_uri.go                  |  3 +++
 jsonapi/fteid.go                        | 20 ++++++++++++++++++
 jsonapi/n1n2/handover_command.go        | 22 +++++++++++++++++++
 jsonapi/n1n2/handover_confirm.go        | 22 +++++++++++++++++++
 jsonapi/n1n2/handover_notify.go         | 22 +++++++++++++++++++
 jsonapi/n1n2/handover_request.go        | 22 +++++++++++++++++++
 jsonapi/n1n2/handover_request_ack.go    | 22 +++++++++++++++++++
 jsonapi/n1n2/handover_required.go       | 22 +++++++++++++++++++
 jsonapi/n1n2/n2_pdu_session_req_msg.go  |  5 +----
 jsonapi/n1n2/n2_pdu_session_resp_msg.go |  7 ++++---
 jsonapi/n1n2/session.go                 | 23 ++++++++++++++++++++
 jsonapi_test/n1n2_test/session_test.go  | 28 +++++++++++++++++++++++++
 12 files changed, 211 insertions(+), 7 deletions(-)
 create mode 100644 jsonapi/fteid.go
 create mode 100644 jsonapi/n1n2/handover_command.go
 create mode 100644 jsonapi/n1n2/handover_confirm.go
 create mode 100644 jsonapi/n1n2/handover_notify.go
 create mode 100644 jsonapi/n1n2/handover_request.go
 create mode 100644 jsonapi/n1n2/handover_request_ack.go
 create mode 100644 jsonapi/n1n2/handover_required.go
 create mode 100644 jsonapi/n1n2/session.go
 create mode 100644 jsonapi_test/n1n2_test/session_test.go

diff --git a/jsonapi/control_uri.go b/jsonapi/control_uri.go
index 61c4a16..74a5607 100644
--- a/jsonapi/control_uri.go
+++ b/jsonapi/control_uri.go
@@ -16,6 +16,9 @@ type ControlURI struct {
 }
 
 func (u *ControlURI) UnmarshalText(text []byte) error {
+	if len(text) == 0 {
+		return fmt.Errorf("Control URI should not be empty.")
+	}
 	if text[len(text)-1] == '/' {
 		return fmt.Errorf("Control URI should not contains trailing slash.")
 	}
diff --git a/jsonapi/fteid.go b/jsonapi/fteid.go
new file mode 100644
index 0000000..5184c57
--- /dev/null
+++ b/jsonapi/fteid.go
@@ -0,0 +1,20 @@
+// Copyright 2024 Louis Royer and the NextMN contributors. All rights reserved.
+// Use of this source code is governed by a MIT-style license that can be
+// found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+package jsonapi
+
+import "net/netip"
+
+type Fteid struct {
+	Addr netip.Addr `json:"addr"`
+	Teid uint32     `json:"teid"`
+}
+
+func NewFteid(addr netip.Addr, teid uint32) *Fteid {
+	return &Fteid{
+		Addr: addr,
+		Teid: teid,
+	}
+}
diff --git a/jsonapi/n1n2/handover_command.go b/jsonapi/n1n2/handover_command.go
new file mode 100644
index 0000000..016a12b
--- /dev/null
+++ b/jsonapi/n1n2/handover_command.go
@@ -0,0 +1,22 @@
+// Copyright 2024 Louis Royer and the NextMN contributors. All rights reserved.
+// Use of this source code is governed by a MIT-style license that can be
+// found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+package n1n2
+
+import (
+	"github.com/nextmn/json-api/jsonapi"
+)
+
+// HandoverCommand is sent by the CP to the source gNB to start the execution of handover, and forwarded to the UE
+type HandoverCommand struct {
+	// Header
+	UeCtrl    jsonapi.ControlURI `json:"ue-ctrl"`
+	Cp        jsonapi.ControlURI `json:"cp"`
+	SourceGnb jsonapi.ControlURI `json:"source-gnb"`
+
+	// Handover Command
+	Sessions  []Session          `json:"sessions"` // contains new ForwardDownlinkFteid
+	TargetGnb jsonapi.ControlURI `json:"target-gnb"`
+}
diff --git a/jsonapi/n1n2/handover_confirm.go b/jsonapi/n1n2/handover_confirm.go
new file mode 100644
index 0000000..52c59f3
--- /dev/null
+++ b/jsonapi/n1n2/handover_confirm.go
@@ -0,0 +1,22 @@
+// Copyright 2024 Louis Royer and the NextMN contributors. All rights reserved.
+// Use of this source code is governed by a MIT-style license that can be
+// found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+package n1n2
+
+import (
+	"github.com/nextmn/json-api/jsonapi"
+)
+
+// HandoverConfirm is send by the target UE to the target gNB after the UE is synchronized to the new cell
+type HandoverConfirm struct {
+	// Header
+	UeCtrl jsonapi.ControlURI `json:"ue-ctrl"`
+	Cp     jsonapi.ControlURI `json:"cp"`
+
+	// Handover Confirm
+	Sessions  []Session          `json:"sessions"`
+	SourceGnb jsonapi.ControlURI `json:"source-gnb"`
+	TargetGnb jsonapi.ControlURI `json:"target-gnb"`
+}
diff --git a/jsonapi/n1n2/handover_notify.go b/jsonapi/n1n2/handover_notify.go
new file mode 100644
index 0000000..a3c3f01
--- /dev/null
+++ b/jsonapi/n1n2/handover_notify.go
@@ -0,0 +1,22 @@
+// Copyright 2024 Louis Royer and the NextMN contributors. All rights reserved.
+// Use of this source code is governed by a MIT-style license that can be
+// found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+package n1n2
+
+import (
+	"github.com/nextmn/json-api/jsonapi"
+)
+
+// HandoverNotify is send by the target gNB to the CP after handover have been confirmed by the UE
+type HandoverNotify struct {
+	// Header
+	UeCtrl    jsonapi.ControlURI `json:"ue-ctrl"`
+	Cp        jsonapi.ControlURI `json:"cp"`
+	TargetGnb jsonapi.ControlURI `json:"target-gnb"`
+
+	// Handover Notify
+	Sessions  []Session          `json:"sessions"`
+	SourceGnb jsonapi.ControlURI `json:"source-gnb"`
+}
diff --git a/jsonapi/n1n2/handover_request.go b/jsonapi/n1n2/handover_request.go
new file mode 100644
index 0000000..30519ef
--- /dev/null
+++ b/jsonapi/n1n2/handover_request.go
@@ -0,0 +1,22 @@
+// Copyright 2024 Louis Royer and the NextMN contributors. All rights reserved.
+// Use of this source code is governed by a MIT-style license that can be
+// found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+package n1n2
+
+import (
+	"github.com/nextmn/json-api/jsonapi"
+)
+
+// HandoverRequest is send by the CP to the target gNB during the handover preparation phase
+type HandoverRequest struct {
+	// Header
+	UeCtrl    jsonapi.ControlURI `json:"ue-ctrl"`
+	Cp        jsonapi.ControlURI `json:"cp"`
+	TargetgNB jsonapi.ControlURI `json:"target-gnb"`
+
+	// Handover Request
+	SourcegNB jsonapi.ControlURI `json:"source-gnb"`
+	Sessions  []Session          `json:"sessions"` // contains new UL FTeid
+}
diff --git a/jsonapi/n1n2/handover_request_ack.go b/jsonapi/n1n2/handover_request_ack.go
new file mode 100644
index 0000000..13b1e22
--- /dev/null
+++ b/jsonapi/n1n2/handover_request_ack.go
@@ -0,0 +1,22 @@
+// Copyright 2024 Louis Royer and the NextMN contributors. All rights reserved.
+// Use of this source code is governed by a MIT-style license that can be
+// found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+package n1n2
+
+import (
+	"github.com/nextmn/json-api/jsonapi"
+)
+
+// HandoverRequestAck is send by the target gNB to the CP in response to an HandoverRequest
+type HandoverRequestAck struct {
+	// Header
+	Cp        jsonapi.ControlURI `json:"cp"`
+	TargetgNB jsonapi.ControlURI `json:"target-gnb"`
+
+	// Handover Request Ack
+	SourcegNB jsonapi.ControlURI `json:"source-gnb"`
+	UeCtrl    jsonapi.ControlURI `json:"ue-ctrl"`
+	Sessions  []Session          `json:"sessions"` // contains new DL FTeid
+}
diff --git a/jsonapi/n1n2/handover_required.go b/jsonapi/n1n2/handover_required.go
new file mode 100644
index 0000000..69a392d
--- /dev/null
+++ b/jsonapi/n1n2/handover_required.go
@@ -0,0 +1,22 @@
+// Copyright 2024 Louis Royer and the NextMN contributors. All rights reserved.
+// Use of this source code is governed by a MIT-style license that can be
+// found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+package n1n2
+
+import (
+	"github.com/nextmn/json-api/jsonapi"
+)
+
+// HandoverRequired is send by the source gNB to the CP to start the handover preparation phase
+type HandoverRequired struct {
+	// Header
+	SourcegNB jsonapi.ControlURI `json:"source-gnb"`
+	Cp        jsonapi.ControlURI `json:"cp"`
+
+	// Handover Required content
+	Ue        jsonapi.ControlURI `json:"ue"`
+	Sessions  []Session          `json:"sessions"` // list of all pdu sessions of the UE to be moved
+	TargetgNB jsonapi.ControlURI `json:"target-gnb"`
+}
diff --git a/jsonapi/n1n2/n2_pdu_session_req_msg.go b/jsonapi/n1n2/n2_pdu_session_req_msg.go
index eab8118..7426ecc 100644
--- a/jsonapi/n1n2/n2_pdu_session_req_msg.go
+++ b/jsonapi/n1n2/n2_pdu_session_req_msg.go
@@ -6,8 +6,6 @@
 package n1n2
 
 import (
-	"net/netip"
-
 	"github.com/nextmn/json-api/jsonapi"
 )
 
@@ -17,6 +15,5 @@ type N2PduSessionReqMsg struct {
 	UeInfo PduSessionEstabAcceptMsg `json:"ue-info"` // information to forward to the UE
 
 	// Uplink FTEID: the gNB will establish an Uplink GTP Tunnel using the following
-	Upf        netip.Addr `json:"upf"`
-	UplinkTeid uint32     `json:"uplink-teid"`
+	UplinkFteid jsonapi.Fteid `json:"uplink-fteid"`
 }
diff --git a/jsonapi/n1n2/n2_pdu_session_resp_msg.go b/jsonapi/n1n2/n2_pdu_session_resp_msg.go
index 0b8e5d3..d78f43a 100644
--- a/jsonapi/n1n2/n2_pdu_session_resp_msg.go
+++ b/jsonapi/n1n2/n2_pdu_session_resp_msg.go
@@ -5,13 +5,14 @@
 
 package n1n2
 
-import "net/netip"
+import (
+	"github.com/nextmn/json-api/jsonapi"
+)
 
 // N2PduSessionRespMsg is sent to the CP by the gNB as a response of N2PduSessionReqMsg.
 type N2PduSessionRespMsg struct {
 	UeInfo PduSessionEstabAcceptMsg `json:"ue-info"` // used to identify the PDU Session
 
 	// Downlink FTEID: the CP will use this to configure a downlink GTP Tunnel in Upf-i
-	DownlinkTeid uint32     `json:"downlink-teid"`
-	Gnb          netip.Addr `json:"gnb"`
+	DownlinkFteid jsonapi.Fteid `json:"downlink-fteid"`
 }
diff --git a/jsonapi/n1n2/session.go b/jsonapi/n1n2/session.go
new file mode 100644
index 0000000..68047b4
--- /dev/null
+++ b/jsonapi/n1n2/session.go
@@ -0,0 +1,23 @@
+// Copyright 2024 Louis Royer and the NextMN contributors. All rights reserved.
+// Use of this source code is governed by a MIT-style license that can be
+// found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+package n1n2
+
+import (
+	"net/netip"
+
+	"github.com/nextmn/json-api/jsonapi"
+)
+
+type Session struct {
+	Addr          netip.Addr     `json:"ue-addr"`
+	Dnn           string         `json:"dnn"`
+	UplinkFteid   *jsonapi.Fteid `json:"uplink-fteid,omitempty"`
+	DownlinkFteid *jsonapi.Fteid `json:"downlink-fteid,omitempty"`
+
+	// when ForwardDownlinkFteid is not empty,
+	// PDUs received on DownlinkFteid must be forwarded to it
+	ForwardDownlinkFteid *jsonapi.Fteid `json:"forward-fteid,omitempty"`
+}
diff --git a/jsonapi_test/n1n2_test/session_test.go b/jsonapi_test/n1n2_test/session_test.go
new file mode 100644
index 0000000..ab6dec9
--- /dev/null
+++ b/jsonapi_test/n1n2_test/session_test.go
@@ -0,0 +1,28 @@
+// Copyright 2024 Louis Royer and the NextMN contributors. All rights reserved.
+// Use of this source code is governed by a MIT-style license that can be
+// found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+package n1n2_test
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/nextmn/json-api/jsonapi/n1n2"
+)
+
+func TestSession(t *testing.T) {
+	s := &n1n2.Session{}
+	if err := json.Unmarshal([]byte("{\"ue-addr\": \"127.0.0.1\", \"uplink-fteid\": {\"addr\": \"127.0.0.2\", \"teid\": 80}}"), s); err != nil {
+		t.Errorf("Session with only uplink FTeid could not be unmarshaled")
+	}
+
+	if s.DownlinkFteid != nil {
+		t.Errorf("Downlink Fteid was not defined but is not nil")
+	}
+	if s.UplinkFteid == nil {
+		t.Errorf("Uplink Fteid was defined but is nil")
+	}
+
+}