コード例 #1
0
def test_array_aggregate_invalid_element_type() -> None:
    inner = Message(
        "P.I",
        [Link(INITIAL, Field("F")),
         Link(Field("F"), FINAL)],
        {Field("F"): MODULAR_INTEGER},
    )
    array_type = Array("P.Array", inner)

    f = Field("F")
    with pytest.raises(
            RecordFluxError,
            match=r"^<stdin>:90:10: model: error: invalid array element type"
            ' "P.I" for aggregate comparison$',
    ):
        Message(
            "P.M",
            [
                Link(INITIAL, f, length=Number(18)),
                Link(
                    f,
                    FINAL,
                    condition=Equal(
                        Variable("F"),
                        Aggregate(Number(1), Number(2), Number(64)),
                        Location((90, 10)),
                    ),
                ),
            ],
            {Field("F"): array_type},
        )
コード例 #2
0
def test_message_copy() -> None:
    message = Message(
        "P.M",
        [Link(INITIAL, Field("F")),
         Link(Field("F"), FINAL)],
        {Field("F"): MODULAR_INTEGER},
    )
    assert_equal(
        message.copy(identifier="A.B"),
        Message(
            "A.B",
            [Link(INITIAL, Field("F")),
             Link(Field("F"), FINAL)],
            {Field("F"): MODULAR_INTEGER},
        ),
    )
    assert_equal(
        message.copy(
            structure=[Link(INITIAL, Field("C")),
                       Link(Field("C"), FINAL)],
            types={Field("C"): RANGE_INTEGER},
        ),
        Message(
            "P.M",
            [Link(INITIAL, Field("C")),
             Link(Field("C"), FINAL)],
            {Field("C"): RANGE_INTEGER},
        ),
    )
コード例 #3
0
ファイル: test_parser.py プロジェクト: kug1977/RecordFlux
def test_message_type_message() -> None:
    simple_structure = [
        Link(INITIAL, Field("Bar")),
        Link(Field("Bar"), Field("Baz")),
        Link(Field("Baz"), FINAL),
    ]

    simple_types = {
        Field("Bar"): ModularInteger("Message_Type.T", Number(256)),
        Field("Baz"): ModularInteger("Message_Type.T", Number(256)),
    }

    simple_message = Message("Message_Type.Simple_PDU", simple_structure, simple_types)

    structure = [
        Link(INITIAL, Field("Foo")),
        Link(Field("Foo"), Field("Bar"), LessEqual(Variable("Foo"), Number(30, 16))),
        Link(Field("Foo"), Field("Baz"), Greater(Variable("Foo"), Number(30, 16))),
        Link(Field("Bar"), Field("Baz")),
        Link(Field("Baz"), FINAL),
    ]

    types = {
        **simple_types,
        **{Field("Foo"): ModularInteger("Message_Type.T", Number(256))},
    }

    message = Message("Message_Type.PDU", structure, types)

    empty_message = Message("Message_Type.Empty_PDU", [], {})

    assert_messages_files(
        [f"{TESTDIR}/message_type.rflx"], [message, simple_message, empty_message]
    )
コード例 #4
0
ファイル: test_parser.py プロジェクト: kug1977/RecordFlux
def test_type_derivation_refinements() -> None:
    message_foo = Message(
        "Test.Foo",
        [Link(INITIAL, Field("Baz"), length=Number(48)), Link(Field("Baz"), FINAL)],
        {Field("Baz"): Opaque()},
    )
    message_bar = DerivedMessage("Test.Bar", message_foo)

    assert_refinements_string(
        """
            package Test is
               type Foo is
                  message
                     null
                        then Baz
                           with Length => 48;
                     Baz : Opaque;
                  end message;
               for Foo use (Baz => Foo);
               type Bar is new Foo;
               for Bar use (Baz => Bar);
            end Test;
        """,
        [
            Refinement("Test", message_foo, Field("Baz"), message_foo),
            Refinement("Test", message_bar, Field("Baz"), message_bar),
        ],
    )
