rtckit/php-sdp

View on GitHub
src/Grammar.php

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
<?php

declare(strict_types = 1);

namespace RTCKit\SDP;

use stdClass;

/**
 * Grammar class
 */
class Grammar
{
    /** @var array<string, array<int<0, max>, array<string, mixed>>> */
    private array $rules;

    /** @var array<string, array<int<0, max>, array<string, mixed>>> */
    private array $compiled;

    public function __construct()
    {
        $this->rules = [
            'v' => [
                [
                    'name' => 'version',
                    'reg' => '^(\d*)$',
                ],
            ],
            'o' => [
                [
                    'name' => 'origin',
                    'reg' => '^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)',
                    'names' => ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'],
                    'format' => '%s %s %d %s IP%d %s',
                ],
            ],
            's' => [
                ['name' => 'name'],
            ],
            'i' => [
                ['name' => 'description'],
            ],
            'u' => [
                ['name' => 'uri'],
            ],
            'e' => [
                ['name' => 'email'],
            ],
            'p' => [
                ['name' => 'phone'],
            ],
            'z' => [
                ['name' => 'timezones'],
            ],
            'r' => [
                ['name' => 'repeats'],
            ],
            't' => [
                [
                    'name' => 'timing',
                    'reg' => '^(\d*) (\d*)',
                    'names' => ['start', 'stop'],
                    'format' => '%d %d',
                ],
            ],
            'c' => [
                [
                    'name' => 'connection',
                    'reg' => '^IN IP(\d) (\S*)',
                    'names' => ['version', 'ip'],
                    'format' => 'IN IP%d %s',
                ],
            ],
            'b' => [
                [
                    'push' => 'bandwidth',
                    'reg' => '^(TIAS|AS|CT|RR|RS):(\d*)',
                    'names' => ['type', 'limit'],
                    'format' => '%s:%s',
                ],
            ],
            'm' => [
                [
                    'reg' => '^(\w*) (\d*) ([\w\/]*)(?: (.*))?',
                    'names' => ['type', 'port', 'protocol', 'payloads'],
                    'format' => '%s %d %s %s',
                ],
            ],
            'a' => [
                [
                    'push' => 'rtp',
                    'reg' => '^rtpmap:(\d*) ([\w\-.]*)(?:\s*\/(\d*)(?:\s*\/(\S*))?)?',
                    'names' => ['payload', 'codec', 'rate', 'encoding'],
                    'format' => function (stdClass $o): string {
                        return isset($o->encoding)
                            ? 'rtpmap:%d %s/%s/%s'
                            : (isset($o->rate) ? 'rtpmap:%d %s/%s' : 'rtpmap:%d %s');
                    },
                ],
                [
                    'push' => 'fmtp',
                    'reg' => '^fmtp:(\d*) ([\S| ]*)',
                    'names' => ['payload', 'config'],
                    'format' => 'fmtp:%d %s',
                ],
                [
                    'name' => 'control',
                    'reg' => '^control:(.*)',
                    'format' => 'control:%s',
                ],
                [
                    'name' => 'rtcp',
                    'reg' => '^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?',
                    'names' => ['port', 'netType', 'ipVer', 'address'],
                    'format' => function (stdClass $o): string {
                        return isset($o->address) ? 'rtcp:%d %s IP%d %s' : 'rtcp:%d';
                    },
                ],
                [
                    'push' => 'rtcpFbTrrInt',
                    'reg' => '^rtcp-fb:(\*|\d*) trr-int (\d*)',
                    'names' => ['payload', 'value'],
                    'format' => 'rtcp-fb:%s trr-int %d',
                ],
                [
                    'push' => 'rtcpFb',
                    'reg' => '^rtcp-fb:(\*|\d*) ([\w\-_]*)(?: ([\w\-_]*))?',
                    'names' => ['payload', 'type', 'subtype'],
                    'format' => function (stdClass $o): string {
                        return isset($o->subtype) ? 'rtcp-fb:%s %s %s' : 'rtcp-fb:%s %s';
                    },
                ],
                [
                    'push' => 'ext',
                    'reg' => '^extmap:(\d+)(?:\/(\w+))?(?: (urn:ietf:params:rtp-hdrext:encrypt))? (\S*)(?: (\S*))?',
                    'names' => ['value', 'direction', 'encrypt-uri', 'uri', 'config'],
                    'format' => function (stdClass $o): string {
                        return 'extmap:%d'
                            . (isset($o->direction) ? '/%s' : '')
                            . (isset($o->{'encrypt-uri'}) ? ' %s' : '')
                            . ' %s'
                            . (isset($o->config) ? ' %s' : '');
                    },
                ],
                [
                    'name' => 'extmapAllowMixed',
                    'reg' => '^(extmap-allow-mixed)',
                ],
                [
                    'push' => 'crypto',
                    'reg' => '^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?',
                    'names' => ['id', 'suite', 'config', 'sessionConfig'],
                    'format' => function (stdClass $o): string {
                        return isset($o->sessionConfig) ? 'crypto:%d %s %s %s' : 'crypto:%d %s %s';
                    },
                ],
                [
                    'name' => 'setup',
                    'reg' => '^setup:(\w*)',
                    'format' => 'setup:%s',
                ],
                [
                    'name' => 'connectionType',
                    'reg' => '^connection:(new|existing)',
                    'format' => 'connection:%s',
                ],
                [
                    'name' => 'msid',
                    'reg' => '^msid:(.*)',
                    'format' => 'msid:%s',
                ],
                [
                    'name' => 'ptime',
                    'reg' => '^ptime:(\d*(?:\.\d*)*)',
                    'format' => function (int|float $o): string {
                        return is_int($o) ? 'ptime:%d' : 'ptime:%g';
                    },
                ],
                [
                    'name' => 'maxptime',
                    'reg' => '^maxptime:(\d*(?:\.\d*)*)',
                    'format' => 'maxptime:%d',
                ],
                [
                    'name' => 'direction',
                    'reg' => '^(sendrecv|recvonly|sendonly|inactive)',
                ],
                [
                    'name' => 'icelite',
                    'reg' => '^(ice-lite)',
                ],
                [
                    'name' => 'iceUfrag',
                    'reg' => '^ice-ufrag:(\S*)',
                    'format' => 'ice-ufrag:%s',
                ],
                [
                    'name' => 'icePwd',
                    'reg' => '^ice-pwd:(\S*)',
                    'format' => 'ice-pwd:%s',
                ],
                [
                    'name' => 'fingerprint',
                    'reg' => '^fingerprint:(\S*) (\S*)',
                    'names' => ['type', 'hash'],
                    'format' => 'fingerprint:%s %s',
                ],
                [
                    'push' => 'candidates',
                    'reg' => '^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: tcptype (\S*))?(?: generation (\d*))?(?: network-id (\d*))?(?: network-cost (\d*))?',
                    'names' => [
                        'foundation',
                        'component',
                        'protocol',
                        'priority',
                        'ip',
                        'port',
                        'type',
                        'raddr',
                        'rport',
                        'tcptype',
                        'generation',
                        'network-id',
                        'network-cost',
                    ],
                    'format' => function (stdClass $o): string {
                        $result = 'candidate:%s %d %s %d %s %d typ %s';
                        $result .= isset($o->raddr) ? ' raddr %s rport %d' : '';
                        $result .= isset($o->tcptype) ? ' tcptype %s' : '';
                        $result .= isset($o->generation) ? ' generation %d' : '';
                        $result .= isset($o->{'network-id'}) ? ' network-id %d' : '';
                        $result .= isset($o->{'network-cost'}) ? ' network-cost %d' : '';

                        return $result;
                    },
                ],
                [
                    'name' => 'endOfCandidates',
                    'reg' => '^(end-of-candidates)',
                ],
                [
                    'name' => 'remoteCandidates',
                    'reg' => '^remote-candidates:(.*)',
                    'format' => 'remote-candidates:%s',
                ],
                [
                    'name' => 'iceOptions',
                    'reg' => '^ice-options:(\S*)',
                    'format' => 'ice-options:%s',
                ],
                [
                    'push' => 'ssrcs',
                    'reg' => '^ssrc:(\d*) ([\w_-]*)(?::(.*))?',
                    'names' => ['id', 'attribute', 'value'],
                    'format' => function (stdClass $o): string {
                        $result = 'ssrc:%d';
                        $result .= isset($o->attribute) ? ' %s' : '';
                        $result .= isset($o->value) ? ':%s' : '';

                        return $result;
                    },
                ],
                [
                    'push' => 'ssrcGroups',
                    'reg' => '^ssrc-group:([\x21\x23\x24\x25\x26\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]*) (.*)',
                    'names' => ['semantics', 'ssrcs'],
                    'format' => 'ssrc-group:%s %s',
                ],
                [
                    'name' => 'msidSemantic',
                    'reg' => '^msid-semantic:\s?(\w*) (\S*)',
                    'names' => ['semantic', 'token'],
                    'format' => 'msid-semantic: %s %s',
                ],
                [
                    'push' => 'groups',
                    'reg' => '^group:(\w*) (.*)',
                    'names' => ['type', 'mids'],
                    'format' => 'group:%s %s',
                ],
                [
                    'name' => 'rtcpMux',
                    'reg' => '^(rtcp-mux)',
                ],
                [
                    'name' => 'rtcpRsize',
                    'reg' => '^(rtcp-rsize)',
                ],
                [
                    'name' => 'sctpmap',
                    'reg' => '^sctpmap:([\w_\/]*) (\S*)(?: (\S*))?',
                    'names' => ['sctpmapNumber', 'app', 'maxMessageSize'],
                    'format' => function (stdClass $o): string {
                        return isset($o->maxMessageSize) ? 'sctpmap:%s %s %s' : 'sctpmap:%s %s';
                    },
                ],
                [
                    'name' => 'xGoogleFlag',
                    'reg' => '^x-google-flag:([^\s]*)',
                    'format' => 'x-google-flag:%s',
                ],
                [
                    'push' => 'rids',
                    'reg' => '^rid:([\d\w]+) (\w+)(?: ([\S| ]*))?',
                    'names' => ['id', 'direction', 'params'],
                    'format' => function (stdClass $o): string {
                        return isset($o->params) ? 'rid:%s %s %s' : 'rid:%s %s';
                    },
                ],
            [
                    'push' => 'imageattrs',
                    'reg' => '^imageattr:(\\d+|\\*)[\\s\\t]+(send|recv)[\\s\\t]+(\\*|\\[\\S+\\](?:[\\s\\t]+\\[\\S+\\])*)(?:[\\s\\t]+(recv|send)[\\s\\t]+(\\*|\\[\\S+\\](?:[\\s\\t]+\\[\\S+\\])*))?$',
                    'names' => ['pt', 'dir1', 'attrs1', 'dir2', 'attrs2'],
                    'format' => function (stdClass $o): string {
                        $result = 'imageattr:%s %s %s';
                        $result .= isset($o->dir2) ? ' %s %s' : '';

                        return $result;
                    },
                ],
                [
                    'name' => 'simulcast',
                    'reg' => '^simulcast:(send|recv) ([a-zA-Z0-9\\-_~;,]+)(?:\\s?(send|recv) ([a-zA-Z0-9\\-_~;,]+))?$',
                    'names' => ['dir1', 'list1', 'dir2', 'list2'],
                    'format' => function (stdClass $o): string {
                        $result = 'simulcast:%s %s';
                        $result .= isset($o->dir2) ? ' %s %s' : '';

                        return $result;
                    },
                ],
                [
                    'name' => 'simulcast_03',
                    'reg' => '^simulcast:[\s\t]+([\S+\s\t]+)$',
                    'names' => ['value'],
                    'format' => 'simulcast: %s',
                ],
                [
                    'name' => 'framerate',
                    'reg' => '^framerate:(\d+(?:$|\.\d+))',
                    'format' => 'framerate:%s',
                ],
                [
                    'name' => 'sourceFilter',
                    'reg' => '^source-filter: *(excl|incl) (\S*) (IP4|IP6|\*) (\S*) (.*)',
                    'names' => ['filterMode', 'netType', 'addressTypes', 'destAddress', 'srcList'],
                    'format' => 'source-filter: %s %s %s %s %s',
                ],
                [
                    'name' => 'bundleOnly',
                    'reg' => '^(bundle-only)',
                ],
                [
                    'name' => 'label',
                    'reg' => '^label:(.+)',
                    'format' => 'label:%s',
                ],
                [
                    'name' => 'sctpPort',
                    'reg' => '^sctp-port:(\d+)$',
                    'format' => 'sctp-port:%s',
                ],
                [
                    'name' => 'maxMessageSize',
                    'reg' => '^max-message-size:(\d+)$',
                    'format' => 'max-message-size:%s',
                ],
                [
                    'push' => 'tsRefClocks',
                    'reg' => '^ts-refclk:([^\s=]*)(?:=(\S*))?',
                    'names' => ['clksrc', 'clksrcExt'],
                    'format' => function (stdClass $o): string {
                        $result = 'ts-refclk:%s';
                        $result .= isset($o->clksrcExt) ? '=%s' : '';

                        return $result;
                    },
                ],
                [
                    'name' => 'mediaClk',
                    'reg' => '^mediaclk:(?:id=(\S*))? *([^\s=]*)(?:=(\S*))?(?: *rate=(\d+)\/(\d+))?',
                    'names' => ['id', 'mediaClockName', 'mediaClockValue', 'rateNumerator', 'rateDenominator'],
                    'format' => function (stdClass $o): string {
                        $result = 'mediaclk:';
                        $result .= isset($o->id) ? 'id=%s %s' : '%s';
                        $result .= isset($o->mediaClockValue) ? '=%s' : '';
                        $result .= isset($o->rateNumerator) ? ' rate=%s' : '';
                        $result .= isset($o->rateDenominator) ? '/%s' : '';

                        return $result;
                    },
                ],
                [
                    'name' => 'keywords',
                    'reg' => '^keywds:(.+)$',
                    'format' => 'keywds:%s',
                ],
                [
                    'name' => 'content',
                    'reg' => '^content:(.+)$',
                    'format' => 'content:%s',
                ],
                [
                    'name' => 'bfcpFloorCtrl',
                    'reg' => '^floorctrl:(c-only|s-only|c-s)',
                    'format' => 'floorctrl:%s',
                ],
                [
                    'name' => 'bfcpConfId',
                    'reg' => '^confid:(\d+)',
                    'format' => 'confid:%s',
                ],
                [
                    'name' => 'bfcpUserId',
                    'reg' => '^userid:(\d+)',
                    'format' => 'userid:%s',
                ],
                [
                    'name' => 'bfcpFloorId',
                    'reg' => '^floorid:(.+) (?:m-stream|mstrm):(.+)',
                    'names' => ['id', 'mStream'],
                    'format' => 'floorid:%s mstrm:%s',
                ],
                [
                    'name' => 'mid',
                    'reg' => '^mid:([^\s]*)',
                    'format' => 'mid:%s',
                ],
                [
                    'push' => 'invalid',
                    'names' => ['value'],
                ],
            ],
        ];
    }

    /**
     * Produces a usable SDP parsing/serializing grammar
     *
     * @return array<string, array<int<0, max>, array<string, mixed>>>
     */
    public function compile(): array
    {
        if (isset($this->compiled)) {
            return $this->compiled;
        }

        $this->compiled = $this->rules;

        foreach ($this->compiled as $key => $objs) {
            foreach ($objs as $objKey => $obj) {
                if (!isset($obj['reg'])) {
                    $this->compiled[$key][$objKey]['reg'] = '(.*)';
                }

                if (!isset($obj['format'])) {
                    $this->compiled[$key][$objKey]['format'] = '%s';
                }
            }
        }

        return $this->compiled;
    }
}