structstd.crypto.tls.Decoder[src]

An abstraction to ensure that protocol-parsing code does not perform an out-of-bounds read.

Fields

buf: []u8
idx: usize = 0

Points to the next byte in buffer that will be decoded.

our_end: usize = 0

Up to this point in buf we have already checked that cap is greater than it.

their_end: usize = 0

Beyond this point in buf is extra tag-along bytes beyond the amount we requested with readAtLeast.

cap: usize = 0

Points to the end within buffer that has been filled. Beyond this point in buf is undefined bytes.

disable_reads: bool = false

Debug helper to prevent illegal calls to read functions.

Functions

FunctionfromTheirSlice[src]

pub fn fromTheirSlice(buf: []u8) Decoder

Parameters

buf: []u8

Source Code

Source code
pub fn fromTheirSlice(buf: []u8) Decoder {
    return .{
        .buf = buf,
        .their_end = buf.len,
        .cap = buf.len,
        .disable_reads = true,
    };
}

FunctionreadAtLeast[src]

pub fn readAtLeast(d: *Decoder, stream: anytype, their_amt: usize) !void

Use this function to increase their_end.

Parameters

their_amt: usize

Source Code

Source code
pub fn readAtLeast(d: *Decoder, stream: anytype, their_amt: usize) !void {
    assert(!d.disable_reads);
    const existing_amt = d.cap - d.idx;
    d.their_end = d.idx + their_amt;
    if (their_amt <= existing_amt) return;
    const request_amt = their_amt - existing_amt;
    const dest = d.buf[d.cap..];
    if (request_amt > dest.len) return error.TlsRecordOverflow;
    const actual_amt = try stream.readAtLeast(dest, request_amt);
    if (actual_amt < request_amt) return error.TlsConnectionTruncated;
    d.cap += actual_amt;
}

FunctionreadAtLeastOurAmt[src]

pub fn readAtLeastOurAmt(d: *Decoder, stream: anytype, our_amt: usize) !void

Same as readAtLeast but also increases our_end by exactly our_amt. Use when our_amt is calculated by us, not by them.

Parameters

our_amt: usize

Source Code

Source code
pub fn readAtLeastOurAmt(d: *Decoder, stream: anytype, our_amt: usize) !void {
    assert(!d.disable_reads);
    try readAtLeast(d, stream, our_amt);
    d.our_end = d.idx + our_amt;
}

Functionensure[src]

pub fn ensure(d: *Decoder, amt: usize) !void

Use this function to increase our_end. This should always be called with an amount provided by us, not them.

Parameters

amt: usize

Source Code

Source code
pub fn ensure(d: *Decoder, amt: usize) !void {
    d.our_end = @max(d.idx + amt, d.our_end);
    if (d.our_end > d.their_end) return error.TlsDecodeError;
}

Functiondecode[src]

pub fn decode(d: *Decoder, comptime T: type) T

Use this function to increase idx.

Parameters

T: type

Source Code

Source code
pub fn decode(d: *Decoder, comptime T: type) T {
    switch (@typeInfo(T)) {
        .int => |info| switch (info.bits) {
            8 => {
                skip(d, 1);
                return d.buf[d.idx - 1];
            },
            16 => {
                skip(d, 2);
                const b0: u16 = d.buf[d.idx - 2];
                const b1: u16 = d.buf[d.idx - 1];
                return (b0 << 8) | b1;
            },
            24 => {
                skip(d, 3);
                const b0: u24 = d.buf[d.idx - 3];
                const b1: u24 = d.buf[d.idx - 2];
                const b2: u24 = d.buf[d.idx - 1];
                return (b0 << 16) | (b1 << 8) | b2;
            },
            else => @compileError("unsupported int type: " ++ @typeName(T)),
        },
        .@"enum" => |info| {
            if (info.is_exhaustive) @compileError("exhaustive enum cannot be used");
            return @enumFromInt(d.decode(info.tag_type));
        },
        else => @compileError("unsupported type: " ++ @typeName(T)),
    }
}

Functionarray[src]

pub fn array(d: *Decoder, comptime len: usize) *[len]u8

Use this function to increase idx.

Parameters

len: usize

Source Code

Source code
pub fn array(d: *Decoder, comptime len: usize) *[len]u8 {
    skip(d, len);
    return d.buf[d.idx - len ..][0..len];
}

Functionslice[src]

pub fn slice(d: *Decoder, len: usize) []u8

Use this function to increase idx.

Parameters

len: usize

Source Code

Source code
pub fn slice(d: *Decoder, len: usize) []u8 {
    skip(d, len);
    return d.buf[d.idx - len ..][0..len];
}

Functionskip[src]

pub fn skip(d: *Decoder, amt: usize) void

Use this function to increase idx.

Parameters

amt: usize

Source Code

Source code
pub fn skip(d: *Decoder, amt: usize) void {
    d.idx += amt;
    assert(d.idx <= d.our_end); // insufficient ensured bytes
}

Functioneof[src]

pub fn eof(d: Decoder) bool

Parameters

Source Code

Source code
pub fn eof(d: Decoder) bool {
    assert(d.our_end <= d.their_end);
    assert(d.idx <= d.our_end);
    return d.idx == d.their_end;
}

Functionsub[src]

pub fn sub(d: *Decoder, their_len: usize) !Decoder

