Standard library · Network

net.vmess

The VMess proxy protocol, AEAD form only: encrypted client and server, TCP and UDP, over TCP, TLS, WebSocket or any tunnel stream, with the cryptographic building blocks exposed.

import net.vmess

VMess is the older protocol of the V2Ray family, and unlike VLESS it encrypts for itself. The client proves it holds a user's UUID with an authentication id that contains the current time, sends the request (the target address and the session keys) encrypted under keys derived from the UUID, and then sends the payload in encrypted chunks. The server answers with its own encrypted header and its own chunks. Anyone watching sees random bytes, whatever carries them.

This package implements only the AEAD form, the one used since V2Ray 4.28 and the only one current clients still produce: the user is identified by the encrypted authentication id and the header is sealed with AES-128-GCM. The older MD5 based form, the one that needs alterId to be above 0, is not implemented. Set alterId to 0 on both sides (it is the default in current clients).

The package is both halves. Dial connects a client through net.transport and sends the header, Accept reads it on a server, Read and Write move the payload and ReadPacket reads one chunk as a UDP datagram. AsStream, DialOver and AcceptOver connect it to net.tunnel streams. The lower level functions (key derivation, the sealed header, the chunk encoder) are public, because the protocol is easiest to understand, and to test, one piece at a time.

The body encryptions

ConstantWhat it does
SecAES128GCMEach chunk is sealed with AES-128-GCM. The default choice.
SecChaCha20Poly1305Each chunk is sealed with ChaCha20-Poly1305. A good choice on a processor without AES instructions.
SecNoneThe chunks are framed but not encrypted. The header is still sealed.
SecZeroNo chunks and no encryption: the payload follows the header as it is. Only for use inside TLS.

The header is always sealed with AES-128-GCM under keys derived from the UUID, whichever body type is chosen. The options in Request.option are bits: OptChunkStream frames the body in chunks, OptChunkMask hides the chunk lengths with a SHAKE-128 stream, OptPadding adds random padding to each chunk, OptAuthLength authenticates the lengths with the body cipher. Dial uses DefaultOption for the security type; to choose the bits yourself, call Client on a connection you opened.

What trips people up

  • The two clocks must agree. The authentication id contains the time and a server accepts only ids within TimeWindow seconds, 120, of its own clock. A phone or a server with a wrong clock gets unknown user or stale timestamp and nothing else. This is the first thing to check when a correct UUID is refused.
  • Share one replay filter. Accept remembers the authentication ids it has seen and refuses a repeat. Make one with NewReplayFilter and pass the same one to every Accept call of a server; it is safe to use from several threads. Once it holds more than 4096 ids it drops the ones older than four minutes.
  • A refused client is not told. Dial only sends the header. A wrong UUID, a stale clock or a replayed id makes the server close the connection, and the client sees that as a failed read.
  • The server must support the security type. An Accept that reads a type other than the four above fails with unsupported security type.
  • There are no vmess:// links. The base64 JSON share format of V2RayN and others is not parsed here. Take the fields from it yourself.
  • Close frees the keys. Call Close once on each side when you are done.

The building blocks

This program derives a user's command key from a UUID, makes an authentication id for a given time, opens it again (which also gives back the time), shows that another user's key cannot open it, and seals a small header. The numbers that come out are fixed, so they can be compared with another implementation.

net-vmess-guide-primitives.salam
import net.transport
import net.vmess

const ID := "b831381d-6324-4d53-ad4f-8cda48b30811"

func main:
    key := vmess.CmdKey(transport.ParseUUID(ID))
    println "command key", transport.ToHex(key)

    stamp := 1700000000 as i64
    salt := transport.FromHex("a1b2c3d4")
    authid := vmess.CreateAuthID(key, stamp, salt)
    println "auth id", transport.ToHex(authid)

    mut when := 0 as i64
    println "opens with the right key:", vmess.OpenAuthID(key, authid, when), when == stamp
    other := vmess.CmdKey(transport.ParseUUID("5783a3e7-e373-51cd-8642-c83782b807c5"))
    println "refused for another user:", not vmess.OpenAuthID(other, authid, when)

    nonce := transport.FromHex("1112131415161718")
    sealed := vmess.SealHeader(key, transport.FromStr("a small header"), stamp, salt, nonce)
    println "sealed header is", sealed.len, "bytes"
    println "time window in seconds:", vmess.TimeWindow
    println "default option for aes-gcm:", vmess.DefaultOption(vmess.SecAES128GCM)
    println "default option for none:", vmess.DefaultOption(vmess.SecNone)
    println "default option for zero:", vmess.DefaultOption(vmess.SecZero)
