1 /**
2  * The goal of these tests is to verify the behavior of all blob commands given
3  * the current state is uploadInProgress.  This state is achieved when an image
4  * or hash blob is opened and the handler is expected to receive bytes.
5  */
6 #include "firmware_handler.hpp"
7 #include "firmware_unittest.hpp"
8 #include "util.hpp"
9 
10 #include <cstdint>
11 #include <string>
12 #include <vector>
13 
14 #include <gtest/gtest.h>
15 
16 namespace ipmi_flash
17 {
18 namespace
19 {
20 
21 using ::testing::ContainerEq;
22 using ::testing::IsEmpty;
23 using ::testing::Return;
24 using ::testing::UnorderedElementsAreArray;
25 
26 /*
27  * There are the following calls (parameters may vary):
28  * canHandleBlob(blob)
29  * getBlobIds
30  * deleteBlob(blob)
31  * stat(blob)
32  * stat(session)
33  * open(blob)
34  * close(session)
35  * writemeta(session)
36  * write(session)
37  * read(session)
38  * commit(session)
39  *
40  * Testing canHandleBlob is uninteresting in this state.  Getting the BlobIDs
41  * will inform what canHandleBlob will return.
42  */
43 class FirmwareHandlerUploadInProgressTest : public IpmiOnlyFirmwareStaticTest
44 {};
45 
TEST_F(FirmwareHandlerUploadInProgressTest,GetBlobIdsVerifyOutputActiveImage)46 TEST_F(FirmwareHandlerUploadInProgressTest, GetBlobIdsVerifyOutputActiveImage)
47 {
48     /* Opening the image file will add the active image blob id */
49     openToInProgress(staticLayoutBlobId);
50 
51     EXPECT_THAT(handler->getBlobIds(),
52                 UnorderedElementsAreArray(
53                     {staticLayoutBlobId, hashBlobId, activeImageBlobId}));
54 }
55 
TEST_F(FirmwareHandlerUploadInProgressTest,GetBlobIdsVerifyOutputActiveHash)56 TEST_F(FirmwareHandlerUploadInProgressTest, GetBlobIdsVerifyOutputActiveHash)
57 {
58     /* Opening the image file will add the active image blob id */
59     openToInProgress(hashBlobId);
60 
61     EXPECT_THAT(handler->getBlobIds(),
62                 UnorderedElementsAreArray(
63                     {staticLayoutBlobId, hashBlobId, activeHashBlobId}));
64 }
65 
66 /*
67  * stat(blob)
68  */
TEST_F(FirmwareHandlerUploadInProgressTest,StatOnActiveImageReturnsFailure)69 TEST_F(FirmwareHandlerUploadInProgressTest, StatOnActiveImageReturnsFailure)
70 {
71     /* you cannot call stat() on the active image or the active hash or the
72      * verify blob.
73      */
74     openToInProgress(staticLayoutBlobId);
75     ASSERT_TRUE(handler->canHandleBlob(activeImageBlobId));
76 
77     blobs::BlobMeta meta;
78     EXPECT_FALSE(handler->stat(activeImageBlobId, &meta));
79 }
80 
TEST_F(FirmwareHandlerUploadInProgressTest,StatOnActiveHashReturnsFailure)81 TEST_F(FirmwareHandlerUploadInProgressTest, StatOnActiveHashReturnsFailure)
82 {
83     /* this test is separate from the active image one so that the state doesn't
84      * change from close.
85      */
86     openToInProgress(hashBlobId);
87     ASSERT_TRUE(handler->canHandleBlob(activeHashBlobId));
88 
89     blobs::BlobMeta meta;
90     EXPECT_FALSE(handler->stat(activeHashBlobId, &meta));
91 }
92 
TEST_F(FirmwareHandlerUploadInProgressTest,StatOnNormalBlobsReturnsSuccess)93 TEST_F(FirmwareHandlerUploadInProgressTest, StatOnNormalBlobsReturnsSuccess)
94 {
95     /* Calling stat() on the normal blobs (not the active) ones will work and
96      * return the same information as in the notYetStarted state.
97      */
98     openToInProgress(staticLayoutBlobId);
99 
100     std::vector<std::string> testBlobs = {staticLayoutBlobId, hashBlobId};
101     for (const auto& blob : testBlobs)
102     {
103         blobs::BlobMeta meta = {};
104         EXPECT_TRUE(handler->stat(blob, &meta));
105         EXPECT_EQ(expectedIdleMeta, meta);
106     }
107 }
108 
109 /*
110  * stat(session)
111  */
TEST_F(FirmwareHandlerUploadInProgressTest,CallingStatOnActiveImageOrHashSessionReturnsDetails)112 TEST_F(FirmwareHandlerUploadInProgressTest,
113        CallingStatOnActiveImageOrHashSessionReturnsDetails)
114 {
115     /* This test will verify that the underlying image handler is called with
116      * this stat, in addition to the normal information.
117      */
118     openToInProgress(staticLayoutBlobId);
119 
120     EXPECT_CALL(*imageMock2, getSize()).WillOnce(Return(32));
121 
122     blobs::BlobMeta meta, expectedMeta = {};
123     expectedMeta.size = 32;
124     expectedMeta.blobState =
125         static_cast<std::uint16_t>(blobs::OpenFlags::write) |
126         FirmwareFlags::UpdateFlags::ipmi;
127     EXPECT_TRUE(handler->stat(session, &meta));
128     EXPECT_EQ(expectedMeta, meta);
129 }
130 
131 /*
132  * open(blob) - While any blob is open, all other fail.
133  *
134  * The fullBlobsList is all the blob_ids present if both /flash/image and
135  * /flash/hash are opened, and one is left open (so there's no verify blob). if
136  * left closed, we'd be in verificationPending, not uploadInProgress.
137  */
138 const std::vector<std::string> fullBlobsList = {
139     activeHashBlobId, activeImageBlobId, hashBlobId, staticLayoutBlobId};
140 
TEST_F(FirmwareHandlerUploadInProgressTest,OpeningHashFileWhileImageOpenFails)141 TEST_F(FirmwareHandlerUploadInProgressTest, OpeningHashFileWhileImageOpenFails)
142 {
143     /* To be in this state, something must be open (and specifically either an
144      * active image (or tarball) or the hash file. Also verifies you can't just
145      * re-open the currently open file.
146      */
147     openToInProgress(staticLayoutBlobId);
148 
149     for (const auto& blob : fullBlobsList)
150     {
151         EXPECT_FALSE(handler->open(2, flags, blob));
152     }
153 }
154 
TEST_F(FirmwareHandlerUploadInProgressTest,OpeningImageFileWhileHashOpenFails)155 TEST_F(FirmwareHandlerUploadInProgressTest, OpeningImageFileWhileHashOpenFails)
156 {
157     openToInProgress(hashBlobId);
158 
159     for (const auto& blob : fullBlobsList)
160     {
161         EXPECT_FALSE(handler->open(2, flags, blob));
162     }
163 }
164 
165 /*
166  * close(session) - closing the hash or image will trigger a state transition to
167  * verificationPending.
168  *
169  * NOTE: Re-opening /flash/image will transition back to uploadInProgress, but
170  * that is verified in the verificationPending::open tests.
171  */
TEST_F(FirmwareHandlerUploadInProgressTest,ClosingImageFileTransitionsToVerificationPending)172 TEST_F(FirmwareHandlerUploadInProgressTest,
173        ClosingImageFileTransitionsToVerificationPending)
174 {
175     EXPECT_FALSE(handler->canHandleBlob(verifyBlobId));
176     openToInProgress(staticLayoutBlobId);
177 
178     handler->close(session);
179     expectedState(FirmwareBlobHandler::UpdateState::verificationPending);
180 
181     EXPECT_TRUE(handler->canHandleBlob(verifyBlobId));
182 }
183 
TEST_F(FirmwareHandlerUploadInProgressTest,ClosingHashFileTransitionsToVerificationPending)184 TEST_F(FirmwareHandlerUploadInProgressTest,
185        ClosingHashFileTransitionsToVerificationPending)
186 {
187     EXPECT_FALSE(handler->canHandleBlob(verifyBlobId));
188     openToInProgress(hashBlobId);
189 
190     handler->close(session);
191     expectedState(FirmwareBlobHandler::UpdateState::verificationPending);
192 
193     EXPECT_FALSE(handler->canHandleBlob(verifyBlobId));
194 }
195 
196 /*
197  * writemeta(session)
198  */
TEST_F(FirmwareHandlerUploadInProgressTest,WriteMetaAgainstImageReturnsFailureIfNoDataHandler)199 TEST_F(FirmwareHandlerUploadInProgressTest,
200        WriteMetaAgainstImageReturnsFailureIfNoDataHandler)
201 {
202     /* Calling write/read/writeMeta are uninteresting against the open blob in
203      * this case because the blob will just pass the call along.  Whereas
204      * calling against the verify or update blob may be more interesting.
205      */
206     openToInProgress(staticLayoutBlobId);
207 
208     /* TODO: Consider adding a test that has a data handler, but that test
209      * already exists under the general writeMeta test suite.
210      */
211     /* Note: with IPMI as the transport there's no data handler, so this should
212      * fail nicely. */
213     std::vector<std::uint8_t> bytes = {0x01, 0x02};
214     EXPECT_FALSE(handler->writeMeta(session, 0, bytes));
215 }
216 
217 /*
218  * write(session)
219  */
TEST_F(FirmwareHandlerUploadInProgressTest,WriteToImageReturnsSuccess)220 TEST_F(FirmwareHandlerUploadInProgressTest, WriteToImageReturnsSuccess)
221 {
222     openToInProgress(staticLayoutBlobId);
223     std::vector<std::uint8_t> bytes = {0x01, 0x02};
224     EXPECT_CALL(*imageMock2, write(0, ContainerEq(bytes)))
225         .WillOnce(Return(true));
226     EXPECT_TRUE(handler->write(session, 0, bytes));
227 }
228 
TEST_F(FirmwareHandlerUploadInProgressTest,WriteToHashReturnsSuccess)229 TEST_F(FirmwareHandlerUploadInProgressTest, WriteToHashReturnsSuccess)
230 {
231     openToInProgress(hashBlobId);
232     std::vector<std::uint8_t> bytes = {0x01, 0x02};
233     EXPECT_CALL(*hashImageMock, write(0, ContainerEq(bytes)))
234         .WillOnce(Return(true));
235     EXPECT_TRUE(handler->write(session, 0, bytes));
236 }
237 
238 /*
239  * read(session)
240  */
TEST_F(FirmwareHandlerUploadInProgressTest,ReadImageFileReturnsFailure)241 TEST_F(FirmwareHandlerUploadInProgressTest, ReadImageFileReturnsFailure)
242 {
243     /* Read is not supported. */
244     openToInProgress(staticLayoutBlobId);
245     EXPECT_THAT(handler->read(session, 0, 32), IsEmpty());
246 }
247 
248 /*
249  * commit(session)
250  */
TEST_F(FirmwareHandlerUploadInProgressTest,CommitAgainstImageFileReturnsFailure)251 TEST_F(FirmwareHandlerUploadInProgressTest,
252        CommitAgainstImageFileReturnsFailure)
253 {
254     /* Commit is only valid against specific blobs. */
255     openToInProgress(staticLayoutBlobId);
256     EXPECT_FALSE(handler->commit(session, {}));
257 }
258 
TEST_F(FirmwareHandlerUploadInProgressTest,CommitAgainstHashFileReturnsFailure)259 TEST_F(FirmwareHandlerUploadInProgressTest, CommitAgainstHashFileReturnsFailure)
260 {
261     openToInProgress(hashBlobId);
262     EXPECT_FALSE(handler->commit(session, {}));
263 }
264 
265 /*
266  * deleteBlob(blob)
267  */
TEST_F(FirmwareHandlerUploadInProgressTest,DeleteBlobReturnsFalse)268 TEST_F(FirmwareHandlerUploadInProgressTest, DeleteBlobReturnsFalse)
269 {
270     /* Try deleting all blobs, it doesn't really matter which though because you
271      * cannot close out an open session, therefore you must fail to delete
272      * anything unless everything is closed.
273      */
274     openToInProgress(staticLayoutBlobId);
275     auto blobs = handler->getBlobIds();
276     for (const auto& b : blobs)
277     {
278         EXPECT_FALSE(handler->deleteBlob(b));
279     }
280 }
281 
282 /*
283  * expire(session)
284  */
TEST_F(FirmwareHandlerUploadInProgressTest,ExpireAbortsProcess)285 TEST_F(FirmwareHandlerUploadInProgressTest, ExpireAbortsProcess)
286 {
287     openToInProgress(staticLayoutBlobId);
288 
289     ASSERT_TRUE(handler->expire(session));
290     EXPECT_THAT(handler->getBlobIds(),
291                 UnorderedElementsAreArray(startingBlobs));
292     expectedState(FirmwareBlobHandler::UpdateState::notYetStarted);
293 }
294 
295 } // namespace
296 } // namespace ipmi_flash
297