1 #include "data.hpp"
2 #include "internal_sys_mock.hpp"
3 #include "net.hpp"
4 #include "progress_mock.hpp"
5 
6 #include <ipmiblob/test/blob_interface_mock.hpp>
7 
8 #include <cstring>
9 
10 #include <gtest/gtest.h>
11 
12 namespace host_tool
13 {
14 namespace
15 {
16 
17 using namespace std::literals;
18 
19 using ::testing::_;
20 using ::testing::AllOf;
21 using ::testing::ContainerEq;
22 using ::testing::DoAll;
23 using ::testing::Field;
24 using ::testing::Ge;
25 using ::testing::Gt;
26 using ::testing::InSequence;
27 using ::testing::NotNull;
28 using ::testing::Pointee;
29 using ::testing::Return;
30 using ::testing::SetArgPointee;
31 using ::testing::SetErrnoAndReturn;
32 using ::testing::StrEq;
33 
34 class NetHandleTest : public ::testing::Test
35 {
36   protected:
37     NetHandleTest() : handler(&blobMock, &progMock, host, port, &sysMock)
38     {
39         sa.sin6_family = AF_INET6;
40         sa.sin6_port = htons(622);
41         sa.sin6_flowinfo = 0;
42         sa.sin6_addr = in6addr_loopback; // ::1
43         sa.sin6_scope_id = 0;
44 
45         addr.ai_family = AF_INET6;
46         addr.ai_socktype = SOCK_STREAM;
47         addr.ai_addr = reinterpret_cast<struct sockaddr*>(&sa);
48         addr.ai_addrlen = sizeof(sa);
49         addr.ai_protocol = 0;
50         addr.ai_next = nullptr;
51     }
52 
53     void expectOpenFile()
54     {
55         EXPECT_CALL(sysMock, open(StrEq(filePath.c_str()), _))
56             .WillOnce(Return(inFd));
57         EXPECT_CALL(sysMock, close(inFd)).WillOnce(Return(0));
58         EXPECT_CALL(sysMock, getSize(StrEq(filePath.c_str())))
59             .WillOnce(Return(fakeFileSize));
60     }
61 
62     void expectAddrInfo()
63     {
64         EXPECT_CALL(
65             sysMock,
66             getaddrinfo(StrEq(host), StrEq(port),
67                         AllOf(Field(&addrinfo::ai_flags, AI_NUMERICHOST),
68                               Field(&addrinfo::ai_family, AF_UNSPEC),
69                               Field(&addrinfo::ai_socktype, SOCK_STREAM)),
70                         NotNull()))
71             .WillOnce(DoAll(SetArgPointee<3>(&addr), Return(0)));
72         EXPECT_CALL(sysMock, freeaddrinfo(&addr));
73     }
74 
75     void expectConnection()
76     {
77         EXPECT_CALL(sysMock, socket(AF_INET6, SOCK_STREAM, 0))
78             .WillOnce(Return(connFd));
79         EXPECT_CALL(sysMock, close(connFd)).WillOnce(Return(0));
80         EXPECT_CALL(sysMock,
81                     connect(connFd, reinterpret_cast<struct sockaddr*>(&sa),
82                             sizeof(sa)))
83             .WillOnce(Return(0));
84     }
85 
86     internal::InternalSysMock sysMock;
87     ipmiblob::BlobInterfaceMock blobMock;
88     ProgressMock progMock;
89 
90     const std::string host = "::1"s;
91     const std::string port = "622"s;
92 
93     struct sockaddr_in6 sa;
94     struct addrinfo addr;
95 
96     static constexpr std::uint16_t session = 0xbeef;
97     const std::string filePath = "/asdf"s;
98     static constexpr int inFd = 5;
99     static constexpr int connFd = 7;
100     static constexpr size_t fakeFileSize = 128;
101     static constexpr size_t chunkSize = 16;
102 
103     NetDataHandler handler;
104 };
105 
106 TEST_F(NetHandleTest, openFileFail)
107 {
108     EXPECT_CALL(sysMock, open(StrEq(filePath.c_str()), _))
109         .WillOnce(SetErrnoAndReturn(EACCES, -1));
110 
111     EXPECT_FALSE(handler.sendContents(filePath, session));
112 }
113 
114 TEST_F(NetHandleTest, getaddrinfoFail)
115 {
116     expectOpenFile();
117 
118     EXPECT_CALL(sysMock,
119                 getaddrinfo(StrEq(host), StrEq(port),
120                             AllOf(Field(&addrinfo::ai_flags, AI_NUMERICHOST),
121                                   Field(&addrinfo::ai_family, AF_UNSPEC),
122                                   Field(&addrinfo::ai_socktype, SOCK_STREAM)),
123                             NotNull()))
124         .WillOnce(Return(EAI_ADDRFAMILY));
125 
126     EXPECT_FALSE(handler.sendContents(filePath, session));
127 }
128 
129 TEST_F(NetHandleTest, connectFail)
130 {
131     expectOpenFile();
132     expectAddrInfo();
133 
134     EXPECT_CALL(sysMock, socket(AF_INET6, SOCK_STREAM, 0))
135         .WillOnce(Return(connFd));
136     EXPECT_CALL(sysMock, close(connFd)).WillOnce(Return(0));
137     EXPECT_CALL(
138         sysMock,
139         connect(connFd, reinterpret_cast<struct sockaddr*>(&sa), sizeof(sa)))
140         .WillOnce(SetErrnoAndReturn(ECONNREFUSED, -1));
141 
142     EXPECT_FALSE(handler.sendContents(filePath, session));
143 }
144 
145 TEST_F(NetHandleTest, sendfileFail)
146 {
147     expectOpenFile();
148     expectAddrInfo();
149     expectConnection();
150 
151     EXPECT_CALL(sysMock, sendfile(connFd, inFd, Pointee(0), _))
152         .WillOnce(SetErrnoAndReturn(ETIMEDOUT, -1));
153 
154     EXPECT_FALSE(handler.sendContents(filePath, session));
155 }
156 
157 TEST_F(NetHandleTest, successOneChunk)
158 {
159     expectOpenFile();
160     expectAddrInfo();
161     expectConnection();
162 
163     {
164         InSequence seq;
165 
166         EXPECT_CALL(sysMock,
167                     sendfile(connFd, inFd, Pointee(0), Gt(fakeFileSize)))
168             .WillOnce(
169                 DoAll(SetArgPointee<2>(fakeFileSize), Return(fakeFileSize)));
170         EXPECT_CALL(sysMock, sendfile(connFd, inFd, Pointee(fakeFileSize),
171                                       Gt(fakeFileSize)))
172             .WillOnce(Return(0));
173     }
174 
175     struct ipmi_flash::ExtChunkHdr chunk;
176     chunk.length = fakeFileSize;
177     std::vector<std::uint8_t> chunkBytes(sizeof(chunk));
178     std::memcpy(chunkBytes.data(), &chunk, sizeof(chunk));
179     EXPECT_CALL(blobMock, writeBytes(session, 0, ContainerEq(chunkBytes)));
180 
181     EXPECT_CALL(progMock, updateProgress(fakeFileSize));
182 
183     EXPECT_TRUE(handler.sendContents(filePath, session));
184 }
185 
186 TEST_F(NetHandleTest, successMultiChunk)
187 {
188     expectOpenFile();
189     expectAddrInfo();
190     expectConnection();
191 
192     struct ipmi_flash::ExtChunkHdr chunk;
193     chunk.length = chunkSize;
194     std::vector<std::uint8_t> chunkBytes(sizeof(chunk));
195     std::memcpy(chunkBytes.data(), &chunk, sizeof(chunk));
196 
197     {
198         InSequence seq;
199 
200         for (std::uint32_t offset = 0; offset < fakeFileSize;
201              offset += chunkSize)
202         {
203             EXPECT_CALL(sysMock,
204                         sendfile(connFd, inFd, Pointee(offset), Gt(chunkSize)))
205                 .WillOnce(DoAll(SetArgPointee<2>(offset + chunkSize),
206                                 Return(chunkSize)));
207 
208             EXPECT_CALL(blobMock,
209                         writeBytes(session, offset, ContainerEq(chunkBytes)));
210             EXPECT_CALL(progMock, updateProgress(chunkSize));
211         }
212         EXPECT_CALL(sysMock, sendfile(connFd, inFd, Pointee(fakeFileSize),
213                                       Gt(chunkSize)))
214             .WillOnce(Return(0));
215     }
216 
217     EXPECT_TRUE(handler.sendContents(filePath, session));
218 }
219 
220 TEST_F(NetHandleTest, successFallback)
221 {
222     expectOpenFile();
223     expectAddrInfo();
224     expectConnection();
225 
226     struct ipmi_flash::ExtChunkHdr chunk;
227     chunk.length = chunkSize;
228     std::vector<std::uint8_t> chunkBytes(sizeof(chunk));
229     std::memcpy(chunkBytes.data(), &chunk, sizeof(chunk));
230 
231     {
232         InSequence seq;
233         EXPECT_CALL(sysMock, sendfile(connFd, inFd, _, _))
234             .WillOnce([](int, int, off_t*, size_t) {
235                 errno = EINVAL;
236                 return -1;
237             });
238 
239         std::vector<uint8_t> chunk(chunkSize);
240         for (std::uint32_t offset = 0; offset < fakeFileSize;
241              offset += chunkSize)
242         {
243             for (size_t i = 0; i < chunkSize; ++i)
244             {
245                 chunk[i] = i + offset;
246             }
247             EXPECT_CALL(sysMock, read(inFd, _, Ge(chunkSize)))
248                 .WillOnce([chunk](int, void* buf, size_t) {
249                     memcpy(buf, chunk.data(), chunkSize);
250                     return chunkSize;
251                 });
252             EXPECT_CALL(sysMock, send(connFd, _, chunkSize, 0))
253                 .WillOnce([chunk](int, const void* data, size_t len, int) {
254                     std::vector<uint8_t> dcopy(len);
255                     memcpy(dcopy.data(), data, len);
256                     EXPECT_THAT(dcopy, ContainerEq(chunk));
257                     return chunkSize;
258                 });
259             EXPECT_CALL(blobMock,
260                         writeBytes(session, offset, ContainerEq(chunkBytes)));
261             EXPECT_CALL(progMock, updateProgress(chunkSize));
262         }
263         EXPECT_CALL(sysMock, read(inFd, _, Ge(chunkSize))).WillOnce(Return(0));
264         EXPECT_CALL(sysMock, send(connFd, _, 0, 0)).WillOnce(Return(0));
265     }
266 
267     EXPECT_TRUE(handler.sendContents(filePath, session));
268 }
269 
270 } // namespace
271 } // namespace host_tool
272