///|
/// Negotiated framebuffer metadata. Name is opaque server UTF-8 bytes.
pub(all) struct Screen {
width : Int
height : Int
format : PixelFormat
name : Array[Int]
} derive(Debug, Eq)
///|
priv enum Phase {
Version
Security(Int)
Challenge(Int)
SecurityResult(Int)
Init
Running
Failed
Closed
} derive(Eq)
///|
pub(all) struct Options {
password : Array[Int]?
allow_none : Bool
shared : Bool
max_pixels : Int
max_buffer : Int
max_text : Int
} derive(Debug)
///|
/// Unauthenticated servers are rejected unless explicitly opted in.
pub fn default_options() -> Options {
{
password: None,
allow_none: false,
shared: true,
max_pixels: 4194304,
max_buffer: 33554432,
max_text: 1048576,
}
}
///|
fn parse_version(r : Reader) -> Int raise RfbError {
let b = r.take(12)
if b[0:4].to_owned() != [82, 70, 66, 32] || b[7] != 46 || b[11] != 10 {
raise Invalid("RFB version banner")
}
for i in [4, 5, 6, 8, 9, 10] {
if b[i] < 48 || b[i] > 57 {
raise Invalid("RFB version digits")
}
}
let major = (b[4] - 48) * 100 + (b[5] - 48) * 10 + b[6] - 48
let minor = (b[8] - 48) * 100 + (b[9] - 48) * 10 + b[10] - 48
if major != 3 || minor < 3 {
raise Invalid("unsupported RFB version")
}
// Unknown minor versions negotiate down to the newest supported protocol.
if minor >= 8 {
8
} else if minor >= 7 {
7
} else {
3
}
}
///|
fn banner(minor : Int) -> Array[Int] {
[82, 70, 66, 32, 48, 48, 51, 46, 48, 48, 48 + minor, 10]
}
///|
fn choose_security(
r : Reader,
minor : Int,
opt : Options,
) -> Int raise RfbError {
let types = if minor == 3 {
[r.i32()]
} else {
let n = r.u8()
if n == 0 {
[0]
} else {
r.take(n)
}
}
if types.contains(0) {
let n = r.i32()
if n < 0 || n > opt.max_text {
raise Limit("security failure reason")
}
ignore(r.take(n))
raise AuthenticationFailed
}
if opt.password is Some(_) && types.contains(2) {
return 2
}
if opt.allow_none && types.contains(1) {
return 1
}
raise AuthenticationFailed
}
///|
fn read_screen(r : Reader, opt : Options) -> Screen raise RfbError {
let width = r.u16()
let height = r.u16()
if width == 0 || height == 0 || width > opt.max_pixels / height {
raise Limit("framebuffer")
}
let format = read_format(r)
let n = r.i32()
if n < 0 || n > opt.max_text {
raise Limit("desktop name")
}
{ width, height, format, name: r.take(n), }
}
///|
/// One handshake transition; caller commits only after the entire transition parses.
fn handshake_step(
r : Reader,
phase : Phase,
opt : Options,
) -> (Phase, Array[Int], Screen?) raise RfbError {
match phase {
Version => {
let minor = parse_version(r)
(Security(minor), banner(minor), None)
}
Security(minor) => {
let kind = choose_security(r, minor, opt)
let out = if minor == 3 { [] } else { [kind] }
if kind == 2 {
(Challenge(minor), out, None)
} else if minor == 8 {
(SecurityResult(minor), out, None)
} else {
out.push(if opt.shared { 1 } else { 0 })
(Init, out, None)
}
}
Challenge(minor) => {
let challenge = r.take(16)
match opt.password {
Some(p) => (SecurityResult(minor), vnc_response(p, challenge), None)
None => raise AuthenticationFailed
}
}
SecurityResult(minor) => {
let result = r.i32()
if result != 0 {
if minor == 8 {
let n = r.i32()
if n < 0 || n > opt.max_text {
raise Limit("authentication reason")
}
ignore(r.take(n))
}
raise AuthenticationFailed
}
(Init, [if opt.shared { 1 } else { 0 }], None)
}
Init => {
let screen = read_screen(r, opt)
(Running, [], Some(screen))
}
_ => raise Invalid("not in handshake")
}
}