-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrpc.mbt
More file actions
188 lines (175 loc) · 5.79 KB
/
Copy pathrpc.mbt
File metadata and controls
188 lines (175 loc) · 5.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
///|
/// The default cap on a single received gRPC message (4 MiB, matching gRPC's default
/// `MaxRecvMsgSize`). A length prefix above this — including one whose 4 bytes decode
/// to a negative `Int` because the high bit is set — is rejected rather than trusted,
/// so a hostile prefix can neither slice out of bounds nor pin unbounded buffer.
pub let max_message_size : Int = 4 * 1024 * 1024
///|
/// The cap on one accumulated header block (HEADERS plus its CONTINUATION frames).
/// Without it a peer could stream endless non-final CONTINUATION frames and grow the
/// buffer without bound (a CONTINUATION flood); 128 KiB is far above any real gRPC
/// request's headers.
pub let max_header_list_size : Int = 128 * 1024
///|
/// Encode a payload as a gRPC *Length-Prefixed-Message*: a 1-byte compression
/// flag, a 4-byte big-endian length, then the payload. This is the framing every
/// gRPC transport shares (gRPC-Web over HTTP/1.1 and real gRPC over HTTP/2 alike).
pub fn encode_message(payload : Bytes, compressed? : Bool = false) -> Bytes {
let n = payload.length()
let buf = Buffer()
buf.write_byte((if compressed { 1 } else { 0 }).to_byte())
@fixed.write_u32(buf, n.reinterpret_as_uint())
buf.write_bytes(payload)
buf.to_bytes()
}
///|
/// Decode one gRPC length-prefixed message from the front of `data`, returning
/// `(compressed, payload)`, or `None` if fewer than a full frame is present.
pub fn decode_message(data : Bytes) -> (Bool, Bytes)? {
guard message_len(data[:]) is Some(len) else { return None }
// A high-bit-set prefix decodes to a negative `Int`; compare without recomputing
// `5 + len` (which would wrap) so a hostile length can't slice past the buffer.
if len < 0 || len > data.length() - 5 {
return None
}
Some((data[0].to_int() != 0, data[5:5 + len].to_owned()))
}
///|
/// The payload length of the length-prefixed message at `at`, or `None` when fewer
/// than the five octets of a prefix are there.
///
/// A prefix with its high bit set reads back negative, which is how a hostile length
/// is told from a real one before anything is sliced on it. Every reader of this
/// framing goes through here — there are five of them across the client, the server
/// and the socket transport, and they have to agree.
pub fn message_len(data : BytesView, at? : Int = 0) -> Int? {
@fixed.read_u32(data, at=at + 1).map(v => v.reinterpret_as_int())
}
///|
/// Split a run of concatenated length-prefixed messages into their payloads, stopping
/// at the first one that has not fully arrived.
pub fn split_messages(data : Bytes) -> Array[Bytes] {
let out : Array[Bytes] = []
let mut off = 0
while message_len(data[:], at=off) is Some(len) {
if len < 0 || data.length() - off < 5 + len {
break
}
out.push(data[off + 5:off + 5 + len].to_owned())
off = off + 5 + len
}
out
}
///|
/// The 17 canonical gRPC status codes (`grpc-status`).
pub(all) enum Status {
Ok
Cancelled
Unknown
InvalidArgument
DeadlineExceeded
NotFound
AlreadyExists
PermissionDenied
ResourceExhausted
FailedPrecondition
Aborted
OutOfRange
Unimplemented
Internal
Unavailable
DataLoss
Unauthenticated
} derive(Eq)
///|
/// The numeric `grpc-status` code.
pub fn Status::code(self : Status) -> Int {
match self {
Ok => 0
Cancelled => 1
Unknown => 2
InvalidArgument => 3
DeadlineExceeded => 4
NotFound => 5
AlreadyExists => 6
PermissionDenied => 7
ResourceExhausted => 8
FailedPrecondition => 9
Aborted => 10
OutOfRange => 11
Unimplemented => 12
Internal => 13
Unavailable => 14
DataLoss => 15
Unauthenticated => 16
}
}
///|
/// The canonical uppercase status name.
pub fn Status::name(self : Status) -> String {
match self {
Ok => "OK"
Cancelled => "CANCELLED"
Unknown => "UNKNOWN"
InvalidArgument => "INVALID_ARGUMENT"
DeadlineExceeded => "DEADLINE_EXCEEDED"
NotFound => "NOT_FOUND"
AlreadyExists => "ALREADY_EXISTS"
PermissionDenied => "PERMISSION_DENIED"
ResourceExhausted => "RESOURCE_EXHAUSTED"
FailedPrecondition => "FAILED_PRECONDITION"
Aborted => "ABORTED"
OutOfRange => "OUT_OF_RANGE"
Unimplemented => "UNIMPLEMENTED"
Internal => "INTERNAL"
Unavailable => "UNAVAILABLE"
DataLoss => "DATA_LOSS"
Unauthenticated => "UNAUTHENTICATED"
}
}
///|
/// The status a call ends with when the peer resets its stream instead of sending
/// trailers, from the RST_STREAM error code (gRPC PROTOCOL-HTTP2, "HTTP2 Error Code →
/// Status"). A refused stream was never processed, so it is UNAVAILABLE and safe to
/// retry; anything the table does not name is INTERNAL, since the peer broke the
/// transport rather than the call.
pub fn Status::from_h2_error(code : Int) -> Status {
if code == @http2.error_refused_stream {
Unavailable
} else if code == @http2.error_enhance_your_calm {
ResourceExhausted
} else if code == @http2.error_inadequate_security {
PermissionDenied
} else if code == @http2.error_cancel {
Cancelled
} else {
Internal
}
}
///|
/// The status a non-200 HTTP response maps to when it carries no `grpc-status` — a
/// proxy or a plain HTTP server answering on the gRPC port (gRPC
/// http-grpc-status-mapping). Anything outside the table is UNKNOWN.
pub fn Status::from_http(code : Int) -> Status {
match code {
400 => Internal
401 => Unauthenticated
403 => PermissionDenied
404 => Unimplemented
429 | 502 | 503 | 504 => Unavailable
_ => Unknown
}
}
///|
/// A fully-qualified RPC method: `package.Service` and the method name.
pub(all) struct Method {
service : String
name : String
}
///|
/// The gRPC HTTP/2 `:path`, i.e. `/package.Service/Method`.
pub fn Method::path(self : Method) -> String {
"/" + self.service + "/" + self.name
}
///|
pub extend Status with Eq::{not_equal, equal}