end
Outputcommand key b50d916ac0cec067981af8e5f38a758f auth id afabbebf298c2611f08228d760111288 opens with the right key: true true refused for another user: true sealed header is 72 bytes time window in seconds: 120 default option for aes-gcm: 5 default option for none: 1 default option for zero: 0

Quick start: a client and a server over TCP

The server listens with transport.Listen, accepts with transport.Accept and hands each connection to vmess.Accept with its list of users and the shared replay filter. The client dials with vmess.Dial and picks the body encryption. The target in the header is what a real server would connect onward to; this echo server just reports it.

net-vmess-guide-loopback.salam
import mem
import str
import sync
import net.transport
import net.vmess

const ID := "b831381d-6324-4d53-ad4f-8cda48b30811"

mut port := 0
mut ready := sync.NewWaitGroup()
mut seen := ""

func server():
    l := transport.Listen(0, transport.Settings {})
    port = transport.ListenerPort(l)
    sync.Done(ready)
    mut users := Vector {} as Vector<vmess.User>
    users.push(vmess.NewUser(ID))
    mut rf := vmess.NewReplayFilter()
    buf := mem.Allocate(4096 as u64)
    repeat 3:
        mut c := vmess.Accept(transport.Accept(l), users, rf)
        if not c.ok:
            seen += "refused(" + c.err + ") "
            continue
        end
        seen += c.req.addr.host + " security " + str.FromInt(c.req.security as i64) + "; "
        n := vmess.Read(c, buf, 4096)
        vmess.Write(c, buf, n)
        vmess.Close(c)
    end
    mem.Free(buf)
    transport.CloseListener(l)
end

func talk(security: int):
    target := transport.NewAddr("example.com", 443)
    mut c := vmess.Dial("127.0.0.1", port, transport.Settings {}, ID, security, vmess.CmdTCP, target)
    mut msg := transport.FromStr("hello through vmess")
    vmess.Write(c, msg.p, msg.len)
    buf := mem.Allocate(4096 as u64)
    n := vmess.Read(c, buf, 4096)
    println security, (buf as str).substr(0, n)
    mem.Free(buf)
    transport.FreeBytes(msg)
    vmess.Close(c)
end

func main:
    sync.Add(ready, 1 as i64)
    t := spawn(server)
    sync.Wait(ready)
    talk(vmess.SecAES128GCM)
    talk(vmess.SecChaCha20Poly1305)
    talk(vmess.SecNone)
    join(t)
    println seen
end
Output3 hello through vmess 4 hello through vmess 5 hello through vmess example.com security 3; example.com security 4; example.com security 5;

A user the server does not know

The client cannot tell by itself. The server finds no matching key, says unknown user or stale timestamp, and closes the connection. A real server should do nothing else: sending an error would tell a scanner that it found a proxy.

net-vmess-guide-wrong-user.salam
import sync
import net.transport
import net.vmess

const GOOD := "b831381d-6324-4d53-ad4f-8cda48b30811"
const OTHER := "5783a3e7-e373-51cd-8642-c83782b807c5"

mut port := 0
mut ready := sync.NewWaitGroup()
mut verdict := ""

func server():
    l := transport.Listen(0, transport.Settings {})
    port = transport.ListenerPort(l)
    sync.Done(ready)
    mut users := Vector {} as Vector<vmess.User>
    users.push(vmess.NewUser(GOOD))
    mut rf := vmess.NewReplayFilter()
    mut c := vmess.Accept(transport.Accept(l), users, rf)
    verdict = c.ok ? "accepted " + c.user : "refused: " + c.err
    vmess.Close(c)
    transport.CloseListener(l)
end

func main:
    sync.Add(ready, 1 as i64)
    t := spawn(server)
    sync.Wait(ready)
    mut c := vmess.Dial("127.0.0.1", port, transport.Settings {}, OTHER, vmess.SecAES128GCM, vmess.CmdTCP, transport.NewAddr("example.com", 80))
    join(t)
    println verdict
    vmess.Close(c)
end
Outputrefused: unknown user or stale timestamp

VLESS carrying VMess

