refactor(server): Improve Wireguard config; Improve the parser

Signed-off-by: Myzel394 <github.7a2op@simplelogin.co>
This commit is contained in:
Myzel394 2025-02-23 18:44:06 +01:00 committed by Myzel394
parent bf05d07fc9
commit ba056d6ae9
No known key found for this signature in database
GPG Key ID: B603E877F73D4ABB
6 changed files with 350 additions and 0 deletions

View File

@ -0,0 +1,212 @@
package ast
import (
"config-lsp/common"
"config-lsp/utils"
"fmt"
"regexp"
"strings"
)
func NewWGConfig() *WGConfig {
config := &WGConfig{}
config.Clear()
return config
}
func (c *WGConfig) Clear() {
c.Sections = make([]*WGSection, 0, 2)
c.CommentLines = make(map[uint32]struct{})
}
var commentPattern = regexp.MustCompile(`^\s*([;#])`)
var emptyPattern = regexp.MustCompile(`^\s*$`)
var headerPattern = regexp.MustCompile(`^\s*\[(\w+)]?`)
var linePattern = regexp.MustCompile(`^\s*(?P<key>.+?)\s*(?P<separator>=)\s*(?P<value>\S.*?)?\s*(?:[;#].*)?\s*$`)
func (c *WGConfig) Parse(input string) []common.LSPError {
errors := make([]common.LSPError, 0)
lines := utils.SplitIntoLines(input)
var currentSection *WGSection
for rawLineNumber, line := range lines {
lineNumber := uint32(rawLineNumber)
if emptyPattern.MatchString(line) {
continue
}
if commentPattern.MatchString(line) {
c.CommentLines[lineNumber] = struct{}{}
continue
}
if headerPattern.MatchString(line) {
name := headerPattern.FindStringSubmatch(line)[1]
currentSection = &WGSection{
LocationRange: common.LocationRange{
Start: common.Location{
Line: lineNumber,
Character: 0,
},
End: common.Location{
Line: lineNumber,
Character: uint32(len(line)) + 1,
},
},
Header: WGHeader{
LocationRange: common.LocationRange{
Start: common.Location{
Line: lineNumber,
Character: 0,
},
End: common.Location{
Line: lineNumber,
Character: uint32(len(line)) + 1,
},
},
Name: name,
},
Properties: make(map[uint32]*WGProperty),
}
c.Sections = append(c.Sections, currentSection)
continue
}
// Else property
if currentSection == nil {
// Root properties are not allowed
errors = append(errors, common.LSPError{
Range: common.LocationRange{
Start: common.Location{
Line: lineNumber,
Character: 0,
},
End: common.Location{
Line: lineNumber,
Character: uint32(len(line)),
},
},
Err: fmt.Errorf("A header is missing before a property. This property has no header above it."),
})
continue
}
if !strings.Contains(line, "=") {
// Incomplete property
indexes := utils.GetTrimIndex(line)
currentSection.Properties[lineNumber] = &WGProperty{
Key: WGPropertyKey{
LocationRange: common.LocationRange{
Start: common.Location{
Line: lineNumber,
Character: uint32(indexes[0]),
},
End: common.Location{
Line: lineNumber,
Character: uint32(indexes[1]),
},
},
Name: line[indexes[0]:indexes[1]],
},
}
} else {
// Fully written out property
indexes := linePattern.FindStringSubmatchIndex(line)
if indexes == nil || len(indexes) == 0 {
// Error
errors = append(errors, common.LSPError{
Range: common.LocationRange{
Start: common.Location{
Line: lineNumber,
Character: 0,
},
End: common.Location{
Line: lineNumber,
Character: uint32(len(line)),
},
},
Err: fmt.Errorf("This property seems to be malformed"),
})
continue
}
// Construct key
keyStart := uint32(indexes[2])
keyEnd := uint32(indexes[3])
key := WGPropertyKey{
LocationRange: common.LocationRange{
Start: common.Location{
Line: lineNumber,
Character: keyStart,
},
End: common.Location{
Line: lineNumber,
Character: keyEnd,
},
},
Name: line[keyStart:keyEnd],
}
// Construct separator
separatorStart := uint32(indexes[4])
separatorEnd := uint32(indexes[5])
separator := WGPropertySeparator{
LocationRange: common.LocationRange{
Start: common.Location{
Line: lineNumber,
Character: separatorStart,
},
End: common.Location{
Line: lineNumber,
Character: separatorEnd,
},
},
}
// Construct value
var value *WGPropertyValue
if indexes[6] != -1 && indexes[7] != -1 {
// value exists
valueStart := uint32(indexes[6])
valueEnd := uint32(indexes[7])
value = &WGPropertyValue{
LocationRange: common.LocationRange{
Start: common.Location{
Line: lineNumber,
Character: valueStart,
},
End: common.Location{
Line: lineNumber,
Character: valueEnd,
},
},
Value: line[valueStart:valueEnd],
}
}
// And lastly, add the property
currentSection.Properties[lineNumber] = &WGProperty{
Key: key,
Separator: &separator,
Value: value,
}
}
}
return errors
}

