diff --git a/internal/domain/aftn.go b/internal/domain/aftn.go index 28114c0..9734ffe 100644 --- a/internal/domain/aftn.go +++ b/internal/domain/aftn.go @@ -11,7 +11,7 @@ type AFTN struct { Header Header `json:"header"` // 报文头部信息 PriorityAndSender PriorityAndSender `json:"priority_and_sender"` // 优先级和发送地址信息 TimeAndReceiver TimeAndReceiver `json:"time_and_receiver"` // 时间和接收地址信息 - Text string `json:"text"` // 报文内容 + Body string `json:"body"` // 报文内容 ReceivedTime time.Time `json:"received_time"` // 收报时间,表示电报接收到的时间 Category string `json:"category"` BodyData interface{} `json:"body_data"` diff --git a/internal/domain/aftn_test.go b/internal/domain/aftn_test.go index 35cf1f5..596ac2e 100644 --- a/internal/domain/aftn_test.go +++ b/internal/domain/aftn_test.go @@ -26,7 +26,7 @@ var _ = Describe("AFTN", func() { Time: "151524", Receiver: "ZGGGZPZX", }, - Text: "Test message", + Body: "Test message", ReceivedTime: time.Now(), Category: "Test", BodyData: nil, @@ -45,7 +45,7 @@ var _ = Describe("AFTN", func() { Expect(unmarshalled.Header).To(Equal(original.Header)) Expect(unmarshalled.PriorityAndSender).To(Equal(original.PriorityAndSender)) Expect(unmarshalled.TimeAndReceiver).To(Equal(original.TimeAndReceiver)) - Expect(unmarshalled.Text).To(Equal(original.Text)) + Expect(unmarshalled.Body).To(Equal(original.Body)) Expect(unmarshalled.Category).To(Equal(original.Category)) }) }) diff --git a/internal/domain/sita.go b/internal/domain/sita.go new file mode 100644 index 0000000..387f638 --- /dev/null +++ b/internal/domain/sita.go @@ -0,0 +1,114 @@ +package domain + +import ( + "caatsm/pkg/utils" + "fmt" + "time" +) + +/* +QU TSNZPCA + +.HAKUOHU 151234 + +DISPATCH RELEASE: + +HU7670/15MAY ETD1440 B2113/B733 + +DEP:TSN/ALTN:NIL + +ROUTE ALTN:WUH KWL + +DEST:HAK/ALTN:NNG SYX + +FLT RULE:IFR + +TRIP FUEL:9187KGS/20254LBS + +TTL FUEL:13600KGS/29983LBS + +CREW:YANG XIAOHUI/YANG ZHENGYIN + +CREW NUMBER:2/4 + +SI:CFP AND CAUTION:MXSH 8/FIR + +TEL:0898-65756523 + +FAX:0898-65751587 + +DSP SIGN:WUKEYONG + +PIC SINGN: + +(FPL-CHH7670-IS + +-B733/M-SDHIRW/S + +-ZBTJ1440 + +-M074S0980 CG A326 VYK A461 LKO R343 LBN/M074S0950 J427 BHY + + W70 NYB + +-ZJHK0323 ZGNN ZJSY + +-EET/ZHWH0038 ZGZU0144 ZJSA0307 + + REG/B2113 SEL/DGEH + + RMK/ACAS EQPT) + +NNNN + + +*/ + +// SITA defines the structure of a SITA telegram +type SITA struct { + Header SITAHeader `json:"header"` // Header information of the telegram + PriorityAndSender PrioritySender `json:"priority_sender"` // Priority and sender information + TimeAndReceiver TimeReceiver `json:"time_receiver"` // Time and receiver information + Text string `json:"text"` // Content of the telegram + ReceivedTime time.Time `json:"received_time"` // Time the telegram was received + Category string `json:"category"` // Category of the telegram + BodyData interface{} `json:"body_data"` // Additional body data +} + +// SITAHeader defines the header of a SITA telegram +type SITAHeader struct { + StartSignal string `json:"start_signal"` // Start signal indicating the beginning of the telegram + SendID string `json:"send_id"` // Sending ID uniquely identifying the telegram + SendTime string `json:"send_time"` // Sending time in the format DDHHMM +} + +// PrioritySender defines priority and sender address +type PrioritySender struct { + Priority string `json:"priority"` // Priority level + Sender string `json:"sender"` // Sending address +} + +// TimeReceiver defines time and receiver address +type TimeReceiver struct { + Time string `json:"time"` // Time of the telegram + Receiver string `json:"receiver"` // Receiving address +} + +func (h *SITAHeader) Validate() error { + // Validate SendTime format (e.g., DDHHMM) + if len(h.SendTime) != 6 { + err := "invalid send_time format" + + utils.Logger.Error(err) + return fmt.Errorf(err) + } + return nil +} + +func (s *SITA) Validate() error { + if err := s.Header.Validate(); err != nil { + return err + } + // Add more validation as needed + return nil +} diff --git a/internal/parsers/aftn_parser.go b/internal/parsers/aftn_parser.go index f3ea109..6a4e7d1 100644 --- a/internal/parsers/aftn_parser.go +++ b/internal/parsers/aftn_parser.go @@ -60,12 +60,12 @@ func (p *AFTNParser) Parse(rawMessage string) (*domain.AFTN, error) { return nil, err } - text, bodyType, err := parseTextInfo(strings.Join(lines[3:], "\n")) + body, category, err := extractBodyAndCategory(strings.Join(lines[3:], "\n")) if err != nil { return nil, err } - bodyData, err := p.extractBodyData(text) + bodyData, err := p.extractBodyData(body) if err != nil { return nil, err } @@ -79,8 +79,8 @@ func (p *AFTNParser) Parse(rawMessage string) (*domain.AFTN, error) { Header: header, PriorityAndSender: priorityAndSender, TimeAndReceiver: timeAndReceiver, - Text: text, - Category: bodyType, + Body: body, + Category: category, BodyData: aftn, ReceivedTime: time.Now(), }, nil @@ -124,7 +124,7 @@ func parseTimeAndReceiver(line string) (domain.TimeAndReceiver, error) { } // parseTextInfo parses the text and extracts the body type from an AFTN message. -func parseTextInfo(text string) (string, string, error) { +func extractBodyAndCategory(text string) (string, string, error) { match := textPattern.FindStringSubmatch(text) if len(match) > 1 { return match[0], match[1], nil diff --git a/internal/parsers/sita_parser.go b/internal/parsers/sita_parser.go new file mode 100644 index 0000000..73a5c8e --- /dev/null +++ b/internal/parsers/sita_parser.go @@ -0,0 +1,207 @@ +package parsers + +import ( + "caatsm/internal/config" + "caatsm/internal/domain" + "caatsm/pkg/utils" + "fmt" + "regexp" + "strings" + "time" +) + +var ( + sitaTextPattern = regexp.MustCompile(`\(([A-Z]{3})(.*)\)`) + validSitaPriority = regexp.MustCompile(`^(SS|DD|FF|GG|KK)$`) + validSitaAddress = regexp.MustCompile(`^[A-Z]{8}$`) +) + +// SITAParser is responsible for parsing SITA messages. +type SITAParser struct { + bodyPattern map[string]config.BodyConfig // Injected configuration for body patterns. +} + +// NewSITAParser creates a new instance of SITAParser. +func NewSITAParser(myPatterns map[string]config.BodyConfig) *SITAParser { + return &SITAParser{bodyPattern: myPatterns} +} + +// DefaultSITAParser creates a new instance of SITAParser with default patterns. +func DefaultSITAParser() *SITAParser { + return &SITAParser{bodyPattern: config.GetBodyPatterns()} +} + +func (p *SITAParser) GetBodyPatterns() map[string]config.BodyConfig { + return p.bodyPattern +} + +// Parse is the main method to parse a SITA message. +func (p *SITAParser) Parse(rawMessage string) (*domain.SITA, error) { + cleanedText := removeEmptyLines(rawMessage) + lines := strings.Split(cleanedText, "\n") + + if len(lines) < 4 { + return nil, fmt.Errorf("invalid SITA message format: insufficient lines") + } + + header, err := parseSITAHeader(lines[0]) + if err != nil { + return nil, err + } + + priorityAndSender, err := parseSITAPriorityAndSender(lines[1]) + if err != nil { + return nil, err + } + + timeAndReceiver, err := parseSITATimeAndReceiver(lines[2]) + if err != nil { + return nil, err + } + + text, bodyType, err := parseSITATextInfo(strings.Join(lines[3:], "\n")) + if err != nil { + return nil, err + } + + bodyData, err := p.extractBodyData(text) + if err != nil { + return nil, err + } + + sita, err := p.createSITA(bodyData) + if err != nil { + return nil, err + } + + return &domain.SITA{ + Header: header, + PriorityAndSender: priorityAndSender, + TimeAndReceiver: timeAndReceiver, + Text: text, + Category: bodyType, + BodyData: sita, + ReceivedTime: time.Now(), + }, nil +} + +// parseSITAHeader parses the header line of a SITA message. +func parseSITAHeader(line string) (domain.SITAHeader, error) { + parts := strings.Fields(line) + if len(parts) < 3 { + return domain.SITAHeader{}, fmt.Errorf("invalid header format: %s", line) + } + return domain.SITAHeader{ + StartSignal: parts[0], + SendID: parts[1], + SendTime: parts[2], + }, nil +} + +// parseSITAPriorityAndSender parses the priority and sender line of a SITA message. +func parseSITAPriorityAndSender(line string) (domain.PrioritySender, error) { + parts := strings.Fields(line) + if len(parts) < 2 { + return domain.PrioritySender{}, fmt.Errorf("invalid priority and sender format: %s", line) + } + return domain.PrioritySender{ + Priority: parts[0], + Sender: parts[1], + }, nil +} + +// parseSITATimeAndReceiver parses the time and receiver line of a SITA message. +func parseSITATimeAndReceiver(line string) (domain.TimeReceiver, error) { + parts := strings.Fields(line) + if len(parts) < 2 { + return domain.TimeReceiver{}, fmt.Errorf("invalid time and receiver format: %s", line) + } + return domain.TimeReceiver{ + Time: parts[0], + Receiver: parts[1], + }, nil +} + +// parseSITATextInfo parses the text and extracts the body type from a SITA message. +func parseSITATextInfo(text string) (string, string, error) { + match := sitaTextPattern.FindStringSubmatch(text) + if len(match) > 1 { + return match[0], match[1], nil + } + return "", "", fmt.Errorf("invalid text format: %s", text) +} + +func (p *SITAParser) ParseBody(body string) (interface{}, error) { + bodyData, err := p.extractBodyData(body) + if err != nil { + return nil, err + } + + sita, err := p.createSITA(bodyData) + if err != nil { + return nil, err + } + + return sita, nil +} + +// createSITA parses the body data of a SITA message. +func (p *SITAParser) createSITA(data map[string]string) (interface{}, error) { + switch data["type"] { + + default: + return nil, fmt.Errorf("invalid message type: %s", data["type"]) + } +} + +// extractBodyData extracts the body data from a SITA message. +func (p *SITAParser) extractBodyData(text string) (map[string]string, error) { + log := utils.Logger + data := make(map[string]string) + for _, pattern := range p.GetBodyPatterns() { + for i, p := range pattern.Patterns { + log.Debugf("Trying pattern %d: %s\n %s\n", i, p.Comments, p.Pattern) + re := p.Expression + match := re.FindStringSubmatch(text) + if match != nil { + log.Debugf("Matched : %v \n", match) + for i, name := range re.SubexpNames() { + if i != 0 && name != "" { + data[name] = match[i] + } + } + return data, nil + } + log.Debugf("No match for pattern %d\n", i) + } + } + log.Errorf("No matching pattern found for text: %s", text) + return nil, fmt.Errorf("no matching pattern found for text: %s", text) +} + +// ValidateSITA validates the fields of a SITA message. +func ValidateSITA(msg *domain.SITA) error { + if missingSitaFields(msg) { + return fmt.Errorf("invalid SITA message: missing fields") + } + + if !validSitaPriority.MatchString(msg.PriorityAndSender.Priority) { + return fmt.Errorf("invalid priority code: %s", msg.PriorityAndSender.Priority) + } + + if !validSitaAddress.MatchString(msg.TimeAndReceiver.Receiver) || !validSitaAddress.MatchString(msg.PriorityAndSender.Sender) { + return fmt.Errorf("invalid address format") + } + + return nil +} + +func missingSitaFields(msg *domain.SITA) bool { + if msg.Header.StartSignal == "" || msg.Header.SendID == "" || msg.Header.SendTime == "" || + msg.PriorityAndSender.Priority == "" || msg.PriorityAndSender.Sender == "" || + msg.TimeAndReceiver.Time == "" || msg.TimeAndReceiver.Receiver == "" || + msg.Category == "" { + return true + } + return false +}