A stream wrapper turns any protocol into the carrier of another. Here an outer VLESS connection runs over an in-memory pipe and an inner VMess connection runs inside it, with different users and different targets. On a real network the outer one would be VLESS over WebSocket and TLS and the inner one VMess, which encrypts the payload end to end even when a CDN terminates the outer TLS.

net-vmess-guide-over-stream.salam
import mem
import sync
import net.transport
import net.tunnel
import net.vless
import net.vmess

const OUTER := "b831381d-6324-4d53-ad4f-8cda48b30811"
const INNER := "5783a3e7-e373-51cd-8642-c83782b807c5"

mut pipe := tunnel.NewPipe()
mut done := sync.NewWaitGroup()
mut seen := ""

func server():
    mut vl := Vector {} as Vector<vless.User>
    vl.push(vless.NewUser(OUTER, ""))
    mut vm := Vector {} as Vector<vmess.User>
    vm.push(vmess.NewUser(INNER))
    mut rf := vmess.NewReplayFilter()
    outer := vless.AcceptOver(tunnel.PipeB(pipe), vl)
    inner := vmess.AcceptOver(vless.AsStream(outer), vm, rf)
    seen = outer.req.addr.host + " then " + inner.req.addr.host
    s := vmess.AsStream(inner)
    buf := mem.Allocate(256 as u64)
    n := s.ReadBytes(buf, 256)
    s.WriteBytes(buf, n)
    mem.Free(buf)
    s.Close()
    sync.Done(done)
end

func main:
    sync.Add(done, 1 as i64)
    _t := spawn(server)
    outer := vless.DialOver(tunnel.PipeA(pipe), OUTER, "", vless.CmdTCP, transport.NewAddr("hop.example", 443))
    inner := vmess.DialOver(vless.AsStream(outer), INNER, vmess.SecChaCha20Poly1305, vmess.CmdTCP, transport.NewAddr("final.example", 443))
    s := vmess.AsStream(inner)
    s.Write("two layers deep")
    buf := mem.AllocateZeroed(256 as u64)
    n := s.ReadBytes(buf, 256)
    println (buf as str).substr(0, n)
    mem.Free(buf)
    s.Close()
    sync.Wait(done)
    println "server saw", seen
end
Outputtwo layers deep server saw hop.example then final.example

VMess over WebSocket and TLS behind a CDN

Do the CDN steps from the net.transport page: a proxied domain, a secret WebSocket path, TLS to the CDN with the front name as sni, your domain as host. VMess does the encryption, so it also protects the payload from the CDN.

net-vmess-guide-cdn.salam
import net.transport
import net.vmess

func main:
    mut s := transport.Settings {}
    s.network = "ws"
    s.tls = true
    s.sni = "front.example.net"
    s.host = "vpn.example.org"
    s.path = "/vm-31c9"
    s.alpn = "http/1.1"

    target := transport.NewAddr("example.com", 80)
    mut c := vmess.Dial("front.example.net", 443, s, "b831381d-6324-4d53-ad4f-8cda48b30811", vmess.SecAES128GCM, vmess.CmdTCP, target)
    if not c.ok:
        println "could not connect:", c.err
        ret
    end
    mut req := transport.FromStr("HEAD / HTTP/1.0\r\nHost: example.com\r\n\r\n")
    vmess.Write(c, req.p, req.len)
    transport.FreeBytes(req)
    vmess.Close(c)
end

On the server, listen with network = "ws", the same path and host and with tls off when the CDN or your reverse proxy terminates TLS. For each connection, in a worker thread, call transport.AcceptRaw, transport.Upgrade and then vmess.Accept. Keep the server's clock right (the time window is 120 seconds), and tell your users to do the same.

Apps share a VMess server as vmess:// followed by base64 of a JSON object (the v2rayN form, the same one tunnel.ParseLink reads). ParseLink reads it into a Link: check ok and read err when it is false. FormatLink writes one back, LinkSettings turns it into the net.transport settings (WebSocket path and ?ed= early data, gRPC service name, TLS name) and DialLink dials it. SecurityByName and SecurityName convert between names such as chacha20-poly1305 and the security numbers; auto means AES-128-GCM. The legacy aid is parsed and kept but only 0 is meaningful.

