snapshot of https://github.com/pion/webrtc.git (main, 2026-09-15; blobs > 131072B omitted)
Some checks failed
REUSE Compliance Check / lint (push) Failing after 0s
Browser E2E / Test (push) Failing after 3s
Test / test (1.24) (push) Failing after 0s
Test / test-i386 (1.25) (push) Failing after 0s
Test / test-i386 (1.24) (push) Failing after 0s
Test / test-windows (1.25) (push) Failing after 0s
Test / test-windows (1.24) (push) Failing after 0s
Test / test (1.25) (push) Failing after 0s
Examples Tests / Test (push) Failing after 3s
Test / test-macos (1.25) (push) Failing after 0s
Test / test-macos (1.24) (push) Failing after 0s
Go mod tidy / tidy (push) Failing after 0s
Test / test-wasm (push) Failing after 0s
CodeQL / analyze (push) Failing after 1s
Fuzz / fuzz (push) Failing after 0s
Some checks failed
REUSE Compliance Check / lint (push) Failing after 0s
Browser E2E / Test (push) Failing after 3s
Test / test (1.24) (push) Failing after 0s
Test / test-i386 (1.25) (push) Failing after 0s
Test / test-i386 (1.24) (push) Failing after 0s
Test / test-windows (1.25) (push) Failing after 0s
Test / test-windows (1.24) (push) Failing after 0s
Test / test (1.25) (push) Failing after 0s
Examples Tests / Test (push) Failing after 3s
Test / test-macos (1.25) (push) Failing after 0s
Test / test-macos (1.24) (push) Failing after 0s
Go mod tidy / tidy (push) Failing after 0s
Test / test-wasm (push) Failing after 0s
CodeQL / analyze (push) Failing after 1s
Fuzz / fuzz (push) Failing after 0s
This commit is contained in:
768
internal/fmtp/fmtp_test.go
Normal file
768
internal/fmtp/fmtp_test.go
Normal file
@@ -0,0 +1,768 @@
|
||||
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package fmtp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseParameters(t *testing.T) {
|
||||
for _, ca := range []struct {
|
||||
name string
|
||||
line string
|
||||
parameters map[string]string
|
||||
}{
|
||||
{
|
||||
"one param",
|
||||
"key-name=value",
|
||||
map[string]string{
|
||||
"key-name": "value",
|
||||
},
|
||||
},
|
||||
{
|
||||
"one param with white spaces",
|
||||
"\tkey-name=value ",
|
||||
map[string]string{
|
||||
"key-name": "value",
|
||||
},
|
||||
},
|
||||
{
|
||||
"two params",
|
||||
"key-name=value;key2=value2",
|
||||
map[string]string{
|
||||
"key-name": "value",
|
||||
"key2": "value2",
|
||||
},
|
||||
},
|
||||
{
|
||||
"two params with white spaces",
|
||||
"key-name=value; \n\tkey2=value2 ",
|
||||
map[string]string{
|
||||
"key-name": "value",
|
||||
"key2": "value2",
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
parameters := parseParameters(ca.line)
|
||||
assert.Equal(t, ca.parameters, parameters)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
for _, ca := range []struct {
|
||||
name string
|
||||
mimeType string
|
||||
clockRate uint32
|
||||
channels uint16
|
||||
line string
|
||||
expected FMTP
|
||||
}{
|
||||
{
|
||||
"generic",
|
||||
"generic",
|
||||
90000,
|
||||
2,
|
||||
"key-name=value",
|
||||
&genericFMTP{
|
||||
mimeType: "generic",
|
||||
clockRate: 90000,
|
||||
channels: 2,
|
||||
parameters: map[string]string{
|
||||
"key-name": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"generic case normalization",
|
||||
"generic",
|
||||
90000,
|
||||
2,
|
||||
"Key=value",
|
||||
&genericFMTP{
|
||||
mimeType: "generic",
|
||||
clockRate: 90000,
|
||||
channels: 2,
|
||||
parameters: map[string]string{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"h264",
|
||||
"video/h264",
|
||||
90000,
|
||||
0,
|
||||
"key-name=value",
|
||||
&h264FMTP{
|
||||
parameters: map[string]string{
|
||||
"key-name": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"h265",
|
||||
"video/h265",
|
||||
90000,
|
||||
0,
|
||||
"key-name=value",
|
||||
&h265FMTP{
|
||||
parameters: map[string]string{
|
||||
"key-name": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"vp9",
|
||||
"video/vp9",
|
||||
90000,
|
||||
0,
|
||||
"key-name=value",
|
||||
&vp9FMTP{
|
||||
parameters: map[string]string{
|
||||
"key-name": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"av1",
|
||||
"video/av1",
|
||||
90000,
|
||||
0,
|
||||
"key-name=value",
|
||||
&av1FMTP{
|
||||
parameters: map[string]string{
|
||||
"key-name": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
f := Parse(ca.mimeType, ca.clockRate, ca.channels, ca.line)
|
||||
|
||||
assert.Equal(t, ca.expected, f)
|
||||
assert.Equal(t, ca.mimeType, f.MimeType())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatch(t *testing.T) { //nolint:maintidx
|
||||
consistString := map[bool]string{true: "consist", false: "inconsist"}
|
||||
parseH265 := func(line string) FMTP {
|
||||
return Parse("video/h265", 90000, 0, line)
|
||||
}
|
||||
|
||||
for _, ca := range []struct {
|
||||
name string
|
||||
a FMTP
|
||||
b FMTP
|
||||
consist bool
|
||||
}{
|
||||
{
|
||||
"generic equal",
|
||||
&genericFMTP{
|
||||
mimeType: "generic",
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
&genericFMTP{
|
||||
mimeType: "generic",
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"generic one extra param",
|
||||
&genericFMTP{
|
||||
mimeType: "generic",
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
&genericFMTP{
|
||||
mimeType: "generic",
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
"key4": "value4",
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"generic inferred channels",
|
||||
&genericFMTP{
|
||||
mimeType: "generic",
|
||||
channels: 1,
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
&genericFMTP{
|
||||
mimeType: "generic",
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"generic inconsistent different kind",
|
||||
&genericFMTP{
|
||||
mimeType: "generic",
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
&h264FMTP{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"generic inconsistent different mime type",
|
||||
&genericFMTP{
|
||||
mimeType: "generic1",
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
&genericFMTP{
|
||||
mimeType: "generic2",
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"generic inconsistent different clock rate",
|
||||
&genericFMTP{
|
||||
mimeType: "generic",
|
||||
clockRate: 90000,
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
&genericFMTP{
|
||||
mimeType: "generic",
|
||||
clockRate: 48000,
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"generic inconsistent different channels",
|
||||
&genericFMTP{
|
||||
mimeType: "generic",
|
||||
clockRate: 90000,
|
||||
channels: 2,
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
&genericFMTP{
|
||||
mimeType: "generic",
|
||||
clockRate: 90000,
|
||||
channels: 1,
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"generic inconsistent different parameters",
|
||||
&genericFMTP{
|
||||
mimeType: "generic",
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
&genericFMTP{
|
||||
mimeType: "generic",
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "different_value",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"h264 equal",
|
||||
&h264FMTP{
|
||||
parameters: map[string]string{
|
||||
"level-asymmetry-allowed": "1",
|
||||
"packetization-mode": "1",
|
||||
"profile-level-id": "42e01f",
|
||||
},
|
||||
},
|
||||
&h264FMTP{
|
||||
parameters: map[string]string{
|
||||
"level-asymmetry-allowed": "1",
|
||||
"packetization-mode": "1",
|
||||
"profile-level-id": "42e01f",
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"h264 one extra param",
|
||||
&h264FMTP{
|
||||
parameters: map[string]string{
|
||||
"level-asymmetry-allowed": "1",
|
||||
"packetization-mode": "1",
|
||||
"profile-level-id": "42e01f",
|
||||
},
|
||||
},
|
||||
&h264FMTP{
|
||||
parameters: map[string]string{
|
||||
"packetization-mode": "1",
|
||||
"profile-level-id": "42e01f",
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"h264 different profile level ids version",
|
||||
&h264FMTP{
|
||||
parameters: map[string]string{
|
||||
"packetization-mode": "1",
|
||||
"profile-level-id": "42e01f",
|
||||
},
|
||||
},
|
||||
&h264FMTP{
|
||||
parameters: map[string]string{
|
||||
"packetization-mode": "1",
|
||||
"profile-level-id": "42e029",
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"h264 inconsistent different kind",
|
||||
&h264FMTP{
|
||||
parameters: map[string]string{
|
||||
"packetization-mode": "0",
|
||||
"profile-level-id": "42e01f",
|
||||
},
|
||||
},
|
||||
&genericFMTP{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"h264 inconsistent different parameters",
|
||||
&h264FMTP{
|
||||
parameters: map[string]string{
|
||||
"packetization-mode": "0",
|
||||
"profile-level-id": "42e01f",
|
||||
},
|
||||
},
|
||||
&h264FMTP{
|
||||
parameters: map[string]string{
|
||||
"packetization-mode": "1",
|
||||
"profile-level-id": "42e01f",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"h264 inconsistent missing packetization mode",
|
||||
&h264FMTP{
|
||||
parameters: map[string]string{
|
||||
"packetization-mode": "0",
|
||||
"profile-level-id": "42e01f",
|
||||
},
|
||||
},
|
||||
&h264FMTP{
|
||||
parameters: map[string]string{
|
||||
"profile-level-id": "42e01f",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"h264 inconsistent missing profile level id",
|
||||
&h264FMTP{
|
||||
parameters: map[string]string{
|
||||
"packetization-mode": "1",
|
||||
"profile-level-id": "42e01f",
|
||||
},
|
||||
},
|
||||
&h264FMTP{
|
||||
parameters: map[string]string{
|
||||
"packetization-mode": "1",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"h264 inconsistent invalid profile level id",
|
||||
&h264FMTP{
|
||||
parameters: map[string]string{
|
||||
"packetization-mode": "1",
|
||||
"profile-level-id": "42e029",
|
||||
},
|
||||
},
|
||||
&h264FMTP{
|
||||
parameters: map[string]string{
|
||||
"packetization-mode": "1",
|
||||
"profile-level-id": "41e029",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"h265 downgraded level",
|
||||
parseH265("level-id=186;profile-id=1;tier-flag=0;tx-mode=SRST"),
|
||||
parseH265("level-id=93;profile-id=1;tier-flag=0;tx-mode=SRST"),
|
||||
true,
|
||||
},
|
||||
{
|
||||
"h265 inferred defaults and ignored sprop parameter",
|
||||
parseH265(""),
|
||||
parseH265("profile-id=1;tier-flag=0;tx-mode=srst;sprop-vps=ignored"),
|
||||
true,
|
||||
},
|
||||
{
|
||||
"h265 inconsistent different kind",
|
||||
parseH265(""),
|
||||
&genericFMTP{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"h265 inconsistent different profile",
|
||||
parseH265("profile-id=1"),
|
||||
parseH265("profile-id=2"),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"h265 inconsistent different tier",
|
||||
parseH265("tier-flag=0"),
|
||||
parseH265("tier-flag=1"),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"h265 inconsistent different transmission mode",
|
||||
parseH265("tx-mode=SRST"),
|
||||
parseH265("tx-mode=MRST"),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"vp9 equal",
|
||||
&vp9FMTP{
|
||||
parameters: map[string]string{
|
||||
"profile-id": "1",
|
||||
},
|
||||
},
|
||||
&vp9FMTP{
|
||||
parameters: map[string]string{
|
||||
"profile-id": "1",
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"vp9 missing profile",
|
||||
&vp9FMTP{
|
||||
parameters: map[string]string{},
|
||||
},
|
||||
&vp9FMTP{
|
||||
parameters: map[string]string{},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"vp9 inferred profile",
|
||||
&vp9FMTP{
|
||||
parameters: map[string]string{
|
||||
"profile-id": "0",
|
||||
},
|
||||
},
|
||||
&vp9FMTP{
|
||||
parameters: map[string]string{},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"vp9 inconsistent different kind",
|
||||
&vp9FMTP{
|
||||
parameters: map[string]string{
|
||||
"profile-id": "0",
|
||||
},
|
||||
},
|
||||
&genericFMTP{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"vp9 inconsistent different profile",
|
||||
&vp9FMTP{
|
||||
parameters: map[string]string{
|
||||
"profile-id": "0",
|
||||
},
|
||||
},
|
||||
&vp9FMTP{
|
||||
parameters: map[string]string{
|
||||
"profile-id": "1",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"vp9 inconsistent different inferred profile",
|
||||
&vp9FMTP{
|
||||
parameters: map[string]string{},
|
||||
},
|
||||
&vp9FMTP{
|
||||
parameters: map[string]string{
|
||||
"profile-id": "1",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"av1 equal",
|
||||
&av1FMTP{
|
||||
parameters: map[string]string{
|
||||
"profile": "1",
|
||||
},
|
||||
},
|
||||
&av1FMTP{
|
||||
parameters: map[string]string{
|
||||
"profile": "1",
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"av1 missing profile",
|
||||
&av1FMTP{
|
||||
parameters: map[string]string{},
|
||||
},
|
||||
&av1FMTP{
|
||||
parameters: map[string]string{},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"av1 inferred profile",
|
||||
&av1FMTP{
|
||||
parameters: map[string]string{
|
||||
"profile": "0",
|
||||
},
|
||||
},
|
||||
&av1FMTP{
|
||||
parameters: map[string]string{},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"av1 inconsistent different kind",
|
||||
&av1FMTP{
|
||||
parameters: map[string]string{
|
||||
"profile": "0",
|
||||
},
|
||||
},
|
||||
&genericFMTP{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"av1 inconsistent different profile",
|
||||
&av1FMTP{
|
||||
parameters: map[string]string{
|
||||
"profile": "0",
|
||||
},
|
||||
},
|
||||
&av1FMTP{
|
||||
parameters: map[string]string{
|
||||
"profile": "1",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"av1 inconsistent different inferred profile",
|
||||
&av1FMTP{
|
||||
parameters: map[string]string{},
|
||||
},
|
||||
&av1FMTP{
|
||||
parameters: map[string]string{
|
||||
"profile": "1",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"pcmu channels",
|
||||
&genericFMTP{
|
||||
mimeType: "audio/pcmu",
|
||||
clockRate: 8000,
|
||||
channels: 0,
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
&genericFMTP{
|
||||
mimeType: "audio/pcmu",
|
||||
clockRate: 8000,
|
||||
channels: 1,
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"pcmu inconsistent channels",
|
||||
&genericFMTP{
|
||||
mimeType: "audio/pcmu",
|
||||
clockRate: 8000,
|
||||
channels: 0,
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
&genericFMTP{
|
||||
mimeType: "audio/pcmu",
|
||||
clockRate: 8000,
|
||||
channels: 2,
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"pcmu clockrate",
|
||||
&genericFMTP{
|
||||
mimeType: "audio/pcmu",
|
||||
clockRate: 0,
|
||||
channels: 0,
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
&genericFMTP{
|
||||
mimeType: "audio/pcmu",
|
||||
clockRate: 8000,
|
||||
channels: 0,
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"pcmu inconsistent clockrate",
|
||||
&genericFMTP{
|
||||
mimeType: "audio/pcmu",
|
||||
clockRate: 0,
|
||||
channels: 0,
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
&genericFMTP{
|
||||
mimeType: "audio/pcmu",
|
||||
clockRate: 16000,
|
||||
channels: 0,
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"opus clockrate",
|
||||
&genericFMTP{
|
||||
mimeType: "audio/opus",
|
||||
clockRate: 0,
|
||||
channels: 0,
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
&genericFMTP{
|
||||
mimeType: "audio/opus",
|
||||
clockRate: 48000,
|
||||
channels: 2,
|
||||
parameters: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
} {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
c := ca.a.Match(ca.b)
|
||||
assert.Equal(t, ca.consist, c)
|
||||
assert.Equal(
|
||||
t, ca.consist, c,
|
||||
"'%s' and '%s' are expected to be %s, but treated as %s",
|
||||
ca.a, ca.b, consistString[ca.consist], consistString[c],
|
||||
)
|
||||
|
||||
c = ca.b.Match(ca.a)
|
||||
assert.Equalf(
|
||||
t, ca.consist, c,
|
||||
"'%s' and '%s' are expected to be %s, but treated as %s",
|
||||
ca.b, ca.a, consistString[ca.consist], consistString[c],
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user