utest.cpp
Go to the documentation of this file.
1 /*
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2011, Southwest Research Institute
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions are met:
9  *
10  * * Redistributions of source code must retain the above copyright
11  * notice, this list of conditions and the following disclaimer.
12  * * Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in the
14  * documentation and/or other materials provided with the distribution.
15  * * Neither the name of the Southwest Research Institute, nor the names
16  * of its contributors may be used to endorse or promote products derived
17  * from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
50 
51 #include <gtest/gtest.h>
52 // Use pthread instead of boost::thread so we can cancel the TCP/UDP server
53 // threads
54 //#include <boost/thread/thread.hpp>
55 #include <pthread.h>
56 #include <limits>
57 
58 using namespace industrial::simple_message;
59 using namespace industrial::byte_array;
60 using namespace industrial::shared_types;
61 using namespace industrial::smpl_msg_connection;
62 using namespace industrial::udp_socket;
63 using namespace industrial::udp_client;
64 using namespace industrial::udp_server;
65 using namespace industrial::tcp_socket;
66 using namespace industrial::tcp_client;
67 using namespace industrial::tcp_server;
68 using namespace industrial::ping_message;
69 using namespace industrial::ping_handler;
70 using namespace industrial::joint_data;
71 using namespace industrial::joint_message;
72 using namespace industrial::message_manager;
74 using namespace industrial::joint_traj_pt;
75 using namespace industrial::joint_traj_pt_message;
76 using namespace industrial::typed_message;
77 using namespace industrial::joint_traj;
78 
79 // Multiple tests require TEST_PORT_BASE to be defined. This is defined
80 // by the make file at compile time.
81 //#define TEST_PORT_BASE 11000
82 
83 TEST(ByteArraySuite, init)
84 {
85 
86  const shared_int SIZE = 100;
87 
88  ByteArray bytes;
89  char buffer[SIZE];
90 
91  // Valid byte arrays
92  EXPECT_TRUE(bytes.init(&buffer[0], SIZE));
93  EXPECT_EQ((shared_int)bytes.getBufferSize(), SIZE);
94 
95  // Invalid init (too big)
96  if (bytes.getMaxBufferSize() < std::numeric_limits<shared_int>::max())
97  {
98  shared_int TOO_BIG = bytes.getMaxBufferSize()+1;
99  char bigBuffer[TOO_BIG];
100  EXPECT_FALSE(bytes.init(&bigBuffer[0], TOO_BIG));
101  }
102  else
103  std::cout << std::string(15, ' ')
104  << "ByteArray.MaxSize==INT_MAX. Skipping TOO_BIG tests" << std::endl;
105 
106 }
107 
108 TEST(ByteArraySuite, loading)
109 {
110  const shared_int SIZE = 100;
111  char buffer[SIZE];
112 
113  ByteArray bytes;
114  ByteArray empty;
115 
116  ASSERT_TRUE(bytes.init(&buffer[0], SIZE));
117 
118  shared_bool bIN = true, bOUT = false;
119  shared_int iIN = 999, iOUT = 0;
120  shared_real rIN = 9999.9999, rOUT = 0;
121 
122  // Boolean loading
123  EXPECT_TRUE(bytes.load(bIN));
124  EXPECT_EQ(bytes.getBufferSize(), SIZE+sizeof(shared_bool));
125  EXPECT_TRUE(bytes.unload(bOUT));
126  EXPECT_EQ((shared_int)bytes.getBufferSize(), SIZE);
127  EXPECT_EQ(bOUT, bIN);
128 
129  // Integer loading
130  EXPECT_TRUE(bytes.load(iIN));
131  EXPECT_EQ(bytes.getBufferSize(), SIZE+sizeof(shared_int));
132  EXPECT_TRUE(bytes.unload(iOUT));
133  EXPECT_EQ((shared_int)bytes.getBufferSize(), SIZE);
134  EXPECT_EQ(iOUT, iIN);
135 
136  // Real loading
137  EXPECT_TRUE(bytes.load(rIN));
138  EXPECT_EQ(bytes.getBufferSize(), SIZE+sizeof(shared_real));
139  EXPECT_TRUE(bytes.unload(rOUT));
140  EXPECT_EQ((shared_int)bytes.getBufferSize(), SIZE);
141  EXPECT_EQ(rOUT, rIN);
142 
143  // Unloading a single member (down to an empty buffer size)
144  EXPECT_TRUE(empty.load(bIN));
145  EXPECT_EQ(empty.getBufferSize(), sizeof(shared_bool));
146  EXPECT_TRUE(empty.unload(bOUT));
147  EXPECT_EQ((int)empty.getBufferSize(), 0);
148  EXPECT_EQ(bOUT, bIN);
149 
150  // Loading two members (unloading the first) and then checking the value of the second
151  rOUT = 0.0;
152  iOUT = 0;
153  EXPECT_TRUE(empty.load(rIN));
154  EXPECT_EQ(empty.getBufferSize(), sizeof(shared_real));
155  EXPECT_TRUE(empty.load(iIN));
156  EXPECT_EQ(empty.getBufferSize(), sizeof(shared_real)+sizeof(shared_int));
157  EXPECT_TRUE(empty.unloadFront(rOUT));
158  EXPECT_EQ(rOUT, rIN);
159  EXPECT_TRUE(empty.unload(iOUT));
160  EXPECT_EQ((int)empty.getBufferSize(), 0);
161  EXPECT_EQ(iOUT, iIN);
162 }
163 
164 TEST(ByteArraySuite, byteSwapping)
165 {
166  if(ByteArray::isByteSwapEnabled())
167  {
168  ASSERT_TRUE(ByteArray::isByteSwapEnabled());
169 
170  ByteArray swapped;
171  unsigned char buffer[] = {
172  0x00, 0x00, 0x00, 0x38, // be: 56
173  0x00, 0x00, 0x00, 0x0a, // be: 10
174  0x00, 0x00, 0x00, 0x01, // be: 1
175 
176  0x3e, 0x81, 0x32, 0x64, // be: 0.25233757495880127
177  0x3f, 0x30, 0x4b, 0x75, // be: 0.68865138292312622
178  0x3f, 0xa8, 0x9d, 0xd2, // be: 1.3173162937164307
179  0x3f, 0x85, 0x93, 0xdd, // be: 1.0435749292373657
180  0xbf, 0xf4, 0x8c, 0xc5, // be: -1.9105459451675415
181 
182  };
183  const unsigned int bufferLength = 32;
184  shared_int tempInt;
185  shared_real tempReal;
186 
187  swapped.init((const char*) buffer, bufferLength);
188  ASSERT_EQ(swapped.getBufferSize(), bufferLength);
189 
190  ASSERT_TRUE(swapped.unload(tempReal));
191  EXPECT_FLOAT_EQ(tempReal, -1.9105459451675415);
192 
193  ASSERT_TRUE(swapped.unload(tempReal));
194  EXPECT_FLOAT_EQ(tempReal, 1.0435749292373657);
195 
196  ASSERT_TRUE(swapped.unload(tempReal));
197  EXPECT_FLOAT_EQ(tempReal, 1.3173162937164307);
198 
199  ASSERT_TRUE(swapped.unload(tempReal));
200  EXPECT_FLOAT_EQ(tempReal, 0.68865138292312622);
201 
202  ASSERT_TRUE(swapped.unload(tempReal));
203  EXPECT_FLOAT_EQ(tempReal, 0.25233757495880127);
204 
205  ASSERT_TRUE(swapped.unload(tempInt));
206  EXPECT_EQ(tempInt, 1);
207 
208  ASSERT_TRUE(swapped.unload(tempInt));
209  EXPECT_EQ(tempInt, 10);
210 
211  ASSERT_TRUE(swapped.unload(tempInt));
212  EXPECT_EQ(tempInt, 56);
213 
214  ASSERT_EQ(swapped.getBufferSize(), 0);
215  }
216 
217 }
218 
219 TEST(ByteArraySuite, copy)
220 {
221 
222  const shared_int SIZE = 100;
223  char buffer[SIZE];
224 
225  // Copy
226  ByteArray copyFrom;
227  ByteArray copyTo;
228 
229  EXPECT_TRUE(copyFrom.init(&buffer[0], SIZE));
230  EXPECT_TRUE(copyTo.load(copyFrom));
231  EXPECT_EQ((shared_int)copyTo.getBufferSize(), SIZE);
232  EXPECT_TRUE(copyTo.load(copyFrom));
233  EXPECT_EQ((shared_int)copyTo.getBufferSize(), 2*SIZE);
234 
235  // Copy too large
236  ByteArray tooBig;
237  if (tooBig.getMaxBufferSize()-1 <= std::numeric_limits<shared_int>::max())
238  {
239  shared_int TOO_BIG = tooBig.getMaxBufferSize()-1;
240  char bigBuffer[TOO_BIG];
241 
242  EXPECT_TRUE(tooBig.init(&bigBuffer[0], TOO_BIG));
243  EXPECT_FALSE(copyTo.load(tooBig));
244  // A failed load should not change the buffer.
245  EXPECT_EQ((shared_int)copyTo.getBufferSize(), 2*SIZE);
246  }
247  else
248  std::cout << std::string(15, ' ')
249  << "ByteArray.MaxSize==INT_MAX. Skipping TOO_BIG tests" << std::endl;
250 }
251 
252 // Need access to protected members for testing
253 #ifndef UDP_TEST
254 class TestClient : public TcpClient
255 {
256  public:
257  bool sendBytes(ByteArray & buffer)
258  {
259  return TcpClient::sendBytes(buffer);
260  };
261 };
262 class TestServer : public TcpServer
263 {
264  public:
265  bool receiveBytes(ByteArray & buffer, shared_int num_bytes)
266  {
267  return TcpServer::receiveBytes(buffer, num_bytes);
268  }
269 };
270 #else
271 class TestClient : public UdpClient
272 {
273  public:
274  bool sendBytes(ByteArray & buffer)
275  {
276  return UdpClient::sendBytes(buffer);
277  };
278 };
279 class TestServer : public UdpServer
280 {
281  public:
282  bool receiveBytes(ByteArray & buffer, shared_int num_bytes)
283  {
284  return UdpServer::receiveBytes(buffer, num_bytes);
285  }
286 };
287 #endif
288 
289 void*
291 {
292  TestServer* server = (TestServer*)arg;
293  server->makeConnect();
294  return NULL;
295 }
296 
297 TEST(SocketSuite, read)
298 {
299  const int port = TEST_PORT_BASE;
300  char ipAddr[] = "127.0.0.1";
301 
302  TestClient client;
303  TestServer server;
304  ByteArray send, recv;
305  shared_int DATA = 99;
306  shared_int TWO_INTS = 2 * sizeof(shared_int);
307  shared_int ONE_INTS = 1 * sizeof(shared_int);
308 
309  // Construct server
310  ASSERT_TRUE(server.init(port));
311 
312  // Construct a client
313  ASSERT_TRUE(client.init(&ipAddr[0], port));
314  pthread_t serverConnectThrd;
315  pthread_create(&serverConnectThrd, NULL, connectServerFunc, &server);
316 
317  ASSERT_TRUE(client.makeConnect());
318  pthread_join(serverConnectThrd, NULL);
319 
320  ASSERT_TRUE(send.load(DATA));
321 
322  // Send just right amount
323  ASSERT_TRUE(client.sendBytes(send));
324  ASSERT_TRUE(client.sendBytes(send));
325  sleep(2);
326  ASSERT_TRUE(server.receiveBytes(recv, TWO_INTS));
327  ASSERT_EQ(TWO_INTS, recv.getBufferSize());
328 
329  // Send too many bytes
330  ASSERT_TRUE(client.sendBytes(send));
331  ASSERT_TRUE(client.sendBytes(send));
332  ASSERT_TRUE(client.sendBytes(send));
333  ASSERT_TRUE(server.receiveBytes(recv, TWO_INTS));
334  ASSERT_EQ(TWO_INTS, recv.getBufferSize());
335  ASSERT_TRUE(server.receiveBytes(recv, ONE_INTS));
336  ASSERT_EQ(ONE_INTS, recv.getBufferSize());
337 }
338 
339 
340 // Utility for running tcp client in sending loop
341 void*
342 spinSender(void* arg)
343 {
344  TestClient* client = (TestClient*)arg;
345  ByteArray send;
346  const int DATA = 256;
347 
348  send.load(DATA);
349 
350  while(true)
351  {
352  client->sendBytes(send);
353  sleep(0.1);
354  }
355 }
356 
357 TEST(SocketSuite, splitPackets)
358 {
359  const int port = TEST_PORT_BASE + 1;
360  char ipAddr[] = "127.0.0.1";
361  const int RECV_LENGTH = 64;
362 
363  TestClient client;
364  TestServer server;
365  ByteArray recv;
366 // Construct server
367  ASSERT_TRUE(server.init(port));
368 
369  // Construct a client
370  ASSERT_TRUE(client.init(&ipAddr[0], port));
371  pthread_t serverConnectThrd;
372  pthread_create(&serverConnectThrd, NULL, connectServerFunc, &server);
373 
374  ASSERT_TRUE(client.makeConnect());
375  pthread_join(serverConnectThrd, NULL);
376 
377  pthread_t senderThrd;
378  pthread_create(&senderThrd, NULL, spinSender, &client);
379 
380  ASSERT_TRUE(server.receiveBytes(recv, RECV_LENGTH));
381  ASSERT_EQ(RECV_LENGTH, recv.getBufferSize());
382 
383  pthread_cancel(senderThrd);
384  pthread_join(senderThrd, NULL);
385 }
386 
387 
388 TEST(SimpleMessageSuite, init)
389 {
390  SimpleMessage msg;
391  ByteArray bytes;
392 
393  // Valid messages
398 
399  // Unused command
401 
402  // Service request with a reply
405 }
406 
407 TEST(PingMessageSuite, init)
408 {
409  PingMessage ping;
410  SimpleMessage msg;
411 
412  EXPECT_FALSE(ping.init(msg));
413  ping.init();
414  EXPECT_EQ(StandardMsgTypes::PING, ping.getMessageType());
415 
416  ping = PingMessage();
418  EXPECT_TRUE(ping.init(msg));
419  EXPECT_EQ(StandardMsgTypes::PING, ping.getMessageType());
420 }
421 
422 TEST(PingMessageSuite, toMessage)
423 {
424  PingMessage ping;
425  SimpleMessage msg;
426 
427  ping.init();
428 
429  ASSERT_TRUE(ping.toReply(msg, ReplyTypes::SUCCESS));
430  EXPECT_EQ(StandardMsgTypes::PING, msg.getMessageType());
431  EXPECT_EQ(CommTypes::SERVICE_REPLY, msg.getCommType());
432  EXPECT_EQ(ReplyTypes::SUCCESS, msg.getReplyCode());
433 
434  ASSERT_TRUE(ping.toRequest(msg));
435  EXPECT_EQ(StandardMsgTypes::PING, msg.getMessageType());
436  EXPECT_EQ(CommTypes::SERVICE_REQUEST, msg.getCommType());
437  EXPECT_EQ(ReplyTypes::INVALID, msg.getReplyCode());
438 
439  EXPECT_FALSE(ping.toTopic(msg));
440 
441 }
442 
443 TEST(PingHandlerSuite, init)
444 {
445  PingHandler handler;
446  TestClient client;
447 
448  ASSERT_TRUE(handler.init(&client));
449  EXPECT_EQ(StandardMsgTypes::PING, handler.getMsgType());
450 
451  EXPECT_FALSE(handler.init(NULL));
452 
453 }
454 
455 TEST(MessageManagerSuite, init)
456 {
457  MessageManager manager;
458  TestClient client;
459 
460  EXPECT_TRUE(manager.init(&client));
461  EXPECT_FALSE(manager.init(NULL));
462 
463 }
464 
465 TEST(MessageManagerSuite, addHandler)
466 {
467  MessageManager manager;
468  TestClient client;
469  PingHandler handler;
470 
471  EXPECT_EQ(0, (int)manager.getNumHandlers());
472 
473  ASSERT_TRUE(manager.init(&client));
474  EXPECT_EQ(1, (int)manager.getNumHandlers());
475  EXPECT_FALSE(manager.add(NULL));
476 
477  ASSERT_TRUE(handler.init(&client));
478  EXPECT_FALSE(manager.add(&handler));
479 }
480 
481 // wrapper around MessageManager::spin() that can be passed to
482 // pthread_create()
483 void*
484 spinFunc(void* arg)
485 {
486  MessageManager* mgr = (MessageManager*)arg;
487  mgr->spin();
488  return NULL;
489 }
490 
491 /* Commenting out this test because build shows "unstable" with disabled tests
492 // See https://github.com/ros-industrial/industrial_core/issues/149 for details
493 TEST(DISABLED_MessageManagerSuite, tcp)
494 {
495  const int port = TEST_PORT_BASE + 201;
496  char ipAddr[] = "127.0.0.1";
497 
498  TestClient* client = new TestClient();
499  TestServer server;
500  SimpleMessage pingRequest, pingReply;
501  MessageManager msgManager;
502 
503  // MessageManager uses ros::ok, which needs ros spinner
504  ros::AsyncSpinner spinner(0);
505  spinner.start();
506 
507  ASSERT_TRUE(pingRequest.init(StandardMsgTypes::PING, CommTypes::SERVICE_REQUEST, ReplyTypes::INVALID));
508 
509  // TCP Socket testing
510 
511  // Construct server
512  ASSERT_TRUE(server.init(port));
513 
514  // Construct a client
515  ASSERT_TRUE(client->init(&ipAddr[0], port));
516 
517  // Connect server and client
518  pthread_t serverConnectThrd;
519  pthread_create(&serverConnectThrd, NULL, connectServerFunc, &server);
520 
521  ASSERT_TRUE(client->makeConnect());
522  pthread_join(serverConnectThrd, NULL);
523 
524  // Listen for client connection, init manager and start thread
525  ASSERT_TRUE(msgManager.init(&server));
526 
527  // TODO: The message manager is not thread safe (threads are used for testing,
528  // but running the message manager in a thread results in errors when the
529  // underlying connection is deconstructed before the manager
530  //boost::thread spinSrvThrd(boost::bind(&MessageManager::spin, &msgManager));
531  pthread_t spinSrvThrd;
532  pthread_create(&spinSrvThrd, NULL, spinFunc, &msgManager);
533 
534  // Ping the server
535  ASSERT_TRUE(client->sendMsg(pingRequest));
536  ASSERT_TRUE(client->receiveMsg(pingReply));
537  ASSERT_TRUE(client->sendAndReceiveMsg(pingRequest, pingReply));
538 
539  // Delete client and try to reconnect
540 
541  delete client;
542  sleep(10); //Allow time for client to destruct and free up port
543  client = new TestClient();
544 
545  ASSERT_TRUE(client->init(&ipAddr[0], port));
546  ASSERT_TRUE(client->makeConnect());
547  ASSERT_TRUE(client->sendAndReceiveMsg(pingRequest, pingReply));
548 
549  pthread_cancel(spinSrvThrd);
550  pthread_join(spinSrvThrd, NULL);
551 
552  delete client;
553 }
554 */
555 
556 // Run all the tests that were declared with TEST()
557 int main(int argc, char **argv)
558 {
559  ros::init(argc, argv, "test"); // some tests need ROS framework
560  testing::InitGoogleTest(&argc, argv);
561  return RUN_ALL_TESTS();
562 }
563 
Defines TCP server functions.
Definition: tcp_server.h:49
bool init(char *buff, int port_num)
initializes TCP client socket. Object can either be a client OR a server, NOT BOTH.
Definition: tcp_client.cpp:54
void * spinFunc(void *arg)
Definition: utest.cpp:484
bool init(industrial::simple_message::SimpleMessage &msg)
Initializes message from a simple message.
Contains platform specific type definitions that guarantee the size of primitive data types...
Definition: shared_types.h:52
void init()
Initializes or Reinitializes an empty buffer.
Definition: byte_array.cpp:62
bool init(industrial::smpl_msg_connection::SmplMsgConnection *connection)
Class initializer.
ROSCPP_DECL void init(int &argc, char **argv, const std::string &name, uint32_t options=0)
This class defines a simple messaging protocol for communicating with an industrial robot controller...
int getMessageType()
Gets message type(see StandardMsgType)
bool unloadFront(industrial::shared_types::shared_real &value)
unloads a double value from the beginning of the byte array. If byte swapping is enabled, then the bytes are swapped.
Definition: byte_array.cpp:334
bool load(industrial::shared_types::shared_bool value)
loads a boolean into the byte array
Definition: byte_array.cpp:142
virtual bool toRequest(industrial::simple_message::SimpleMessage &msg)
creates a simple_message request
Definition: typed_message.h:96
Class encapsulated ping message generation methods (either to or from a SimpleMessage type...
Definition: ping_message.h:60
unsigned int getNumHandlers()
Gets number of handlers.
bool receiveBytes(ByteArray &buffer, shared_int num_bytes)
Method used by receive message interface method. This should be overridden for the specific connectio...
Definition: utest.cpp:265
bool makeConnect()
connects to the remote host
Definition: tcp_server.cpp:118
int getMsgType()
Gets message type that callback expects.
void spin()
Perform a indefinite execution of the message manager.
bool add(industrial::message_handler::MessageHandler *handler, bool allow_replace=false)
Adds a message handler to the manager.
Message handler that handles ping messages.
Definition: ping_handler.h:60
The byte array wraps a dynamic array of bytes (i.e. char).
Definition: byte_array.h:80
bool sendBytes(ByteArray &buffer)
Method used by send message interface method. This should be overridden for the specific connection t...
Definition: utest.cpp:257
int main(int argc, char **argv)
Definition: utest.cpp:557
bool toTopic(industrial::simple_message::SimpleMessage &msg)
The ping message overrides the base method toTopic to always return false. A ping cannot be sent as a...
Definition: ping_message.h:96
unsigned int getMaxBufferSize()
gets current buffer size
Definition: byte_array.cpp:392
The message manager handles communications for a simple server.
bool init(int msgType, int commType, int replyCode, industrial::byte_array::ByteArray &data)
Initializes a fully populated simple message.
int getMessageType() const
gets message type (enumeration)
virtual bool toReply(industrial::simple_message::SimpleMessage &msg, industrial::simple_message::ReplyType reply)
creates a simple_message reply
bool makeConnect()
connects to the remote host
Definition: tcp_client.cpp:108
void * connectServerFunc(void *arg)
Definition: utest.cpp:290
unsigned int getBufferSize()
gets current buffer size
Definition: byte_array.cpp:387
TEST(ByteArraySuite, init)
Definition: utest.cpp:83
bool unload(industrial::shared_types::shared_bool &value)
unloads a boolean value from the byte array
Definition: byte_array.cpp:233
void * spinSender(void *arg)
Definition: utest.cpp:342
bool init(int port_num)
initializes TCP server socket. The connect method must be called following initialization in order to...
Definition: tcp_server.cpp:57
bool init(industrial::smpl_msg_connection::SmplMsgConnection *connection)
Class initializer.
Defines TCP client functions.
Definition: tcp_client.h:51
int getReplyCode()
Gets reply code(see ReplyType)
int getCommType()
Gets message type(see CommType)


simple_message
Author(s): Shaun Edwards
autogenerated on Sat Sep 21 2019 03:30:09