mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-14 05:11:19 -04:00
implement Snowflake coffee class (issue #5)
This commit is contained in:
parent
c0397bc3e0
commit
7cbbe585e5
1 changed files with 129 additions and 5 deletions
|
@ -1,8 +1,56 @@
|
|||
###
|
||||
A Coffeescript WebRTC snowflake proxy
|
||||
Using Copy-paste signaling for now.
|
||||
|
||||
Uses WebRTC from the client, and websocket to the server.
|
||||
|
||||
Assume that the webrtc client plugin is always the offerer, in which case
|
||||
this must always act as the answerer.
|
||||
###
|
||||
|
||||
Query =
|
||||
###
|
||||
Parse a URL query string or application/x-www-form-urlencoded body. The
|
||||
return type is an object mapping string keys to string values. By design,
|
||||
this function doesn't support multiple values for the same named parameter,
|
||||
for example "a=1&a=2&a=3"; the first definition always wins. Returns null on
|
||||
error.
|
||||
|
||||
Always decodes from UTF-8, not any other encoding.
|
||||
http://dev.w3.org/html5/spec/Overview.html#url-encoded-form-data
|
||||
###
|
||||
parse: (qs) ->
|
||||
result = {}
|
||||
strings = []
|
||||
strings = qs.split '&' if qs
|
||||
return result if 0 == strings.length
|
||||
for i in [1..strings.length]
|
||||
string = strings[i]
|
||||
j = string.indexOf '='
|
||||
if j == -1
|
||||
name = string
|
||||
value = ''
|
||||
else
|
||||
name = string.substr(0, j)
|
||||
value = string.substr(j + 1)
|
||||
name = decodeURIComponent(name.replace(/\+/g, ' '))
|
||||
value = decodeURIComponent(value.replace(/\+/g, ' '))
|
||||
result[name] = value if !(name in result)
|
||||
result
|
||||
|
||||
Params =
|
||||
getBool: (query, param, defaultValue) ->
|
||||
val = query[param]
|
||||
return defaultValue if undefined == val
|
||||
return true if "true" == val || "1" == val || "" == val
|
||||
return false if "false" == val || "0" == val
|
||||
return null
|
||||
|
||||
# HEADLESS is true if we are running not in a browser with a DOM.
|
||||
query = Query.parse(window.location.search.substr(1))
|
||||
HEADLESS = "undefined" == typeof(document)
|
||||
DEBUG = Params.getBool(query, "debug", false)
|
||||
|
||||
# Janky state machine
|
||||
MODE =
|
||||
INIT: 0
|
||||
|
@ -10,6 +58,8 @@ MODE =
|
|||
CHAT: 2
|
||||
currentMode = MODE.INIT
|
||||
|
||||
|
||||
# TODO: Different ICE servers.
|
||||
config = {
|
||||
iceServers: [
|
||||
{ urls: ["stun:stun.l.google.com:19302"] }
|
||||
|
@ -21,22 +71,96 @@ $chatlog = null
|
|||
$send = null
|
||||
$input = null
|
||||
|
||||
|
||||
window.PeerConnection = window.RTCPeerConnection ||
|
||||
window.mozRTCPeerConnection || window.webkitRTCPeerConnection
|
||||
window.mozRTCPeerConnection ||
|
||||
window.webkitRTCPeerConnection
|
||||
window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate;
|
||||
window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription
|
||||
window.RTCSessionDescription = window.RTCSessionDescription ||
|
||||
window.mozRTCSessionDescription
|
||||
|
||||
# TODO: Implement
|
||||
class Badge
|
||||
|
||||
# Minimum viable snowflake for now - just 1 client.
|
||||
class Snowflake
|
||||
|
||||
rateLimit: 0
|
||||
proxyPairs: null
|
||||
badge: null
|
||||
$badge: null
|
||||
MAX_NUM_CLIENTS = 1
|
||||
|
||||
constructor: ->
|
||||
if HEADLESS
|
||||
# No badge
|
||||
else if DEBUG
|
||||
@$badge = debug_div
|
||||
else
|
||||
@badge = new Badge()
|
||||
@$badgem = @badge.elem
|
||||
if (@$badge)
|
||||
@$badge.setAttribute("id", "snowflake-badge")
|
||||
|
||||
start: ->
|
||||
log "Starting up Snowflake..."
|
||||
|
||||
# Poll facilitator when this snowflake can support more clients.
|
||||
proxyMain = ->
|
||||
if @proxyPairs.length >= @MAX_NUM_CLIENTS * CONNECTIONS_PER_CLIENT
|
||||
setTimeout(@proxyMain, @facilitator_poll_interval * 1000)
|
||||
return
|
||||
|
||||
params = [["r", "1"]]
|
||||
params.push ["transport", "websocket"]
|
||||
params.push ["transport", "webrtc"]
|
||||
|
||||
beginProxy = (client, relay) ->
|
||||
for i in [0..CONNECTIONS_PER_CLIENT]
|
||||
makeProxyPair(client, relay)
|
||||
|
||||
makeProxyPair = (client, relay) ->
|
||||
pair = new ProxyPair(client, relay, @rate_limit);
|
||||
@proxyPairs.push pair
|
||||
pair.onCleanup = (event) =>
|
||||
# Delete from the list of active proxy pairs.
|
||||
@proxyPairs.splice(@proxy_pairs.indexOf(pair), 1)
|
||||
@badge.endProxy() if @badge
|
||||
try
|
||||
proxy_pair.connect()
|
||||
catch err
|
||||
log "ProxyPair: exception while connecting: " + safe_repr(err.message) + "."
|
||||
return
|
||||
@badge.beginProxy if @badge
|
||||
|
||||
cease = ->
|
||||
# @start = null
|
||||
# @proxyMain = null
|
||||
# @make_proxy_pair = function(client_addr, relay_addr) { };
|
||||
while @proxyPairs.length > 0
|
||||
@proxyPairs.pop().close()
|
||||
|
||||
disable = ->
|
||||
log "Disabling Snowflake."
|
||||
@cease()
|
||||
@badge.disable() if @badge
|
||||
|
||||
die = ->
|
||||
log "Snowflake died."
|
||||
@cease()
|
||||
@badge.die() if @badge
|
||||
|
||||
|
||||
# TODO: Implement
|
||||
class ProxyPair
|
||||
|
||||
|
||||
#
|
||||
## -- DOM & Input Functionality -- ##
|
||||
#
|
||||
snowflake = new Snowflake()
|
||||
|
||||
welcome = ->
|
||||
log "== snowflake JS proxy =="
|
||||
log "== snowflake browser proxy =="
|
||||
log "Input offer from the snowflake client:"
|
||||
|
||||
# Log to the message window.
|
||||
|
@ -82,7 +206,7 @@ Signalling =
|
|||
log "Invalid JSON."
|
||||
return
|
||||
# Begin as answerer if peerconnection doesn't exist yet.
|
||||
start false if !pc
|
||||
snowflake.start false if !pc
|
||||
desc = recv['sdp']
|
||||
ice = recv['candidate']
|
||||
if !desc && ! ice
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue