99#include < string>
1010#include < utility>
1111
12+ #include " absl/container/fixed_array.h"
1213#include " absl/status/status.h"
1314#include " absl/status/statusor.h"
1415#include " absl/strings/ascii.h"
1516#include " absl/strings/str_cat.h"
1617#include " absl/strings/str_format.h"
18+ #include " absl/strings/str_split.h"
1719#include " absl/strings/string_view.h"
1820#include " absl/types/span.h"
1921#include " quiche/common/platform/api/quiche_bug_tracker.h"
22+ #include " quiche/common/platform/api/quiche_logging.h"
23+ #include " quiche/common/quiche_status_utils.h"
2024
2125namespace moqt {
2226
@@ -53,6 +57,109 @@ void AppendEscapedTrackNameTuple(const MoqtStringTuple& tuple,
5357 }
5458}
5559
60+ [[nodiscard]] bool HexCharToLowerHalfOfByte (char c, char & output) {
61+ if (c >= ' 0' && c <= ' 9' ) {
62+ output |= c - ' 0' ;
63+ return true ;
64+ }
65+ if (c >= ' a' && c <= ' f' ) {
66+ output |= (c - ' a' ) + 0xa ;
67+ return true ;
68+ }
69+ return false ;
70+ }
71+
72+ // Custom hex-to-binary converter that disallows uppercase hex, as the
73+ // specification explicitly requires lowercase hex.
74+ [[nodiscard]] bool HexEncodedBitToByte (char c0, char c1, char & output) {
75+ output = 0 ;
76+ if (!HexCharToLowerHalfOfByte (c0, output)) {
77+ return false ;
78+ }
79+ output <<= 4 ;
80+ if (!HexCharToLowerHalfOfByte (c1, output)) {
81+ return false ;
82+ }
83+ return true ;
84+ }
85+
86+ // The MOQT specification is currently ambiguous regarding how permissive the
87+ // parsing should be. We are intentionally taking a "strict" approach, in which
88+ // any track name that this code parses successfully will result in a byte-exact
89+ // serialization from `ToString()`. For the discussion of this issue, see
90+ // <https://github.com/moq-wg/moq-transport/issues/1501>.
91+ //
92+ // It is up to the caller to reserve the capacity in `output_tuple` in advance.
93+ absl::Status UnescapeTrackNameComponent (absl::string_view input,
94+ MoqtStringTuple& output_tuple) {
95+ // The unescaping algorithm always results in strings of the same or smaller
96+ // size, so the fixed array of size `input.size()` will always fit the output.
97+ absl::FixedArray<char > output_buffer (input.size ());
98+ absl::Span<char > output = absl::MakeSpan (output_buffer);
99+ while (!input.empty ()) {
100+ if (IsTrackNameSafeCharacter (input[0 ])) {
101+ output[0 ] = input[0 ];
102+ input.remove_prefix (1 );
103+ output.remove_prefix (1 );
104+ continue ;
105+ }
106+ if (input[0 ] == ' .' ) {
107+ if (input.size () < 3 ) {
108+ return absl::InvalidArgumentError (" Incomplete escape sequence" );
109+ }
110+ if (!HexEncodedBitToByte (input[1 ], input[2 ], output[0 ])) {
111+ return absl::InvalidArgumentError (" Invalid hex in an escape sequence" );
112+ }
113+ if (IsTrackNameSafeCharacter (output[0 ])) {
114+ return absl::InvalidArgumentError (" Hex-encoding a safe character" );
115+ }
116+ input.remove_prefix (3 );
117+ output.remove_prefix (1 );
118+ continue ;
119+ }
120+ return absl::InvalidArgumentError (
121+ absl::StrFormat (" Invalid character 0x%02x encountered" , input[0 ]));
122+ }
123+ size_t output_size = output_buffer.size () - output.size ();
124+ if (!output_tuple.Add (absl::string_view (output_buffer.data (), output_size))) {
125+ return absl::OutOfRangeError (" Maximum tuple size exceeded" );
126+ }
127+ return absl::OkStatus ();
128+ }
129+
130+ absl::StatusOr<MoqtStringTuple> ParseNameTuple (absl::string_view input) {
131+ // Special-case empty namespace, to indicate it is {} and not {""}.
132+ if (input.empty ()) {
133+ return MoqtStringTuple ();
134+ }
135+
136+ ssize_t bytes_to_reserve = input.size ();
137+ ssize_t elements_to_reserve = 1 ;
138+ for (char c : input) {
139+ if (c == ' -' ) {
140+ ++elements_to_reserve;
141+ --bytes_to_reserve;
142+ }
143+ if (c == ' .' ) {
144+ bytes_to_reserve -= 2 ;
145+ }
146+ }
147+
148+ MoqtStringTuple tuple;
149+ if (bytes_to_reserve > 0 ) {
150+ // A malformed name such as `......` will result in `bytes_to_reserve` being
151+ // negative.
152+ tuple.ReserveDataBytes (bytes_to_reserve);
153+ }
154+ tuple.ReserveTupleElements (elements_to_reserve);
155+ for (absl::string_view bit : absl::StrSplit (input, ' -' )) {
156+ QUICHE_RETURN_IF_ERROR (UnescapeTrackNameComponent (bit, tuple));
157+ }
158+ QUICHE_DCHECK_EQ (tuple.TotalBytes (), bytes_to_reserve);
159+ QUICHE_DCHECK_EQ (tuple.size (), elements_to_reserve);
160+ return tuple;
161+ }
162+
56163} // namespace
57164
58165absl::StatusOr<TrackNamespace> TrackNamespace::Create (MoqtStringTuple tuple) {
@@ -64,6 +171,12 @@ absl::StatusOr<TrackNamespace> TrackNamespace::Create(MoqtStringTuple tuple) {
64171 return TrackNamespace (std::move (tuple));
65172}
66173
174+ absl::StatusOr<TrackNamespace> TrackNamespace::Parse (absl::string_view input) {
175+ absl::StatusOr<MoqtStringTuple> tuple = ParseNameTuple (input);
176+ QUICHE_RETURN_IF_ERROR (tuple.status ());
177+ return Create (*std::move (tuple));
178+ }
179+
67180TrackNamespace::TrackNamespace (std::initializer_list<absl::string_view> tuple) {
68181 bool success = tuple_.Append (tuple);
69182 if (!success) {
@@ -111,6 +224,28 @@ absl::StatusOr<FullTrackName> FullTrackName::Create(TrackNamespace ns,
111224 FullTrackNameIsValidTag ());
112225}
113226
227+ absl::StatusOr<FullTrackName> FullTrackName::Parse (absl::string_view input) {
228+ absl::StatusOr<MoqtStringTuple> tuple = ParseNameTuple (input);
229+ QUICHE_RETURN_IF_ERROR (tuple.status ());
230+ if (tuple->size () < 3 ) {
231+ return absl::InvalidArgumentError (" Full track name is missing elements" );
232+ }
233+ std::string name (tuple->back ());
234+ tuple->Pop ();
235+ if (!tuple->back ().empty ()) {
236+ return absl::InvalidArgumentError (
237+ " Full track name must use -- as a separator" );
238+ }
239+ tuple->Pop ();
240+ if (tuple->size () == 1 && tuple->ValueAt (0 ) == " " ) {
241+ // Special case handling for empty namespace.
242+ tuple->Pop ();
243+ }
244+ absl::StatusOr<TrackNamespace> ns = TrackNamespace::Create (*std::move (tuple));
245+ QUICHE_RETURN_IF_ERROR (ns.status ());
246+ return Create (*std::move (ns), std::move (name));
247+ }
248+
114249FullTrackName::FullTrackName (TrackNamespace ns, absl::string_view name)
115250 : namespace_(std::move(ns)), name_(name) {
116251 if (namespace_.total_length () + name.size () > kMaxFullTrackNameSize ) {
0 commit comments