|
1 | | -============================ |
2 | | -Exercise: Protobuf Parsing |
3 | | -============================ |
4 | | - |
5 | | ------------------------------- |
6 | | -Protobuf Parsing Explanation |
7 | | ------------------------------- |
8 | | - |
9 | | -In this exercise, you will build a parser for the |
10 | | -:url:`protobuf binary encoding <https://protobuf.dev/programming-guides/encoding/>`. Don't |
11 | | -worry, it's simpler than it seems! This illustrates a common parsing |
12 | | -pattern, passing slices of data. The underlying data itself is never |
13 | | -copied. |
14 | | - |
15 | | -Fully parsing a protobuf message requires knowing the types of the |
16 | | -fields, indexed by their field numbers. That is typically provided in a |
17 | | -:rust:`proto` file. In this exercise, we'll encode that information into |
18 | | -:rust:`match` statements in functions that get called for each field. |
19 | | - |
20 | | -We'll use the following proto: |
21 | | - |
22 | | -.. code:: proto |
23 | | -
|
24 | | - message PhoneNumber { |
25 | | - optional string number = 1; |
26 | | - optional string type = 2; |
27 | | - } |
28 | | -
|
29 | | - message Person { |
30 | | - optional string name = 1; |
31 | | - optional int32 id = 2; |
32 | | - repeated PhoneNumber phones = 3; |
33 | | - } |
34 | | -
|
35 | | -
|
36 | | ------------------------------- |
37 | | -More Information on Protobuf |
38 | | ------------------------------- |
39 | | - |
40 | | -* Messages |
41 | | - |
42 | | - A proto message is encoded as a series of fields, one after the next. |
43 | | - Each is implemented as a :dfn:`tag` followed by the value. The tag contains a |
44 | | - field number (e.g., :rust:`2` for the :rust:`id` field of a :rust:`Person` message) |
45 | | - and a wire type defining how the payload should be determined from the |
46 | | - byte stream. These are combined into a single integer, as decoded in |
47 | | - :rust:`unpack_tag` below. |
48 | | - |
49 | | -* Varint |
50 | | - |
51 | | - Integers, including the tag, are represented with a variable-length |
52 | | - encoding called VARINT. Luckily, :rust:`parse_varint` is defined for you |
53 | | - below. |
54 | | - |
55 | | -* Wire Types |
56 | | - |
57 | | - Proto defines several wire types, only two of which are used in this |
58 | | - exercise. |
59 | | - |
60 | | - * The :rust:`Varint` wire type contains a single varint, and is used to encode proto values of type :rust:`int32` such as :rust:`Person.id`. |
61 | | - |
62 | | - * The :rust:`Len` wire type contains a length expressed as a varint, followed by a payload of that number of bytes. This is used to encode proto values of type :rust:`string` such as :rust:`Person.name`. It is also used to encode proto values containing sub-messages such as :rust:`Person.phones`, where the payload contains an encoding of the sub-message. |
63 | | - |
64 | | ----------------- |
65 | | -Protobuf Types |
66 | | ----------------- |
67 | | - |
68 | | -.. container:: source_include 160_lifetimes/src/160_lifetimes.rs :start-after://ANCHOR-types :end-before://ANCHOR-helpers :code:rust |
69 | | - |
70 | | ------------------- |
71 | | -Protobuf Helpers |
72 | | ------------------- |
73 | | - |
74 | | -.. container:: source_include 160_lifetimes/src/160_lifetimes.rs :start-after://ANCHOR-helpers :end-before://ANCHOR-parse_field_solution :code:rust |
| 1 | +===== |
| 2 | +Lab |
| 3 | +===== |
75 | 4 |
|
76 | 5 | ------------------ |
77 | | -Protobuf Problem |
| 6 | +Lab Instructions |
78 | 7 | ------------------ |
79 | 8 |
|
80 | | -The given code also defines callbacks to handle :rust:`Person` and |
81 | | -:rust:`PhoneNumber` fields, and to parse a message into a series of calls to |
82 | | -those callbacks. |
83 | | - |
84 | | -What remains for you is to implement the :rust:`parse_field` function and |
85 | | -the :rust:`ProtoMessage` trait for :rust:`Person` and :rust:`PhoneNumber`. |
86 | | - |
87 | | -.. code:: rust |
88 | | -
|
89 | | - /// Parse a field, returning the remaining bytes |
90 | | - fn parse_field(data: &[u8]) -> (Field, &[u8]) { |
91 | | - let (tag, remainder) = parse_varint(data); |
92 | | - let (field_num, wire_type) = unpack_tag(tag); |
93 | | - let (fieldvalue, remainder) = match wire_type { |
94 | | - _ => todo!("Based on the wire type, build a Field, consuming as many bytes as necessary.") |
95 | | - }; |
96 | | - todo!("Return the field, and any un-consumed bytes.") |
97 | | - } |
98 | | -
|
99 | | -.. container:: source_include 160_lifetimes/src/160_lifetimes.rs :start-after://ANCHOR-parse_message :end-before://ANCHOR-traits_solution :code:rust |
100 | | - |
101 | | -.. code:: rust |
102 | | -
|
103 | | - // TODO: Implement ProtoMessage for Person and PhoneNumber. |
104 | | -
|
105 | | ------------------------ |
106 | | -Protobuf Main Program |
107 | | ------------------------ |
| 9 | +- Solve for compilation errors |
108 | 10 |
|
109 | | -.. container:: source_include 160_lifetimes/src/160_lifetimes.rs :start-after://ANCHOR-main :code:rust |
| 11 | +- Follow the hints! |
110 | 12 |
|
111 | | --------------------- |
112 | | -Protobuf Solutions |
113 | | --------------------- |
| 13 | +- Success is |
114 | 14 |
|
115 | | -.. container:: source_include 160_lifetimes/src/160_lifetimes.rs :start-after://ANCHOR-parse_field_solution :end-before://ANCHOR-parse_message :code:rust |
| 15 | + - Code that compiles |
116 | 16 |
|
117 | | -.. container:: source_include 160_lifetimes/src/160_lifetimes.rs :start-after://ANCHOR-traits_solution :end-before://ANCHOR-main :code:rust |
| 17 | + - ...and that follows any behavior indicated within the hints! |
0 commit comments