コード例 #5
0
ファイル: model_test.py プロジェクト: Componolit/RecordFlux
def test_write_specification_files_missing_deps(tmp_path: Path) -> None:
    s = ModularInteger("P::S", Number(65536))
    t = ModularInteger("P::T", Number(256))
    v = mty.Sequence("P::V", element_type=t)
    m = Message("P::M", [Link(INITIAL, Field("Foo")), Link(Field("Foo"), FINAL)], {Field("Foo"): t})
    Model([s, v, m]).write_specification_files(tmp_path)
    expected_path = tmp_path / Path("p.rflx")
    assert list(tmp_path.glob("*.rflx")) == [expected_path]
    assert expected_path.read_text() == textwrap.dedent(
        """\
        package P is

           type S is mod 65536;

           type T is mod 256;

           type V is sequence of P::T;

           type M is
              message
                 Foo : P::T;
              end message;

        end P;"""
    )
コード例 #6
0
ファイル: models.py プロジェクト: senier/RecordFlux
def create_tlv_message() -> Message:
    tag_type = Enumeration("TLV.Tag", {
        "Msg_Data": Number(1),
        "Msg_Error": Number(3)
    }, Number(2), False)
    length_type = ModularInteger("TLV.Length", Pow(Number(2), Number(14)))

    structure = [
        Link(INITIAL, Field("Tag")),
        Link(Field("Tag"), Field("Length"),
             Equal(Variable("Tag"), Variable("Msg_Data"))),
        Link(Field("Tag"), FINAL, Equal(Variable("Tag"),
                                        Variable("Msg_Error"))),
        Link(Field("Length"),
             Field("Value"),
             length=Mul(Variable("Length"), Number(8))),
        Link(Field("Value"), FINAL),
    ]

    types = {
        Field("Tag"): tag_type,
        Field("Length"): length_type,
        Field("Value"): Payload()
    }

    return Message("TLV.Message", structure, types)
コード例 #7
0
def test_valid_use_message_length() -> None:
    structure = [
        Link(INITIAL, Field("Verify_Data"), length=Length("Message")),
        Link(Field("Verify_Data"), FINAL),
    ]
    types = {Field("Verify_Data"): Opaque()}
    Message("P.M", structure, types)
コード例 #8
0
def test_dot_graph_with_condition() -> None:
    f_type = ModularInteger("P.T", Pow(Number(2), Number(32)))
    m = Message(
        "P.M",
        structure=[
            Link(INITIAL, Field("F1")),
            Link(Field("F1"), FINAL, Greater(Variable("F1"), Number(100))),
        ],
        types={Field("F1"): f_type},
    )
    expected = """
        digraph "P.M" {
            graph [ranksep="0.8 equally", splines=ortho];
            edge [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code"];
            node [color="#6f6f6f", fillcolor="#009641", fontcolor="#ffffff", fontname=Arimo,
                  shape=box, style="rounded,filled", width="1.5"];
            Initial [fillcolor="#ffffff", label="", shape=circle, width="0.5"];
            F1;
            Initial -> F1 [xlabel="(⊤, 32, ⋆)"];
            F1 -> Final [xlabel="(F1 > 100, 0, ⋆)"];
            Final [fillcolor="#6f6f6f", label="", shape=circle, width="0.5"];
        }
        """

    assert_graph(Graph(m), expected)
コード例 #9
0
def test_no_contradiction_multi() -> None:
    structure = [
        Link(INITIAL, Field("F0")),
        Link(Field("F0"),
             Field("F1"),
             condition=Equal(Variable("F0"), Number(1))),
        Link(Field("F0"),
             Field("F2"),
             condition=Equal(Variable("F0"), Number(2))),
        Link(Field("F1"), Field("F3")),
        Link(Field("F2"), Field("F3")),
        Link(Field("F3"),
             Field("F4"),
             condition=Equal(Variable("F0"), Number(1))),
        Link(Field("F3"),
             Field("F5"),
             condition=Equal(Variable("F0"), Number(2))),
        Link(Field("F4"), FINAL),
        Link(Field("F5"), FINAL),
    ]
    types = {
        Field("F0"): RANGE_INTEGER,
        Field("F1"): RANGE_INTEGER,
        Field("F2"): RANGE_INTEGER,
        Field("F3"): RANGE_INTEGER,
        Field("F4"): RANGE_INTEGER,
        Field("F5"): RANGE_INTEGER,
    }
    Message("P.M", structure, types)
