// Message compression: the `grpc-encoding` a call's messages are compressed with, and
// `grpc-accept-encoding`, the list a peer says it will decode. gRPC names three:
// `identity` (none), `gzip` (RFC 1952) and `deflate` — which gRPC means as the raw
// DEFLATE stream of RFC 1951, without zlib's two-octet header, not as the zlib format.
///|
/// The `grpc-accept-encoding` value this library advertises, best first: the encodings a
/// peer may send us.
pub let accept_encodings : String = "identity,gzip,deflate"
///|
/// A message compression the gRPC protocol names.
pub(all) enum Encoding {
Identity
Gzip
Deflate
} derive(Eq, Debug)
///|
pub extend Encoding with Eq::{not_equal, equal}
///|
pub extend Encoding with Debug::{to_repr}
///|
/// The `grpc-encoding` name of this encoding.
pub fn Encoding::code(self : Encoding) -> String {
match self {
Identity => "identity"
Gzip => "gzip"
Deflate => "deflate"
}
}
///|
/// The encoding a `grpc-encoding` value names, or `None` for one we do not decode. An
/// empty value is `identity`, since a call that names no encoding is not compressed.
pub fn Encoding::of(name : BytesView) -> Encoding? {
if name.length() == 0 {
return Some(Identity)
}
if name == b"identity" {
Some(Identity)
} else if name == b"gzip" {
Some(Gzip)
} else if name == b"deflate" {
Some(Deflate)
} else {
None
}
}
///|
/// Compress `body` with this encoding. `identity` is the body itself.
pub fn Encoding::encode(self : Encoding, body : BytesView) -> Bytes {
match self {
Identity => body.to_owned()
Gzip => @gzip.gzip(body)
Deflate => @deflate.deflate(body)
}
}
///|
/// Decompress `body` with this encoding, or `None` when it does not decode as one — a
/// corrupt member, or one that expands past `limit`, which bounds what a compressed
/// message can cost us (gRPC's own decompression limit).
pub fn Encoding::decode(
self : Encoding,
body : BytesView,
limit~ : Int32 = max_message_size,
) -> Bytes? {
if body.length() > limit {
return None
}
match self {
Identity => Some(body.to_owned())
Gzip => Some(@gzip.gunzip(body, limit~)) catch { _ => None }
Deflate => Some(@deflate.inflate(body, limit~)) catch { _ => None }
}
}
///|
/// Whether a `grpc-accept-encoding` value — a comma-separated list, case-insensitive, with
/// optional spaces — names this encoding.
pub fn Encoding::accepted(self : Encoding, accept : BytesView) -> Bool {
let want = @utf8.encode(self.code())
let mut at = 0
while at < accept.length() {
let mut end = at
while end < accept.length() && accept[end] != b',' {
end = end + 1
}
let mut from = at
let mut to = end
while from < to && (accept[from] == b' ' || accept[from] == b'\t') {
from = from + 1
}
while to > from && (accept[to - 1] == b' ' || accept[to - 1] == b'\t') {
to = to - 1
}
if to - from == want.length() {
let mut same = true
for i = 0; i < want.length(); i = i + 1 {
if lower(accept[from + i]) != want[i] {
same = false
}
}
if same {
return true
}
}
at = end + 1
}
false
}
///|
/// Lower-case one ASCII octet, leaving everything else alone: a header value is
/// case-insensitive ASCII, and a byte above 0x7F is not part of an encoding name.
fn lower(b : Byte) -> Byte {
if b >= b'A' && b <= b'Z' {
(b.to_int() + 32).to_byte()
} else {
b
}
}
///|
/// What to compress a reply with, given what the server wants and what the caller said it
/// accepts: the server's encoding when the caller listed it, `identity` otherwise — never
/// a body the far end would have to guess at.
pub fn negotiated(server : Encoding, accept : BytesView) -> Encoding {
match server {
Identity => Identity
encoding => if encoding.accepted(accept) { encoding } else { Identity }
}
}