如何反序列化非标准大小的字段?
How to deserialize a non-standard size field?
我必须反序列化一些来自另一个应用程序的二进制消息。我很想使用 restruct.io 但消息结构中的某些字段使用 "non-standard" 位数(5 位,3 位,... 10 位...)。
有什么方法可以处理这种类型的结构吗?我一直在寻找一段时间但没有成功,因此非常欢迎任何帮助。
提前致谢
我会试着举个例子来澄清我的问题。给定代码:
package main
import (
"encoding/binary"
"fmt"
restruct "gopkg.in/restruct.v1"
)
type MessageType uint8
const (
MessageTypeOne MessageType = iota + 1
MessageTypeTwo
MessageTypeThree
)
// Message is the data to deserialize from the binary stream
type Message struct {
Length uint32 `struct:"uint32"` // message size in bytes (including length)
Type MessageType `struct:"uint8"`
Version uint8 `struct:"uint8:4"` // Just need 4 bits
Subversion uint8 `struct:"uint8:2"` // just need 2 bits
Optional uint8 `struct:"uint8:1"` // just one bit --> '1' means next field is NOT present
NodeName string ``
ANumber uint16 `struct:"uint16:10"` // just need 10 bits
}
// (length(4)+type(1)+(version(4bits)+Subversion(2bits)+Optional(1bit))) = 6 bytes
// need 32bit alignment
func main() {
var inStream = []byte{0x08, // just 8 bytes needed
0x01, // messge type = MessageTypeOne
0x4a, // Version=0100 Subversion=10 Optional=1 ANumber = 0 (MSB bit)
0x00, 0x60, // ANumber(000 0000 011) Padding = 0 0000 for 32 bits alignment
}
var msg Message
err := restruct.Unpack(inStream, binary.BigEndian, &msg)
if err != nil {
panic(err)
}
fmt.Println(msg)
// Expected:
// msg.Length = 8
// msg.Type = 1
// msg.Version = 4
// msg.Subversion = 2
// msg.Optional = 1
// msg.NodeName = ""
// msg.ANumber = 3
}
我将从 TCP 连接接收 inStream 并希望反序列化二进制数据并获得具有预期值的 Message 结构...
希望这能澄清我的问题。
再次感谢 ;)
虽然可能没有实现此自定义结构打包的通用包,但您可以轻松创建自己的方法,仅提取每个字段所需的位。
func (m *Message) UnmarshalBinary(data []byte) error {
m.Length = binary.BigEndian.Uint32(data[:4])
if int(m.Length) > len(data) {
return fmt.Errorf("not enough bytes")
}
m.Type = MessageType(data[4])
m.Version = data[5] >> 4
m.Subversion = data[5] >> 2 & 0x03
m.Optional = data[5] >> 1 & 0x01
// move the index for ANumber back if there's an optional string
idx := 6
if m.Optional == 0 {
// remove the last two bytes for ANumber
end := int(m.Length) - 2
m.NodeName = string(data[6:end])
idx = end
}
m.ANumber = uint16(data[idx]&0xc0)<<2 | uint16(data[idx]&0x3f<<2|data[idx+1]>>6)
return nil
}
您当然可以为 return 错误添加更多绑定检查,而不是在索引越界时让这种恐慌。
我稍微修改了您的 inStream
切片以匹配您的定义,您可以在此处查看示例输出:https://play.golang.org/p/FoNoazluOF
我一直在为 restruct.io 开发一些补丁,以便能够使用位域....仍然没有经过全面测试,但似乎可以工作...
测试后将尝试发送拉取请求...
func (e *encoder) writeBits(f field, inBuf []byte) {
var inputLength uint8 = uint8(len(inBuf))
if f.BitSize == 0 {
// Having problems with complex64 type ... so we asume we want to read all
//f.BitSize = uint8(f.Type.Bits())
f.BitSize = 8 * inputLength
}
// destPos: Destination position ( in the result ) of the first bit in the first byte
var destPos uint8 = 8 - e.bitCounter
// originPos: Original position of the first bit in the first byte
var originPos uint8 = f.BitSize % 8
if originPos == 0 {
originPos = 8
}
// numBytes: number of complete bytes to hold the result
var numBytes uint8 = f.BitSize / 8
// numBits: number of remaining bits in the first non-complete byte of the result
var numBits uint8 = f.BitSize % 8
// number of positions we have to shift the bytes to get the result
var shift uint8
if originPos > destPos {
shift = originPos - destPos
} else {
shift = destPos - originPos
}
shift = shift % 8
var inputInitialIdx uint8 = inputLength - numBytes
if numBits > 0 {
inputInitialIdx = inputInitialIdx - 1
}
if originPos < destPos {
// shift left
carry := func(idx uint8) uint8 {
if (idx + 1) < inputLength {
return (inBuf[idx+1] >> (8 - shift))
}
return 0x00
}
mask := func(idx uint8) uint8 {
if idx == 0 {
return (0x01 << destPos) - 1
}
return 0xFF
}
var idx uint8 = 0
for inIdx := inputInitialIdx; inIdx < inputLength; inIdx++ {
e.buf[idx] |= ((inBuf[inIdx] << shift) | carry(inIdx)) & mask(idx)
idx++
}
} else {
// originPos >= destPos => shift right
var idx uint8 = 0
// carry : is a little bit tricky in this case because of the first case
// when idx == 0 and there is no carry at all
carry := func(idx uint8) uint8 {
if idx == 0 {
return 0x00
}
return (inBuf[idx-1] << (8 - shift))
}
mask := func(idx uint8) uint8 {
if idx == 0 {
return (0x01 << destPos) - 1
}
return 0xFF
}
inIdx := inputInitialIdx
for ; inIdx < inputLength; inIdx++ {
//note: Should the mask be done BEFORE the OR with carry?
e.buf[idx] |= ((inBuf[inIdx] >> shift) | carry(inIdx)) & mask(idx)
idx++
}
if ((e.bitCounter + f.BitSize) % 8) > 0 {
e.buf[idx] |= carry(inIdx)
}
}
//now we should update buffer and bitCounter
e.bitCounter = (e.bitCounter + f.BitSize) % 8
// move the head to the next non-complete byte used
headerUpdate := func() uint8 {
if (e.bitCounter == 0) && ((f.BitSize % 8) != 0) {
return (numBytes + 1)
}
return numBytes
}
e.buf = e.buf[headerUpdate():]
return
}
我必须反序列化一些来自另一个应用程序的二进制消息。我很想使用 restruct.io 但消息结构中的某些字段使用 "non-standard" 位数(5 位,3 位,... 10 位...)。
有什么方法可以处理这种类型的结构吗?我一直在寻找一段时间但没有成功,因此非常欢迎任何帮助。
提前致谢
我会试着举个例子来澄清我的问题。给定代码:
package main
import (
"encoding/binary"
"fmt"
restruct "gopkg.in/restruct.v1"
)
type MessageType uint8
const (
MessageTypeOne MessageType = iota + 1
MessageTypeTwo
MessageTypeThree
)
// Message is the data to deserialize from the binary stream
type Message struct {
Length uint32 `struct:"uint32"` // message size in bytes (including length)
Type MessageType `struct:"uint8"`
Version uint8 `struct:"uint8:4"` // Just need 4 bits
Subversion uint8 `struct:"uint8:2"` // just need 2 bits
Optional uint8 `struct:"uint8:1"` // just one bit --> '1' means next field is NOT present
NodeName string ``
ANumber uint16 `struct:"uint16:10"` // just need 10 bits
}
// (length(4)+type(1)+(version(4bits)+Subversion(2bits)+Optional(1bit))) = 6 bytes
// need 32bit alignment
func main() {
var inStream = []byte{0x08, // just 8 bytes needed
0x01, // messge type = MessageTypeOne
0x4a, // Version=0100 Subversion=10 Optional=1 ANumber = 0 (MSB bit)
0x00, 0x60, // ANumber(000 0000 011) Padding = 0 0000 for 32 bits alignment
}
var msg Message
err := restruct.Unpack(inStream, binary.BigEndian, &msg)
if err != nil {
panic(err)
}
fmt.Println(msg)
// Expected:
// msg.Length = 8
// msg.Type = 1
// msg.Version = 4
// msg.Subversion = 2
// msg.Optional = 1
// msg.NodeName = ""
// msg.ANumber = 3
}
我将从 TCP 连接接收 inStream 并希望反序列化二进制数据并获得具有预期值的 Message 结构...
希望这能澄清我的问题。
再次感谢 ;)
虽然可能没有实现此自定义结构打包的通用包,但您可以轻松创建自己的方法,仅提取每个字段所需的位。
func (m *Message) UnmarshalBinary(data []byte) error {
m.Length = binary.BigEndian.Uint32(data[:4])
if int(m.Length) > len(data) {
return fmt.Errorf("not enough bytes")
}
m.Type = MessageType(data[4])
m.Version = data[5] >> 4
m.Subversion = data[5] >> 2 & 0x03
m.Optional = data[5] >> 1 & 0x01
// move the index for ANumber back if there's an optional string
idx := 6
if m.Optional == 0 {
// remove the last two bytes for ANumber
end := int(m.Length) - 2
m.NodeName = string(data[6:end])
idx = end
}
m.ANumber = uint16(data[idx]&0xc0)<<2 | uint16(data[idx]&0x3f<<2|data[idx+1]>>6)
return nil
}
您当然可以为 return 错误添加更多绑定检查,而不是在索引越界时让这种恐慌。
我稍微修改了您的 inStream
切片以匹配您的定义,您可以在此处查看示例输出:https://play.golang.org/p/FoNoazluOF
我一直在为 restruct.io 开发一些补丁,以便能够使用位域....仍然没有经过全面测试,但似乎可以工作...
测试后将尝试发送拉取请求...
func (e *encoder) writeBits(f field, inBuf []byte) {
var inputLength uint8 = uint8(len(inBuf))
if f.BitSize == 0 {
// Having problems with complex64 type ... so we asume we want to read all
//f.BitSize = uint8(f.Type.Bits())
f.BitSize = 8 * inputLength
}
// destPos: Destination position ( in the result ) of the first bit in the first byte
var destPos uint8 = 8 - e.bitCounter
// originPos: Original position of the first bit in the first byte
var originPos uint8 = f.BitSize % 8
if originPos == 0 {
originPos = 8
}
// numBytes: number of complete bytes to hold the result
var numBytes uint8 = f.BitSize / 8
// numBits: number of remaining bits in the first non-complete byte of the result
var numBits uint8 = f.BitSize % 8
// number of positions we have to shift the bytes to get the result
var shift uint8
if originPos > destPos {
shift = originPos - destPos
} else {
shift = destPos - originPos
}
shift = shift % 8
var inputInitialIdx uint8 = inputLength - numBytes
if numBits > 0 {
inputInitialIdx = inputInitialIdx - 1
}
if originPos < destPos {
// shift left
carry := func(idx uint8) uint8 {
if (idx + 1) < inputLength {
return (inBuf[idx+1] >> (8 - shift))
}
return 0x00
}
mask := func(idx uint8) uint8 {
if idx == 0 {
return (0x01 << destPos) - 1
}
return 0xFF
}
var idx uint8 = 0
for inIdx := inputInitialIdx; inIdx < inputLength; inIdx++ {
e.buf[idx] |= ((inBuf[inIdx] << shift) | carry(inIdx)) & mask(idx)
idx++
}
} else {
// originPos >= destPos => shift right
var idx uint8 = 0
// carry : is a little bit tricky in this case because of the first case
// when idx == 0 and there is no carry at all
carry := func(idx uint8) uint8 {
if idx == 0 {
return 0x00
}
return (inBuf[idx-1] << (8 - shift))
}
mask := func(idx uint8) uint8 {
if idx == 0 {
return (0x01 << destPos) - 1
}
return 0xFF
}
inIdx := inputInitialIdx
for ; inIdx < inputLength; inIdx++ {
//note: Should the mask be done BEFORE the OR with carry?
e.buf[idx] |= ((inBuf[inIdx] >> shift) | carry(inIdx)) & mask(idx)
idx++
}
if ((e.bitCounter + f.BitSize) % 8) > 0 {
e.buf[idx] |= carry(inIdx)
}
}
//now we should update buffer and bitCounter
e.bitCounter = (e.bitCounter + f.BitSize) % 8
// move the head to the next non-complete byte used
headerUpdate := func() uint8 {
if (e.bitCounter == 0) && ((f.BitSize % 8) != 0) {
return (numBytes + 1)
}
return numBytes
}
e.buf = e.buf[headerUpdate():]
return
}