/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include "PosixSerialParityDecodeStream.h" #include "gtest/gtest.h" #include "mozilla/RefPtr.h" #include "nsError.h" #include "nsTArray.h" using namespace mozilla; using namespace mozilla::dom; namespace { // A minimal nsIAsyncInputStream that serves a fixed byte sequence, at most // aMaxPerRead bytes per Read() call so that tests can exercise marker/escape // sequences split across read boundaries. Returns NS_OK with 0 bytes at EOF. class FakeSerialInputStream final : public nsIAsyncInputStream { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIINPUTSTREAM NS_DECL_NSIASYNCINPUTSTREAM FakeSerialInputStream(nsTArray&& aData, uint32_t aMaxPerRead) : mData(std::move(aData)), mMaxPerRead(aMaxPerRead) {} // Test hook: invoke the most recently registered AsyncWait callback, passing // this inner stream as the ready stream (as a real async stream would). void FireWaitCallback() { nsCOMPtr cb = mWaitCallback.forget(); if (cb) { cb->OnInputStreamReady(this); } } private: ~FakeSerialInputStream() = default; nsTArray mData; uint32_t mPos = 0; const uint32_t mMaxPerRead; nsCOMPtr mWaitCallback; }; NS_IMPL_ISUPPORTS(FakeSerialInputStream, nsIInputStream, nsIAsyncInputStream) NS_IMETHODIMP FakeSerialInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aResult) { uint32_t remaining = mData.Length() - mPos; uint32_t n = std::min({aCount, remaining, mMaxPerRead}); if (n) { memcpy(aBuf, mData.Elements() + mPos, n); mPos += n; } *aResult = n; return NS_OK; } NS_IMETHODIMP FakeSerialInputStream::ReadSegments(nsWriteSegmentFun, void*, uint32_t, uint32_t*) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP FakeSerialInputStream::Available(uint64_t* aResult) { *aResult = mData.Length() - mPos; return NS_OK; } NS_IMETHODIMP FakeSerialInputStream::StreamStatus() { return mPos < mData.Length() ? NS_OK : NS_BASE_STREAM_CLOSED; } NS_IMETHODIMP FakeSerialInputStream::Close() { mPos = mData.Length(); return NS_OK; } NS_IMETHODIMP FakeSerialInputStream::IsNonBlocking(bool* aResult) { *aResult = true; return NS_OK; } NS_IMETHODIMP FakeSerialInputStream::CloseWithStatus(nsresult) { mPos = mData.Length(); return NS_OK; } NS_IMETHODIMP FakeSerialInputStream::AsyncWait( nsIInputStreamCallback* aCallback, uint32_t, uint32_t, nsIEventTarget*) { mWaitCallback = aCallback; return NS_OK; } // Records the stream it is handed by OnInputStreamReady. class RecordingCallback final : public nsIInputStreamCallback { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIINPUTSTREAMCALLBACK bool mCalled = false; nsCOMPtr mReadyStream; private: ~RecordingCallback() = default; }; NS_IMPL_ISUPPORTS(RecordingCallback, nsIInputStreamCallback) NS_IMETHODIMP RecordingCallback::OnInputStreamReady( nsIAsyncInputStream* aStream) { mCalled = true; mReadyStream = aStream; return NS_OK; } // Feeds aInput through PosixSerialParityDecodeStream (reading at most // aMaxPerRead raw bytes per inner read), collecting decoded output into aOut. // Returns the terminating status: NS_OK on clean EOF, or the latched error. nsresult RunDecoder(const nsTArray& aInput, uint32_t aMaxPerRead, nsTArray& aOut) { aOut.Clear(); auto inner = MakeRefPtr(aInput.Clone(), aMaxPerRead); auto decoder = MakeRefPtr(inner); // Bound iterations to guard against an unexpected infinite WOULD_BLOCK loop. for (uint32_t i = 0; i < 100000; ++i) { char buf[64]; uint32_t got = 0; nsresult rv = decoder->Read(buf, sizeof(buf), &got); if (rv == NS_BASE_STREAM_WOULD_BLOCK) { continue; } if (NS_FAILED(rv)) { return rv; } if (got == 0) { return NS_OK; } aOut.AppendElements(reinterpret_cast(buf), got); } ADD_FAILURE() << "RunDecoder did not terminate"; return NS_ERROR_FAILURE; } void ExpectBytes(const nsTArray& aActual, const nsTArray& aExpected) { ASSERT_EQ(aActual.Length(), aExpected.Length()); for (size_t i = 0; i < aExpected.Length(); ++i) { EXPECT_EQ(aActual[i], aExpected[i]) << "at index " << i; } } } // namespace TEST(WebSerialParityDecode, PlainBytesPassThrough) { nsTArray input{1, 2, 3, 4, 5}; nsTArray expected{1, 2, 3, 4, 5}; for (uint32_t chunk : {1u, 2u, 3u, 64u}) { nsTArray out; EXPECT_EQ(RunDecoder(input, chunk, out), NS_OK); ExpectBytes(out, expected); } } TEST(WebSerialParityDecode, EscapedFF) { // 0xff 0xff is a single legitimate 0xff byte. nsTArray input{0x01, 0xff, 0xff, 0x02}; nsTArray expected{0x01, 0xff, 0x02}; for (uint32_t chunk : {1u, 2u, 3u, 64u}) { nsTArray out; EXPECT_EQ(RunDecoder(input, chunk, out), NS_OK); ExpectBytes(out, expected); } } TEST(WebSerialParityDecode, ConsecutiveEscapedFF) { nsTArray input{0xff, 0xff, 0xff, 0xff}; nsTArray expected{0xff, 0xff}; for (uint32_t chunk : {1u, 2u, 3u, 64u}) { nsTArray out; EXPECT_EQ(RunDecoder(input, chunk, out), NS_OK); ExpectBytes(out, expected); } } TEST(WebSerialParityDecode, ParityErrorAfterValidBytes) { // 0xff 0x00 marks a parity error. Valid bytes before it are delivered, // then the stream fails. nsTArray input{0x01, 0x02, 0xff, 0x00, 0x41, 0x03}; nsTArray expected{0x01, 0x02}; for (uint32_t chunk : {1u, 2u, 3u, 4u, 64u}) { nsTArray out; EXPECT_EQ(RunDecoder(input, chunk, out), NS_ERROR_DOM_SERIAL_PARITY_ERROR); ExpectBytes(out, expected); } } TEST(WebSerialParityDecode, ParityErrorAtStart) { nsTArray input{0xff, 0x00, 0x00}; nsTArray expected{}; for (uint32_t chunk : {1u, 2u, 3u, 64u}) { nsTArray out; EXPECT_EQ(RunDecoder(input, chunk, out), NS_ERROR_DOM_SERIAL_PARITY_ERROR); ExpectBytes(out, expected); } } TEST(WebSerialParityDecode, EscapeThenParityError) { // A real 0xff byte followed by a parity error marker. nsTArray input{0xff, 0xff, 0x05, 0xff, 0x00, 0x42}; nsTArray expected{0xff, 0x05}; for (uint32_t chunk : {1u, 2u, 3u, 64u}) { nsTArray out; EXPECT_EQ(RunDecoder(input, chunk, out), NS_ERROR_DOM_SERIAL_PARITY_ERROR); ExpectBytes(out, expected); } } TEST(WebSerialParityDecode, EmptyInput) { nsTArray input{}; nsTArray out; EXPECT_EQ(RunDecoder(input, 64u, out), NS_OK); EXPECT_EQ(out.Length(), 0u); } TEST(WebSerialParityDecode, AvailableReportsDecodedCount) { // 4 raw bytes (0x01, 0xff 0xff -> 0xff, 0x02) decode to 3 deliverable bytes. // Available() must report 3, not the inner stream's raw count of 4. nsTArray input{0x01, 0xff, 0xff, 0x02}; for (uint32_t chunk : {1u, 2u, 3u, 64u}) { auto inner = MakeRefPtr(input.Clone(), chunk); auto decoder = MakeRefPtr(inner); uint64_t avail = 0; EXPECT_EQ(decoder->Available(&avail), NS_OK); EXPECT_LE(avail, 3u); EXPECT_GT(avail, 0u); } } TEST(WebSerialParityDecode, AvailableSurfacesParityErrorAtStart) { // With no deliverable bytes ahead of the marker, Available() surfaces the // parity error directly rather than reporting phantom available bytes. nsTArray input{0xff, 0x00, 0x41}; auto inner = MakeRefPtr(input.Clone(), 64u); auto decoder = MakeRefPtr(inner); uint64_t avail = 0; EXPECT_EQ(decoder->Available(&avail), NS_ERROR_DOM_SERIAL_PARITY_ERROR); } TEST(WebSerialParityDecode, AvailableReportsBytesBeforeError) { // Valid bytes precede the error marker: Available() reports those bytes and // defers the error until they have been consumed. nsTArray input{0x01, 0x02, 0xff, 0x00, 0x41}; auto inner = MakeRefPtr(input.Clone(), 64u); auto decoder = MakeRefPtr(inner); uint64_t avail = 0; EXPECT_EQ(decoder->Available(&avail), NS_OK); EXPECT_EQ(avail, 2u); } TEST(WebSerialParityDecode, AvailableWithIncompleteEscape) { // A lone trailing 0xff is an incomplete escape: nothing is deliverable yet, // so Available() reports 0 without failing. nsTArray input{0xff}; auto inner = MakeRefPtr(input.Clone(), 64u); auto decoder = MakeRefPtr(inner); uint64_t avail = 0; EXPECT_EQ(decoder->Available(&avail), NS_OK); EXPECT_EQ(avail, 0u); } TEST(WebSerialParityDecode, AsyncWaitForwardsDecoderAsReadyStream) { // When the inner stream signals readiness, the consumer's callback must be // handed the decoder (so reads go through decoding), not the raw inner // stream. nsTArray input{0x01, 0x02}; auto inner = MakeRefPtr(input.Clone(), 64u); auto decoder = MakeRefPtr(inner); auto callback = MakeRefPtr(); EXPECT_EQ(decoder->AsyncWait(callback, 0, 0, nullptr), NS_OK); inner->FireWaitCallback(); EXPECT_TRUE(callback->mCalled); EXPECT_EQ(callback->mReadyStream.get(), static_cast(decoder.get())); } TEST(WebSerialParityDecode, AsyncWaitClearedCallbackIsNotForwarded) { // Clearing the wait (null callback) must prevent a later inner notification // from reaching the original consumer. nsTArray input{0x01}; auto inner = MakeRefPtr(input.Clone(), 64u); auto decoder = MakeRefPtr(inner); auto callback = MakeRefPtr(); EXPECT_EQ(decoder->AsyncWait(callback, 0, 0, nullptr), NS_OK); EXPECT_EQ(decoder->AsyncWait(nullptr, 0, 0, nullptr), NS_OK); inner->FireWaitCallback(); EXPECT_FALSE(callback->mCalled); }