package loadsim import ( "encoding/binary" "fmt" "time" ) type FrameFactory struct { protocol Protocol base []byte } func NewFrameFactory(protocol Protocol, template string) (*FrameFactory, error) { base, err := FrameTemplate(protocol, template) if err != nil { return nil, err } return &FrameFactory{protocol: protocol, base: base}, nil } func (f *FrameFactory) Frame(connectionIndex int, frameIndex int64) ([]byte, error) { switch f.protocol { case ProtocolJT808: return mutateJT808Frame(f.base, connectionIndex, frameIndex) case ProtocolGB32960: return mutateGB32960Frame(f.base, connectionIndex, frameIndex) default: out := append([]byte(nil), f.base...) return out, nil } } func mutateJT808Frame(base []byte, 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", 139000000000+connectionIndex%100000000) 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 }