154 lines
3.7 KiB
Go
154 lines
3.7 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|