コード例 #10
0
    def test_field_coverage_2(self) -> None:
        foo_type = ModularInteger("P.Foo", Pow(Number(2), Number(32)))
        structure = [
            Link(INITIAL, Field("F1")),
            Link(Field("F1"), Field("F2")),
            Link(Field("F2"), Field("F4"), Greater(Variable("F1"),
                                                   Number(100))),
            Link(
                Field("F2"),
                Field("F3"),
                LessEqual(Variable("F1"), Number(100)),
                first=Add(Last("F2"), Number(64)),
            ),
            Link(Field("F3"), Field("F4")),
            Link(Field("F4"), FINAL),
        ]

        types = {
            Field("F1"): foo_type,
            Field("F2"): foo_type,
            Field("F3"): foo_type,
            Field("F4"): foo_type,
        }
        with mock.patch("rflx.model.Message._Message__verify_conditions",
                        lambda x: None):
            with self.assertRaisesRegex(
                    ModelError,
                    "^path F1 -> F2 -> F3 -> F4 does not cover whole message"):
                Message("P.M", structure, types)
コード例 #11
0
def test_dot_graph(tmp_path: Path) -> None:
    f_type = ModularInteger("P::T", Pow(Number(2), Number(32)))
    m = Message(
        "P::M",
        structure=[Link(INITIAL, Field("X")),
                   Link(Field("X"), FINAL)],
        types={Field("X"): f_type},
    )
    expected = """
        digraph "P::M" {
            graph [bgcolor="#00000000", pad="0.1", ranksep="0.1 equally", splines=true,
                   truecolor=true];
            edge [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code", penwidth="2.5"];
            node [color="#6f6f6f", fillcolor="#009641", fontcolor="#ffffff", fontname=Arimo,
                  shape=box, style="rounded,filled", width="1.5"];
            Initial [fillcolor="#ffffff", label="", shape=circle, width="0.5"];
            X;
            intermediate_0 [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code", height=0,
                            label="(⊤, 32, ⋆)", penwidth=0, style="", width=0];
            Initial -> intermediate_0 [arrowhead=none];
            intermediate_0 -> X [minlen=1];
            intermediate_1 [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code", height=0,
                            label="(⊤, 0, ⋆)", penwidth=0, style="", width=0];
            X -> intermediate_1 [arrowhead=none];
            intermediate_1 -> Final [minlen=1];
            Final [fillcolor="#6f6f6f", label="", shape=circle, width="0.5"];
        }
        """

    assert_graph(create_message_graph(m), expected, tmp_path)
コード例 #12
0
ファイル: models.py プロジェクト: senier/RecordFlux
def create_array_message() -> Message:
    length_type = ModularInteger("Arrays.Length", Pow(Number(2), Number(8)))

    modular_type = ModularInteger("Arrays.Modular_Integer",
                                  Pow(Number(2), Number(16)))
    modular_vector_type = Array("Arrays.Modular_Vector", modular_type)

    range_type = RangeInteger("Arrays.Range_Integer", Number(1), Number(100),
                              Number(8))
    range_vector_type = Array("Arrays.Range_Vector", range_type)

    enum_type = Enumeration(
        "Arrays.Enumeration",
        {
            "ZERO": Number(0),
            "ONE": Number(1),
            "TWO": Number(2)
        },
        Number(8),
        False,
    )
    enum_vector_type = Array("Arrays.Enumeration_Vector", enum_type)

    av_enum_type = Enumeration(
        "Arrays.AV_Enumeration",
        {
            "AV_ZERO": Number(0),
            "AV_ONE": Number(1),
            "AV_TWO": Number(2)
        },
        Number(8),
        True,
    )
    av_enum_vector_type = Array("Arrays.AV_Enumeration_Vector", av_enum_type)

    structure = [
        Link(INITIAL, Field("Length")),
        Link(Field("Length"),
             Field("Modular_Vector"),
             length=Mul(Variable("Length"), Number(8))),
        Link(Field("Modular_Vector"), Field("Range_Vector"),
             length=Number(16)),
        Link(Field("Range_Vector"),
             Field("Enumeration_Vector"),
             length=Number(16)),
        Link(Field("Enumeration_Vector"),
             Field("AV_Enumeration_Vector"),
             length=Number(16)),
        Link(Field("AV_Enumeration_Vector"), FINAL),
    ]

    types = {
        Field("Length"): length_type,
        Field("Modular_Vector"): modular_vector_type,
        Field("Range_Vector"): range_vector_type,
        Field("Enumeration_Vector"): enum_vector_type,
        Field("AV_Enumeration_Vector"): av_enum_vector_type,
    }

    return Message("Arrays.Message", structure, types)
