From ba056d6ae9d6c295a2dbb58b282ea9ec09fbcb2d Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 23 Feb 2025 18:44:06 +0100 Subject: [PATCH] refactor(server): Improve Wireguard config; Improve the parser Signed-off-by: Myzel394 --- server/handlers/wireguard/ast/parser.go | 212 ++++++++++++++++++ server/handlers/wireguard/ast/parser_test.go | 54 +++++ server/handlers/wireguard/ast/wireguard.go | 43 ++++ .../wireguard/ast/wireguard_fields.go | 39 ++++ server/handlers/wireguard/indexes/indexes.go | 1 + server/handlers/wireguard/shared.go | 1 + 6 files changed, 350 insertions(+) create mode 100644 server/handlers/wireguard/ast/parser.go create mode 100644 server/handlers/wireguard/ast/parser_test.go create mode 100644 server/handlers/wireguard/ast/wireguard.go create mode 100644 server/handlers/wireguard/ast/wireguard_fields.go create mode 100644 server/handlers/wireguard/indexes/indexes.go create mode 100644 server/handlers/wireguard/shared.go diff --git a/server/handlers/wireguard/ast/parser.go b/server/handlers/wireguard/ast/parser.go new file mode 100644 index 0000000..eb6c153 --- /dev/null +++ b/server/handlers/wireguard/ast/parser.go @@ -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.+?)\s*(?P=)\s*(?P\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 +} diff --git a/server/handlers/wireguard/ast/parser_test.go b/server/handlers/wireguard/ast/parser_test.go new file mode 100644 index 0000000..761baa8 --- /dev/null +++ b/server/handlers/wireguard/ast/parser_test.go @@ -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") + } +} diff --git a/server/handlers/wireguard/ast/wireguard.go b/server/handlers/wireguard/ast/wireguard.go new file mode 100644 index 0000000..c648b07 --- /dev/null +++ b/server/handlers/wireguard/ast/wireguard.go @@ -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{} +} diff --git a/server/handlers/wireguard/ast/wireguard_fields.go b/server/handlers/wireguard/ast/wireguard_fields.go new file mode 100644 index 0000000..46460c3 --- /dev/null +++ b/server/handlers/wireguard/ast/wireguard_fields.go @@ -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] +} diff --git a/server/handlers/wireguard/indexes/indexes.go b/server/handlers/wireguard/indexes/indexes.go new file mode 100644 index 0000000..fce2081 --- /dev/null +++ b/server/handlers/wireguard/indexes/indexes.go @@ -0,0 +1 @@ +package indexes diff --git a/server/handlers/wireguard/shared.go b/server/handlers/wireguard/shared.go new file mode 100644 index 0000000..0b280a9 --- /dev/null +++ b/server/handlers/wireguard/shared.go @@ -0,0 +1 @@ +package wireguard