Limits

  • AEAD only. There is no legacy (alterId above 0) mode, and no compatibility with servers or clients that need it.
  • The authenticated length option follows Xray. OptAuthLength derives its length key and nonce from the request key and IV in both directions, seals the length with AES-128-GCM (also for ChaCha20-Poly1305 bodies) and was checked against Xray and sing-box in the interop run (see docs/VPN_INTEROP.md for the matrix). Dial never sets it; choose the bits with Client.
  • Interoperability was tested separately. The header, the authentication id, the key derivation and the chunk formats are checked in this repository against fixed byte vectors and the two halves of this package against each other (over TCP, TLS, WebSocket and a pipe). The interop harness runs the package against real Xray and sing-box builds; its results are in docs/VPN_INTEROP.md.
  • Multiplexing is Mux.Cool. Dial with CmdMux and call MuxClient; on the server, when the accepted request has CmdMux, call MuxServer. Sessions come from net.transport (MuxOpen, MuxAccept).
  • Weak primitives by design. VMess uses MD5 for the command key and AES in ECB mode for one block of the authentication id; these are the protocol's, not choices of this package. The payload ciphers are authenticated (AES-GCM, ChaCha20-Poly1305).
  • SecNone and SecZero do not encrypt the payload. Use them only inside TLS you trust.
  • Replay protection is per process. The filter lives in memory, so a restart forgets it, but the time window still limits what can be replayed.

Types

User

struct User
A user a server accepts.
NameTypeDescription
idstr = ""The user id as text.
cmd_keytransport.BytesThe command key derived from the user id. NewUser computes it.

ReplayFilter

struct ReplayFilter
The memory of authentication ids a server has seen. Share one between all Accept calls; it has a lock.
NameTypeDescription
seenHashMapThe ids already seen and the time each was seen.
locksync.MutexThe lock that guards seen.

Conn

struct Conn
A VMess connection, client or server side.
NameTypeDescription
ttransport.ConnThe transport connection underneath.
clientbool = falseTrue on the client side.
okbool = falseTrue when the header exchange succeeded and the connection is usable.
errstr = ""The reason it failed, empty otherwise.
reqRequestThe request header: on a server what the client sent, on a client what it sent.
userstr = ""On a server: the id of the user that matched.
wcCodecInternal: the encoder of the chunks this side writes.
rcCodecInternal: the decoder of the chunks this side reads.
resp_pendingbool = falseInternal: the server's response header has not been read yet.
resp_keytransport.BytesInternal: the key of the response header.
resp_ivtransport.BytesInternal: the initialization vector of the response.
rbuftransport.BytesInternal: decrypted bytes that have not been read yet.
eofbool = falseInternal: the peer ended the stream.
closedbool = falseInternal: Close has been called.
struct Link
A parsed vmess:// link, the form v2rayN and most apps share.
NameTypeDescription
okbool = falseTrue when the link is complete and supported.
errstr = ""Why parsing failed.
remarkstr = ""The ps label.
hoststr = ""The server address.
portint = 443The server port.
uuidstr = ""The user id.
alter_idint = 0The legacy aid number, kept for round trips. Only 0 is meaningful here.
securitystr = "auto"The body cipher name: auto, aes-128-gcm, chacha20-poly1305, none or zero.
networkstr = "tcp"tcp, ws, httpupgrade or grpc.
header_typestr = "none"The type field, usually none.
host_headerstr = ""The host field: the Host header of WebSocket and HTTP upgrade.
pathstr = ""The WebSocket path, or the gRPC service name.
tlsbool = falseTrue when the link says tls.
snistr = ""The TLS server name.
alpnstr = ""The ALPN text.
fingerprintstr = ""The fp browser name.
allow_insecurebool = falseSkip certificate verification.

Request

struct Request
The request header sealed inside the first bytes of a connection: session keys, options, command and target.
NameTypeDescription
versionint = 1The header version. Always 1.
ivtransport.BytesThe 16 byte initialization vector of the request body. The response keys are derived from it.
keytransport.BytesThe 16 byte key of the request body.
resp_vint = 0One byte the server must echo back in its response header. It tells the client the answer belongs to this request.
optionint = 0The option bits: OptChunkStream, OptChunkMask, OptPadding, OptAuthLength.
securityint = 3The body encryption: SecAES128GCM, SecChaCha20Poly1305, SecNone or SecZero.
commandint = 1CmdTCP, CmdUDP or CmdMux.
addrtransport.AddrThe target address. Unused by CmdMux.
paddingint = 0How many random bytes of padding the header carries, 0 to 15.