View File

@ -0,0 +1,54 @@
package ast
import (
"config-lsp/utils"
"testing"
)
func TestExample1Works(
t *testing.T,
) {
sample := utils.Dedent(`
# A comment at the very top
[Interface]
PrivateKey = 1234567890 # Some comment
Address = 10.0.0.1
[Peer]
PublicKey = 1234567890
; I'm a comment
`)
config := NewWGConfig()
errors := config.Parse(sample)
if len(errors) > 0 {
t.Fatalf("Parse: Expected no errors, but got %v", errors)
}
if !(utils.KeyExists(config.CommentLines, 0) && utils.KeyExists(config.CommentLines, 12)) {
t.Errorf("Parse: Expected comments to be present on lines 0 and 12")
}
if !(config.Sections[0].Header.Name == "Interface" && config.Sections[1].Header.Name == "Peer") {
t.Errorf("Parse: Expected sections to be present on lines 0, 1, and 2")
}
if !(config.Sections[0].Properties[4].Key.Name == "PrivateKey" && config.Sections[0].Properties[4].Value.Value == "1234567890") {
t.Errorf("Parse: Expected property line 4 to be correct")
}
if !(config.Sections[0].Properties[5].Key.Name == "Address" && config.Sections[0].Properties[5].Value.Value == "10.0.0.1") {
t.Errorf("Parse: Expected property line 5 to be correct")
}
if !(config.Sections[1].Properties[10].Key.Name == "PublicKey" && config.Sections[1].Properties[10].Value.Value == "1234567890") {
t.Errorf("Parse: Expected property line 10 to be correct")
}
}

View File

@ -0,0 +1,43 @@
package ast
import (
"config-lsp/common"
)
type WGPropertyKey struct {
common.LocationRange
Name string
}
type WGPropertyValue struct {
common.LocationRange
Value string
}
type WGPropertySeparator struct {
common.LocationRange
}
type WGProperty struct {
Key WGPropertyKey
Separator *WGPropertySeparator
Value *WGPropertyValue
}
type WGHeader struct {
common.LocationRange
Name string
}
type WGSection struct {
common.LocationRange
Header WGHeader
// map of: line number -> WGProperty
Properties map[uint32]*WGProperty
}
type WGConfig struct {
Sections []*WGSection
// Used to identify where not to show diagnostics
CommentLines map[uint32]struct{}
}

View File

@ -0,0 +1,39 @@
package ast
import (
"slices"
)
func (c *WGConfig) FindSectionByLine(line uint32) *WGSection {
index, found := slices.BinarySearchFunc(
c.Sections,
line,
func(current *WGSection, target uint32) int {
if target < current.Start.Line {
return 1
}
if target > current.Start.Line {
return -1
}
return 0
},
)
if !found {
return nil
}
return c.Sections[index]
}
func (c *WGConfig) FindPropertyByLine(line uint32) *WGProperty {
section := c.FindSectionByLine(line)
if section == nil {
return nil
}
return section.Properties[line]
}

View File

@ -0,0 +1 @@
package indexes

View File

@ -0,0 +1 @@
package wireguard