175 lines
4.7 KiB
Go
175 lines
4.7 KiB
Go
package loadsim
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
type FrameFactory struct {
|
|
protocol Protocol
|
|
base []byte
|
|
jt808PhoneBase int64
|
|
}
|
|
|
|
func NewFrameFactory(protocol Protocol, template string) (*FrameFactory, error) {
|
|
return NewFrameFactoryWithJT808PhoneBase(protocol, template, DefaultJT808PhoneBase)
|
|
}
|
|
|
|
func NewFrameFactoryWithJT808PhoneBase(protocol Protocol, template string, phoneBase int64) (*FrameFactory, error) {
|
|
base, err := FrameTemplate(protocol, template)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if phoneBase <= 0 {
|
|
phoneBase = DefaultJT808PhoneBase
|
|
}
|
|
return &FrameFactory{protocol: protocol, base: base, jt808PhoneBase: phoneBase}, nil
|
|
}
|
|
|
|
func (f *FrameFactory) Frame(connectionIndex int, frameIndex int64) ([]byte, error) {
|
|
switch f.protocol {
|
|
case ProtocolJT808:
|
|
return mutateJT808Frame(f.base, f.jt808PhoneBase, connectionIndex, frameIndex)
|
|
case ProtocolGB32960:
|
|
return mutateGB32960Frame(f.base, connectionIndex, frameIndex)
|
|
default:
|
|
out := append([]byte(nil), f.base...)
|
|
return out, nil
|
|
}
|
|
}
|
|
|
|
func mutateJT808Frame(base []byte, phoneBase int64, connectionIndex int, frameIndex int64) ([]byte, error) {
|
|
if len(base) < 2 || base[0] != 0x7e || base[len(base)-1] != 0x7e {
|
|
return nil, fmt.Errorf("jt808 template must include 0x7e delimiters")
|
|
}
|
|
payload, err := jt808Unescape(base[1 : len(base)-1])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(payload) < 13 {
|
|
return nil, fmt.Errorf("jt808 template too short: %d", len(payload))
|
|
}
|
|
phone := fmt.Sprintf("%012d", phoneBase+int64(connectionIndex))
|
|
copy(payload[4:10], encodeBCD(phone, 6))
|
|
binary.BigEndian.PutUint16(payload[10:12], uint16((int(frameIndex)+connectionIndex)%65536))
|
|
bodySize := int(binary.BigEndian.Uint16(payload[2:4]) & 0x03ff)
|
|
bodyStart := 12
|
|
if len(payload) >= bodyStart+bodySize+1 && bodySize >= 28 {
|
|
writeJT808Time(payload[bodyStart+22:bodyStart+28], time.Now().Add(time.Duration(frameIndex)*time.Second))
|
|
}
|
|
payload[len(payload)-1] = jt808Checksum(payload[:len(payload)-1])
|
|
escaped := jt808Escape(payload)
|
|
out := make([]byte, 0, len(escaped)+2)
|
|
out = append(out, 0x7e)
|
|
out = append(out, escaped...)
|
|
out = append(out, 0x7e)
|
|
return out, nil
|
|
}
|
|
|
|
func mutateGB32960Frame(base []byte, connectionIndex int, frameIndex int64) ([]byte, error) {
|
|
if len(base) < 25 || base[0] != '#' || base[1] != '#' {
|
|
return nil, fmt.Errorf("gb32960 template too short or missing start symbols")
|
|
}
|
|
frame := append([]byte(nil), base...)
|
|
vin := fmt.Sprintf("LNSIM%012d", connectionIndex%1000000000000)
|
|
copy(frame[4:21], []byte(vin[:17]))
|
|
bodyLen := int(binary.BigEndian.Uint16(frame[22:24]))
|
|
bodyStart := 24
|
|
if len(frame) >= bodyStart+bodyLen+1 && bodyLen >= 6 {
|
|
writeGB32960Time(frame[bodyStart:bodyStart+6], time.Now().Add(time.Duration(frameIndex)*time.Second))
|
|
}
|
|
frame[len(frame)-1] = gb32960BCC(frame[2 : len(frame)-1])
|
|
return frame, nil
|
|
}
|
|
|
|
func encodeBCD(value string, size int) []byte {
|
|
out := make([]byte, size)
|
|
if len(value)%2 == 1 {
|
|
value = "0" + value
|
|
}
|
|
if len(value) > size*2 {
|
|
value = value[len(value)-size*2:]
|
|
}
|
|
for len(value) < size*2 {
|
|
value = "0" + value
|
|
}
|
|
for i := 0; i < size; i++ {
|
|
out[i] = (value[i*2]-'0')<<4 | (value[i*2+1] - '0')
|
|
}
|
|
return out
|
|
}
|
|
|
|
func jt808Checksum(data []byte) byte {
|
|
var out byte
|
|
for _, value := range data {
|
|
out ^= value
|
|
}
|
|
return out
|
|
}
|
|
|
|
func jt808Escape(payload []byte) []byte {
|
|
out := make([]byte, 0, len(payload))
|
|
for _, value := range payload {
|
|
switch value {
|
|
case 0x7e:
|
|
out = append(out, 0x7d, 0x02)
|
|
case 0x7d:
|
|
out = append(out, 0x7d, 0x01)
|
|
default:
|
|
out = append(out, value)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func jt808Unescape(payload []byte) ([]byte, error) {
|
|
out := make([]byte, 0, len(payload))
|
|
for i := 0; i < len(payload); i++ {
|
|
if payload[i] != 0x7d {
|
|
out = append(out, payload[i])
|
|
continue
|
|
}
|
|
if i+1 >= len(payload) {
|
|
return nil, fmt.Errorf("invalid jt808 escape at end")
|
|
}
|
|
i++
|
|
switch payload[i] {
|
|
case 0x01:
|
|
out = append(out, 0x7d)
|
|
case 0x02:
|
|
out = append(out, 0x7e)
|
|
default:
|
|
return nil, fmt.Errorf("invalid jt808 escape sequence 0x%02x", payload[i])
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func writeJT808Time(out []byte, value time.Time) {
|
|
value = value.In(time.FixedZone("CST", 8*3600))
|
|
year := value.Year() % 100
|
|
parts := []int{year, int(value.Month()), value.Day(), value.Hour(), value.Minute(), value.Second()}
|
|
for i, part := range parts {
|
|
out[i] = byte(part/10)<<4 | byte(part%10)
|
|
}
|
|
}
|
|
|
|
func writeGB32960Time(out []byte, value time.Time) {
|
|
value = value.In(time.FixedZone("CST", 8*3600))
|
|
out[0] = byte(value.Year() % 100)
|
|
out[1] = byte(value.Month())
|
|
out[2] = byte(value.Day())
|
|
out[3] = byte(value.Hour())
|
|
out[4] = byte(value.Minute())
|
|
out[5] = byte(value.Second())
|
|
}
|
|
|
|
func gb32960BCC(data []byte) byte {
|
|
var out byte
|
|
for _, value := range data {
|
|
out ^= value
|
|
}
|
|
return out
|
|
}
|