Constants and variables

Commands Cmd…

The command byte of a request: CmdTCP opens a TCP connection to the target, CmdUDP carries UDP datagrams, CmdMux is the multiplexing command, which the header can carry but nothing implements.
NameValue
CmdTCP1
CmdUDP2
CmdMux3

Body encryptions Sec…

Which cipher seals the body, for the security of a request and for Dial: SecAES128GCM (AES-128-GCM), SecChaCha20Poly1305 (ChaCha20-Poly1305), SecNone (framed, not encrypted), SecZero (no framing, no encryption).
NameValue
SecAES128GCM3
SecChaCha20Poly13054
SecNone5
SecZero6

Option bits Opt…

Bits for the option of a request, combined with |: OptChunkStream frames the body, OptChunkMask masks the chunk lengths, OptPadding pads each chunk, OptAuthLength authenticates the lengths (not verified against a real peer).
NameValue
OptChunkStream1
OptChunkMask4
OptPadding8
OptAuthLength16

TimeWindow

const TimeWindow: i32 = 120
How many seconds the time in an authentication id may differ from the server's clock. 120.

Functions

AsStream

func AsStream(c: Conn): dyn tunnel.Stream
Wraps a connection as a tunnel.Stream so it can carry another protocol or be handed to tunnel code.

DialOver

func DialOver(s: dyn tunnel.Stream, uuid: str, security: int, command: int, target: transport.Addr): Conn
Runs the client half over an existing stream instead of a new network connection.

AcceptOver

func AcceptOver(s: dyn tunnel.Stream, users: Vector<User>, rf &: ReplayFilter): Conn
Runs the server half over an existing stream, with the users and the replay filter.

MuxClient

func MuxClient(c: Conn): transport.Mux
Starts a Mux.Cool client multiplexer on a connection made with CmdMux.

MuxServer

func MuxServer(c: Conn): transport.Mux
Starts a Mux.Cool server demultiplexer on an accepted connection whose command is CmdMux.

NewUser

func NewUser(uuid: str): User
Makes a user from a UUID text and computes its command key. A bad UUID leaves the key empty.

NewReplayFilter

func NewReplayFilter(): ReplayFilter
Makes an empty replay filter.

DefaultOption

func DefaultOption(security: int): int
The option bits to use with a security type: none for SecZero, chunks for SecNone, chunks and masked lengths otherwise.

Client

func Client(t: transport.Conn, uuid: str, security: int, option: int, command: int, addr: transport.Addr): Conn
Sends the sealed request on a transport connection that is already open, with the option bits you give, and returns the VMess connection. ok is false when the UUID is bad or the write fails.

Dial

func Dial(host: str, port: int, s: transport.Settings, uuid: str, security: int, command: int, target: transport.Addr): Conn
Connects with transport.Dial and the given settings, then sends the request with DefaultOption and a random session key. The reply is not read yet: a refused user shows up on the first read.

Accept

func Accept(t: transport.Conn, users: Vector<User>, rf &: ReplayFilter): Conn
Reads and opens the header on a transport connection, finds the user whose key matches within the time window, refuses a replayed id, and sends the response header. On failure ok is false, err says why and the transport is closed.

Read

func Read(c &: Conn, buf: void*, max: int): int
Reads decrypted payload. On a client the first call reads and checks the server's response header. Returns 0 or less when the connection ended.

ReadPacket

func ReadPacket(c &: Conn, out &: transport.Bytes): bool
Reads one chunk into the buffer, appending to it, which is one datagram for CmdUDP. False when the connection ended.

Write

func Write(c &: Conn, p: void*, n: int): bool
Writes payload from a pointer as one or more encrypted chunks. False when it fails.

Close

func Close(c &: Conn)
Sends the end chunk if the body is chunked and closes the connection and the transport under it.

EncodeChunks

func EncodeChunks(security: int, option: int, key: transport.Bytes, iv: transport.Bytes, parts: Vector<transport.Bytes>, with_end: bool): transport.Bytes
Encodes the parts as the chunks a sender with these keys would produce, with an end chunk when with_end is true. For tests and for implementing a variant.

SecurityByName

func SecurityByName(name: str): int
The security type number for a link name such as aes-128-gcm; auto means AES-128-GCM. -1 for an unknown name.

SecurityName

