Current section

Files

Jump to
open_pgp lib open_pgp packet body_chunk.ex
Raw

lib/open_pgp/packet/body_chunk.ex

defmodule OpenPGP.Packet.BodyChunk do
@moduledoc """
Packet data is represented as a list of body chunks. Each body
chunk includes chunk length header information and chunk data.
Most of the packets will have only one body chunk. Only packets with
Partial Body Length expected to have more than one body chunk.
The `OpenPGP.Util.concat_body/1` can be applied to a packet body or a
list of body chunks to concatenate chunks into a binary to be further
interpreted.
---
## [RFC4880](https://www.ietf.org/rfc/rfc4880.txt)
#### 4.2.1. Old Format Packet Lengths
The meaning of the length-type in old format packets is:
0 - The packet has a one-octet length. The header is 2 octets long.
1 - The packet has a two-octet length. The header is 3 octets long.
2 - The packet has a four-octet length. The header is 5 octets long.
3 - The packet is of indeterminate length. The header is 1 octet
long, and the implementation must determine how long the packet
is. If the packet is in a file, this means that the packet
extends until the end of the file. In general, an implementation
SHOULD NOT use indeterminate-length packets except where the end
of the data will be clear from the context, and even then it is
better to use a definite length, or a new format header. The new
format headers described below have a mechanism for precisely
encoding data of indeterminate length.
### 4.2.2. New Format Packet Lengths
New format packets have four possible ways of encoding length:
1. A one-octet Body Length header encodes packet lengths of up to 191
octets.
2. A two-octet Body Length header encodes packet lengths of 192 to
8383 octets.
3. A five-octet Body Length header encodes packet lengths of up to
4,294,967,295 (0xFFFFFFFF) octets in length. (This actually
encodes a four-octet scalar number.)
4. When the length of the packet body is not known in advance by the
issuer, Partial Body Length headers encode a packet of
indeterminate length, effectively making it a stream.
#### 4.2.2.1. One-Octet Lengths
A one-octet Body Length header encodes a length of 0 to 191 octets.
This type of length header is recognized because the one octet value
is less than 192. The body length is equal to:
bodyLen = 1st_octet;
#### 4.2.2.2. Two-Octet Lengths
A two-octet Body Length header encodes a length of 192 to 8383
octets. It is recognized because its first octet is in the range 192
to 223. The body length is equal to:
bodyLen = ((1st_octet - 192) << 8) + (2nd_octet) + 192
#### 4.2.2.3. Five-Octet Lengths
A five-octet Body Length header consists of a single octet holding
the value 255, followed by a four-octet scalar. The body length is
equal to:
bodyLen = (2nd_octet << 24) | (3rd_octet << 16) |
(4th_octet << 8) | 5th_octet
This basic set of one, two, and five-octet lengths is also used
internally to some packets.
#### 4.2.2.4. Partial Body Lengths
A Partial Body Length header is one octet long and encodes the length
of only part of the data packet. This length is a power of 2, from 1
to 1,073,741,824 (2 to the 30th power). It is recognized by its one
octet value that is greater than or equal to 224, and less than 255.
The Partial Body Length is equal to:
partialBodyLen = 1 << (1st_octet & 0x1F);
Each Partial Body Length header is followed by a portion of the
packet body data. The Partial Body Length header specifies this
portion's length. Another length header (one octet, two-octet,
five-octet, or partial) follows that portion. The last length header
in the packet MUST NOT be a Partial Body Length header. Partial Body
Length headers may only be used for the non-final parts of the
packet.
Note also that the last Body Length header can be a zero-length
header.
An implementation MAY use Partial Body Lengths for data packets, be
they literal, compressed, or encrypted. The first partial length
MUST be at least 512 octets long. Partial Body Lengths MUST NOT be
used for any other packet types.
"""
import Bitwise
alias OpenPGP.Packet.PacketTag, as: PTag
defstruct [:data, :header_length, :chunk_length]
@type t :: %__MODULE__{
header_length: header_length(),
chunk_length: chunk_length(),
data: binary()
}
@typedoc "The packet has a N-octet length header."
@type header_length :: non_neg_integer()
@typedoc "Chunk length in octets (bytes). Payload size."
@type chunk_length :: {:fixed | :partial | :indeterminate, non_neg_integer()}
@doc """
Decode body chunk given input binary and packet tag.
Return structured body chunk and remaining binary.
Expect input to start with Body Length header octets.
### Example:
iex> alias OpenPGP.Packet.{BodyChunk, PacketTag}
iex> BodyChunk.decode(<<5, "Hello", " world!">>, %PacketTag{format: :old, length_type: {0, "one-octet"}})
{%BodyChunk{data: "Hello", header_length: 1, chunk_length: {:fixed, 5}}, " world!"}
"""
@spec decode(input :: binary(), PTag.t()) :: {t(), rest :: binary()}
def decode("" <> _ = input, %PTag{} = ptag) do
{header_length, {_, blen} = chunk_length, next} = length_header(input, ptag)
<<data::bytes-size(blen), rest::binary>> = next
chunk = %__MODULE__{data: data, header_length: header_length, chunk_length: chunk_length}
{chunk, rest}
end
@spec length_header(data :: binary(), PTag.t()) ::
{header_length(), chunk_length(), rest :: binary()}
defp length_header(<<blength::8, rest::binary>>, %PTag{format: :old, length_type: {0, _}}) do
{1, {:fixed, blength}, rest}
end
defp length_header(<<blength::16, rest::binary>>, %PTag{format: :old, length_type: {1, _}}) do
{2, {:fixed, blength}, rest}
end
defp length_header(<<blength::32, rest::binary>>, %PTag{format: :old, length_type: {2, _}}) do
{4, {:fixed, blength}, rest}
end
defp length_header(<<rest::binary>>, %PTag{format: :old, length_type: {3, _}}) do
{0, {:indeterminate, byte_size(rest)}, rest}
end
defp length_header(<<blength::8, rest::binary>>, %PTag{format: :new}) when blength < 192 do
{1, {:fixed, blength}, rest}
end
defp length_header(<<b1::8, b2::8, rest::binary>>, %PTag{format: :new}) when b1 in 192..223 do
blength = ((b1 - 192) <<< 8) + b2 + 192
{2, {:fixed, blength}, rest}
end
defp length_header(<<255::8, blength::32, rest::binary>>, %PTag{format: :new}) do
{5, {:fixed, blength}, rest}
end
# To understand this case, we need to look at 224-255 on a bit level:
# 0x1F = 0b00011111
# 224 = 0b11100000
# 254 = 0b11111110 (255 is taken by five-octet length header)
# The partial length header has all ones in the three most significant bits.
# Then, whatever number we have in the five least significant bits will be
# the power of two, according to the formula `1 << (1st_octet & 0x1F)`.
# Example:
# The encoded body length of 64 (2**6) in one octet partial length header on
# a bit level will be <<0b11100110::8>> (which is 230).
# To verify: `1 << (230 & 0x1F) = 64`
defp length_header(<<b1::8, rest::binary>>, %PTag{format: :new}) when b1 in 224..254 do
plength = 1 <<< (b1 &&& 0x1F)
{1, {:partial, plength}, rest}
end
end