コード例 #13
0
def test_message_incorrect_name() -> None:
    with pytest.raises(
            RecordFluxError,
            match=
            '^<stdin>:10:8: model: error: unexpected format of type name "M"$'
    ):
        Message("M", [], {}, Location((10, 8)))
コード例 #14
0
def test_array_aggregate_out_of_range() -> None:
    array_type = Array("P.Array", ModularInteger("P.Element", Number(64)))

    f = Field("F")
    with pytest.raises(
            RecordFluxError,
            match=
            r"^<stdin>:44:3: model: error: aggregate element out of range 0 .. 63",
    ):
        Message(
            "P.M",
            [
                Link(INITIAL, f, length=Number(18)),
                Link(
                    f,
                    FINAL,
                    condition=Equal(
                        Variable("F"),
                        Aggregate(Number(1), Number(2),
                                  Number(64, location=Location((44, 3)))),
                    ),
                ),
            ],
            {Field("F"): array_type},
        )
コード例 #15
0
def test_opaque_not_byte_aligned_dynamic() -> None:
    with pytest.raises(
            RecordFluxError,
            match=
            r'^<stdin>:44:3: model: error: opaque field "O2" not aligned to'
            r" 8 bit boundary [(]L1 -> O1 -> L2 -> O2[)]",
    ):
        o2 = Field(ID("O2", location=Location((44, 3))))
        Message(
            "P.M",
            [
                Link(INITIAL, Field("L1")),
                Link(
                    Field("L1"),
                    Field("O1"),
                    length=Variable("L1"),
                    condition=Equal(Mod(Variable("L1"), Number(8)), Number(0)),
                ),
                Link(Field("O1"), Field("L2")),
                Link(Field("L2"), o2, length=Number(128)),
                Link(o2, FINAL),
            ],
            {
                Field("L1"): MODULAR_INTEGER,
                Field("L2"): ModularInteger("P.T", Number(4)),
                Field("O1"): Opaque(),
                o2: Opaque(),
            },
        )
コード例 #16
0
def test_graph_object() -> None:
    f_type = ModularInteger("P::T", Pow(Number(2), Number(32)))
    m = Message(
        "P::M",
        structure=[Link(INITIAL, Field("X")),
                   Link(Field("X"), FINAL)],
        types={Field("X"): f_type},
    )
    g = create_message_graph(m)
    assert [(e.get_source(), e.get_destination()) for e in g.get_edges()] == [
        ("Initial", "intermediate_0"),
        ("intermediate_0", "X"),
        ("X", "intermediate_1"),
        ("intermediate_1", "Final"),
    ]
    assert [n.get_name() for n in g.get_nodes()] == [
        "graph",
        "edge",
        "node",
        "Initial",
        "X",
        "intermediate_0",
        "intermediate_1",
        "Final",
    ]
コード例 #17
0
def test_derived_message_incorrect_base_name() -> None:
    with pytest.raises(
            RecordFluxError,
            match=
            '^<stdin>:40:8: model: error: unexpected format of type name "M"$'
    ):
        DerivedMessage("P.M", Message("M", [], {}, location=Location((40, 8))))
コード例 #18
0
def test_message_proven() -> None:
    message = Message(
        "P.M",
        [Link(INITIAL, Field("F")),
         Link(Field("F"), FINAL)],
        {Field("F"): MODULAR_INTEGER},
    )
    assert message.proven() == message
コード例 #19
0
def test_invalid_message_field_type() -> None:
    with pytest.raises(AssertionError, match=r"rflx/model.py"):
        Message(
            "P.M",
            [Link(INITIAL, Field("F")),
             Link(Field("F"), FINAL)],
            {Field("F"): NewType("T")},
        )
コード例 #20
0
ファイル: test_model.py プロジェクト: senier/RecordFlux
    def test_message_missing_type(self) -> None:
        structure = [
            Link(INITIAL, Field("X")),
            Link(Field("X"), FINAL),
        ]

        with self.assertRaisesRegex(ModelError, '^missing type for field "X" of "P.M"$'):
            Message("P.M", structure, {})