func SecurityName(sec: int): str
The link name of a security type number.
func ParseLink(link: str): Link
Reads a vmess:// link. Check ok and err.
func FormatLink(l: Link): str
Writes a Link as a vmess:// text (base64 of the v2rayN JSON).

LinkSettings

func LinkSettings(l: Link): transport.Settings
The transport.Settings a link asks for.
func DialLink(l: Link, command: int, target: transport.Addr): Conn
Dials the server a link names and starts a VMess session to a target.

KDF

func KDF(key: transport.Bytes, label: str, a: transport.Bytes, b: transport.Bytes): transport.Bytes
The VMess key derivation: nested HMAC-SHA-256 over a label and up to two more inputs, giving 32 bytes.

KDF16

func KDF16(key: transport.Bytes, label: str, a: transport.Bytes, b: transport.Bytes): transport.Bytes
KDF cut to the first 16 bytes.

KDF12

func KDF12(key: transport.Bytes, label: str, a: transport.Bytes, b: transport.Bytes): transport.Bytes
KDF cut to the first 12 bytes.

CmdKey

func CmdKey(uuid: transport.Bytes): transport.Bytes
The 16 byte command key of a user: the MD5 of the UUID bytes and a fixed text.

Sha256

func Sha256(data: transport.Bytes): transport.Bytes
The SHA-256 hash of the bytes.

AES128Block

func AES128Block(key: transport.Bytes, block: transport.Bytes, decrypt: bool): transport.Bytes
Encrypts or, with decrypt, decrypts one 16 byte block with AES-128.

Shake128

func Shake128(msg: transport.Bytes, outlen: int): transport.Bytes
The SHAKE-128 output of a message, outlen bytes long.

CRC32

func CRC32(data: transport.Bytes): i64
The CRC-32 checksum of the bytes.

FNV1a

func FNV1a(data: transport.Bytes): i64
The 32 bit FNV-1a hash of the bytes, which the request header ends with.

CreateAuthID

func CreateAuthID(cmd_key: transport.Bytes, ts: i64, rand4: transport.Bytes): transport.Bytes
Makes the 16 byte authentication id: the time, four random bytes and a checksum, encrypted with a key derived from the command key.

OpenAuthID

func OpenAuthID(cmd_key: transport.Bytes, authid: transport.Bytes, ts &: i64): bool
Decrypts an authentication id with a command key, checks its checksum and gives back the time it holds. False when the key is not the right one.

SealHeader

func SealHeader(cmd_key: transport.Bytes, data: transport.Bytes, ts: i64, rand4: transport.Bytes, nonce: transport.Bytes): transport.Bytes
Seals a request header: the authentication id, the encrypted length, the nonce and the encrypted header.

SealHeaderNow

func SealHeaderNow(cmd_key: transport.Bytes, data: transport.Bytes): transport.Bytes
SealHeader with the current time and fresh random bytes.

OpenHeaderLength

func OpenHeaderLength(cmd_key: transport.Bytes, authid: transport.Bytes, enc_len: transport.Bytes, nonce: transport.Bytes): int
Opens the encrypted header length. Returns -1 when it fails to authenticate.

OpenHeader

func OpenHeader(cmd_key: transport.Bytes, authid: transport.Bytes, enc: transport.Bytes, nonce: transport.Bytes): transport.Bytes
Opens the encrypted header. The result has bad set when it fails to authenticate.

SealResponseHeader

func SealResponseHeader(resp_key: transport.Bytes, resp_iv: transport.Bytes, header: transport.Bytes): transport.Bytes
Seals the server's response header.

OpenResponseLength

func OpenResponseLength(resp_key: transport.Bytes, resp_iv: transport.Bytes, enc_len: transport.Bytes): int
Opens the encrypted length of a response header. Returns -1 when it fails to authenticate.

OpenResponseHeader

func OpenResponseHeader(resp_key: transport.Bytes, resp_iv: transport.Bytes, enc: transport.Bytes): transport.Bytes
Opens a response header. The result has bad set when it fails to authenticate.

EncodeRequest

func EncodeRequest(r: Request): transport.Bytes
The request header as plain bytes, with its FNV-1a checksum at the end. Free the result with transport.FreeBytes.

DecodeRequest

func DecodeRequest(b: transport.Bytes, r &: Request): bool
Reads a request header from bytes. False when the checksum or the version is wrong, the command is unknown or the bytes do not fit.