解析混合类型数据的最佳实践?

时间:2018-11-10 04:34:30

标签: c parsing

我想知道是否有已知的最佳实践/方法来解析数据包的混合类型。

例如,假设数据为10个字节,并且包含:

字节0-1:制造商ID(int)
字节2:类型(int)
字节3-4:设备ID(ASCII字符)

我可以简单地将每个数据类型的大小和位置定义为#define,然后使用这些定义进行解析。但我想知道是否有任何组织可以更好地组织这一工作。

3 个答案:

答案 0 :(得分:2)

最佳做法是假设程序外部的所有数据(例如来自用户,文件,网络,其他进程的数据)均可能不正确(并且可能不安全/恶意)。

然后,基于“潜在不正确”的假设,定义类型以区分“未检查的,潜在的不正确数据”和“已检查的,已知正确的数据”。对于您的示例,可以将uint8_t packet[10];用作未检查数据的数据类型,并使用正常结构(带有填充且不包含__attribute__((packed));)作为已检查数据。这使程序员在认为自己正在使用安全/检查过的数据时意外地使用不安全的数据极为困难。

当然,您还需要在这些数据类型之间进行转换的代码,这需要进行尽可能多的健全性检查(并且还可能担心诸如耐久性)。对于您的示例,这些检查可能是:

  • 应该是ASCII字符> = 0x80的任何字节,并且其中的任何字节都是无效的(例如,不允许使用诸如退格键之类的控制字符)。
  • 制造商ID是否有效(例如,可能需要枚举才能匹配)
  • 类型是否有效(例如,可能需要枚举来匹配)

请注意,此函数应返回某种状态,以指示转换是否成功,并且在大多数情况下,该状态还应指示转换失败的原因是什么(以便呼叫者可以通知用户或记录问题或以最适合问题的方式处理问题)。例如,“未知的制造商ID”可能意味着该程序需要更新才能处理新的制造商,并且数据正确无误,而“无效的制造商ID”则意味着数据肯定是错误的。

答案 1 :(得分:0)

赞:

selected

必须使用packed属性(或平台的等效属性),以避免协议中不存在隐式填充。

有了上述结构后,您只需转换从任何地方收到的char数组(的一部分)即可:

handleSelectedChanged()

答案 2 :(得分:0)

对于完全可移植的版本,建议您以以下方式进行阅读:

    struct {
        uint16_t e1;
        uint8_t e2;
        uint16_t e3;
    } d;

    uint8_t *cursor;
    uint8_t rbuf[5];

    read(sock, rbuf, sizeof(rbuf));
    memcpy(&s.e1, &rbuf[0], sizeof(s.e1));
    s.e2 = rbuf[2];
    memcpy(&s.e3, &rbuf[3], sizeof(s.e3));

    s.e1 = ntohs(s.e1);
    s.e3 = ntohs(s.e3);

您可能会想像别人回答说的那样做,比如:

    struct s {
        uint16_t e1;
        uint8_t e2;
        uint16_t e3;
    } __attribute__((packed));

    struct s d;
    read(sock, &d, sizeof(d));
    s.e1 = ntohs(s.e1);
    s.e3 = ntohs(s.e3);

但是,此代码不是完全可移植的,可能会导致问题,因为您正在访问的内存未对齐的项(s.e3)本身就是未定义的行为。在某些情况下,这种方式是可行的并且很理想(较少的缓存污染,因为更多的结构可以填充不同的缓存行,并且代码可能更简单),但是在其他情况下,这可能会导致总线错误,并使您的代码与某些体系结构不兼容。 / p>

除此之外,您还应该遵循其他最佳实践,例如尝试在read()调用之间读取尽可能多的结构,编写有关网络到主机字节顺序转换的更好的代码...但是我认为避免非标准属性应该是第一件事。

请注意,如果您不进行不对齐访问,则所有这些内容(甚至是__packed__属性都完全不必要),并且您可以阅读以下结构:

struct {
    uint16_t e1;
    uint8_t e2;
    uint8_t e2;
    uint16_t e3;
} d;

read(rsock, &d, sizeof(d));