Skip to content

Commit 31f4e2f

Browse files
baylesjactions-user
authored andcommitted
[Cast Streaming] Add Input event API for unidirectional flow
Implements the Input event API to support unidirectional flow from Receiver to Sender, using idiomatic C++20 data handling with ByteView. * Create InputProducer (Receiver-side) and InputConsumer (Sender-side) into their respective targets. * Use ByteView for ProcessMessageFromRemote and SendMessageToRemote to avoid raw pointer/size pairs. * Tie InputProducer lifecycle to specific negotiation sessions via ConfiguredReceivers. * Ensure ANSWER is dispatched before OnNegotiated to maintain correct message ordering in tests. Bug: 482442349 Change-Id: Ie65c0eb96939c08f3ca0017a1e31c7cab57ff260 Reviewed-on: https://chromium-review.googlesource.com/c/openscreen/+/7553551 Reviewed-by: Mark Foltz <mfoltz@chromium.org> Commit-Queue: Jordan Bayles <jophba@chromium.org>
1 parent 571620a commit 31f4e2f

17 files changed

Lines changed: 786 additions & 149 deletions

.github/workflows/main.yml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: Exact OpenScreen Mirror with .github Protection
2+
3+
on:
4+
workflow_dispatch: # Manuel olarak tetiklenebilir
5+
schedule:
6+
- cron: '0 0 */3 * *' # Her 3 günde bir otomatik olarak çalışır
7+
8+
jobs:
9+
mirror:
10+
runs-on: ubuntu-latest # İş akışı Ubuntu üzerinde çalışır
11+
steps:
12+
# Adım 1: Mevcut depoyu tam tarihçe ile al
13+
- name: Checkout Full History
14+
uses: actions/checkout@v4
15+
with:
16+
fetch-depth: 0 # Tüm commit geçmişini al
17+
18+
# Adım 2: OpenScreen verilerini mevcut depoya entegre et
19+
- name: Integrate OpenScreen
20+
run: |
21+
# 'actions' branch'ine geç
22+
git checkout actions
23+
24+
# OpenScreen'i uzak depo olarak ekle
25+
git remote add openscreen https://chromium.googlesource.com/openscreen
26+
27+
# OpenScreen'den tüm branch'leri ve tag'leri al
28+
git fetch openscreen --tags
29+
30+
# OpenScreen'deki tüm branch'leri yerel olarak oluştur (main hariç)
31+
git branch -r | grep 'openscreen/' | grep -v 'HEAD' | while read remote; do
32+
branch_name=${remote#openscreen/}
33+
git branch -f "$branch_name" "$remote"
34+
done
35+
36+
# Adım 3: Main branch'i koru ve .github dosyalarını güncelle
37+
- name: Protect Main Branch
38+
run: |
39+
# Main branch'e geç
40+
git checkout main
41+
42+
# Git kullanıcı bilgilerini ayarla (commit için)
43+
git config user.name "GitHub Actions"
44+
git config user.email "actions@github.com"
45+
46+
# 'actions' branch'inden .github klasörünü al ve main branch'ine merge et
47+
git checkout actions -- .github
48+
git add .github
49+
git commit --amend --no-edit # Commit'i güncelle (fast-forward olmadan)
50+
51+
# Adım 4: Tüm branch'leri ve tag'leri force push et
52+
- name: Force Push Mirrored Branches
53+
env:
54+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # GitHub token'ı kullan
55+
run: |
56+
# Tüm branch'leri ve tag'leri force push et
57+
git push origin --all --force
58+
git push origin --tags --force

cast/streaming/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ openscreen_source_set("common") {
6363
"public/environment.h",
6464
"public/frame_id.h",
6565
"public/offer_messages.h",
66+
"public/protobuf_messenger.h",
6667
"public/receiver_message.h",
6768
"public/rpc_messenger.h",
6869
"public/session_messenger.h",
@@ -263,6 +264,7 @@ openscreen_source_set("unittests") {
263264
"impl/offer_messages_unittest.cc",
264265
"impl/packet_receive_stats_tracker_unittest.cc",
265266
"impl/packet_util_unittest.cc",
267+
"impl/protobuf_messenger_unittest.cc",
266268
"impl/receiver_constraints_unittest.cc",
267269
"impl/receiver_message_unittest.cc",
268270
"impl/receiver_session_unittest.cc",

cast/streaming/DEPS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,9 @@ include_rules = [
99
'+openssl',
1010
'+json',
1111
]
12+
13+
specific_include_rules = {
14+
'protobuf_messenger.h': [
15+
'+google/protobuf/message_lite.h',
16+
],
17+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright 2026 The Chromium Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "cast/streaming/public/protobuf_messenger.h"
6+
7+
#include <utility>
8+
#include <vector>
9+
10+
#include "cast/streaming/input.pb.h"
11+
#include "gmock/gmock.h"
12+
#include "gtest/gtest.h"
13+
14+
namespace openscreen::cast {
15+
16+
using ::testing::_;
17+
18+
TEST(ProtobufMessengerTest, ProcessesMessageFromRemote) {
19+
bool called = false;
20+
ProtobufMessenger<InputMessage> messenger(
21+
[](std::vector<uint8_t> message) {},
22+
[&called](std::unique_ptr<InputMessage> received) {
23+
called = true;
24+
EXPECT_EQ(received->events_size(), 1);
25+
EXPECT_EQ(received->events(0).type(),
26+
InputMessage::INPUT_TYPE_KEY_DOWN);
27+
EXPECT_EQ(received->events(0).key_event().key_value(), "a");
28+
});
29+
30+
InputMessage message;
31+
auto* event = message.add_events();
32+
event->set_type(InputMessage::INPUT_TYPE_KEY_DOWN);
33+
auto* timestamp = event->mutable_timestamp();
34+
timestamp->set_seconds(1234);
35+
timestamp->set_nanos(500000000);
36+
auto* key_event = event->mutable_key_event();
37+
key_event->set_key_code("KeyA");
38+
key_event->set_key_value("a");
39+
40+
std::vector<uint8_t> serialized(message.ByteSizeLong());
41+
message.SerializeToArray(serialized.data(), serialized.size());
42+
43+
messenger.ProcessMessageFromRemote(serialized);
44+
EXPECT_TRUE(called);
45+
}
46+
47+
TEST(ProtobufMessengerTest, SendsMessageToRemote) {
48+
std::vector<uint8_t> captured_message;
49+
ProtobufMessenger<InputMessage> messenger(
50+
[&captured_message](std::vector<uint8_t> message) {
51+
captured_message = std::move(message);
52+
});
53+
54+
InputMessage message;
55+
auto* event = message.add_events();
56+
event->set_type(InputMessage::INPUT_TYPE_MOUSE_MOVE);
57+
auto* mouse_event = event->mutable_mouse_event();
58+
mouse_event->mutable_location()->set_x(0.5f);
59+
mouse_event->mutable_location()->set_y(0.5f);
60+
61+
messenger.SendMessageToRemote(message);
62+
63+
ASSERT_FALSE(captured_message.empty());
64+
InputMessage parsed;
65+
ASSERT_TRUE(
66+
parsed.ParseFromArray(captured_message.data(), captured_message.size()));
67+
EXPECT_EQ(parsed.events_size(), 1);
68+
EXPECT_EQ(parsed.events(0).type(), InputMessage::INPUT_TYPE_MOUSE_MOVE);
69+
EXPECT_EQ(parsed.events(0).mouse_event().location().x(), 0.5f);
70+
}
71+
72+
TEST(ProtobufMessengerTest, HandlesInvalidData) {
73+
bool called = false;
74+
ProtobufMessenger<InputMessage> messenger(
75+
[](std::vector<uint8_t> message) {},
76+
[&called](std::unique_ptr<InputMessage> received) { called = true; });
77+
78+
uint8_t invalid_data[] = {0xff, 0x00, 0xff};
79+
messenger.ProcessMessageFromRemote(invalid_data);
80+
EXPECT_FALSE(called);
81+
}
82+
83+
} // namespace openscreen::cast

cast/streaming/impl/receiver_session_unittest.cc

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44

55
#include "cast/streaming/public/receiver_session.h"
66

7+
#include <functional>
8+
#include <memory>
79
#include <utility>
810

11+
#include "cast/streaming/input.pb.h"
912
#include "cast/streaming/public/receiver.h"
1013
#include "cast/streaming/testing/mock_environment.h"
1114
#include "cast/streaming/testing/simple_message_port.h"
@@ -16,6 +19,7 @@
1619
#include "platform/test/fake_task_runner.h"
1720
#include "util/chrono_helpers.h"
1821
#include "util/json/json_serialization.h"
22+
#include "util/std_util.h"
1923

2024
using ::testing::_;
2125
using ::testing::InSequence;
@@ -314,6 +318,44 @@ constexpr char kRpcMessage[] = R"({
314318
"type" : "RPC"
315319
})";
316320

321+
constexpr char kValidOfferMessageWithInput[] = R"({
322+
"type": "OFFER",
323+
"seqNum": 1337,
324+
"offer": {
325+
"castMode": "mirroring",
326+
"supportedStreams": [
327+
{
328+
"index": 31338,
329+
"type": "video_source",
330+
"codecName": "vp8",
331+
"rtpProfile": "cast",
332+
"rtpPayloadType": 127,
333+
"ssrc": 19088745,
334+
"maxFrameRate": "60000/1000",
335+
"timeBase": "1/90000",
336+
"maxBitRate": 5000000,
337+
"profile": "main",
338+
"level": "4",
339+
"aesKey": "040d756791711fd3adb939066e6d8690",
340+
"aesIvMask": "9ff0f022a959150e70a2d05a6c184aed",
341+
"rtpExtensions": ["input_events"],
342+
"resolutions": [
343+
{
344+
"width": 1280,
345+
"height": 720
346+
}
347+
]
348+
}
349+
]
350+
}
351+
})";
352+
353+
constexpr char kInputMessage[] = R"({
354+
"input" : "CGQQnBiCGQgSAggMGgIIBg==",
355+
"seqNum" : 3,
356+
"type" : "INPUT"
357+
})";
358+
317359
class FakeClient : public ReceiverSession::Client {
318360
public:
319361
MOCK_METHOD(void,
@@ -1068,4 +1110,140 @@ TEST_F(ReceiverSessionTest, EnablesDscpInAnswer) {
10681110
ASSERT_TRUE(dscp2.empty());
10691111
}
10701112

1113+
TEST_F(ReceiverSessionTest, InputEventsOptIn) {
1114+
ReceiverConstraints constraints;
1115+
constraints.supports_input_events = true;
1116+
SetUpWithConstraints(std::move(constraints));
1117+
1118+
EXPECT_CALL(client_, OnNegotiated(session_.get(), _))
1119+
.WillOnce([](const ReceiverSession* session,
1120+
ReceiverSession::ConfiguredReceivers cr) {
1121+
EXPECT_TRUE(cr.input_enabled);
1122+
});
1123+
EXPECT_CALL(client_,
1124+
OnReceiversDestroying(session_.get(),
1125+
ReceiverSession::Client::kEndOfSession));
1126+
1127+
message_port_->ReceiveMessage(kValidOfferMessageWithInput);
1128+
1129+
const std::vector<std::string>& messages = message_port_->posted_messages();
1130+
ASSERT_EQ(1u, messages.size());
1131+
Json::Value message = ExpectIsValidAnswer(messages[0]);
1132+
const Json::Value& answer = message["answer"];
1133+
1134+
bool found_extension = false;
1135+
for (const auto& ext : answer["rtpExtensions"]) {
1136+
if (ext.asString() == "input_events") {
1137+
found_extension = true;
1138+
break;
1139+
}
1140+
}
1141+
EXPECT_TRUE(found_extension);
1142+
}
1143+
1144+
TEST_F(ReceiverSessionTest, HandlesInputMessage) {
1145+
ReceiverConstraints constraints;
1146+
constraints.supports_input_events = true;
1147+
SetUpWithConstraints(std::move(constraints));
1148+
1149+
message_port_->ReceiveMessage(kInputMessage);
1150+
// Nothing should happen yet, the session doesn't have a messenger.
1151+
ASSERT_EQ(0u, message_port_->posted_messages().size());
1152+
1153+
InSequence s;
1154+
bool received_negotiation = false;
1155+
EXPECT_CALL(client_, OnNegotiated(session_.get(), _))
1156+
.WillOnce([&received_negotiation](
1157+
const ReceiverSession* session,
1158+
ReceiverSession::ConfiguredReceivers receivers) mutable {
1159+
ASSERT_TRUE(receivers.input_enabled);
1160+
received_negotiation = true;
1161+
});
1162+
EXPECT_CALL(client_,
1163+
OnReceiversDestroying(session_.get(),
1164+
ReceiverSession::Client::kEndOfSession));
1165+
1166+
message_port_->ReceiveMessage(kValidOfferMessageWithInput);
1167+
ASSERT_TRUE(received_negotiation);
1168+
}
1169+
1170+
TEST_F(ReceiverSessionTest, HandlesInputMessengerNotNegotiated) {
1171+
ReceiverConstraints constraints;
1172+
constraints.supports_input_events = false;
1173+
SetUpWithConstraints(std::move(constraints));
1174+
1175+
EXPECT_CALL(client_, OnNegotiated(session_.get(), _))
1176+
.WillOnce([](const ReceiverSession* session,
1177+
ReceiverSession::ConfiguredReceivers cr) {
1178+
EXPECT_FALSE(cr.input_enabled);
1179+
});
1180+
EXPECT_CALL(client_,
1181+
OnReceiversDestroying(session_.get(),
1182+
ReceiverSession::Client::kEndOfSession));
1183+
1184+
message_port_->ReceiveMessage(kValidOfferMessageWithInput);
1185+
}
1186+
1187+
TEST_F(ReceiverSessionTest, HandlesInputMessengerSendsMessage) {
1188+
ReceiverConstraints constraints;
1189+
constraints.supports_input_events = true;
1190+
SetUpWithConstraints(std::move(constraints));
1191+
1192+
EXPECT_CALL(client_, OnNegotiated(session_.get(), _))
1193+
.WillOnce([this](const ReceiverSession* session,
1194+
ReceiverSession::ConfiguredReceivers cr) {
1195+
ASSERT_TRUE(cr.input_enabled);
1196+
1197+
InputMessage message;
1198+
auto* event = message.add_events();
1199+
event->set_type(InputMessage::INPUT_TYPE_KEY_DOWN);
1200+
this->session_->SendInputMessage(message);
1201+
});
1202+
EXPECT_CALL(client_,
1203+
OnReceiversDestroying(session_.get(),
1204+
ReceiverSession::Client::kEndOfSession));
1205+
1206+
message_port_->ReceiveMessage(kValidOfferMessageWithInput);
1207+
1208+
// Verify message was sent through message port.
1209+
const auto& messages = message_port_->posted_messages();
1210+
1211+
// We expect at least 2 messages: ANSWER and INPUT.
1212+
ASSERT_GE(messages.size(), 2u);
1213+
1214+
// Index 0 is ANSWER.
1215+
ExpectIsValidAnswer(messages[0]);
1216+
1217+
// Index 1 should be INPUT.
1218+
ErrorOr<Json::Value> input_json = json::Parse(messages[1]);
1219+
ASSERT_TRUE(input_json.is_value());
1220+
EXPECT_EQ("INPUT", input_json.value()["type"].asString());
1221+
EXPECT_FALSE(input_json.value()["input"].asString().empty());
1222+
}
1223+
1224+
TEST_F(ReceiverSessionTest, HandlesInputMessengerReceivesMessage) {
1225+
ReceiverConstraints constraints;
1226+
constraints.supports_input_events = true;
1227+
SetUpWithConstraints(std::move(constraints));
1228+
1229+
bool received_input = false;
1230+
session_->SetInputCallback(
1231+
[&received_input](InputMessage message) { received_input = true; });
1232+
1233+
EXPECT_CALL(client_, OnNegotiated(session_.get(), _))
1234+
.WillOnce([](const ReceiverSession* session,
1235+
ReceiverSession::ConfiguredReceivers cr) {
1236+
ASSERT_TRUE(cr.input_enabled);
1237+
});
1238+
1239+
message_port_->ReceiveMessage(kValidOfferMessageWithInput);
1240+
1241+
message_port_->ReceiveMessage(kInputMessage);
1242+
ASSERT_TRUE(received_input);
1243+
1244+
EXPECT_CALL(client_,
1245+
OnReceiversDestroying(session_.get(),
1246+
ReceiverSession::Client::kEndOfSession));
1247+
}
1248+
10711249
} // namespace openscreen::cast

cast/streaming/impl/rpc_messenger_unittest.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class RpcMessengerTest : public testing::Test {
7676
void ProcessMessage(const RpcMessage& rpc) {
7777
std::vector<uint8_t> message(rpc.ByteSizeLong());
7878
rpc.SerializeToArray(message.data(), message.size());
79-
rpc_messenger_->ProcessMessageFromRemote(message.data(), message.size());
79+
rpc_messenger_->ProcessMessageFromRemote(message);
8080
}
8181

8282
std::unique_ptr<FakeMessenger> fake_messenger_;

0 commit comments

Comments
 (0)