第十四天 - 使用 Raku 设计(小)航天器

Designing a (little) Spacecraft with Raku

寻找共同点

大家好!

那些日子我花了一些时间在基础部件上工作,揭示了可能的惊喜,Raku 的 LDAP(轻量级目录访问协议)实现。

然而,现在谈论这个还为时尚早,所以我现在将有一些神秘的封面覆盖这个话题,因为我们有另一个 - 宇宙飞船!

航天器和LDAP之间的共同点是:LDAP规范使用一种称为符号的符号 ASN.1,它允许使用特定的文本语法定义抽象类型,并在 ASN.1编译器的帮助下,为特定的编程语言创建类型定义,以及什么是更多:此类型值的编码器和解码器,可以将您的值序列化为某些数据,例如,可以通过网络发送并在另一台计算机上很好地解析。

通过这种方式,您可以轻松地在应用程序中获得跨平台类型。编码器和解码器可以自动生成,不仅针对某些指定的编码格式,而且针对整个范围的二进制(例如 BERPER和其他)和文本(例如SOAP)编码格式。

因此,为了完成工作,我必须至少实现 ASN.1Raku中的一些子集- 不是完整的规范,这很大,只关注LDAP规范中使用的功能。

“这听起来很有趣,但我们的宇宙飞船在哪里!?”,你可能会问。事实证明,这种 Rocket 类型是您在 ASN.1 Playground 网站上看到的第一件事,它让您可以免费访问 ASN.1编译器,它可以作为参考!

ASN.1 和限制

这是花哨的代码:

World-Schema DEFINITIONS AUTOMATIC TAGS ::=
BEGIN
  Rocket ::= SEQUENCE
  {
     name      UTF8String (SIZE(1..16)),
     message   UTF8String DEFAULT "Hello World" ,
     fuel      ENUMERATED {solid, liquid, gas},
     speed     CHOICE
     {
        mph    INTEGER,
        kmph   INTEGER
     }  OPTIONAL,
     payload   SEQUENCE OF UTF8String
  }
END

让我们快速浏览一下这个定义:

  • Rocket 是一个 SEQUENCE - 一组某类型的有序值,可以看作是异构列表/数组或类。
  • namemessageUTF8String 型,这是肯定的,一种字符串表示的 ASN.1。字段name已应用长度限制,(SIZE(1..16))message 具有指定的默认值 DEFAULT "Hello World"
  • 字段 fuelENUMERATED 类型:它只是一个可供选择的标签枚举。
  • 字段 speed 是一个 CHOICE,它是一种特殊类型,它描述了一个字段,该值可以是指定类型之一。不同的是 ENUMERATED,价值不仅仅是标签。OPTIONAL 如你所知,关键字意味着如果不存在,该字段可能会被省略。
  • 字段 payload 是一个 SEQUENCE,但指定了类型。这意味着我们可以根据需要在这里拥有尽可能多的 UTF8String 值。

这里我们将应用两个重要的限制:

  • 我们将使用 Basic Encoding Rules(BER) - 将 ASN.1类型编码指定为特定字节序列的规则。如上所述,有不同的格式,但我们将使用这一种。

Basic Encoding Rules 标准是基于一个所谓的“TLV编码”的事情-的类型的值被编码为字节序列表示:“ Ť AG”,“ 大号 ength”和“ V传递类型的某些值的ALUE”。让我们更仔细地看一下……以相反的顺序!

“值”是包含值的字节表示的部分。每种类型都有自己的编码模式(例如,INTEGER 编码方式不同 UTF8String)。

“长度”是表示“值”部分中的字节数的数字。这允许我们很好地处理增量解析(通常也是!)。它也可以具有“未知”值,这允许我们以未知的长度流式传输数据,但我们将把它放在一边。

“标签”简单地说是一个字节或一些字节,我们可以用它来确定我们手头有什么类型。其确切值由标记规则的数量(“标记模式”)确定,并且存在好的或更差的不同模式。

并且,如果您已经等待某些段落的第二个限制,那么它是:

我们将 IMPLICIT 在这里使用 BER 的类型标记模式。正如您所猜测的那样,EXPLICIT 标记模式也同时存在 AUTOMATIC(在上面的 Rocket 示例中使用)。

考虑到这一点,我们需要将 ASN.1 上面的类型更改为:

World-Schema DEFINITIONS IMPLICIT TAGS ::=
BEGIN
  Rocket ::= SEQUENCE
  {
     name      UTF8String (SIZE(1..16)),
     message   UTF8String DEFAULT "Hello World" ,
     fuel      ENUMERATED {solid, liquid, gas},
     speed     CHOICE
     {
        mph   [0] INTEGER,
        kmph  [1] INTEGER
     }  OPTIONAL,
     payload   SEQUENCE OF UTF8String
  }