コード例 #21
0
ファイル: test_parser.py プロジェクト: kug1977/RecordFlux
def test_message_in_message() -> None:
    length = ModularInteger("Message_In_Message.Length", Pow(Number(2), Number(16)))

    length_value = Message(
        "Message_In_Message.Length_Value",
        [
            Link(INITIAL, Field("Length")),
            Link(Field("Length"), Field("Value"), length=Mul(Number(8), Variable("Length"))),
            Link(Field("Value"), FINAL),
        ],
        {Field("Length"): length, Field("Value"): Opaque()},
    )

    derived_length_value = DerivedMessage("Message_In_Message.Derived_Length_Value", length_value)

    message = Message(
        "Message_In_Message.Message",
        [
            Link(INITIAL, Field("Foo_Length")),
            Link(Field("Foo_Value"), Field("Bar_Length")),
            Link(Field("Bar_Value"), FINAL),
            Link(
                Field("Foo_Length"),
                Field("Foo_Value"),
                length=Mul(Variable("Foo_Length"), Number(8)),
            ),
            Link(
                Field("Bar_Length"),
                Field("Bar_Value"),
                length=Mul(Variable("Bar_Length"), Number(8)),
            ),
        ],
        {
            Field("Foo_Length"): length,
            Field("Foo_Value"): Opaque(),
            Field("Bar_Length"): length,
            Field("Bar_Value"): Opaque(),
        },
    )

    derived_message = DerivedMessage("Message_In_Message.Derived_Message", message)

    assert_messages_files(
        [f"{TESTDIR}/message_in_message.rflx"],
        [length_value, derived_length_value, message, derived_message],
    )
コード例 #22
0
def test_tlv_message_with_not_operator_exhausting() -> None:
    message = Message(
        "TLV::Message_With_Not_Operator_Exhausting",
        [
            Link(INITIAL, Field("Tag")),
            Link(
                Field("Tag"),
                Field("Length"),
                Not(Not(Not(NotEqual(Variable("Tag"), Variable("Msg_Data"))))),
            ),
            Link(
                Field("Tag"),
                FINAL,
                reduce(
                    lambda acc, f: f(acc),
                    [Not, Not] * 16,
                    Not(
                        Or(
                            Not(
                                Not(
                                    Equal(Variable("Tag"),
                                          Variable("Msg_Data")))),
                            Not(Equal(Variable("Tag"), Variable("Msg_Error"))),
                        )),
                ),
            ),
            Link(Field("Length"),
                 Field("Value"),
                 size=Mul(Variable("Length"), Number(8))),
            Link(Field("Value"), FINAL),
        ],
        {
            Field("Tag"): TLV_TAG,
            Field("Length"): TLV_LENGTH,
            Field("Value"): OPAQUE
        },
    )

    with pytest.raises(
            FatalError,
            match=re.escape(
                "failed to simplify complex expression `not (not (not (not "
                "(not (not (not (not (not (not (not (not (not (not (not (not "
                "(not (not (not (not (not (not (not (not (not (not (not (not "
                "(not (not (not (not (not (not (not (Tag = TLV::Msg_Data))\n"
                "                                 "
                "or not (Tag = TLV::Msg_Error))))))))))))))))))))))))))))))))))` "
                "after `16` iterations, best effort: "
                "`not (not (not (not (not (not (not (not (not (not (not (not (not "
                "(not (not (not (not (Tag = TLV::Msg_Data\n"
                "                 or Tag /= TLV::Msg_Error)))))))))))))))))`"),
    ):
        model = PyRFLX(model=Model([TLV_TAG, TLV_LENGTH, message]))
        pkg = model.package("TLV")
        msg = pkg.new_message("Message_With_Not_Operator_Exhausting")
        test_bytes = b"\x01\x00\x04\x00\x00\x00\x00"
        msg.parse(test_bytes)
コード例 #23
0
ファイル: models.py プロジェクト: senier/RecordFlux
def create_expression_message() -> Message:
    structure = [
        Link(INITIAL, Field("Payload"), length=Number(16)),
        Link(Field("Payload"), FINAL,
             Equal(Variable("Payload"), Aggregate(Number(1), Number(2)))),
    ]

    types = {Field("Payload"): Payload()}

    return Message("Expression.Message", structure, types)
