feat: complete cmpp gateway delivery recovery workflows
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user