Files

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)
}
}
}