package upstream import ( "fmt" "strings" "time" ) const ( maxSingleMessageBytes = 140 maxMultipartPayloadBytes = 134 concatUDHLength = 6 maxMultipartSegments = 255 ) type submitPart struct { PkTotal uint8 PkNumber uint8 TpUdhi uint8 MsgContent string } type longUplinkAssembly struct { msgFmt uint8 total uint8 parts map[uint8]string updatedAt time.Time } func splitSubmitContent(format int, content string) ([]submitPart, error) { encoded, err := encodeContent(format, content) if err != nil { return nil, err } if len(encoded) <= maxSingleMessageBytes { return []submitPart{{ PkTotal: 1, PkNumber: 1, TpUdhi: 0, MsgContent: encoded, }}, nil } chunks, err := splitEncodedContent(format, content, maxMultipartPayloadBytes) if err != nil { return nil, err } if len(chunks) > maxMultipartSegments { return nil, fmt.Errorf("message requires %d segments, maximum is %d", len(chunks), maxMultipartSegments) } ref := uint8(time.Now().UnixNano()) parts := make([]submitPart, 0, len(chunks)) for i, chunk := range chunks { total := uint8(len(chunks)) number := uint8(i + 1) udh := []byte{0x05, 0x00, 0x03, ref, total, number} content := append(udh, []byte(chunk)...) parts = append(parts, submitPart{ PkTotal: total, PkNumber: number, TpUdhi: 1, MsgContent: string(content), }) } return parts, nil } func splitEncodedContent(format int, content string, limit int) ([]string, error) { var chunks []string var current strings.Builder currentLen := 0 for _, r := range content { encoded, err := encodeContent(format, string(r)) if err != nil { return nil, err } if len(encoded) > limit { return nil, fmt.Errorf("single character exceeds segment payload limit") } if currentLen > 0 && currentLen+len(encoded) > limit { chunks = append(chunks, current.String()) current.Reset() currentLen = 0 } current.WriteString(encoded) currentLen += len(encoded) } if currentLen > 0 { chunks = append(chunks, current.String()) } if len(chunks) == 0 { chunks = append(chunks, "") } return chunks, nil } func parseConcatSegment(content string) (ref uint8, total uint8, number uint8, payload string, ok bool) { raw := []byte(content) if len(raw) < concatUDHLength { return 0, 0, 0, "", false } if raw[0] != 0x05 || raw[1] != 0x00 || raw[2] != 0x03 { return 0, 0, 0, "", false } ref = raw[3] total = raw[4] number = raw[5] if total == 0 || number == 0 || number > total { return 0, 0, 0, "", false } return ref, total, number, string(raw[concatUDHLength:]), true } func assembleLongUplink(assemblies map[string]*longUplinkAssembly, key string, msgFmt uint8, total uint8, number uint8, payload string) (string, bool, error) { assembly := assemblies[key] if assembly == nil || assembly.total != total || assembly.msgFmt != msgFmt { assembly = &longUplinkAssembly{ msgFmt: msgFmt, total: total, parts: make(map[uint8]string, int(total)), } assemblies[key] = assembly } assembly.parts[number] = payload assembly.updatedAt = time.Now() if len(assembly.parts) < int(total) { return "", false, nil } var raw strings.Builder for i := uint8(1); i <= total; i++ { part, ok := assembly.parts[i] if !ok { return "", false, nil } raw.WriteString(part) } delete(assemblies, key) content, err := decodeContent(msgFmt, raw.String()) if err != nil { return "", false, err } return content, true, nil } func pruneLongUplinkAssemblies(assemblies map[string]*longUplinkAssembly, now time.Time, ttl time.Duration) { for key, assembly := range assemblies { if now.Sub(assembly.updatedAt) > ttl { delete(assemblies, key) } } }