diff --git a/bytepacket/README.md b/bytepacket/README.md new file mode 100644 index 0000000..022f873 --- /dev/null +++ b/bytepacket/README.md @@ -0,0 +1,31 @@ +# bytepacket + +二进制封包/解包工具(小端序):按偏移读写整数/字符串/字节集,兼容易语言协议—— +含 GBK 字符串转换与易语言日期类型。 + +> 迁移自 go-hua/bytepacket,代码保持原样。 + +## 用法 + +```go +import "git.zeroonesoft.cn/golib/zogo/bytepacket" + +// 解包:从字节集创建后按序读取 +p := bytepacket.NewPacket(data) +v := p.ReadInt32() +s := p.ReadStringGbk() // GBK → UTF-8 +t := p.ReadElangDateTime() // 易语言日期 → time.Time + +// 封包:按序写入后取结果 +w := bytepacket.NewPacket(nil) +w.WriteInt32(100) +w.WriteStringGbk("中文") +wire := w.GetData() +``` + +完整可运行例程:[examples/bytepacket/main.go](../examples/bytepacket/main.go) + +## 注意 + +- 小端序,与常见网络字节序(大端)不同,跨语言对接时先确认字节序。 +- 主要面向易语言系既有协议的兼容场景;新协议建议用本仓库 `frame` 或 `proto`。 diff --git a/bytepacket/bytepacket.go b/bytepacket/bytepacket.go new file mode 100644 index 0000000..904ef26 --- /dev/null +++ b/bytepacket/bytepacket.go @@ -0,0 +1,281 @@ +// Package bytepacket 提供二进制封包/解包(小端序,兼容易语言协议,含 GBK 字符串与易语言日期转换)。 +package bytepacket + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "math" + + "time" + + "golang.org/x/text/encoding/simplifiedchinese" + "golang.org/x/text/transform" +) + +// Packet 二进制封包读写器(小端序),写入后经 GetData 取出完整报文 +type Packet struct { + buffer *bytes.Buffer +} + +// NewPacket 新建封包 +func NewPacket(b []byte) *Packet { + return &Packet{buffer: bytes.NewBuffer(b)} +} + +// SetData 设置数据 +func (p *Packet) SetData(b []byte) { + p.buffer = bytes.NewBuffer(b) +} + +// GetData 获取数据 +func (p *Packet) GetData() []byte { + return p.buffer.Bytes() +} + +// ReadInt8 读取int8 +func (p *Packet) ReadInt8() int8 { + var val int8 = 0 + binary.Read(p.buffer, binary.LittleEndian, &val) + return val +} + +// WriteInt8 写入int8 +func (p *Packet) WriteInt8(val int8) { + binary.Write(p.buffer, binary.LittleEndian, &val) +} + +// ReadUInt8 读取uint8 +func (p *Packet) ReadUInt8() uint8 { + var val uint8 = 0 + binary.Read(p.buffer, binary.LittleEndian, &val) + return val +} + +// WriteUInt8 写入uint8 +func (p *Packet) WriteUInt8(val uint8) { + binary.Write(p.buffer, binary.LittleEndian, &val) +} + +// ReadInt16 读取int16 +func (p *Packet) ReadInt16() int16 { + var val int16 = 0 + binary.Read(p.buffer, binary.LittleEndian, &val) + return val +} + +// WriteInt16 写入int16 +func (p *Packet) WriteInt16(val int16) { + binary.Write(p.buffer, binary.LittleEndian, &val) +} + +// ReadUInt16 读取uint16 +func (p *Packet) ReadUInt16() uint16 { + var val uint16 = 0 + binary.Read(p.buffer, binary.LittleEndian, &val) + return val +} + +// WriteUInt16 写入uint16 +func (p *Packet) WriteUInt16(val uint16) { + binary.Write(p.buffer, binary.LittleEndian, &val) +} + +// ReadInt32 读取int32 +func (p *Packet) ReadInt32() int32 { + var val int32 = 0 + binary.Read(p.buffer, binary.LittleEndian, &val) + return val +} + +// WriteUInt32 写入uint32 +func (p *Packet) WriteUInt32(val uint32) { + binary.Write(p.buffer, binary.LittleEndian, &val) +} + +// ReadUInt32 读取uint32 +func (p *Packet) ReadUInt32() uint32 { + var val uint32 = 0 + binary.Read(p.buffer, binary.LittleEndian, &val) + return val +} + +// WriteInt32 写入int32 +func (p *Packet) WriteInt32(val int32) { + binary.Write(p.buffer, binary.LittleEndian, &val) +} + +// ReadInt64 读取int64 +func (p *Packet) ReadInt64() int64 { + var val int64 = 0 + binary.Read(p.buffer, binary.LittleEndian, &val) + return val +} + +// WriteInt64 写入int64 +func (p *Packet) WriteInt64(val int64) { + binary.Write(p.buffer, binary.LittleEndian, &val) +} + +// ReadUInt64 读取uint64 +func (p *Packet) ReadUInt64() uint64 { + var val uint64 = 0 + binary.Read(p.buffer, binary.LittleEndian, &val) + return val +} + +// WriteUInt64 写入uint64 +func (p *Packet) WriteUInt64(val uint64) { + binary.Write(p.buffer, binary.LittleEndian, &val) +} + +// ReadFloat32 读取float32 +func (p *Packet) ReadFloat32() float32 { + var val float32 = 0 + binary.Read(p.buffer, binary.LittleEndian, &val) + return val +} + +// WriteFloat32 写入float32 +func (p *Packet) WriteFloat32(val float32) { + binary.Write(p.buffer, binary.LittleEndian, &val) +} + +// ReadFloat64 读取float64 +func (p *Packet) ReadFloat64() float64 { + var val float64 = 0 + binary.Read(p.buffer, binary.LittleEndian, &val) + return val +} + +// WriteFloat64 写入float64 +func (p *Packet) WriteFloat64(val float64) { + binary.Write(p.buffer, binary.LittleEndian, &val) +} + +// ReadStringGbk 读取GBK字符串 +func (p *Packet) ReadStringGbk() (string, error) { + var len int16 = 0 + binary.Read(p.buffer, binary.LittleEndian, &len) + buf := p.buffer.Next(int(len)) + if len == 0 { + return "", nil + } + if len < 0 { + return "", fmt.Errorf("invalid string length: %d", len) + } + bytes, err := GbkToUtf8(buf) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// WriteStringGbk 写入GBK字符串 +func (p *Packet) WriteStringGbk(str string) error { + bytes, err := Utf8ToGbk([]byte(str)) + if err != nil { + return err + } + var len int16 = int16(len(bytes)) + err = binary.Write(p.buffer, binary.LittleEndian, &len) + if err != nil { + return err + } + _, err = p.buffer.Write(bytes) + if err != nil { + return err + } + return err +} + +// ReadString 读取字符串 +func (p *Packet) ReadString() (string, error) { + var len int16 = 0 + err := binary.Read(p.buffer, binary.LittleEndian, &len) + if err != nil { + return "", err + } + if len == 0 { + return "", nil + } + if len < 0 { + return "", fmt.Errorf("invalid string length: %d", len) + } + buf := p.buffer.Next(int(len)) + return string(buf), nil +} + +// WriteString 写入字符串 +func (p *Packet) WriteString(str string) error { + bytes := []byte(str) + var len int16 = int16(len(bytes)) + err := binary.Write(p.buffer, binary.LittleEndian, &len) + if err != nil { + return err + } + _, err = p.buffer.Write(bytes) + if err != nil { + return err + } + return nil +} + +// ReadBytes 读取字节集 +func (p *Packet) ReadBytes() []byte { + var len int32 = 0 + binary.Read(p.buffer, binary.LittleEndian, &len) + if len == 0 { + return nil + } + if len < 0 { + return nil + } + buf := p.buffer.Next(int(len)) + return buf +} + +// WriteBytes 写入字节集 +func (p *Packet) WriteBytes(data []byte) error { + var len int32 = int32(len(data)) + err := binary.Write(p.buffer, binary.LittleEndian, &len) + if err != nil { + return err + } + _, err = p.buffer.Write(data) + if err != nil { + return err + } + return err +} + +// ReadElangDateTime 读取易语言日期时间 +func (p *Packet) ReadElangDateTime() time.Time { + var f float64 + binary.Read(p.buffer, binary.LittleEndian, &f) + t := time.Date(1899, 12, 30, 0, 0, 0, 0, time.Local) + msStr := fmt.Sprint("+", uint64(math.Floor(f*86400*1000+0.5)), "ms") + ms, _ := time.ParseDuration(msStr) + return t.Add(ms) +} + +// GbkToUtf8 GBK 转 UTF-8 +func GbkToUtf8(s []byte) ([]byte, error) { + reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GBK.NewDecoder()) + d, e := io.ReadAll(reader) + if e != nil { + return nil, e + } + return d, nil +} + +// Utf8ToGbk UTF-8 转 GBK +func Utf8ToGbk(s []byte) ([]byte, error) { + reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GBK.NewEncoder()) + d, e := io.ReadAll(reader) + if e != nil { + return nil, e + } + return d, nil +} diff --git a/bytepacket/bytepacket_test.go b/bytepacket/bytepacket_test.go new file mode 100644 index 0000000..b501382 --- /dev/null +++ b/bytepacket/bytepacket_test.go @@ -0,0 +1,126 @@ +package bytepacket + +import ( + "bytes" + "testing" +) + +func TestIntReadWrite(t *testing.T) { + p := NewPacket(nil) + p.WriteInt8(-8) + p.WriteUInt8(8) + p.WriteInt16(-16) + p.WriteUInt16(16) + p.WriteInt32(-32) + p.WriteUInt32(32) + p.WriteInt64(-64) + p.WriteUInt64(64) + p.WriteFloat32(1.5) + p.WriteFloat64(2.5) + + r := NewPacket(p.GetData()) + if got := r.ReadInt8(); got != -8 { + t.Errorf("ReadInt8 = %d, want -8", got) + } + if got := r.ReadUInt8(); got != 8 { + t.Errorf("ReadUInt8 = %d, want 8", got) + } + if got := r.ReadInt16(); got != -16 { + t.Errorf("ReadInt16 = %d, want -16", got) + } + if got := r.ReadUInt16(); got != 16 { + t.Errorf("ReadUInt16 = %d, want 16", got) + } + if got := r.ReadInt32(); got != -32 { + t.Errorf("ReadInt32 = %d, want -32", got) + } + if got := r.ReadUInt32(); got != 32 { + t.Errorf("ReadUInt32 = %d, want 32", got) + } + if got := r.ReadInt64(); got != -64 { + t.Errorf("ReadInt64 = %d, want -64", got) + } + if got := r.ReadUInt64(); got != 64 { + t.Errorf("ReadUInt64 = %d, want 64", got) + } + if got := r.ReadFloat32(); got != 1.5 { + t.Errorf("ReadFloat32 = %v, want 1.5", got) + } + if got := r.ReadFloat64(); got != 2.5 { + t.Errorf("ReadFloat64 = %v, want 2.5", got) + } +} + +func TestStringReadWrite(t *testing.T) { + p := NewPacket(nil) + if err := p.WriteString("hello"); err != nil { + t.Fatalf("WriteString: %v", err) + } + if err := p.WriteStringGbk("中文GBK"); err != nil { + t.Fatalf("WriteStringGbk: %v", err) + } + + r := NewPacket(p.GetData()) + s, err := r.ReadString() + if err != nil || s != "hello" { + t.Errorf("ReadString = %q, %v, want hello", s, err) + } + g, err := r.ReadStringGbk() + if err != nil || g != "中文GBK" { + t.Errorf("ReadStringGbk = %q, %v, want 中文GBK", g, err) + } +} + +func TestBytesReadWrite(t *testing.T) { + p := NewPacket(nil) + want := []byte{1, 2, 3, 0xFF} + if err := p.WriteBytes(want); err != nil { + t.Fatalf("WriteBytes: %v", err) + } + got := NewPacket(p.GetData()).ReadBytes() + if !bytes.Equal(got, want) { + t.Errorf("ReadBytes = %v, want %v", got, want) + } +} + +func TestSetDataGetData(t *testing.T) { + p := NewPacket(nil) + p.SetData([]byte{9, 9}) + if !bytes.Equal(p.GetData(), []byte{9, 9}) { + t.Errorf("GetData = %v, want [9 9]", p.GetData()) + } +} + +func TestGbkUtf8Convert(t *testing.T) { + src := []byte("编码转换") + gbk, err := Utf8ToGbk(src) + if err != nil { + t.Fatalf("Utf8ToGbk: %v", err) + } + utf8Bytes, err := GbkToUtf8(gbk) + if err != nil { + t.Fatalf("GbkToUtf8: %v", err) + } + if !bytes.Equal(utf8Bytes, src) { + t.Errorf("GBK 往返不一致: %q != %q", utf8Bytes, src) + } +} + +func TestReadElangDateTime(t *testing.T) { + // 45292.5 天 ≈ 2024-01-01 12:00:00 + p := NewPacket(nil) + p.WriteFloat64(45292.5) + got := NewPacket(p.GetData()).ReadElangDateTime() + if got.Format("2006-01-02 15:04:05") != "2024-01-01 12:00:00" { + t.Errorf("ReadElangDateTime = %s, want 2024-01-01 12:00:00", got.Format("2006-01-02 15:04:05")) + } +} + +func TestReadStringEmpty(t *testing.T) { + p := NewPacket(nil) + p.WriteInt16(0) + s, err := NewPacket(p.GetData()).ReadString() + if err != nil || s != "" { + t.Errorf("ReadString 空串 = %q, %v", s, err) + } +} diff --git a/examples/bytepacket/main.go b/examples/bytepacket/main.go new file mode 100644 index 0000000..876a73f --- /dev/null +++ b/examples/bytepacket/main.go @@ -0,0 +1,43 @@ +// bytepacket 包示例:二进制封包/解包(小端序,兼容易语言协议,含 GBK 字符串与易语言日期) +package main + +import ( + "fmt" + + "git.zeroonesoft.cn/golib/zogo/bytepacket" +) + +func main() { + // ===== 写入封包 ===== + p := bytepacket.NewPacket(nil) + p.WriteInt32(100) + p.WriteUInt16(200) + _ = p.WriteString("hello") + _ = p.WriteStringGbk("中文GBK") + _ = p.WriteBytes([]byte{1, 2, 3}) + + data := p.GetData() + fmt.Printf("封包总长: %d 字节\n", len(data)) + + // ===== 读取封包(按写入顺序读出)===== + r := bytepacket.NewPacket(data) + fmt.Println("ReadInt32:", r.ReadInt32()) + fmt.Println("ReadUInt16:", r.ReadUInt16()) + s, _ := r.ReadString() + fmt.Println("ReadString:", s) + gbk, _ := r.ReadStringGbk() + fmt.Println("ReadStringGbk:", gbk) + fmt.Println("ReadBytes:", r.ReadBytes()) + + // ===== GBK / UTF-8 互转 ===== + gbkBytes, _ := bytepacket.Utf8ToGbk([]byte("编码转换")) + fmt.Printf("UTF-8→GBK: % X\n", gbkBytes) + utf8Bytes, _ := bytepacket.GbkToUtf8(gbkBytes) + fmt.Println("GBK→UTF-8:", string(utf8Bytes)) + + // ===== 易语言日期时间(float64 天数,起点 1899-12-30)===== + d := bytepacket.NewPacket(nil) + d.WriteFloat64(45292.5) // ≈ 2024-01-01 12:00:00 + t := d.ReadElangDateTime() + fmt.Println("ReadElangDateTime:", t.Format("2006-01-02 15:04:05")) +}