Skip to content

Commit

Permalink
Generated nasMessage (#18)
Browse files Browse the repository at this point in the history
* Extraction tables from spec. .docx file

* Initial encode/decode code generator

* Add encode code

* Add decode code

* Move generator files

* Refine type treatment in generator

* Commit generated codes

* Add generated marker

* Add error check codes

* generate some codes in nas.go

* Refactor generator

* Add encode/decode test for IEs whose length is maximum one

* IEs length check, phase 1

* IEs length check, phase 2

* Special length check for PDU address IE

* Fix IEI value of Mapped EPS bearer contexts

* Add IE types

* Add comments and refactor

* fix golangci-lint error

* Add encode/decode test for IEs whose length is minimum one

* Change R/W codes

* Fuzzing code and data

* Fix crash by empty message

* Expand fuzzing test

* fix golangci-lint error

* Disable fuzzing in older golang
  • Loading branch information
ShouheiNishi authored May 2, 2023
1 parent 4831f29 commit 67b0067
Show file tree
Hide file tree
Showing 325 changed files with 10,141 additions and 1,703 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ cscope.*
.DS_Store

support

# NAS spec
24501-*.zip
spec.csv
70 changes: 70 additions & 0 deletions fuzz_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//go:build go1.18
// +build go1.18

package nas_test

import (
"bytes"
"fmt"
"testing"

"github.com/free5gc/nas"
)

func FuzzNAS(f *testing.F) {
f.Fuzz(func(t *testing.T, d []byte) {
msg := new(nas.Message)
err := msg.PlainNasDecode(&d)
if err == nil {
buf, err := msg.PlainNasEncode()
if err != nil {
panic(fmt.Sprintf("Re-encoding failed: %s", err.Error()))
}
msg2 := new(nas.Message)
err = msg2.PlainNasDecode(&buf)
if err != nil {
panic(fmt.Sprintf("Re-decoding failed: %s", err.Error()))
}
}
})
}

func FuzzGmmMessageDecode(f *testing.F) {
f.Fuzz(func(t *testing.T, d []byte) {
msg := new(nas.Message)
err := msg.GmmMessageDecode(&d)
if err == nil {
buf := new(bytes.Buffer)
err := msg.GmmMessageEncode(buf)
if err != nil {
panic(fmt.Sprintf("Re-encoding failed: %s", err.Error()))
}
msg2 := new(nas.Message)
buf2 := buf.Bytes()
err = msg2.GmmMessageDecode(&buf2)
if err != nil {
panic(fmt.Sprintf("Re-decoding failed: %s", err.Error()))
}
}
})
}

func FuzzGsmMessageDecode(f *testing.F) {
f.Fuzz(func(t *testing.T, d []byte) {
msg := new(nas.Message)
err := msg.GsmMessageDecode(&d)
if err == nil {
buf := new(bytes.Buffer)
err := msg.GsmMessageEncode(buf)
if err != nil {
panic(fmt.Sprintf("Re-encoding failed: %s", err.Error()))
}
msg2 := new(nas.Message)
buf2 := buf.Bytes()
err = msg2.GsmMessageDecode(&buf2)
if err != nil {
panic(fmt.Sprintf("Re-decoding failed: %s", err.Error()))
}
}
})
}
23 changes: 23 additions & 0 deletions generate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/sh

SPEC=24501-f70

cd `dirname $0`

if [ ! -f spec.csv ] ; then
if [ ! -f ${SPEC}.zip ] ; then
wget https://www.3gpp.org/ftp/Specs/archive/24_series/24.501/${SPEC}.zip
fi
if [ ! -f ${SPEC}.zip ] ; then
echo "Download failed."
exit 1
fi
python3 internal/tools/extract.py
fi

rm -rf testdata/GmmMessage testdata/GsmMessage
mkdir -p testdata/GmmMessage testdata/GsmMessage
rm -f testdata/fuzz/FuzzGmmMessageDecode/msg* testdata/fuzz/FuzzGsmMessageDecode/msg*
ls nasMessage/*go | grep -v "_test" | grep -v "NAS_EPD" | grep -v "NAS_CommInfoIE" | xargs rm -f
go run internal/tools/generator_sub.go
go run internal/tools/generator/cmd/cmd.go
30 changes: 30 additions & 0 deletions internal/tools/extract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/python3

import zipfile
import docx
import csv
from docx.oxml import CT_P
from docx.text.paragraph import Paragraph
from docx.oxml.table import CT_Tbl
from docx.table import _Cell, Table

with zipfile.ZipFile('24501-f70.zip') as zf:
with zf.open('24501-f70.docx') as inf:
with open('spec.csv', 'w', encoding='utf8', newline='') as outf:
csvw = csv.writer(outf)
doc = docx.Document(inf)
parent = doc._body._body
prev = None
for c in parent.iterchildren():
if isinstance(c, CT_P):
prev = Paragraph(c, parent)
elif isinstance(c, CT_Tbl):
tab = Table(c, parent)
if isinstance(prev, Paragraph):
csvw.writerow([prev.text])
for row in tab.rows:
crow = []
for cell in row.cells:
crow.append(cell.text)
csvw.writerow(crow)
prev = tab
13 changes: 13 additions & 0 deletions internal/tools/generator/cmd/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main

import "github.com/free5gc/nas/internal/tools/generator"

func main() {
generator.ParseSpecs()

generator.GenerateNasMessage()

generator.GenerateNasEncDec()

generator.GenerateTestLarge()
}
93 changes: 93 additions & 0 deletions internal/tools/generator/nas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package generator

import (
"fmt"
"strings"
)

// Generate nas_generated.go
func GenerateNasEncDec() {
fOut := NewOutputFile("nas_generated.go", "nas", []string{
"\"bytes\"",
"\"encoding/binary\"",
"\"fmt\"",
"",
"\"github.com/free5gc/nas/nasMessage\"",
})

for _, isGMM := range []bool{true, false} {
gmmGsm := ""
if isGMM {
gmmGsm = "Gmm"
} else {
gmmGsm = "Gsm"
}

// Generate (Gmm|Gsm)Message struct
fmt.Fprintf(fOut, "type %sMessage struct {\n", gmmGsm)
fmt.Fprintf(fOut, "%sHeader\n", gmmGsm)
for _, msgDef := range msgOrder {
if msgDef != nil && msgDef.isGMM == isGMM {
fmt.Fprintf(fOut, "*nasMessage.%s // %s\n", msgDef.structName, msgDef.section)
}
}
fmt.Fprintln(fOut, "}")
fmt.Fprintln(fOut, "")

// Generate constant for message ID
fmt.Fprintln(fOut, "const (")
for msgType, msgDef := range type2Msg {
if msgDef != nil && msgDef.isGMM == isGMM {
fmt.Fprintf(fOut, "MsgType%s uint8 = %d\n", msgDef.structName, msgType)
}
}
fmt.Fprintln(fOut, ")")
fmt.Fprintln(fOut, "")

// Generate (Gmm|Gsm)MessageDecode functions
fmt.Fprintf(fOut, "func (a *Message) %sMessageDecode(byteArray *[]byte) error {\n", gmmGsm)
fmt.Fprintf(fOut, "buffer := bytes.NewBuffer(*byteArray)\n")
fmt.Fprintf(fOut, "a.%sMessage = New%sMessage()\n", gmmGsm, gmmGsm)
fmt.Fprintf(fOut, "if err := binary.Read(buffer, binary.BigEndian, &a.%sMessage.%sHeader); err != nil {\n",
gmmGsm, gmmGsm)
fmt.Fprintf(fOut, "return fmt.Errorf(\"%s NAS decode Fail: read fail - %%+v\", err)\n", strings.ToUpper(gmmGsm))
fmt.Fprintf(fOut, "}\n")
fmt.Fprintf(fOut, "switch a.%sMessage.%sHeader.GetMessageType() {\n", gmmGsm, gmmGsm)
for _, msgDef := range type2Msg {
if msgDef != nil && msgDef.isGMM == isGMM {
fmt.Fprintf(fOut, "case MsgType%s:\n", msgDef.structName)
fmt.Fprintf(fOut, "a.%sMessage.%s = nasMessage.New%s(MsgType%s)\n",
gmmGsm, msgDef.structName, msgDef.structName, msgDef.structName)
fmt.Fprintf(fOut, "return a.%sMessage.Decode%s(byteArray)\n", gmmGsm, msgDef.structName)
}
}
fmt.Fprintf(fOut, "default:\n")
fmt.Fprintf(fOut, "return fmt.Errorf(\"NAS decode Fail: MsgType[%%d] doesn't exist in %s Message\",\n",
strings.ToUpper(gmmGsm))
fmt.Fprintf(fOut, "a.%sMessage.%sHeader.GetMessageType())\n", gmmGsm, gmmGsm)
fmt.Fprintln(fOut, "}")
fmt.Fprintln(fOut, "}")
fmt.Fprintln(fOut, "")

// Generate (Gmm|Gsm)MessageEncode functions
fmt.Fprintf(fOut, "func (a *Message) %sMessageEncode(buffer *bytes.Buffer) error {\n", gmmGsm)
fmt.Fprintf(fOut, "switch a.%sMessage.%sHeader.GetMessageType() {\n", gmmGsm, gmmGsm)
for _, msgDef := range type2Msg {
if msgDef != nil && msgDef.isGMM == isGMM {
fmt.Fprintf(fOut, "case MsgType%s:\n", msgDef.structName)
fmt.Fprintf(fOut, "return a.%sMessage.Encode%s(buffer)\n", gmmGsm, msgDef.structName)
}
}
fmt.Fprintf(fOut, "default:\n")
fmt.Fprintf(fOut, "return fmt.Errorf(\"NAS Encode Fail: MsgType[%%d] doesn't exist in %s Message\",\n",
strings.ToUpper(gmmGsm))
fmt.Fprintf(fOut, "a.%sMessage.%sHeader.GetMessageType())\n", gmmGsm, gmmGsm)
fmt.Fprintln(fOut, "}")
fmt.Fprintln(fOut, "}")
fmt.Fprintln(fOut, "")
}

if err := fOut.Close(); err != nil {
panic(err)
}
}
Loading

0 comments on commit 67b0067

Please sign in to comment.