END

注意 IMPLICIT TAGS 用于代替字段中的 AUTOMATIC TAGS[$n] 字符串 speed

如果你看一下这个模式,事实证明,这是,其实,暧昧,因为 mphkmph 都有 INTEGER 型。因此,如果我们 INTEGER 从字节流中读取了一个,它是 mph 值还是 kmph 值?如果我们谈论宇宙飞船,它会产生巨大的变化!

为了避免这种混淆,使用了特殊的标签,这里我们指定了我们想要的标签,因为与 AUTOMATIC 模式不同,IMPLICIT 它不适用于我们。

逐步建设。问题答案。

那么,我们可以用 Raku 中的所有功能做什么呢?虽然编译器可能很有趣,但是可以通过可扩展的方式编译成 Raku,并且包含了奇特的功能?必须有一些更简单的东西。

比方说,我们有一个适用于航天器的脚本。当然,我们需要一个类型来表示一个,特别是一个类,让我们称之为 Rocket

class  Rocket {}

当然,我们想知道一些有关它的数据:

class Rocket {
    has $.name;
    has $.message is default("Hello World");
    has $.fuel;
    has $.speed;
    has @.payload;
}

如果我们必须使我们的 Rocket 定义更明确,那么我们指定一些类型:

enum Fuel <Solid Liquid Gas>;

class Rocket {
    has Str $.name;
    has Str $.message is default("Hello World");
    has Fuel $.fuel;
    has $.speed;
    has Str @.payload;
}

现在它开始提醒我们一些事情……

  • Str 类似 UTF8String,只是我们不能离开它这样,因为 ASN.1我们不仅有 UTF8String,而且 BIT STRINGOCTET STRING 和其他字符串类型。
  • Fuel 枚举类似于 ENUMERATED 类型。
  • @.payload 中的 @ 符号告诉我们,这将是一个序列,而且 Str 指定其元素的类型。
  • 但是虽然有一些类似的观点,但从我们 ASN.1的观点来看,我们没有足够的数据。让我们一步一步解决这些问题!

我们怎么知道这完全Rocket是 ASN.1序列类型?

通过应用角色:class Rocket does ASNSequence

我们怎么知道确切的字段顺序?

通过实现此角色的存根方法:method ASN-order { <$!name $!message $!fuel $!speed @!payload> }

我们怎么知道这 $.speed 是可选的?

我们只是应用它的特征!Traits 允许我们在代码部分上执行自定义代码,特别是 Attributes。例如,虚构的API可以是这样的:has $.speed is optional

我们怎么知道 $.speed 是多少?

由于 CHOICE 类型是“特殊的”,但仍然是一流的(例如,你可以使它递归),我们需要在这里发挥作用:ASNChoice 来救援。

我们怎么知道 ASN.1我们的 Str 类型是什么类型的字符串?

我们来写吧 has Str $.name is UTF8String;

我们如何指定字段的默认值?

虽然 Raku 已经具有内置 is default 特性,但对我们来说不好的是我们无法“很好地”检测到它。因此,我们必须引入另一个自定义特征,以满足我们的目的并应用内置特征:has Str $.message is default-value("Hello World");

让我们在一个包中回答所有这些问题:

role ASNSequence { #`[ Elves Special Magic Truly Happens Here ] }

role ASNChoice { #`[ And even here ]  }

class SpeedChoice does ASNChoice {
    method ASN-choice() {
        # Description of: names, tags, types specificed by this CHOICE
        { mph => (0 => Int), kmph => (1 => Int) }
    }
}

class Rocket does ASNSequence {
    has Str $.name is UTF8String;
    has Str $.message is default-value("Hello World") is UTF8String;
    has Fuel $.fuel;
    has SpeedChoice $.speed is optional;
    has Str @.payload is UTF8String;

    method ASN-order { <$!name $!message $!fuel $!speed @!payload> }
}

值可能类似于:

my $rocket = Rocket.new(
    name => 'Falcon',
    fuel => Solid,
    speed => SpeedChoice.new((mph => 18000)),
    payload => [ "Car", "GPS" ]);

答案越多,问题就越多

对于这个微小的例子(另一方面,它已经 ASN.1展示了许多特性),实际上,我们需要在我们的应用程序中使用这个类的实例,并可能根据需要对其进行编码和解码。

那么精灵们对我们的数据秘密做了什么?让我们在下一篇文章中找到答案!

comments powered by Disqus