cli/engine/transfer; the browser has its
own in client/lib/transfer.
Everything travels on one data channel, opened by the side that makes the WebRTC offer. That is
the sender in a room joined by a room code or a
share link, and the
receiving Floe Desktop for a request link. The Go engine names the
channel floe, and no Floe receiver reads the name: it takes the first channel the other
peer opens. A Go peer closes the connection if the other side opens a second
channel.
The short version
A side that holds the link key proves it first, in akc frame. Then, for
each file, the sender sends a metadata frame, waits for the receiver’s ack, sends the bytes as
binary frames, and closes the file with end. A text frame is a control message and a binary frame
is file data, whatever it contains. A peer that stops on purpose says why in an incompatible
frame, usually with a fixed code, and the receiver confirms the whole batch with received.
The sequence
Ahead of everything else, each side that holds the link’s link key sends onekc. The sender
then runs this for each file, in order:
metadata, sender to receiver, naming the file and its size.ack, receiver back to sender, before any bytes move.- Binary chunks, sender to receiver, until the file is exhausted.
end, sender to receiver, closing the file off.
received comes once, after every file, from a Go
receiver and from the browser.
What that order does not show:
- A side with no link key sends no
kc. That is a transfer joined by room code, a link with no link key, or a peer older thanfloe1.12, Floe Desktop 1.1 or a web app image of 1.12.0. It drops akcit receives, as it drops any type it does not know. - Step 3 is zero or more frames. An empty file is
metadata,ack,end, with no binary frame between them at all. - A person can sit between steps 1 and 2.
floe receiveprintsAccept? [Y/n]and waits for an answer before it sends the ack. That is the whole reason the ack deadline is 120 seconds by default. A request link moves that person to the far end of a link, so the sending side raises the deadline to 615 seconds for it. - A peer that stops on purpose says why when it can, in an
incompatibleframe sent before it closes. Who sends one, and for what, is listed there. - A close ends a wait at once. A peer that vanishes does not. See When the other side closes.
- The end of a batch depends on what the receiver promised. See How a send ends.
- A second
metadatacan come before the open file’send. That abandons the open file. A Go receiver deletes the unfinished file and starts the new one, and a request link’s host refuses withover-approvedinstead. A browser receiver lets the open file go when the newiddiffers, and treats the sameidagain as a resume, answering with the bytes it already holds.
When the other side closes
Every wait of a Go sender, and a Go receive between frames, watches for the channel closing and gives up at once. So does the browser sender’s wait for an ack, which then shows “Connection lost. The other device may have closed the tab.” A peer that goes away without closing, such as a killed process or a lost network, sends no close, and the deadlines under Timeouts are what end the transfer.floe receive’s Accept? [Y/n] prompt is the one wait that does not watch. It notices a sender
that left only once it is answered, and then ends with
the sender left before the transfer was accepted, having created nothing.
A no at that prompt is a stop with a reason: the receiver sends the code declined, so
floe send prints They declined. Nothing was sent. and floe.one’s send page shows
receiver declined the transfer. A receiver that goes away without answering only closes the
channel, and so does a floe receive of version 1.11.0 or earlier for a no. floe send then
prints connection closed while waiting for the receiver (transfer declined or receiver exited).
Ctrl+C at the prompt is a stop instead, and sends the code stopped.
How a send ends
A Go sender reads the receiver’s promise from the first file’s ack (seeconfirms):
A close while bytes are still in the buffer ends the send with
connection closed before delivery was confirmed (N bytes unacknowledged), where N is the
number still waiting. A close with an empty buffer is a success. The request-link page and
floe send --to wait for received whatever the ack says.
How a frame is classified
There is no length prefix and no envelope. What a frame is comes from how it was sent, not from what is inside it. In the direction file data travels, sender to receiver, a frame is a control message if and only if it is a text frame. A binary frame is file data, whatever its bytes spell. That is the whole rule, and it is what lets a small.json file whose entire content is {"type":"end"}
arrive intact instead of being swallowed as an end marker.
A text frame is then read as a control message when both of these hold:
- It is at most 1000 bytes, measured on the encoded frame. A longer one is a protocol error
and stops the transfer, rather than being written to a file. The receiver tells the sender
first, with the code
path-too-long, because what crosses the limit in practice is themetadataof a deeply nested file. - It is a JSON object with a
typefield naming a message below. JSON whitespace may come before the opening brace.
- The key is
type, exactly, in lowercase. A frame whose only type key isTYPEis not a control message. - Any bytes after the JSON object make the frame something other than a control message.
- A key that appears twice keeps its last value.
- A receiver also ignores a binary frame or an
endthat arrives while no file is open, and anackor areceived, which flow the other way.
metadata frame. The Go engine reads them
with Go’s JSON decoder, which matches a key whatever its letter case, so FileName counts as
fileName. The browser reads the exact keys. A metadata frame whose keys differ from the real
ones only in case is accepted by a Go receiver and refused by a browser. Floe’s own senders always
write the keys exactly as listed below, and the parity table below records the difference.
Both implementations are held to one table of literal frames, each with the decision the Go engine
makes and the decision the browser makes. cli/engine/transfer/parity_test.go and
client/lib/transfer/parity.test.ts carry the same table, and each suite fails when the other
file’s copy drifts. It is the nearest thing to a conformance
list for another client. A change to how either side reads end, received, ack,
incompatible, or metadata is a change to both files.
The other direction, receiver to sender, carries no file data, so nothing there can be confused
with a chunk and a sender accepts a control message in either framing. It has to: a Go receiver
sends kc, ack, received, and incompatible as binary, and a browser receiver sends ack as a
string and kc, received and incompatible as binary. A sender ignores a frame of more than 1000 bytes.
Message types
kc
Either direction, ahead of every other frame the side sends, and at most once. It is the proof that this side holds the link’s link key, and only a side that holds one sends it. The web app speaks it, and so dofloe 1.12 and later and Floe Desktop 1.1 and later.
A file sender sends it as text and a file receiver as binary, the framing of its direction. A browser
sends it once the connection opens, and a Go peer as its send or receive begins. One frame ahead
of it is on purpose: floe send --to over the relay cap sends its stop first, and that stop says the cap was
reached and carries no size. A Ctrl+C during the file walk sends an abort with no kc at all, because the proof comes after the walk.
kc
string
required
32 bytes as exactly 43 unpadded base64url characters, 65 bytes in the whole frame with
type first.
A tag that is anything else is malformed, and a side holding the link key reads a malformed kc as a failed
check. The first kc stands. A later one is dropped.kc tag
kis the link key: 16 random bytes, written in a link as 22 unpadded base64url characters.roleis0x73, the letters, in the file sender’s tag and0x72, the letterr, in the file receiver’s. So one side’s proof cannot be replayed at it as the other’s. The file sender is not always the side that made the offer: a request link’s host offers and receives.len(roomId)is one byte, androomIdis the room id exactly as the link holds it, letter case included.hSandhRare the SHA-256 of the DTLS certificates of the file sender and the file receiver, as DTLS actually used them. Each side reads the other’s certificate off the connection, not from what the signaling server passed along. A browser that cannot read it falls back to the fingerprint in a strictly shaped description, and past that it cannot check at all.
floe 1.12 and later and Floe Desktop 1.1 and later, depends on who it is:
From a side that enforces, a stop ahead of the proof carries no words of the other side’s. A keyed
Go receiver reads the sender’s stop as
the sender stopped the transfer, and a range miss without
the sender’s ver. An enforcing Go sender drops the count and the words of a receiver’s stop, and a
keyed browser receiver refuses any incompatible ahead of the proof. A side that only detects shows
a receiver’s stop as it did before, in its own words.
A receiver that holds the link key gives a silent sender 30 seconds from the connection opening. A
browser then refuses with unverified. A Go receiver ends on
connected, but no data arrived from the sender within 30s, and frames that mean nothing do not
restart that clock.
metadata
Sender to receiver, before each file.metadata
string
required
A per-file identifier. The ack echoes it.
string
required
The name as the sender sees it, including any relative path when a folder is being sent, with
forward slashes. The receiver sanitizes it before writing. The whole frame has to fit the
1000-byte limit, and a Go receiver refuses a path that is too deep or too long to save. See
Limits.
integer
required
The file’s length in bytes, a whole number from
0 to 2^53 - 1. The receiver checks the bytes
it received against this value.integer
required
Position in the batch, 1-based: a whole number of at least 1.
integer
required
How many files are in the batch: a whole number of at least 1.
integer
required
The batch’s total size. Senders before v1.5.0 did not announce it, and a receiver reads an absent
or
0 total as not announced: for a single file a Go receiver uses fileSize instead. A total
that is smaller than fileSize is refused. A request link’s host refuses a drop that announces
no total, unless its first file is empty.integer
Highest protocol version this peer speaks. Absent on releases before v1.6.0, which are treated
as version 1.
integer
Lowest protocol version this peer still supports.
string
The human release string, and its shape depends on the build. The CLI sends a version such as
1.12.0 with no leading v, because GoReleaser strips it. The desktop app sends the tag verbatim,
as desktop-v1.1.0. A build from source sends dev. Browser peers omit the field entirely. It is
informational and never gates a transfer.metadata it cannot read and stops the transfer:
- an
indexortotalthat is not a whole number of at least 1, or anindexgreater thantotal - an
idorfileNamethat is not a string - a
pvorpvMinthat is not a whole number - a size that is not a byte count, or a total below the file size
code, and its reason starts with
receiver rejected the file description:. The browser then shows “The sender described a file in a
way Floe could not read, so the transfer was stopped. Ask the sender to try again.” A Go receiver’s
own error starts with rejected the sender's file description:.
ack
Receiver to sender, once per file, before any bytes move.ack from a browser receiver
string
required
The
id of the metadata this answers. A sender ignores an ack whose id is not the current
file’s.integer
required
Against any current sender it is always
0. It says where the sender should start reading,
and is reserved for mid-file resume. Both senders honor a non-zero value and seek to it. The Go
receiver hard-codes 0. The browser receiver would reply with what it already holds if a second
metadata arrived for a file id it had buffered bytes for, but no current sender ever reuses an
id that way. No resume state is kept on disk or across connections.An offset that is negative or past the end of the file fails the send. A Go sender says
receiver asked to resume at byte N of a M-byte file. The browser says “The receiver asked to
resume” with the file’s name, “from byte N of M, which is not possible. Please try again.”, or
“from a position that is not a number” in place of the bytes.integer
As in
metadata. The sender checks the range again on the first file’s ack.integer
As in
metadata.string
As in
metadata. A Go receiver sends it; a browser receiver omits it.boolean
The receiver’s promise that it ends the batch with a word of its own:
received after the last
file is saved, or a refusal when it keeps a file out.Current receivers send true on every ack: the CLI and Floe Desktop, and the browser. Go
receivers before floe 1.11.0 and Floe Desktop 0.3.0 never sent it, and neither does a
web app that someone hosts and that runs an
older version.A Go sender reads it from the first file’s ack. With it, the sender waits for that word instead
of ending at a drained send buffer, so a file the receiver deletes or cannot save after the last
byte is never reported as delivered. Only the JSON literal true counts. The string "true",
1, null, or any other value is no promise, and never an error. Older peers and the browser
sender ignore the field, so it needs no protocol version bump.ack from a Go receiver of floe 1.12 or later
Binary chunks
Raw bytes, sent until the file is exhausted. Chunk size is a local sender choice and not part of the protocol; every receiver handles any size. Both implementations size chunks to the connection’s negotiated maximum message size, capped at 256 KB. When that size is unavailable they fall back to different defaults: 16 KB in the Go engine, 64 KB in the browser. The browser reads the source file in 4 MB slabs and sub-slices those into chunks. The Go sender reads straight into a chunk-sized buffer.end
Sender to receiver, after the last chunk of a file.end
sha256, the SHA-256 of the bytes it sent for that file,
as 64 lowercase hex characters. It is optional, and type comes first when it is present. A
sender leaves it off a file it resumed from a non-zero offset. A receiver commits a file without
one after the byte count alone, and peers that predate the field ignore it. A browser sender
hashes in a worker while it sends and holds end for the digest only so long (see
Timeouts); past that it sends end without one. See Integrity.
end with a digest
received
Receiver to sender, once, after every file in the batch is kept.received
confirms, it accepts nothing else as success: a close that comes before either
received or a refusal fails the send with
the connection closed before the receiver confirmed delivery. That wait has no deadline of its
own once the buffer has drained. floe send and Floe Desktop close the connection when
ICE gives up on it, about 30 seconds after the last packet from the receiver.
floe.one’s send page does not wait for it. That send is complete when its own send buffer drains to
zero, and the page then reads “All Files Sent!”. The page keeps listening afterward, until a
received or a refusal arrives or the connection closes. A received from any receiver that
reports every file as verified turns the page’s gray check mark green, the
SHA-256 matched mark (see
Sending files). A refusal that arrives in that window replaces
the success with the refusal.
After sending received, a Go receiver keeps the connection open until the sender closes it, which
a Go sender does as soon as it reads the frame. With a sender that does not close, such as a
browser, it waits 5 seconds, and longer while the frame is still in its send buffer, up to 35
seconds from sending it.
Both receivers report verified, the number of files whose sha256 was present and matched. It
is always present, and 0 means no file carried a digest, or none that the receiver could check in
time. A sender shows its SHA-256 mark only when verified equals the number of files it sent. A
count that is not a whole number from 0 to that number is ignored, never rounded or capped into a
match. It is the receiver’s report, not a check the sender makes. What each app shows is in
File checks.
received with a count
incompatible
Two jobs, told apart by thepv range it carries.
A version mismatch. Sent by the receiver in place of the ack when the two protocol version
ranges do not overlap. The receiver stops before creating any file, so the sender fails fast
rather than waiting for an ack that never comes. Senders that predate this message type ignore it
safely.
A deliberate stop with a reason, when the pv range does overlap. A peer reads the overlap
as “this is not about versions” and does not rebuild a version remedy from pv and pvMin. That is
what lets a side that is stopping on purpose name the cause without a new message type and without
a ProtocolVersion bump.
A sender that stops says why in reason alone, apart from a failed kc, which also carries
the code unverified:
- Every sender does so for the 2 GB relay cap.
- A Go sender does so for a source file that changed while it was being sent, and for a file it can no longer
read.
floe1.11.7 and earlier and Floe Desktop 1.0.4 and earlier close the connection without a word for the second. - floe.one’s send page does so for a file it can no longer read, for its own 2-minute wait for an ack, and for an error of its own. It also does so for a connection that needs the relay while Network relay fallback is off.
- A sender does so for its own cancel: the request-link page,
floe send --to, and Ctrl+C on a plainfloe sendall sendThe sender stopped.
reason and, when the cause is in the table below, in two
optional fields. code names the cause from a fixed list of thirteen values, so the sender can use
its own wording instead of the receiver’s prose. saved is the number of files the receiver had
already saved under their final names, or, in a browser, handed to the page.
over-approved covers each way a request-link drop can stray from its first metadata:
- The first
metadataannounced more than 10,000 files, no total while its first file is not empty, or anindexother than 1. - A later
metadataannounced anothertotalortotalBytes, or anindexthat is not the next file, or more than 10,000metadataframes arrived in all. - A
metadataarrived while a file was still open. - More bytes arrived than the first
metadataannounced.
metadata gets over-approved before anyone is asked, although no
data has arrived. Only a client that is not Floe’s sends such a drop.
Each code travels with a stock reason for peers that show it as written, which floe.one’s send
page does for every code but hash-mismatch and unverified:
Three receiver stops carry a
reason and no code:
- A file description the receiver cannot read: the reason starts
receiver rejected the file description:(see metadata). - A file whose byte count did not match the size announced: a Go receiver sends a reason that
starts
receiver discarded a file: incomplete file, followed by the name and both byte counts. - A sender that kept sending past that size: a Go receiver sends a reason that starts
receiver stopped the transfer: sender exceeded the announced size of, followed by the name.
incompatible, never ack, and neither changes the protocol version.
Older peers ignore both and show reason. A reader that does not know a code treats the frame
as if it had none. No reader displays either value as sent, and each clamps saved to the number
of files in the transfer. For a code it knows, the Go engine and the request-link page show a
fixed sentence of their own and nothing of the receiver’s reason. floe.one’s send page has its
own sentences for hash-mismatch and unverified only, and shows the cleaned reason for every
other stop.
A frame with no usable reason gets fallback words. A browser receiver shows “The sender stopped
the transfer.”, and floe.one’s send page shows “The other side rejected the transfer.” A Go peer
ends with peer rejected transfer: protocol incompatible, or, for a sender’s frame it cannot
decode at all, the sender stopped the transfer.
Framing depends on direction, and this is the one rule another client has to get right.
Receiver to sender is binary, which is what every Floe receiver has always sent. Sender to
receiver has to be text, because on that path a binary frame is file data by definition. Go
receivers from v1.1.0 to v1.5.5 will write a binary one into the file being received.
The whole encoded frame stays within 1000 bytes, reason included: the peer that builds it
shortens the reason until the frame fits, and never drops code or saved to make room.
Whichever side displays the reason caps it again, at 300 characters.
Both Go peers wait for the send buffer to drain, up to 2 seconds, before closing, and so does a
browser sender. Without that wait the frame is routinely lost: closing a Pion connection aborts
the SCTP association and discards whatever was still queued. The peer would then see the close and
report a lost connection rather than the reason. A browser receiver sends without waiting,
because it does not close the connection afterward.
incompatible from a receiver that stopped
unverified stop is the one coded stop whose range is narrower, from either side: pv and
pvMin are both 2, the first version that speaks kc. A peer that speaks version 2 overlaps that range and reads
the code. A peer that speaks only version 1 misses it and prints its own update remedy:
incompatible for a failed proof
floe send --link-only from floe 1.12
or later sends this frame to a receiver that shows no proof. A floe receive from 1.10.8 to 1.11.7
then prints Cannot transfer: your floe is too old for this peer. and the update remedy. Before
1.10.8 it prints
connection closed before any file arrived (the sender canceled, or the transfer was blocked).
A receiver would answer a metadata in the same way, in place of the ack. No released sender gives it
cause, since every release still accepts version 1. A Floe Desktop 1.0.4 receiver that was offered a
file by a sender announcing only version 2 would answer with this:
incompatible for a version mismatch
Protocol versioning
The wire protocol carries a version of its own, independent of Floe’s release numbers. That is what lets peers on different releases interoperate while still catching a genuine breaking change cleanly.- Each peer advertises
pv, the highest version it speaks, andpvMin, the lowest it still supports. The web app,floe1.12 and later and Floe Desktop 1.1 and later sendpv2 andpvMin1. Older releases send 1 for both. - Two peers are compatible when their ranges overlap:
max(localMin, remoteMin) <= min(localMax, remoteMax). They operate at the highest common version. - A peer that omits the fields is treated as version 1, so this check passes against every release ever shipped. It is not the whole of compatibility: the Changelog states the floors that were measured.
- When the ranges miss, a receiver sends
incompatiblein place of the ack, and each side prints a remedy for its own surface. That isfloe updateon the CLI, the Microsoft Store or the download page on the desktop, and a page refresh in the browser. Theunverifiedstop misses a version 1 peer’s range on purpose, with the same result.
reason for a
version mismatch: both implementations rebuild the message locally from the frame’s pv and
pvMin, so the side they name is always correct. Both receivers also word the reason they put
on the wire from the recipient’s point of view, in words that name no surface, for peers that
display it verbatim. When a message names a remedy that makes no sense for your surface, the peer
that sent it is older than this convention. Compare the two version strings it prints and update
the older one.
Constants live in cli/engine/transfer/protocol.go as ProtocolVersion and MinProtocolVersion,
and in client/lib/transfer/protocol.ts as PROTOCOL_VERSION and MIN_PROTOCOL_VERSION. Bump
ProtocolVersion on a breaking wire change, and raise MinProtocolVersion only when deliberately
dropping support for an old format. The move to 2 is the one bump that is not a breaking change, on
purpose. Version 2 marks a peer that speaks kc. An older peer drops a kc frame, and its range of
1 to 1 overlaps 1 to 2, so nothing stops working. A later change that makes every sender refuse an
unproven receiver needs a stop that misses only the peers that do not speak kc, and if every peer
that does still said 1, it would tell them too that they are too old. No decision reads pv to pick a
check: a side acts on holding the link key.
Keep the two implementations in step. A test on
each side pins both numbers and names its twin: TestProtocolVersionPinnedToClient in
cli/engine/transfer/protocol_test.go, and the constants block in
client/lib/transfer/protocol.test.ts. A bump cannot land on one side without a failing test
pointing at the other. The decoders themselves are pinned by the
parity table.
Flow control
The sender pauses when the SCTP send buffer reaches 8 MB and resumes once it drains below 4 MB. Both implementations use the same two numbers. If the buffer has not shrunk at all after 60 seconds, the Go sender gives up on the transfer. A wait that is merely slow does not trigger it; only a buffer that never moves does. The browser sender has no such deadline: its wait ends when the buffer drains or the connection goes away. The browser adds two drains the Go engine does not have. It waits for the buffer to fall below 64 KB before sending each file’s metadata, and it drains to zero before declaring the batch complete. A Go receiver reads through an SCTP receive buffer of 16 MB. While its loop is busy elsewhere, at the prompt, during a decision, or while it retries a move, its connection holds at most 256 frames or 64 MiB of them. A sender meets either bound as backpressure.Integrity
A file is committed only when the bytes received matchfileSize exactly. A mismatch in either
direction discards the file, because a short count means truncation and an over-count means a
frame boundary was wrong, which corrupts the result just as thoroughly. A Go receiver does not
wait for end to catch an over-count: it stops at the first chunk that would pass fileSize.
The sender holds itself to the same number. Both senders read at most fileSize bytes, so a file
that grows after its size is announced cannot overrun the announcement. A Go sender that finds its
file shrank or grew while it was being read fails locally, sends no end marker, and tells the
receiver the file changed. The receiver then does not report a byte count that reads like a network
fault. A browser sender that can no longer read its file stops without an end marker, and tells the
receiver why.
A sender can also put a SHA-256 of the file in its end frame. A Go receiver hashes the bytes as
it writes them and never reads the file back from disk; a browser receiver hashes the assembled
file after end. Either compares only once the byte count has passed, so a short file still
reports a short file. A file whose digest does not match, or whose digest is not 64 lowercase hex
characters, is discarded before it takes its final name or is handed to the page. The receiver
then stops the transfer with the hash-mismatch code. A missing digest is not an error: the file
is kept on the byte count alone, exactly as before. The transport already authenticates every
packet, so the digest is not covering corruption in flight; it ties the file written on one
machine to the bytes read on the other. What the check means for the person receiving is in
File checks.
While a browser receiver checks a file, it drops binary frames and holds at most 8 text frames,
each within the 1000-byte limit. It checks every file after end: the digest when there is one,
and always a read-back of the last byte of each part it holds the file in. A sender that goes past that stops the transfer with “The
sender sent more messages than a transfer allows while a file was being checked, so the transfer
was stopped.” A conforming sender has at most one frame in flight then: the next file’s
metadata, or an incompatible. Another client must not send ahead of the next ack.
The two implementations disagree on one input. If a peer announces no fileSize at all, the
browser skips the size check and keeps whatever arrived, while the Go receiver reads the missing
size as 0 and stops at the first chunk. No Floe sender omits the field. A fileSize that is
present but is not a byte count is refused by both.
Receivers that write to disk stage each file under a name ending in .part and rename it only
after those checks pass, so a killed transfer never leaves a file sitting under its final name.
See Known limitations.
Timeouts
These are the waits the Go engine enforces around a transfer. The browser shares five of them. They are the default 120-second ack deadline, the 2-second wait for an abort reason to reach the wire, the 15-second wait for akc on the request-link page, the 30-second wait for a silent sender
on a receiving page that holds the link key, and, on the request-link page, the 10-second wait for
the server to answer a request-join. The request-link page waits its own longer deadline for every
file’s ack, as floe send --to does, because the host acks a file only after it has committed the one
before.
While connecting
Two setup failures are not timeouts. When ICE or DTLS fails before the channel opens, the engine
reports
connection failed (state: failed), with closed in place of failed as the case may be.
The CLI prints it as WebRTC setup failed: connection failed (state: failed). A few seconds of
silence does not end the wait, because ICE recovers from it when the path comes back.
A setup that this side closed itself ends with closed before the connection was established.
While sending
The 120-second ack deadline is sized for a person answering the CLI’s
Accept? [Y/n] prompt. A
caller that puts the deciding person behind a link raises it through SendOptions.AckTimeout, to
VisitorAckTimeout plus VisitorAckGrace.
While receiving
A request link’s host, Floe Desktop, runs four more clocks, and each ends in a
code:
Every other Go receive tries that move 5 times, 200 ms apart, before it sends
save-blocked.
Calls to the server
One deadline sits outside the engine.
floe send and floe receive give the server 20 seconds
to answer join-room before failing with timed out waiting for the server to assign a role.
Floe Desktop uses the same 20 seconds.
Waits of the browser
A browser sender on the main page checks the route and sends the first byte as soon as the peers connect when relay fallback is on and 2 GB or less is queued. With fallback off or more than 2 GB queued, and always on a request link’s page, it waits 2 seconds after the peers connect before it checks the route and sends the first byte. When the sender does not close the connection, as a browser sender does not, afloe receive can linger for up to about 10 seconds after printing its summary. That is the
teardown grace plus the report, and it can reach about 40 seconds when its received frame is
slow to reach the sender.
The browser’s SHA-256 work has two waits of its own, each sized under a Go deadline above. Its
bound for hashing a file is the file’s size at 10 MB/s plus 30 seconds.
- A browser sender holds a file’s
endfor its digest at most that long, and never more than 45 seconds after the last chunk. It then sendsendwithoutsha256, so a Go receiver’s 60-second stall never deletes a file whose bytes all arrived. - A browser receiver hashes each file after its
end. While more files follow, it stops after that bound or 90 seconds, whichever is sooner, and keeps the file unverified. The next file’s ack waits behind the hash, and the sender gives up on it after 120 seconds. The last file of a batch gets the whole bound, and the batch’sreceivedgoes out only after it.
SHA-256 matched.
Message size, and the SDP that sets it
a=max-message-size in a session description tells the other side how large a message this side
accepts. RFC 8841 makes the absent default 65536 bytes, and Chrome refuses to send a message larger
than the peer advertised. Both Floe senders size their chunks to the negotiated maximum (see
Binary chunks), so a small value does not break a transfer. It only shrinks the
chunks.
Pion, the WebRTC stack behind the CLI and the desktop app, has written the attribute into every
description it generates since v4, with a ceiling of its own just under 1 GB. When the Go side
answers, as floe receive, the desktop app’s Receive, and floe send --to do, the engine rewrites
the value in the answer it sends to 1073741824 bytes, 1 GB. The rewrite dates from Pion v3, which
omitted the attribute and so left a browser sender at the 65536 default. Today it only pins a
number Pion already sends. When the Go side makes the offer, nothing is rewritten.
If you are building another client against Floe, advertise at least 262144 bytes (256 KB) in the
descriptions you generate, or senders use smaller chunks. The limits a Go peer puts on candidates
and signaling frames are in
What a Go peer accepts.
Related
HTTP API
The signaling server’s endpoints, up to the point where this protocol takes over.
Architecture
Components, transports, and the shape of a whole session.