<?php
/**
* SeekQuarry/Yioop --
* Open Source Pure PHP Search Engine, Crawler, and Indexer
*
* Copyright (C) 2009 - 2026 Chris Pollett chris@pollett.org
*
* LICENSE:
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
* <a href="https://www.gnu.org/licenses/">https://www.gnu.org/licenses/</a>
*
* END LICENSE
*
* @author Chris Pollett chris@pollett.org
* @license https://www.gnu.org/licenses/ GPL3
* @link https://www.seekquarry.com/
* @copyright 2009 - 2026
* @filesource
*/
namespace seekquarry\yioop\tests;
use seekquarry\yioop\configs as C;
/**
* Says the file may be read for the class it holds, without starting a
* relay or taking a port. Defined before the class is named below.
*/
if (!defined(
"seekquarry\\yioop\\executables\\TURN_SERVER_TEST_LOAD")) {
define("seekquarry\\yioop\\executables\\TURN_SERVER_TEST_LOAD",
true);
}
use seekquarry\yioop\executables\TurnServer;
use seekquarry\yioop\library\UnitTest;
use seekquarry\atto\TurnSite;
/**
* RecordingTurnSite is a relay whose one added method reaches the
* protected logLine helper, so a test can call it the way the relay's
* own request handlers do and see what onLog was given. It starts no
* listener and takes no port; it exists only to exercise the log path.
*/
class RecordingTurnSite extends TurnSite
{
/**
* sayLine hands one line to the relay's log path, standing in for
* the request handlers that call the protected helper when a
* datagram arrives or an allocation is made.
*
* @param string $line the sentence to record
* @return void
*/
public function sayLine($line)
{
$this->logLine($line);
}
/**
* bindForTest reaches the protected bindRelaySocket so a case can
* see which host the relay would hand a browser for an allocation,
* the loopback or the advertised public address, without standing up
* a whole allocation. It closes the socket the bind took so the case
* leaves no port held.
*
* @param string $client_host address a client is treated as coming
* from, which picks the address family
* @return string the host the relay would put in the relayed
* address, or the empty string where no port was free
*/
public function bindForTest($client_host)
{
list($sock, $host, $port) =
$this->bindRelaySocket($client_host);
if ($sock === false) {
return "";
}
fclose($sock);
return $host;
}
}
/**
* Tests for TurnServer::deriveRelayConfig, which turns the site's
* settings into what the relay is told to do. The cases walk the range
* of ports the relay may hand out, since a range written the wrong way
* round, or with one port, or with none, is the part a site owner is
* most likely to get wrong, and a relay handed such a range would
* either refuse every call or take a port it was never meant to.
*
* @author Chris Pollett
*/
class TurnServerTest extends UnitTest
{
/**
* The cases stand on their own; nothing is set up for them.
*/
public function setUp()
{
}
/**
* Nothing is left behind by these cases.
*/
public function tearDown()
{
}
/**
* Checks that the address, port, name, secret and realm a site
* sets are handed to the relay as they stand, with the port read
* as a number so a setting typed as text still binds.
*/
public function whatTheSiteSetsIsWhatTheRelayIsToldTestCase()
{
$told = TurnServer::deriveRelayConfig("127.0.0.1", "3478",
"yioop", "a secret", "yioop", "60000-60100");
$this->assertEqual("127.0.0.1", $told["BIND"],
"the relay is told the address the site set");
$this->assertEqual(3478, $told["TURN_PORT"],
"and the port as a number, so a setting typed as text binds");
$this->assertEqual(["yioop" => "a secret"], $told["users"],
"the name and secret a browser gives are handed over");
$this->assertEqual("yioop", $told["realm"],
"as is the realm the relay names itself by");
}
/**
* Checks that a range of ports written lowest first is handed over
* as two numbers.
*/
public function rangeOfPortsIsReadAsTwoNumbersTestCase()
{
$told = TurnServer::deriveRelayConfig("127.0.0.1", 3478,
"yioop", "", "yioop", "60000-60100");
$this->assertEqual(60000, $told["low"] ?? 0,
"the lowest port the relay may hand out");
$this->assertEqual(60100, $told["high"] ?? 0,
"and the highest");
}
/**
* Checks that a range the relay cannot use leaves it with none: one
* written the wrong way round, one with a single port, and one left
* empty. A relay given such a range would hand out a port outside
* whatever the site opened in its router.
*/
public function rangeTheRelayCannotUseLeavesItNoneTestCase()
{
foreach (["60100-60000", "60000", "", "-", "0-60100"] as $said) {
$told = TurnServer::deriveRelayConfig("127.0.0.1", 3478,
"yioop", "", "yioop", $said);
$this->assertTrue(!isset($told["low"]),
"a range of '$said' leaves the relay choosing for itself");
}
}
/**
* relaySendsALineToWhereverOnLogNamedTestCase checks that a line
* the relay would write about a datagram reaches the function handed
* to onLog, so the daemon can put each such line in its log and a
* browser that reached the relay leaves a trace.
*/
public function relaySendsALineToWhereverOnLogNamedTestCase()
{
$relay = new RecordingTurnSite();
$seen = [];
$relay->onLog(function ($line) use (&$seen) {
$seen[] = $line;
});
$relay->sayLine("STUN/TURN datagram from 10.0.0.1:5555");
$this->assertEqual(1, count($seen),
"the line reaches the function onLog was given");
$this->assertEqual("STUN/TURN datagram from 10.0.0.1:5555",
$seen[0], "and reaches it unchanged");
}
/**
* relayStaysSilentWithNoLogSetTestCase checks that a relay given
* no place to log makes no line and does not fail, so logging is a
* thing a caller turns on rather than something the relay forces.
*/
public function relayStaysSilentWithNoLogSetTestCase()
{
$relay = new RecordingTurnSite();
$relay->sayLine("this line has nowhere to go");
$this->assertTrue(true,
"a relay with no log callback records nothing and does " .
"not fail");
}
/**
* relayHandsOutThePublicAddressWhenSetTestCase checks that a
* relay told a public address puts that address in an allocation
* rather than the loopback, so the other browser is given an address
* it can reach. A loopback address there is why a call failed even
* once both browsers reached the relay.
*/
public function relayHandsOutThePublicAddressWhenSetTestCase()
{
$relay = new RecordingTurnSite();
$relay->relayPortRange(60000, 60100);
$relay->relayAddress("203.0.113.7");
$host = $relay->bindForTest("198.51.100.9");
$this->assertEqual("203.0.113.7", $host,
"the relay hands out the public address it was told");
}
/**
* relayKeepsToLoopbackWithNoAddressSetTestCase checks that a
* relay told no public address keeps to the loopback, so a site that
* has set nothing is no worse off than before and a call between two
* browsers on one machine still works.
*/
public function relayKeepsToLoopbackWithNoAddressSetTestCase()
{
$relay = new RecordingTurnSite();
$relay->relayPortRange(60000, 60100);
$host = $relay->bindForTest("198.51.100.9");
$this->assertEqual("127.0.0.1", $host,
"the relay keeps to the loopback when told no address");
}
/**
* betweenWorkRunsAtItsPaceNotPerPacketTestCase checks that
* betweenWorkDue says yes once per interval rather than every time
* it is asked, since the loop asks once per datagram when traffic
* flows and the work behind it writes files each run: run per
* packet it slowed the relay path until a call's connection checks
* crawled.
*/
public function betweenWorkRunsAtItsPaceNotPerPacketTestCase()
{
$relay = new RecordingTurnSite();
$relay->betweenRounds(function () {
}, 5.0);
$moment = 1000000.0;
$this->assertTrue($relay->betweenWorkDue($moment),
"the first ask after the interval is due");
$this->assertTrue(!$relay->betweenWorkDue($moment + 0.001),
"an ask a packet later is not due");
$this->assertTrue(!$relay->betweenWorkDue($moment + 4.9),
"an ask just inside the interval is not due");
$this->assertTrue($relay->betweenWorkDue($moment + 5.0),
"an ask at the interval is due again");
}
}