コード例 #24
0
def test_valid_first() -> None:
    structure = [
        Link(INITIAL, Field("F1")),
        Link(Field("F1"), Field("F2"), first=First("F1")),
        Link(Field("F2"), FINAL),
    ]
    types = {
        Field("F1"): MODULAR_INTEGER,
        Field("F2"): MODULAR_INTEGER,
    }
    Message("P.M", structure, types)
コード例 #25
0
def test_valid_length_reference() -> None:
    structure = [
        Link(INITIAL, Field("F1")),
        Link(Field("F1"), Field("F2"), length=Mul(Number(8), Variable("F1"))),
        Link(Field("F2"), FINAL),
    ]
    types = {
        Field("F1"): MODULAR_INTEGER,
        Field("F2"): Opaque(),
    }
    Message("P.M", structure, types)
コード例 #26
0
def test_valid_use_message_first_last() -> None:
    structure = [
        Link(
            INITIAL,
            Field("Verify_Data"),
            length=Add(Sub(Last("Message"), First("Message")), Number(1)),
        ),
        Link(Field("Verify_Data"), FINAL),
    ]
    types = {Field("Verify_Data"): Opaque()}
    Message("P.M", structure, types)
コード例 #27
0
def test_refinement_invalid_field_type() -> None:
    x = Field(ID("X", Location((20, 10))))

    message = Message("P.M",
                      [Link(INITIAL, x), Link(x, FINAL)], {x: MODULAR_INTEGER})

    assert_type_error(
        Refinement("P", message, Field(ID("X", Location((33, 22)))), message),
        r'^<stdin>:33:22: model: error: invalid type of field "X" in refinement of "P.M"\n'
        r"<stdin>:20:10: model: info: expected field of type Opaque",
    )
コード例 #28
0
ファイル: test_model.py プロジェクト: senier/RecordFlux
    def test_message_superfluous_type(self) -> None:
        t = ModularInteger("P.T", Number(2))

        structure = [
            Link(INITIAL, Field("X")),
            Link(Field("X"), FINAL),
        ]

        types = {Field("X"): t, Field("Y"): t}

        with self.assertRaisesRegex(ModelError, '^superfluous field "Y" in field types of "P.M"$'):
            Message("P.M", structure, types)
コード例 #29
0
ファイル: model_test.py プロジェクト: Componolit/RecordFlux
def test_write_specification_file_multiple_packages_missing_deps(tmp_path: Path) -> None:
    t = ModularInteger("P::T", Number(256))
    u = mty.Sequence("R::U", element_type=t)
    u1 = mty.Sequence("Q::U1", element_type=t)
    v = ModularInteger("R::V", Number(65536))
    links = [
        Link(INITIAL, Field("Victor")),
        Link(Field("Victor"), Field("Uniform")),
        Link(Field("Uniform"), FINAL),
    ]
    fields = {Field("Victor"): v, Field("Uniform"): u}
    m = Message("R::M", links, fields)
    Model([u1, m, u, v]).write_specification_files(tmp_path)
    p_path, q_path, r_path = (tmp_path / Path(pkg + ".rflx") for pkg in ("p", "q", "r"))
    assert set(tmp_path.glob("*.rflx")) == {p_path, q_path, r_path}
    assert p_path.read_text() == textwrap.dedent(
        """\
        package P is

           type T is mod 256;

        end P;"""
    )
    assert q_path.read_text() == textwrap.dedent(
        """\
        with P;

        package Q is

           type U1 is sequence of P::T;

        end Q;"""
    )
    assert r_path.read_text() == textwrap.dedent(
        """\
        with P;

        package R is

           type V is mod 65536;

           type U is sequence of P::T;

           type M is
              message
                 Victor : R::V
                    then Uniform
                       with Size => Message'Last - Victor'Last;
                 Uniform : R::U;
              end message;

        end R;"""
    )
コード例 #30
0
def test_opaque_length_not_multiple_of_8() -> None:
    with pytest.raises(
            RecordFluxError,
            match=r'^<stdin>:44:3: model: error: length of opaque field "O"'
            " not multiple of 8 bit [(]O[)]",
    ):
        o = Field(ID("O", location=Location((44, 3))))
        Message(
            "P.M",
            [Link(INITIAL, o, length=Number(68)),
             Link(o, FINAL)],
            {o: Opaque()},
        )