mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-14 05:11:19 -04:00
Complete broker spec cases
This commit is contained in:
parent
bb9eb721e2
commit
4f18340c16
2 changed files with 101 additions and 58 deletions
|
@ -9,6 +9,9 @@ STATUS_OK = 200
|
||||||
STATUS_GONE = 410
|
STATUS_GONE = 410
|
||||||
STATUS_GATEWAY_TIMEOUT = 504
|
STATUS_GATEWAY_TIMEOUT = 504
|
||||||
|
|
||||||
|
MESSAGE_TIMEOUT = 'Timed out waiting for a client offer.'
|
||||||
|
MESSAGE_UNEXPECTED = 'Unexpected status.'
|
||||||
|
|
||||||
genSnowflakeID = ->
|
genSnowflakeID = ->
|
||||||
Math.random().toString(36).substring(2)
|
Math.random().toString(36).substring(2)
|
||||||
|
|
||||||
|
@ -17,7 +20,6 @@ class Broker
|
||||||
|
|
||||||
clients: 0
|
clients: 0
|
||||||
id: null
|
id: null
|
||||||
request: null
|
|
||||||
|
|
||||||
# When interacting with the Broker, snowflake must generate a unique session
|
# When interacting with the Broker, snowflake must generate a unique session
|
||||||
# ID so the Broker can keep track of which signalling channel it's speaking
|
# ID so the Broker can keep track of which signalling channel it's speaking
|
||||||
|
@ -29,53 +31,35 @@ class Broker
|
||||||
@url = 'https://' + @url if 0 != @url.indexOf('https://', 0)
|
@url = 'https://' + @url if 0 != @url.indexOf('https://', 0)
|
||||||
@url += '/' if '/' != @url.substr -1
|
@url += '/' if '/' != @url.substr -1
|
||||||
|
|
||||||
# Snowflake registers with the broker using an HTTP POST request, and expects
|
# Promises some client SDP Offer.
|
||||||
# a response from the broker containing some client offer.
|
# Registers this Snowfalke with the broker using an HTTP POST request, and
|
||||||
|
# waits for a response containing some client offer that the Broker chooses
|
||||||
|
# for this proxy..
|
||||||
# TODO: Actually support multiple clients.
|
# TODO: Actually support multiple clients.
|
||||||
getClientOffer: =>
|
getClientOffer: =>
|
||||||
new Promise (fulfill, reject) =>
|
new Promise (fulfill, reject) =>
|
||||||
xhr = new XMLHttpRequest()
|
xhr = new XMLHttpRequest()
|
||||||
@request = xhr
|
xhr.onreadystatechange = ->
|
||||||
@fulfill = fulfill
|
|
||||||
# @request.onreadystatechange = @processOffer
|
|
||||||
xhr.onreadystatechange = =>
|
|
||||||
return if xhr.DONE != xhr.readyState
|
return if xhr.DONE != xhr.readyState
|
||||||
switch xhr.status
|
switch xhr.status
|
||||||
when STATUS_OK
|
when STATUS_OK
|
||||||
fulfill xhr.responseText # Should contain offer.
|
fulfill xhr.responseText # Should contain offer.
|
||||||
when STATUS_GATEWAY_TIMEOUT
|
when STATUS_GATEWAY_TIMEOUT
|
||||||
reject 'Timed out waiting for a client to serve.'
|
reject MESSAGE_TIMEOUT
|
||||||
else
|
else
|
||||||
log 'Broker ERROR: Unexpected ' + xhr.status +
|
log 'Broker ERROR: Unexpected ' + xhr.status +
|
||||||
' - ' + xhr.statusText
|
' - ' + xhr.statusText
|
||||||
Status.set ' failure. Please refresh.'
|
snowflake.ui.setStatus ' failure. Please refresh.'
|
||||||
@sendRequest()
|
reject MESSAGE_UNEXPECTED
|
||||||
|
@_xhr = xhr # Used by spec to fake async Broker interaction
|
||||||
sendRequest: =>
|
@_postRequest xhr, 'proxy', @id
|
||||||
try
|
|
||||||
@request.open 'POST', @url + 'proxy'
|
|
||||||
@request.setRequestHeader('X-Session-ID', @id)
|
|
||||||
catch err
|
|
||||||
###
|
|
||||||
An exception happens here when, for example, NoScript allows the domain
|
|
||||||
on which the proxy badge runs, but not the domain to which it's trying
|
|
||||||
to make the HTTP request. The exception message is like "Component
|
|
||||||
returned failure code: 0x805e0006 [nsIXMLHttpRequest.open]" on Firefox.
|
|
||||||
###
|
|
||||||
log 'Broker: exception while connecting: ' + err.message
|
|
||||||
return
|
|
||||||
@request.send @id
|
|
||||||
|
|
||||||
|
# Assumes getClientOffer happened, and a WebRTC SDP answer has been generated.
|
||||||
|
# Sends it back to the broker, which passes it to back to the original client.
|
||||||
sendAnswer: (answer) ->
|
sendAnswer: (answer) ->
|
||||||
dbg @id + ' - Sending answer back to broker...\n'
|
dbg @id + ' - Sending answer back to broker...\n'
|
||||||
dbg answer.sdp
|
dbg answer.sdp
|
||||||
xhr = new XMLHttpRequest()
|
xhr = new XMLHttpRequest()
|
||||||
try
|
|
||||||
xhr.open 'POST', @url + 'answer'
|
|
||||||
xhr.setRequestHeader('X-Session-ID', @id)
|
|
||||||
catch err
|
|
||||||
log 'Broker: exception while connecting: ' + err.message
|
|
||||||
return
|
|
||||||
xhr.onreadystatechange = ->
|
xhr.onreadystatechange = ->
|
||||||
return if xhr.DONE != xhr.readyState
|
return if xhr.DONE != xhr.readyState
|
||||||
switch xhr.status
|
switch xhr.status
|
||||||
|
@ -87,5 +71,22 @@ class Broker
|
||||||
else
|
else
|
||||||
dbg 'Broker ERROR: Unexpected ' + xhr.status +
|
dbg 'Broker ERROR: Unexpected ' + xhr.status +
|
||||||
' - ' + xhr.statusText
|
' - ' + xhr.statusText
|
||||||
Status.set ' failure. Please refresh.'
|
snowflake.ui.setStatus ' failure. Please refresh.'
|
||||||
xhr.send JSON.stringify(answer)
|
@_postRequest xhr, 'answer', JSON.stringify(answer)
|
||||||
|
|
||||||
|
# urlSuffix for the broker is different depending on what action
|
||||||
|
# is desired.
|
||||||
|
_postRequest: (xhr, urlSuffix, payload) =>
|
||||||
|
try
|
||||||
|
xhr.open 'POST', @url + urlSuffix
|
||||||
|
xhr.setRequestHeader('X-Session-ID', @id)
|
||||||
|
catch err
|
||||||
|
###
|
||||||
|
An exception happens here when, for example, NoScript allows the domain
|
||||||
|
on which the proxy badge runs, but not the domain to which it's trying
|
||||||
|
to make the HTTP xhr. The exception message is like "Component
|
||||||
|
returned failure code: 0x805e0006 [nsIXMLHttpRequest.open]" on Firefox.
|
||||||
|
###
|
||||||
|
log 'Broker: exception while connecting: ' + err.message
|
||||||
|
return
|
||||||
|
xhr.send payload
|
||||||
|
|
|
@ -19,32 +19,74 @@ describe 'Broker', ->
|
||||||
expect(b.url).toEqual 'https://fake/'
|
expect(b.url).toEqual 'https://fake/'
|
||||||
expect(b.id).not.toBeNull()
|
expect(b.id).not.toBeNull()
|
||||||
|
|
||||||
it 'polls and promises a client offer', (done) ->
|
describe 'getClientOffer', ->
|
||||||
b = new Broker 'fake'
|
it 'polls and promises a client offer', (done) ->
|
||||||
# fake successful request
|
b = new Broker 'fake'
|
||||||
spyOn(b, 'sendRequest').and.callFake ->
|
# fake successful request and response from broker.
|
||||||
b.request.readyState = b.request.DONE
|
spyOn(b, '_postRequest').and.callFake ->
|
||||||
b.request.status = STATUS_OK
|
b._xhr.readyState = b._xhr.DONE
|
||||||
b.request.responseText = 'test'
|
b._xhr.status = STATUS_OK
|
||||||
b.request.onreadystatechange()
|
b._xhr.responseText = 'fake offer'
|
||||||
poll = b.getClientOffer()
|
b._xhr.onreadystatechange()
|
||||||
expect(poll).not.toBeNull()
|
poll = b.getClientOffer()
|
||||||
poll.then (desc) =>
|
expect(poll).not.toBeNull()
|
||||||
expect(desc).toEqual 'test'
|
expect(b._postRequest).toHaveBeenCalled()
|
||||||
done()
|
poll.then (desc) ->
|
||||||
|
expect(desc).toEqual 'fake offer'
|
||||||
|
done()
|
||||||
|
.catch ->
|
||||||
|
fail 'should not reject on STATUS_OK'
|
||||||
|
done()
|
||||||
|
|
||||||
it 'requests correctly', ->
|
it 'rejects if the broker timed-out', (done) ->
|
||||||
b = new Broker 'fake'
|
b = new Broker 'fake'
|
||||||
b.request = new XMLHttpRequest()
|
# fake timed-out request from broker
|
||||||
spyOn(b.request, 'open')
|
spyOn(b, '_postRequest').and.callFake ->
|
||||||
spyOn(b.request, 'setRequestHeader')
|
b._xhr.readyState = b._xhr.DONE
|
||||||
spyOn(b.request, 'send')
|
b._xhr.status = STATUS_GATEWAY_TIMEOUT
|
||||||
b.sendRequest()
|
b._xhr.onreadystatechange()
|
||||||
expect(b.request.open).toHaveBeenCalled()
|
poll = b.getClientOffer()
|
||||||
expect(b.request.setRequestHeader).toHaveBeenCalled()
|
expect(poll).not.toBeNull()
|
||||||
expect(b.request.send).toHaveBeenCalled()
|
expect(b._postRequest).toHaveBeenCalled()
|
||||||
|
poll.then (desc) ->
|
||||||
|
fail 'should not fulfill on GATEWAY_TIMEOUT'
|
||||||
|
done()
|
||||||
|
, (err) ->
|
||||||
|
expect(err).toBe MESSAGE_TIMEOUT
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'rejects on any other status', (done) ->
|
||||||
|
b = new Broker 'fake'
|
||||||
|
# fake timed-out request from broker
|
||||||
|
spyOn(b, '_postRequest').and.callFake ->
|
||||||
|
b._xhr.readyState = b._xhr.DONE
|
||||||
|
b._xhr.status = 1337
|
||||||
|
b._xhr.onreadystatechange()
|
||||||
|
poll = b.getClientOffer()
|
||||||
|
expect(poll).not.toBeNull()
|
||||||
|
expect(b._postRequest).toHaveBeenCalled()
|
||||||
|
poll.then (desc) ->
|
||||||
|
fail 'should not fulfill on non-OK status'
|
||||||
|
done()
|
||||||
|
, (err) ->
|
||||||
|
expect(err).toBe MESSAGE_UNEXPECTED
|
||||||
|
expect(b._xhr.status).toBe 1337
|
||||||
|
done()
|
||||||
|
|
||||||
it 'responds to the broker with answer', ->
|
it 'responds to the broker with answer', ->
|
||||||
# TODO: fix
|
|
||||||
b = new Broker 'fake'
|
b = new Broker 'fake'
|
||||||
b.sendAnswer 'foo'
|
spyOn(b, '_postRequest')
|
||||||
|
b.sendAnswer 123
|
||||||
|
expect(b._postRequest).toHaveBeenCalledWith(
|
||||||
|
jasmine.any(Object), 'answer', '123')
|
||||||
|
|
||||||
|
it 'POST XMLHttpRequests to the broker', ->
|
||||||
|
b = new Broker 'fake'
|
||||||
|
b._xhr = new XMLHttpRequest()
|
||||||
|
spyOn(b._xhr, 'open')
|
||||||
|
spyOn(b._xhr, 'setRequestHeader')
|
||||||
|
spyOn(b._xhr, 'send')
|
||||||
|
b._postRequest b._xhr, 'test', 'data'
|
||||||
|
expect(b._xhr.open).toHaveBeenCalled()
|
||||||
|
expect(b._xhr.setRequestHeader).toHaveBeenCalled()
|
||||||
|
expect(b._xhr.send).toHaveBeenCalled()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue