Arduino-Redis
A Redis library for Arduino.
RedisInternal.cpp
1 #include "RedisInternal.h"
2 #include <map>
3 #include <limits.h>
4 
5 #if ARDUINO_REDIS_SERIAL_TRACE && 0
6 void pbytes(uint8_t* bytes, ssize_t len, const char* header = nullptr)
7 {
8  Serial.println();
9  const uint _break = 0x20;
10  Serial.printf("[%d bytes] %s\n", len, header ? header : "");
11  for (int i = 0; i < len; i++)
12  {
13  if (!(i%_break)) Serial.printf("[H] %08x> ", i);
14  Serial.printf("%02x ", *(bytes + i));
15  if (!((i+1)%_break)) Serial.printf("\n");
16  }
17  Serial.println();
18  for (int i = 0; i < len; i++)
19  {
20  if (!(i%_break)) Serial.printf("[C] %08x> ", i);
21  Serial.printf("% 2c ", *(bytes + i));
22  if (!((i+1)%_break)) Serial.printf("\n");
23  }
24  Serial.println();
25  Serial.println();
26 }
27 #else
28 #define pbytes
29 #endif
30 
31 void RedisObject::init(Client& client)
32 {
33  data = client.readStringUntil('\r');
34  pbytes((uint8_t*)data.c_str(), data.length(), "RedisObject::init()::readStringUntil");
35  client.read(); // discard '\n'
36 }
37 
39 {
40  String emitStr((char)_type);
41  // Simple strings cannot contain CRLF, as they must terminate with CRLF
42  // https://redis.io/topics/protocol#resp-simple-strings
43  data.replace(CRLF, F(""));
44  emitStr += data;
45  emitStr += CRLF;
46  return emitStr;
47 }
48 
49 void RedisBulkString::init(Client& client)
50 {
51  auto dLen = data.toInt();
52 
53  // "Null Bulk String" -- https://redis.io/topics/protocol#resp-bulk-strings
54  if (dLen == -1) {
55  data = (const char*)nullptr;
56  return;
57  }
58 
59  auto charBuf = new char[dLen + 1];
60  bzero(charBuf, dLen + 1);
61 
62  auto crlfCstr = String(CRLF).c_str();
63  auto readB = client.readBytes(charBuf, dLen);
64  pbytes((uint8_t*)charBuf, readB, "RedisBulkString::init()::readBytes");
65  if (readB != dLen) {
66  Serial.printf("ERROR! Bad read (%d ?= %d)\n", readB, dLen);
67  exit(-1);
68  }
69 
70  data = String(charBuf);
71  delete [] charBuf;
72 }
73 
75 {
76  String emitStr((char)_type);
77  emitStr += String(data.length());
78  emitStr += CRLF;
79  emitStr += data;
80  emitStr += CRLF;
81  return emitStr;
82 }
83 
84 void RedisArray::init(Client& client)
85 {
86  for (int i = 0; i < data.toInt(); i++)
87  add(RedisObject::parseType(client));
88 }
89 
90 RedisArray::operator std::vector<String>() const
91 {
92  std::vector<String> rv;
93  for (auto ro : vec)
94  rv.push_back((String)*ro.get());
95  return rv;
96 }
97 
99 {
100  String emitStr((char)_type);
101  emitStr += String(vec.size());
102  emitStr += CRLF;
103  for (auto rTypeInst : vec) {
104  emitStr += rTypeInst->RESP();
105  }
106  return emitStr;
107 }
108 
109 std::shared_ptr<RedisObject> RedisCommand::issue(Client& cmdClient)
110 {
111  if (!cmdClient.connected())
112  return std::shared_ptr<RedisObject>(new RedisInternalError("Client is not connected"));
113 
114  auto cmdRespStr = RESP();
115  sprint("----- CMD ----\n%s---- /CMD ----\n", cmdRespStr.c_str());
116  cmdClient.print(cmdRespStr);
117  auto ret = RedisObject::parseType(cmdClient);
118  if (ret && ret->type() == RedisObject::Type::InternalError)
119  _err = (String)*ret;
120  return ret;
121 }
122 
123 template <>
124 int RedisCommand::issue_typed<int>(Client& cmdClient)
125 {
126  auto cmdRet = issue(cmdClient);
127  if (!cmdRet)
128  return INT_MAX - 0x0f;
129  if (cmdRet->type() != RedisObject::Type::Integer)
130  return INT_MAX - 0xf0;
131  return (int)*((RedisInteger*)cmdRet.get());
132 }
133 
134 template <>
135 bool RedisCommand::issue_typed<bool>(Client& cmdClient)
136 {
137  auto cmdRet = issue(cmdClient);
138  if (cmdRet && cmdRet->type() == RedisObject::Type::Integer)
139  return (bool)*((RedisInteger*)cmdRet.get());
140  return false;
141 }
142 
143 template <>
144 String RedisCommand::issue_typed<String>(Client& cmdClient)
145 {
146  return (String)*issue(cmdClient);
147 }
148 
149 typedef std::map<RedisObject::Type, std::function<RedisObject*(Client&)>> TypeParseMap;
150 
151 static TypeParseMap g_TypeParseMap {
152  { RedisObject::Type::SimpleString, [](Client& c) { return new RedisSimpleString(c); } },
153  { RedisObject::Type::BulkString, [](Client& c) { return new RedisBulkString(c); } },
154  { RedisObject::Type::Integer, [](Client& c) { return new RedisInteger(c); } },
155  { RedisObject::Type::Array, [](Client& c) { return new RedisArray(c); } },
156  { RedisObject::Type::Error, [](Client& c) { return new RedisError(c); } }
157 };
158 
159 std::shared_ptr<RedisObject> RedisObject::parseType(Client& client)
160 {
161  if (client.connected()) {
162  while (!client.available());
163 
164 #if ARDUINO_REDIS_SERIAL_TRACE
165  int readB = 0;
166 #endif
167  RedisObject::Type typeChar = RedisObject::Type::NoType;
168  do {
169  typeChar = (RedisObject::Type)client.read();
170 #if ARDUINO_REDIS_SERIAL_TRACE
171  ++readB;
172 #endif
173  } while (typeChar == -1 || typeChar == '\r' || typeChar == '\n');
174 
175 #if ARDUINO_REDIS_SERIAL_TRACE
176  sprint("Parsed type character '%c' (after %d reads) (0x%x, %dd)\n", typeChar, readB, typeChar, (int)typeChar);
177 #endif
178 
179  if (g_TypeParseMap.find(typeChar) != g_TypeParseMap.end()) {
180  auto retVal = g_TypeParseMap[typeChar](client);
181 
182  if (!retVal || retVal->type() == RedisObject::Type::Error) {
183  String err = retVal ? (String)*retVal : "(nil)";
184  return std::shared_ptr<RedisObject>(new RedisInternalError(err));
185  }
186 
187  return std::shared_ptr<RedisObject>(retVal);
188  }
189 
190  return std::shared_ptr<RedisObject>(new RedisInternalError("Unable to find type: " + typeChar));
191  }
192 
193  return std::shared_ptr<RedisObject>(new RedisInternalError("Not connected"));
194 }
195 
virtual String RESP() override
std::shared_ptr< RedisObject > issue(Client &cmdClient)
virtual String RESP() override
virtual void init(Client &client) override
virtual String RESP() override
virtual void init(Client &client) override
virtual String RESP()=0
virtual void init(Client &client)