``` static byte[] BuildGoosePdu() { List<byte> pdu = new List<byte>(); // APPID (2字节) pdu.AddRange(new byte[] { 0x00, 0x01 }); // Length占位符(2字节) int lengthPos = pdu.Count; pdu.AddRange(new byte[] { 0x00, 0x00 }); // ---------- GOOSE PDU顶层SEQUENCE ---------- List<byte> gooseSequence = new List<byte>(); // 1. 控制块引用 (OCTET STRING) AddBerOctetString(gooseSequence, 0x80, "TestLD/LLN0$GO$gocb01"); // 2. 时间允许值 (INTEGER) AddBerInt(gooseSequence, 0x81, 1000); // 3. 数据集引用 (OCTET STRING) AddBerOctetString(gooseSequence, 0x82, "TestLD/LLN0$dsGOOSE"); // 4. GOOSE ID (OCTET STRING) AddBerOctetString(gooseSequence, 0x83, "GOOSE001"); // 5. 状态号+序列号 (SEQUENCE) List<byte> stSq = new List<byte>(); AddBerInt(stSq, 0x84, 1); // stNum AddBerInt(stSq, 0x85, 0); // sqNum gooseSequence.Add(0xA4); // SEQUENCE标签 AddBerLength(gooseSequence, stSq.Count); gooseSequence.AddRange(stSq); // 6. 测试模式 (BOOLEAN) AddBerBoolean(gooseSequence, 0x85, false); // 7. 配置版本 (INTEGER) AddBerInt(gooseSequence, 0x86, 1); // 8. 数据集成员值(SEQUENCE OF) List<byte> dataSetValues = new List<byte>(); // 示例:布尔量true AddBerBoolean(dataSetValues, 0x87, true); gooseSequence.Add(0xA7); // SEQUENCE OF标签 AddBerLength(gooseSequence, dataSetValues.Count); gooseSequence.AddRange(dataSetValues); // 封装顶层SEQUENCE pdu.Add(0x30); // SEQUENCE标签 AddBerLength(pdu, gooseSequence.Count); pdu.AddRange(gooseSequence); // 更新长度字段(总长度-4字节头) int totalLength = pdu.Count - 4; pdu[lengthPos] = (byte)((totalLength >> 8) & 0xFF); pdu[lengthPos + 1] = (byte)(totalLength & 0xFF); return pdu.ToArray(); }```此代码在wireshack上抓包得到的GOOSE PDU格式不正确,请修改成正确的GOOSE报文
时间: 2025-06-16 20:50:43 浏览: 4
<think>嗯,用户给了一段C#代码,用来构建GOOSE PDU,但是在Wireshark抓包的时候发现格式不正确。我需要帮他们找出问题所在,并修改代码生成正确的报文。首先,我得回忆一下GOOSE报文的ASN.1编码规则,特别是BER编码的细节。
首先,用户代码中使用了各种标签,比如0x80、0x81等等。GOOSE使用的是ASN.1 BER编码,每个字段都需要正确的标签和长度编码。比如,控制块引用应该是上下文特定的标签,所以可能用户使用的标签值是否正确?比如,控制块引用是上下文标签[0],即0x80,这个是对的。同样,时间允许值是上下文标签[1],即0x81,也是正确的。不过需要检查每个字段的标签是否正确。
接下来看状态号和序列号的部分。代码中创建了一个SEQUENCE,标签是0xA4,这可能有问题。因为SEQUENCE的BER标签通常是0x30,但如果是上下文标签的话可能需要调整。用户在这里的上下文标签可能设置错了。比如,状态号和序列号的SEQUENCE应该是上下文标签[4],但正确的标签应该是构造的,所以可能需要使用0xA4,但需要确认是否正确。或者,是否应该使用普通的SEQUENCE标签0x30,然后加上长度和内容?
另外,数据集成员值的部分,用户添加了一个标签0xA7(上下文标签[7]构造),而数据集的成员值应该是SEQUENCE OF,所以正确的标签应该是0x30(SEQUENCE)或者如果是在应用类中可能需要不同的标签。不过根据GOOSE的定义,数据集成员值属于上下文标签[7],构造类型,所以标签应为0xA7,这部分可能没问题,但需要检查内部元素的编码是否正确。
另一个问题是长度字段的编码。用户使用了AddBerLength函数,这个函数是否正确处理了不定或定长的情况?比如,当长度超过127时,需要使用多字节表示。例如,如果长度是1000,应该编码为0x82 0x03 0xE8。用户代码中的AddBerInt和AddBerOctetString是否正确处理了长度?
再看状态号和序列号的SEQUENCE部分。代码中添加了标签0xA4,长度是stSq.Count,然后添加内容。这里是否正确?根据GOOSE的ASN.1定义,状态号和序列号应该是在一个SEQUENCE中,并且这个SEQUENCE的上下文标签是[4],所以标签应该是0xA4,构造类型。所以这部分可能是正确的。
测试模式的标签是0x85,但根据定义,测试模式是上下文标签[5],所以标签应为0x85,但测试模式是布尔类型,是否正确使用了布尔值的编码?比如,布尔值的BER编码是0x01,然后是长度01,然后是值0x00或0xFF。用户代码中的AddBerBoolean函数是否正确处理了这一点?比如,如果函数添加了标签0x85,然后添加长度01,然后是0x00(false),那么是正确的。
配置版本的标签是0x86(上下文标签[6]),整数,是否正确?是的。
数据集成员值的部分,用户添加了一个布尔量到dataSetValues列表中,然后添加标签0xA7(上下文标签[7]构造),然后长度和内容。这里是否正确?GOOSE的数据集成员值是一个SEQUENCE OF Data,每个Data元素的结构可能更复杂。比如,布尔值可能需要用BOOLEAN类型,并且可能需要更完整的结构,比如每个数据项需要包含类型、标签等。比如,正确的编码可能需要每个数据项作为一个结构,例如,对于布尔量true,应该是0x83(上下文标签[3])?或者根据IEC 61850的规范,数据值可能需要特定的编码方式,比如布尔值可能被编码为布尔类型,但带有上下文标签。
比如,每个数据集成员值应该是一个SEQUENCE,每个元素可能包含值的类型和内容。例如,对于布尔值,正确的编码可能是:
83 01 FF (标签83,长度1,值FF)
或者,可能需要在更高层次的构造中,比如每个数据项是一个结构,包含属性值。例如,在GOOSE中,数据集成员值是一个SEQUENCE OF Data,每个Data元素是一个CHOICE类型,可能包含不同的类型,比如布尔值、整数等。每个Data元素需要用APPLICATION标签或者上下文标签来标识类型。
用户代码中在dataSetValues直接添加了0x87标签的布尔值,这可能是错误的。正确的做法是每个数据项应该被包装在特定的结构中。例如,布尔值可能需要被包装为一个BOOLEAN类型的值,并带有正确的标签,比如上下文标签[3](0x83)。所以,原来的代码中使用AddBerBoolean(dataSetValues, 0x87, true)可能是错误的,应该使用0x83作为标签。
此外,整个数据集成员值的SEQUENCE OF应该用正确的标签。在用户代码中,添加的是0xA7,即上下文标签[7]构造,这可能正确,因为GOOSE PDU中的数据集成员值对应上下文标签[7],所以构造的标签应为0xA7。然后里面的每个元素需要正确编码。
总结可能的问题点:
1. 状态号和序列号的SEQUENCE标签是否正确?比如,是否应该用0xA4(上下文标签[4]构造),还是普通的SEQUENCE标签0x30?根据ASN.1规范,上下文标签的结构可能需要构造类型的标签,所以0xA4是正确的。
2. 数据集成员值的每个数据项是否正确编码?例如,布尔值的标签应该是0x83(上下文标签[3]),而不是0x87。用户代码中使用了0x87,这可能是错误的,导致Wireshark解析错误。
3. 测试模式的标签是否正确?用户用了0x85,对应的上下文标签[5],正确。
4. 长度字段是否正确处理了多字节情况?例如,如果某个字段的长度超过127,需要正确编码长度部分。
可能的修改:
对于数据集成员值的布尔量,应该使用上下文标签[3](0x83),而不是0x87。用户代码中的AddBerBoolean(dataSetValues, 0x87, true)中的0x87可能应为0x83。因为根据IEC 61850-8-1的ASN.1定义,每个数据项的值根据其类型有不同的上下文标签,布尔值对应的是上下文标签[3]。
例如,正确的布尔值编码应该是:
83 01 FF (true)
所以,将0x87改为0x83。
同时,数据集成员值的SEQUENCE OF的标签是0xA7(上下文标签[7]构造),这部分是对的,因为GOOSE PDU中该字段是上下文标签[7]。
另一个可能的问题是状态号和序列号的顺序。在GOOSE PDU中,状态号(stNum)应该在前面,序列号(sqNum)在后面,用户代码中是正确的。标签0x84和0x85对应上下文标签[4]和[5],是正确的。
另外,检查长度字段是否正确。例如,在顶层GOOSE sequence封装时,pdu添加了0x30标签,这应该是正确的SEQUENCE标签,但GOOSE PDU的顶层应该是应用类标签,比如0x61?或者可能用户在这里混淆了编码层次?
或者,可能整个GOOSE PDU的结构有误。根据IEC 61850-8-1,GOOSE PDU的编码结构应该是:
GOOSEPdu ::= [APPLICATION 1] IMPLICIT SEQUENCE {...}
所以,顶层的标签应该是APPLICATION 1,构造类型,即0x61(因为APPLICATION的标签类别是0x40,1 | 0x40 = 0x41,但因为构造类型,所以最高位是1,即0x60 | 0x1 << 5?或者具体计算可能不同。比如,APPLICATION标签的编码是0x40,构造类型的话,APPLICATION标签的构造类型需要将第6位(从高位开始)置1。例如,APPLICATION 1的标签是0x61(0x60 + 1),因为构造类型的APPLICATION标签是0x60到0x7F。例如,应用类型标签的构造形式是0x60 + tag number,当tag number小于31时。所以,APPLICATION 1的构造标签应为0x61。因此,用户代码中的顶层SEQUENCE的标签写成了0x30(SEQUENCE),而正确应该是0x61。这是一个关键错误。
所以,在用户代码中,当封装顶层SEQUENCE的时候,他们用了:
pdu.Add(0x30); // SEQUENCE标签
而正确的应该是应用标签[1],构造类型,即0x61。所以这会导致整个顶层的结构错误,导致Wireshark无法正确解析为GOOSE PDU。
这可能是最大的问题所在。因此,需要将顶层的SEQUENCE标签从0x30改为0x61。
另一个问题可能是APPID的设置是否正确。用户代码中设置的APPID是0x0001,而GOOSE的APPID通常是0x0000到0x3FFF之间的值,但具体是否正确需要看配置。不过可能用户设置的APPID是正确的,但需要确认。
此外,长度字段的计算是否正确。在用户代码中,总长度是pdu.Count -4,然后写入两个字节。这可能正确,因为APPID和Length字段占用了4字节。
综上所述,主要的错误点:
1. 顶层的SEQUENCE标签错误,应该是应用标签[1]构造类型(0x61),而不是普通的SEQUENCE标签0x30。
2. 数据集成员值中的布尔值标签错误,应该使用上下文标签[3](0x83),而不是0x87。
修改这两个地方应该可以解决大部分问题。
现在需要检查其他部分是否还有错误。比如,时间允许值的编码是否正确?用户使用AddBerInt添加0x81标签,对应的上下文标签[1],整数类型,编码正确。同样,控制块引用是上下文标签[0]的OCTET STRING,正确。
状态号和序列号的SEQUENCE标签是0xA4,对应上下文标签[4]构造类型,正确。因为该SEQUENCE属于GOOSE PDU中的第5项,标签为4。
测试模式的标签是0x85,正确,对应上下文标签[5]。
配置版本的标签是0x86,正确,对应上下文标签[6]。
另外,检查每个字段的顺序是否正确。根据GOOSE PDU的结构,字段的顺序应该是:
gocbRef, timeAllowedtoLive, datSet, goID, stNum, sqNum, test, confRev, ndsCom, numDatSetEntries, dataSet (这最后两个可能不同版本的定义,需要确认)。但用户的代码中顺序可能正确:控制块引用(0),时间允许(1),数据集引用(2),GOOSE ID(3),状态号+序列号(4),测试模式(5),配置版本(6),数据集成员值(7)。这应该是正确的顺序。
此外,在状态号和序列号的SEQUENCE中,用户添加了stNum(0x84)和sqNum(0x85),顺序正确,先stNum后sqNum。
还有一个可能的错误是,在添加数据集成员值的SEQUENCE时,用户可能没有正确处理每个数据项的结构。例如,每个数据项可能需要包装在一个结构中,而不仅仅是直接添加布尔值。例如,在IEC 61850中,数据集成员值中的每个数据项是Data类型的,而Data类型是一个CHOICE类型,可能需要指定类型标签。例如,布尔值可能对应到布尔类型的Data项,使用上下文标签[3]。因此,正确的编码可能是在数据项中使用0x83标签的布尔值,如:
83 01 FF
而用户代码中的AddBerBoolean函数可能已经正确处理了这一点,但传入的标签是否正确。用户代码中在添加数据集成员值时,调用AddBerBoolean(dataSetValues, 0x87, true),这里的标签是0x87,这应该是错误的,应该改为0x83。
所以,总结修改点:
1. 修改顶层的SEQUENCE标签从0x30到0x61(APPLICATION 1构造类型)。
2. 修改数据集成员值中的布尔值标签从0x87到0x83。
此外,可能还有其他细节需要检查,比如每个字段的长度是否正确,特别是OCTET STRING的长度是否正确。
例如,控制块引用"TestLD/LLN0$GO$gocb01"转换为字节数组的长度是否正确。假设使用UTF-8编码,长度应该是该字符串的字节数,然后正确添加OCTET STRING的标签和长度。例如,假设该字符串长度为20字节,那么OCTET STRING的编码应为80(标签) + 长度(例如14,如果是20的话,可能需要多字节长度表示)。
但用户代码中的AddBerOctetString函数是否正确处理了长度?例如,当长度超过127时,是否使用多字节长度编码?
例如,字符串"TestLD/LLN0$GO$gocb01"的字节长度为:
TestLD/LLN0$GO$gocb01 的长度:
T e s t L D / L L N 0 $ G O $ g o c b 0 1 → 每个字符1字节(假设ASCII),总共有20个字符,所以长度是20字节。
所以,OCTET STRING的编码应为:标签0x80(上下文[0]),然后是长度0x14(20),所以编码为80 14 后跟20个字节。如果用户函数AddBerOctetString正确处理了长度,那没问题。否则,可能需要调整。
同样的,其他OCTET STRING字段也需要检查长度是否正确。
综上,修改顶层标签和数据集中的布尔标签应该能解决主要问题。用户需要修改代码中的这两处,然后重新测试。</think>根据代码问题分析和GOOSE报文BER编码规范,以下是修改后的代码及逐项说明:
```csharp
static byte[] BuildGoosePdu()
{
List<byte> pdu = new List<byte>();
// APPID (GOOSE默认0x0000到0x3FFF)
pdu.AddRange(new byte[] { 0x00, 0x01 });
// 长度占位符(后续填充)
int lengthPos = pdu.Count;
pdu.AddRange(new byte[] { 0x00, 0x00 });
// ---------- 顶层GOOSE PDU(应用类标签[1])----------
List<byte> gooseSequence = new List<byte>();
// 1. 控制块引用 (上下文标签[0] OCTET STRING)
AddBerOctetString(gooseSequence, 0x80, "TestLD/LLN0$GO$gocb01"); // 正确
// 2. 时间允许值 (上下文标签[1] INTEGER)
AddBerInt(gooseSequence, 0x81, 1000); // 正确
// 3. 数据集引用 (上下文标签[2] OCTET STRING)
AddBerOctetString(gooseSequence, 0x82, "TestLD/LLN0$dsGOOSE"); // 正确
// 4. GOOSE ID (上下文标签[3] OCTET STRING)
AddBerOctetString(gooseSequence, 0x83, "GOOSE001"); // 正确
// 5. 状态号+序列号 (上下文标签[4] SEQUENCE)
List<byte> stSq = new List<byte>();
AddBerInt(stSq, 0x84, 1); // stNum
AddBerInt(stSq, 0x85, 0); // sqNum
gooseSequence.Add(0xA4); // 构造型上下文标签[4]
AddBerLength(gooseSequence, stSq.Count);
gooseSequence.AddRange(stSq); // 正确
// 6. 测试模式 (上下文标签[5] BOOLEAN)
AddBerBoolean(gooseSequence, 0x85, false); // 正确
// 7. 配置版本 (上下文标签[6] INTEGER)
AddBerInt(gooseSequence, 0x86, 1); // 正确
// 8. 数据集成员值 (上下文标签[7] SEQUENCE OF)
List<byte> dataSetValues = new List<byte>();
// 布尔量必须用上下文标签[3]
AddBerBoolean(dataSetValues, 0x83, true); // 原错误点:标签0x87改为0x83
gooseSequence.Add(0xA7); // 构造型上下文标签[7]
AddBerLength(gooseSequence, dataSetValues.Count);
gooseSequence.AddRange(dataSetValues);
// 封装顶层(关键修改点:应用类标签[1])
pdu.Add(0x61); // APPLICATION[1]构造标签(原错误点:0x30改为0x61)
AddBerLength(pdu, gooseSequence.Count);
pdu.AddRange(gooseSequence);
// 更新长度字段(总长度-4字节头)
int totalLength = pdu.Count - 4;
pdu[lengthPos] = (byte)((totalLength >> 8) & 0xFF);
pdu[lengthPos + 1] = (byte)(totalLength & 0xFF);
return pdu.ToArray();
}
```
**修改说明:**
1. **顶层标签错误**
原代码使用`0x30`(普通SEQUENCE),正确应为`0x61`(APPLICATION类型标签[1]的构造形式)
2. **数据集成员值编码错误**
- 布尔值标签应使用`0x83`(上下文标签[3]),原错误使用`0x87`
- 每个数据项需按IEC 61850 Data类型规范编码
3. **长度计算验证**
确保`AddBerLength`方法能处理多字节长度(如长度>127时自动添加`0x82`前缀)
**辅助函数建议:**
```csharp
// 正确实现BER长度编码(支持多字节)
void AddBerLength(List<byte> list, int length)
{
if (length < 128) {
list.Add((byte)length);
} else {
byte[] lenBytes = BitConverter.GetBytes(length).Reverse().ToArray();
int byteCount = 0;
while (byteCount < lenBytes.Length && lenBytes[byteCount] == 0x00) byteCount++;
list.Add((byte)(0x80 | (lenBytes.Length - byteCount)));
list.AddRange(lenBytes.Skip(byteCount));
}
}
```
**验证方法:**
1. 在Wireshark中过滤`ether proto 0x88b8`
2. 检查解码后的`APPID`和`GoCBRef`字段是否显示正确
3. 展开`GoosePdu`结构验证所有字段层级
**常见错误现象对照:**
- 若仍显示`Malformed Packet`:检查BER长度编码实现
- 若`GoosePdu`无法展开:确认应用标签`0x61`和构造类型
- 布尔值显示错误:检查数据项标签是否为`0x83`
阅读全文
相关推荐

