Provide the length they claim, and receive a sub-decoder specific to that slice. The parent decoder is advanced to the end.

Parameters

their_len: usize

Source Code

Source code
pub fn sub(d: *Decoder, their_len: usize) !Decoder {
    const end = d.idx + their_len;
    if (end > d.their_end) return error.TlsDecodeError;
    const sub_buf = d.buf[d.idx..end];
    d.idx = end;
    d.our_end = end;
    return fromTheirSlice(sub_buf);
}

Functionrest[src]

pub fn rest(d: Decoder) []u8

Parameters

Source Code

Source code
pub fn rest(d: Decoder) []u8 {
    return d.buf[d.idx..d.cap];
}

Source Code

Source code
pub const Decoder = struct {
    buf: []u8,
    /// Points to the next byte in buffer that will be decoded.
    idx: usize = 0,
    /// Up to this point in `buf` we have already checked that `cap` is greater than it.
    our_end: usize = 0,
    /// Beyond this point in `buf` is extra tag-along bytes beyond the amount we
    /// requested with `readAtLeast`.
    their_end: usize = 0,
    /// Points to the end within buffer that has been filled. Beyond this point
    /// in buf is undefined bytes.
    cap: usize = 0,
    /// Debug helper to prevent illegal calls to read functions.
    disable_reads: bool = false,

    pub fn fromTheirSlice(buf: []u8) Decoder {
        return .{
            .buf = buf,
            .their_end = buf.len,
            .cap = buf.len,
            .disable_reads = true,
        };
    }

    /// Use this function to increase `their_end`.
    pub fn readAtLeast(d: *Decoder, stream: anytype, their_amt: usize) !void {
        assert(!d.disable_reads);
        const existing_amt = d.cap - d.idx;
        d.their_end = d.idx + their_amt;
        if (their_amt <= existing_amt) return;
        const request_amt = their_amt - existing_amt;
        const dest = d.buf[d.cap..];
        if (request_amt > dest.len) return error.TlsRecordOverflow;
        const actual_amt = try stream.readAtLeast(dest, request_amt);
        if (actual_amt < request_amt) return error.TlsConnectionTruncated;
        d.cap += actual_amt;
    }

    /// Same as `readAtLeast` but also increases `our_end` by exactly `our_amt`.
    /// Use when `our_amt` is calculated by us, not by them.
    pub fn readAtLeastOurAmt(d: *Decoder, stream: anytype, our_amt: usize) !void {
        assert(!d.disable_reads);
        try readAtLeast(d, stream, our_amt);
        d.our_end = d.idx + our_amt;
    }

    /// Use this function to increase `our_end`.
    /// This should always be called with an amount provided by us, not them.
    pub fn ensure(d: *Decoder, amt: usize) !void {
        d.our_end = @max(d.idx + amt, d.our_end);
        if (d.our_end > d.their_end) return error.TlsDecodeError;
    }

    /// Use this function to increase `idx`.
    pub fn decode(d: *Decoder, comptime T: type) T {
        switch (@typeInfo(T)) {
            .int => |info| switch (info.bits) {
                8 => {
                    skip(d, 1);
                    return d.buf[d.idx - 1];
                },
                16 => {
                    skip(d, 2);
                    const b0: u16 = d.buf[d.idx - 2];
                    const b1: u16 = d.buf[d.idx - 1];
                    return (b0 << 8) | b1;
                },
                24 => {
                    skip(d, 3);
                    const b0: u24 = d.buf[d.idx - 3];
                    const b1: u24 = d.buf[d.idx - 2];
                    const b2: u24 = d.buf[d.idx - 1];
                    return (b0 << 16) | (b1 << 8) | b2;
                },
                else => @compileError("unsupported int type: " ++ @typeName(T)),
            },
            .@"enum" => |info| {
                if (info.is_exhaustive) @compileError("exhaustive enum cannot be used");
                return @enumFromInt(d.decode(info.tag_type));
            },
            else => @compileError("unsupported type: " ++ @typeName(T)),
        }
    }

    /// Use this function to increase `idx`.
    pub fn array(d: *Decoder, comptime len: usize) *[len]u8 {
        skip(d, len);
        return d.buf[d.idx - len ..][0..len];
    }

    /// Use this function to increase `idx`.
    pub fn slice(d: *Decoder, len: usize) []u8 {
        skip(d, len);
        return d.buf[d.idx - len ..][0..len];
    }

    /// Use this function to increase `idx`.
    pub fn skip(d: *Decoder, amt: usize) void {
        d.idx += amt;
        assert(d.idx <= d.our_end); // insufficient ensured bytes
    }

    pub fn eof(d: Decoder) bool {
        assert(d.our_end <= d.their_end);
        assert(d.idx <= d.our_end);
        return d.idx == d.their_end;
    }

    /// Provide the length they claim, and receive a sub-decoder specific to that slice.
    /// The parent decoder is advanced to the end.
    pub fn sub(d: *Decoder, their_len: usize) !Decoder {
        const end = d.idx + their_len;
        if (end > d.their_end) return error.TlsDecodeError;
        const sub_buf = d.buf[d.idx..end];
        d.idx = end;
        d.our_end = end;
        return fromTheirSlice(sub_buf);
    }

    pub fn rest(d: Decoder) []u8 {
        return d.buf[d.idx..d.cap];
    }
}