From 1f6b57a5d2aade703945e8c51aeaf332f6c222de Mon Sep 17 00:00:00 2001 From: Andrew Lindesay Date: Sat, 13 May 2017 18:50:39 +1200 Subject: [PATCH] Implementation of JSON Streaming Parser This change will introduce a streaming parser capability to Haiku. The existing functionality of writing the JSON data to a BMessage in-memory model is retained. The new parser implements a SAX-style listener based interface where the listener accepts parse events. Unit tests have been supplied for the JSON parser as well. --- src/kits/shared/Jamfile | 5 + src/kits/shared/Json.cpp | 1060 +++++++++++------ src/kits/shared/JsonEvent.cpp | 127 ++ src/kits/shared/JsonEventListener.cpp | 16 + src/kits/shared/JsonMessageWriter.cpp | 520 ++++++++ src/kits/shared/JsonTextWriter.cpp | 697 +++++++++++ src/kits/shared/JsonWriter.cpp | 144 +++ src/tests/kits/shared/Jamfile | 4 + src/tests/kits/shared/JsonEndToEndTest.cpp | 215 ++++ src/tests/kits/shared/JsonEndToEndTest.h | 45 + .../kits/shared/JsonErrorHandlingTest.cpp | 354 ++++++ src/tests/kits/shared/JsonErrorHandlingTest.h | 61 + src/tests/kits/shared/JsonSamples.h | 707 +++++++++++ src/tests/kits/shared/JsonTextWriterTest.cpp | 207 ++++ src/tests/kits/shared/JsonTextWriterTest.h | 35 + src/tests/kits/shared/JsonToMessageTest.cpp | 292 +++++ src/tests/kits/shared/JsonToMessageTest.h | 38 + src/tests/kits/shared/SharedTestAddon.cpp | 9 + 18 files changed, 4150 insertions(+), 386 deletions(-) create mode 100644 src/kits/shared/JsonEvent.cpp create mode 100644 src/kits/shared/JsonEventListener.cpp create mode 100644 src/kits/shared/JsonMessageWriter.cpp create mode 100644 src/kits/shared/JsonTextWriter.cpp create mode 100644 src/kits/shared/JsonWriter.cpp create mode 100644 src/tests/kits/shared/JsonEndToEndTest.cpp create mode 100644 src/tests/kits/shared/JsonEndToEndTest.h create mode 100644 src/tests/kits/shared/JsonErrorHandlingTest.cpp create mode 100644 src/tests/kits/shared/JsonErrorHandlingTest.h create mode 100644 src/tests/kits/shared/JsonSamples.h create mode 100644 src/tests/kits/shared/JsonTextWriterTest.cpp create mode 100644 src/tests/kits/shared/JsonTextWriterTest.h create mode 100644 src/tests/kits/shared/JsonToMessageTest.cpp create mode 100644 src/tests/kits/shared/JsonToMessageTest.h diff --git a/src/kits/shared/Jamfile b/src/kits/shared/Jamfile index 8b3dfc9c1f..3266d4ce34 100644 --- a/src/kits/shared/Jamfile +++ b/src/kits/shared/Jamfile @@ -39,6 +39,11 @@ for architectureObject in [ MultiArchSubDirSetup ] { HashString.cpp IconButton.cpp IconView.cpp + JsonWriter.cpp + JsonEventListener.cpp + JsonMessageWriter.cpp + JsonTextWriter.cpp + JsonEvent.cpp Json.cpp Keymap.cpp LongAndDragTrackingFilter.cpp diff --git a/src/kits/shared/Json.cpp b/src/kits/shared/Json.cpp index 057f25d75d..284c3e5406 100644 --- a/src/kits/shared/Json.cpp +++ b/src/kits/shared/Json.cpp @@ -1,447 +1,735 @@ /* + * Copyright 2017, Andrew Lindesay * Copyright 2014-2017, Augustin Cavalier (waddlesplash) * Copyright 2014, Stephan Aßmus * Distributed under the terms of the MIT License. */ - - -#include +#include "Json.h" #include #include +#include #include -#include +#include +#include #include +#include "JsonEventListener.h" +#include "JsonMessageWriter.h" + // #pragma mark - Public methods namespace BPrivate { -class ParseException { +static bool +b_jsonparse_is_hex(char c) { + return isdigit(c) + || (c > 0x41 && c <= 0x46) + || (c > 0x61 && c <= 0x66); +} + + +static bool +b_jsonparse_all_hex(const char* c) { + for (int i = 0; i < 4; i++) { + if (!b_jsonparse_is_hex(c[i])) + return false; + } + + return true; +} + + +/*! This class carries state around the parsing process. */ + +class JsonParseContext { public: - ParseException(int32 position, BString error) + JsonParseContext(BDataIO* data, BJsonEventListener* listener) : - fPosition(position), - fError(error), - fReturnCode(B_BAD_DATA) + fListener(listener), + fData(data), + fLineNumber(1), // 1 is the first line + fPushbackChar(0), + fHasPushbackChar(false) { } - ParseException(int32 position, status_t returnCode) - : - fPosition(position), - fError(""), - fReturnCode(returnCode) - { + BJsonEventListener* Listener() const { + return fListener; } - void PrintToStream() const { - const char* error; - if (fError.Length() > 0) - error = fError.String(); - else - error = strerror(fReturnCode); - printf("Parse error at %" B_PRIi32 ": %s\n", fPosition, error); + BDataIO* Data() const { + return fData; } - status_t ReturnCode() const - { - return fReturnCode; + int LineNumber() const { + return fLineNumber; + } + + int IncrementLineNumber() { + return fLineNumber++; + } + + // TODO; there is considerable opportunity for performance improvements + // here by buffering the input and then feeding it into the parse + // algorithm character by character. + + status_t NextChar(char* buffer) { + + if (fHasPushbackChar) { + buffer[0] = fPushbackChar; + fHasPushbackChar = false; + return B_OK; + } + + return Data()->ReadExactly(buffer, 1); + } + + void PushbackChar(char c) { + fPushbackChar = c; + fHasPushbackChar = true; } private: - int32 fPosition; - BString fError; - status_t fReturnCode; + BJsonEventListener* fListener; + BDataIO* fData; + uint32 fLineNumber; + char fPushbackChar; + bool fHasPushbackChar; }; -status_t -BJson::Parse(const char* JSON, BMessage& message) -{ - BString temp(JSON); - return Parse(temp, message); -} - - status_t BJson::Parse(const BString& JSON, BMessage& message) { - try { - _Parse(JSON, message); - return B_OK; - } catch (ParseException e) { - e.PrintToStream(); - return e.ReturnCode(); - } - return B_ERROR; + return Parse(JSON.String(), message); } -// #pragma mark - Private methods +status_t +BJson::Parse(const char* JSON, BMessage& message) +{ + BMemoryIO* input = new BMemoryIO(JSON, strlen(JSON)); + ObjectDeleter inputDeleter(input); + BJsonMessageWriter* writer = new BJsonMessageWriter(message); + ObjectDeleter writerDeleter(writer); + Parse(input, writer); + status_t result = writer->ErrorStatus(); + + return result; +} + + +/*! The data is read as a stream of JSON data. As the JSON is read, events are + raised such as; + - string + - number + - true + - array start + - object end + Each event is sent to the listener to process as required. +*/ void -BJson::_Parse(const BString& JSON, BMessage& message) +BJson::Parse(BDataIO* data, BJsonEventListener* listener) { - BMessageBuilder builder(message); - int32 pos = 0; - const int32 length = JSON.Length(); - bool hadRootNode = false; - - /* Locals used by the parser. */ - // Keeps track of the hierarchy (e.g. "{[{{") that has - // been read in. Allows the parser to verify that openbraces - // match up to closebraces and so on and so forth. - BString hierarchy(""); - // Stores the key that was just read by the string parser, - // in the case that we are parsing a map. - BString key(""); - - // TODO: Check builder return codes and throw exception, or - // change builder implementation/interface to throw exceptions - // instead of returning errors. - // TODO: Elimitate more duplicated code, for example by moving - // more code into _ParseConstant(). - - while (pos < length) { - switch (JSON[pos]) { - case '{': - hierarchy += "{"; - - if (hierarchy != "{") { - if (builder.What() == JSON_TYPE_ARRAY) - builder.PushObject(builder.CountNames()); - else { - builder.PushObject(key.String()); - key = ""; - } - } else if (hadRootNode == true) { - throw ParseException(pos, - "Got '{' with empty hierarchy but already had a root node"); - } else - hadRootNode = true; - - builder.SetWhat(JSON_TYPE_MAP); - break; - - case '}': - if (key.Length() > 0) - throw ParseException(pos, "Got closebrace but still have a key"); - if (hierarchy.EndsWith("{") && hierarchy.Length() != 1) { - hierarchy.Truncate(hierarchy.Length() - 1); - builder.PopObject(); - } else if (hierarchy.EndsWith("{") && hierarchy.Length() == 1) { - hierarchy.Truncate(hierarchy.Length() - 1); - break; // Should be the end of the data. - } else - throw ParseException(pos, "Unmatched closebrace }"); - - break; - - case '[': - hierarchy += "["; - - if (hierarchy != "[") { - if (builder.What() == JSON_TYPE_ARRAY) - builder.PushObject(builder.CountNames()); - else { - builder.PushObject(key.String()); - key = ""; - } - } else if (hadRootNode == true) { - throw ParseException(pos, - "Got '[' with empty hierarchy but already had a root node"); - } else - hadRootNode = true; - - builder.SetWhat(JSON_TYPE_ARRAY); - break; - - case ']': - if (hierarchy.EndsWith("[") && hierarchy.Length() != 1) { - hierarchy.Truncate(hierarchy.Length() - 1); - builder.PopObject(); - } else if (hierarchy.EndsWith("[") && hierarchy.Length() == 1) { - hierarchy.Truncate(hierarchy.Length() - 1); - break; // Should be the end of the data. - } else - throw ParseException(pos, "Unmatched closebracket ]"); - - break; - - - case ':': - if (hierarchy.Length() == 0) - throw ParseException(pos, "Expected EOF, got ':'"); - if (builder.What() != JSON_TYPE_MAP || key.Length() == 0) { - throw ParseException(pos, "Unexpected ':'"); - } - break; - case ',': - if (builder.What() == JSON_TYPE_MAP && key.Length() != 0) { - throw ParseException(pos, "Unexpected ',' expected ':'"); - } - if (hierarchy.Length() == 0) - throw ParseException(pos, "Expected EOF, got ','"); - break; - - case 't': - { - if (hierarchy.Length() == 0) - throw ParseException(pos, "Expected EOF, got 't'"); - if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) { - throw ParseException(pos, - "'true' cannot be a key, it can only be a value"); - } - - if (_ParseConstant(JSON, pos, "true")) { - if (builder.What() == JSON_TYPE_ARRAY) - key.SetToFormat("%" B_PRIu32, builder.CountNames()); - builder.AddBool(key.String(), true); - key = ""; - } else - throw ParseException(pos, "Unexpected 't'"); - - break; - } - - case 'f': - { - if (hierarchy.Length() == 0) - throw ParseException(pos, "Expected EOF, got 'f'"); - if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) { - throw ParseException(pos, - "'false' cannot be a key, it can only be a value"); - } - - if (_ParseConstant(JSON, pos, "false")) { - if (builder.What() == JSON_TYPE_ARRAY) - key.SetToFormat("%" B_PRIu32, builder.CountNames()); - builder.AddBool(key.String(), false); - key = ""; - } else - throw ParseException(pos, "Unexpected 'f'"); - - break; - } - - case 'n': - { - if (hierarchy.Length() == 0) - throw ParseException(pos, "Expected EOF, got 'n'"); - if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) { - throw ParseException(pos, - "'null' cannot be a key, it can only be a value"); - } - - if (_ParseConstant(JSON, pos, "null")) { - if (builder.What() == JSON_TYPE_ARRAY) - key.SetToFormat("%" B_PRIu32, builder.CountNames()); - builder.AddPointer(key.String(), (void*)NULL); - key = ""; - } else - throw ParseException(pos, "Unexpected 'n'"); - - break; - } - - case '"': - if (hierarchy.Length() == 0) - throw ParseException(pos, "Expected EOF, got '\"'"); - if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) - key = _ParseString(JSON, pos); - else if (builder.What() != JSON_TYPE_ARRAY && key.Length() > 0) { - builder.AddString(key, _ParseString(JSON, pos)); - key = ""; - } else if (builder.What() == JSON_TYPE_ARRAY) { - key << builder.CountNames(); - builder.AddString(key, _ParseString(JSON, pos)); - key = ""; - } else - throw ParseException(pos, "Internal error at encountering \""); - - break; - - case '+': - case '-': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - if (hierarchy.Length() == 0) - throw ParseException(pos, "Expected EOF, got number"); - if (builder.What() != JSON_TYPE_ARRAY && key.Length() == 0) { - throw ParseException(pos, - "Numbers cannot be keys, they can only be values"); - } - - if (builder.What() == JSON_TYPE_ARRAY) - key << builder.CountNames(); - - double number = _ParseNumber(JSON, pos); - builder.AddDouble(key.String(), number); - - key = ""; - break; - } - - case ' ': - case '\t': - case '\n': - case '\r': - // Whitespace; ignore. - break; - - default: - throw ParseException(pos, "Unexpected character"); - } - pos++; - } - - if (hierarchy.Length() != 0) - throw ParseException(pos, "Unexpected EOF"); + JsonParseContext context(data, listener); + ParseAny(context); + listener->Complete(); } -BString -BJson::_ParseString(const BString& JSON, int32& pos) +// #pragma mark - Specific parse logic. + + +bool +BJson::NextChar(JsonParseContext& jsonParseContext, char* c) { - if (JSON[pos] != '"') // Verify we're at the start of a string. - return BString(""); - pos++; + status_t result = jsonParseContext.NextChar(c); - BString str; - while (JSON[pos] != '"' && pos < JSON.Length()) { - if (JSON[pos] == '\\') { - pos++; - switch (JSON[pos]) { - case 'b': - str += "\b"; - break; + switch (result) { + case B_OK: + return true; - case 'f': - str += "\f"; - break; - - case 'n': - str += "\n"; - break; - - case 'r': - str += "\r"; - break; - - case 't': - str += "\t"; - break; - - case 'u': // 4-byte hexadecimal Unicode char (e.g. "\uffff") - { - uint intValue; - BString substr; - JSON.CopyInto(substr, pos + 1, 4); - if (sscanf(substr.String(), "%4x", &intValue) != 1) - return str; - // We probably hit the end of the string. - // This probably should be counted as an error, - // but for now let's soft-fail instead of hard-fail. - - char character[20]; - char* ptr = character; - BUnicodeChar::ToUTF8(intValue, &ptr); - str.AppendChars(character, 1); - pos += 4; - break; - } - - default: - str += JSON[pos]; - break; - } - } else - str += JSON[pos]; - pos++; - } - - return str; -} - - -double -BJson::_ParseNumber(const BString& JSON, int32& pos) -{ - BString value; - bool isDouble = false; - - while (true) { - switch (JSON[pos]) { - case '+': - case '-': - case 'e': - case 'E': - case '.': - isDouble = true; - // fall through - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - value += JSON[pos]; - pos++; - continue; + case B_PARTIAL_READ: + jsonParseContext.Listener()->HandleError(B_BAD_DATA, + jsonParseContext.LineNumber(), "unexpected end of input"); + return false; default: - // We've reached the end of the number, so decrement the - // "pos" value so that the parser picks up on the next char. - pos--; - break; - } - break; + jsonParseContext.Listener()->HandleError(result, -1, + "io related read error"); + return false; } - - errno = 0; - double ret = 0; - if (isDouble) - ret = strtod(value.String(), NULL); - else - ret = strtoll(value.String(), NULL, 10); - if (errno != 0) - throw ParseException(pos, "Invalid number!"); - return ret; } bool -BJson::_ParseConstant(const BString& JSON, int32& pos, const char* constant) +BJson::NextNonWhitespaceChar(JsonParseContext& jsonParseContext, char* c) { - BString value; - JSON.CopyInto(value, pos, strlen(constant)); - if (value == constant) { - // Increase pos by the remainder of the constant, pos will be - // increased for the first letter in the main parse loop. - pos += strlen(constant) - 1; - return true; - } else - return false; + while (true) { + if (!NextChar(jsonParseContext, c)) + return false; + + switch (*c) { + case 0x0a: // newline + case 0x0d: // cr + jsonParseContext.IncrementLineNumber(); + case ' ': // space + // swallow whitespace as it is not syntactically + // significant. + break; + + default: + return true; + } + } } -} // namespace BPrivate +bool +BJson::ParseAny(JsonParseContext& jsonParseContext) +{ + char c; + if (!NextNonWhitespaceChar(jsonParseContext, &c)) + return false; + + switch (c) { + case 'f': // [f]alse + return ParseExpectedVerbatimStringAndRaiseEvent( + jsonParseContext, "alse", 4, 'f', B_JSON_FALSE); + + case 't': // [t]rue + return ParseExpectedVerbatimStringAndRaiseEvent( + jsonParseContext, "rue", 3, 't', B_JSON_TRUE); + + case 'n': // [n]ull + return ParseExpectedVerbatimStringAndRaiseEvent( + jsonParseContext, "ull", 3, 'n', B_JSON_NULL); + + case '"': + return ParseString(jsonParseContext, B_JSON_STRING); + + case '{': + return ParseObject(jsonParseContext); + + case '[': + return ParseArray(jsonParseContext); + + case '+': + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + jsonParseContext.PushbackChar(c); // keeps the parse simple + return ParseNumber(jsonParseContext); + + default: + { + BString errorMessage; + if (c >= 0x20 && c < 0x7f) { + errorMessage.SetToFormat("unexpected character [%" B_PRIu8 "]" + " (%c) when parsing element", static_cast(c), c); + } else { + errorMessage.SetToFormat("unexpected character [%" B_PRIu8 "]" + " when parsing element", (uint8) c); + } + jsonParseContext.Listener()->HandleError(B_BAD_DATA, + jsonParseContext.LineNumber(), errorMessage.String()); + return false; + } + } + + return true; +} + + +/*! This method captures an object name, a separator ':' and then any value. */ + +bool +BJson::ParseObjectNameValuePair(JsonParseContext& jsonParseContext) +{ + bool didParseName = false; + char c; + + while (true) { + if (!NextNonWhitespaceChar(jsonParseContext, &c)) + return false; + + switch (c) { + case '\"': // name of the object + { + if (!didParseName) { + if (!ParseString(jsonParseContext, + B_JSON_OBJECT_NAME)) { + return false; + } + + didParseName = true; + } else { + jsonParseContext.Listener()->HandleError(B_BAD_DATA, + jsonParseContext.LineNumber(), "unexpected" + " [\"] character when parsing object name-" + " value separator"); + return false; + } + break; + } + + case ':': // separator + { + if (didParseName) { + if (!ParseAny(jsonParseContext)) + return false; + return true; + } else { + jsonParseContext.Listener()->HandleError(B_BAD_DATA, + jsonParseContext.LineNumber(), "unexpected" + " [:] character when parsing object name-" + " value pair"); + return false; + } + } + + default: + { + BString errorMessage; + errorMessage.SetToFormat( + "unexpected character [%c] when parsing object" + " name-value pair", + c); + jsonParseContext.Listener()->HandleError(B_BAD_DATA, + jsonParseContext.LineNumber(), errorMessage.String()); + return false; + } + } + } +} + + +bool +BJson::ParseObject(JsonParseContext& jsonParseContext) +{ + if (!jsonParseContext.Listener()->Handle( + BJsonEvent(B_JSON_OBJECT_START))) { + return false; + } + + char c; + bool firstItem = true; + + while (true) { + if (!NextNonWhitespaceChar(jsonParseContext, &c)) + return false; + + switch (c) { + case '}': // terminate the object + if (!jsonParseContext.Listener()->Handle( + BJsonEvent(B_JSON_OBJECT_END))) { + return false; + } + return true; + + case ',': // next value. + if (firstItem) { + jsonParseContext.Listener()->HandleError(B_BAD_DATA, + jsonParseContext.LineNumber(), "unexpected" + " item separator when parsing start of" + " object"); + return false; + } + + if (!ParseObjectNameValuePair(jsonParseContext)) + return false; + break; + + default: + { + if (firstItem) { + jsonParseContext.PushbackChar(c); + if (!ParseObjectNameValuePair(jsonParseContext)) + return false; + firstItem = false; + } else { + jsonParseContext.Listener()->HandleError(B_BAD_DATA, + jsonParseContext.LineNumber(), "expected" + "separator when parsing an object"); + } + } + } + } + + return true; +} + + +bool +BJson::ParseArray(JsonParseContext& jsonParseContext) +{ + if (!jsonParseContext.Listener()->Handle( + BJsonEvent(B_JSON_ARRAY_START))) { + return false; + } + + char c; + bool firstItem = true; + + while (true) { + if (!NextNonWhitespaceChar(jsonParseContext, &c)) + return false; + + switch (c) { + case ']': // terminate the array + if (!jsonParseContext.Listener()->Handle( + BJsonEvent(B_JSON_ARRAY_END))) { + return false; + } + return true; + + case ',': // next value. + if (firstItem) { + jsonParseContext.Listener()->HandleError(B_BAD_DATA, + jsonParseContext.LineNumber(), "unexpected" + " item separator when parsing start of" + " array"); + } + + if (!ParseAny(jsonParseContext)) + return false; + break; + + default: + { + if (firstItem) { + jsonParseContext.PushbackChar(c); + if (!ParseAny(jsonParseContext)) + return false; + firstItem = false; + } else { + jsonParseContext.Listener()->HandleError(B_BAD_DATA, + jsonParseContext.LineNumber(), "expected" + "separator when parsing an array"); + } + } + } + } + + return true; +} + + +bool +BJson::ParseEscapeUnicodeSequence(JsonParseContext& jsonParseContext, + BString& stringResult) +{ + char buffer[5]; + buffer[4] = 0; + + if (!NextChar(jsonParseContext, &buffer[0]) + || !NextChar(jsonParseContext, &buffer[1]) + || !NextChar(jsonParseContext, &buffer[2]) + || !NextChar(jsonParseContext, &buffer[3])) { + return false; + } + + if (!b_jsonparse_all_hex(buffer)) { + BString errorMessage; + errorMessage.SetToFormat( + "malformed unicode sequence [%s] in string parsing", + buffer); + jsonParseContext.Listener()->HandleError(B_BAD_DATA, + jsonParseContext.LineNumber(), errorMessage.String()); + return false; + } + + uint intValue; + + if (sscanf(buffer, "%4x", &intValue) != 1) { + BString errorMessage; + errorMessage.SetToFormat( + "unable to process unicode sequence [%s] in string " + " parsing", buffer); + jsonParseContext.Listener()->HandleError(B_BAD_DATA, + jsonParseContext.LineNumber(), errorMessage.String()); + return false; + } + + char character[7]; + char* ptr = character; + BUnicodeChar::ToUTF8(intValue, &ptr); + int32 sequenceLength = ptr - character; + stringResult.Append(character, sequenceLength); + + return true; +} + + +bool +BJson::ParseStringEscapeSequence(JsonParseContext& jsonParseContext, + BString& stringResult) +{ + char c; + + if (!NextChar(jsonParseContext, &c)) + return false; + + switch (c) { + case 'n': + stringResult += "\n"; + break; + case 'r': + stringResult += "\r"; + break; + case 'b': + stringResult += "\b"; + break; + case 'f': + stringResult += "\f"; + break; + case '\\': + stringResult += "\\"; + break; + case '/': + stringResult += "/"; + break; + case 't': + stringResult += "\t"; + break; + case '"': + stringResult += "\""; + break; + case 'u': + // unicode escape sequence. + if (!ParseEscapeUnicodeSequence(jsonParseContext, + stringResult)) { + return false; + } + break; + default: + { + BString errorMessage; + errorMessage.SetToFormat( + "unexpected escaped character [%c] in string parsing", + c); + jsonParseContext.Listener()->HandleError(B_BAD_DATA, + jsonParseContext.LineNumber(), errorMessage.String()); + return false; + } + } + + return true; +} + + +bool +BJson::ParseString(JsonParseContext& jsonParseContext, + json_event_type eventType) +{ + char c; + BString stringResult; + + while(true) { + if (!NextChar(jsonParseContext, &c)) + return false; + + switch (c) { + case '"': + // terminates the string assembled so far. + jsonParseContext.Listener()->Handle( + BJsonEvent(eventType, stringResult.String())); + return true; + + case '\\': + if (!ParseStringEscapeSequence(jsonParseContext, + stringResult)) { + return false; + } + break; + + default: + uint8 uc = static_cast(c); + + if(uc < 0x20) { // control characters are not allowed + BString errorMessage; + errorMessage.SetToFormat("illegal control character" + " [%" B_PRIu8 "] when parsing a string", uc); + jsonParseContext.Listener()->HandleError(B_BAD_DATA, + jsonParseContext.LineNumber(), + errorMessage.String()); + return false; + } + + stringResult.Append(&c, 1); + break; + } + } +} + + +bool +BJson::ParseExpectedVerbatimStringAndRaiseEvent( + JsonParseContext& jsonParseContext, const char* expectedString, + size_t expectedStringLength, char leadingChar, + json_event_type jsonEventType) +{ + if (ParseExpectedVerbatimString(jsonParseContext, expectedString, + expectedStringLength, leadingChar)) { + if (!jsonParseContext.Listener()->Handle(BJsonEvent(jsonEventType))) + return false; + } + + return true; +} + +/*! This will make sure that the constant string is available at the input. */ + +bool +BJson::ParseExpectedVerbatimString(JsonParseContext& jsonParseContext, + const char* expectedString, size_t expectedStringLength, char leadingChar) +{ + char c; + size_t offset = 0; + + while (offset < expectedStringLength) { + if (!NextChar(jsonParseContext, &c)) + return false; + + if (c != expectedString[offset]) { + BString errorMessage; + errorMessage.SetToFormat("malformed json primative literal; " + "expected [%c%s], but got [%c] at position %" B_PRIdSSIZE, + leadingChar, expectedString, c, offset); + jsonParseContext.Listener()->HandleError(B_BAD_DATA, + jsonParseContext.LineNumber(), errorMessage.String()); + return false; + } + + offset++; + } + + return true; +} + + +/*! This function checks to see that the supplied string is a well formed + JSON number. It does this from a string rather than a stream for + convenience. This is not anticipated to impact performance because + the string values are short. +*/ + +bool +BJson::IsValidNumber(BString& number) +{ + int32 offset = 0; + int32 len = number.Length(); + + if (offset < len && number[offset] == '-') + offset++; + + if (offset >= len) + return false; + + if (isdigit(number[offset]) && number[offset] != '0') { + while (offset < len && isdigit(number[offset])) + offset++; + } else { + if (number[offset] == '0') + offset++; + else + return false; + } + + if (offset < len && number[offset] == '.') { + offset++; + + if (offset >= len) + return false; + + while (offset < len && isdigit(number[offset])) + offset++; + } + + if (offset < len && (number[offset] == 'E' || number[offset] == 'e')) { + offset++; + + if(offset < len && (number[offset] == '+' || number[offset] == '-')) + offset++; + + if (offset >= len) + return false; + + while (offset < len && isdigit(number[offset])) + offset++; + } + + return offset == len; +} + + +/*! Note that this method hits the 'NextChar' method on the context directly + and handles any end-of-file state itself because it is feasible that the + entire JSON payload is a number and because (unlike other structures, the + number can take the end-of-file to signify the end of the number. +*/ + +bool +BJson::ParseNumber(JsonParseContext& jsonParseContext) +{ + BString value; + + while (true) { + char c; + status_t result = jsonParseContext.NextChar(&c); + + switch (result) { + case B_OK: + if (isdigit(c)) { + value += c; + break; + } + + if (NULL != strchr("+-eE.", c)) { + value += c; + break; + } + + jsonParseContext.PushbackChar(c); + // intentional fall through + + case B_PARTIAL_READ: + { + errno = 0; + + if (!IsValidNumber(value)) { + jsonParseContext.Listener()->HandleError(B_BAD_DATA, + jsonParseContext.LineNumber(), "malformed number"); + return false; + } + + jsonParseContext.Listener()->Handle(BJsonEvent(B_JSON_NUMBER, + value.String())); + + return true; + } + + default: + jsonParseContext.Listener()->HandleError(result, -1, + "io related read error"); + return false; + } + } +} + +} // namespace BPrivate \ No newline at end of file diff --git a/src/kits/shared/JsonEvent.cpp b/src/kits/shared/JsonEvent.cpp new file mode 100644 index 0000000000..a63e694435 --- /dev/null +++ b/src/kits/shared/JsonEvent.cpp @@ -0,0 +1,127 @@ +/* + * Copyright 2017, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ +#include "JsonEvent.h" + +#include +#include + +#include + + +BJsonEvent::BJsonEvent(json_event_type eventType, const char* content) + : + fEventType(eventType), + fContent(content), + fOwnedContent(NULL) +{ +} + + +BJsonEvent::BJsonEvent(const char* content) + : + fEventType(B_JSON_STRING), + fContent(content), + fOwnedContent(NULL) +{ +} + + +BJsonEvent::BJsonEvent(double content) { + fEventType = B_JSON_NUMBER; + fContent = NULL; + + int actualLength = snprintf(0, 0, "%f", content) + 1; + char* buffer = (char*) malloc(sizeof(char) * actualLength); + + if (buffer == NULL) { + fprintf(stderr, "memory exhaustion\n"); + // given the risk, this is the only sensible thing to do here. + exit(EXIT_FAILURE); + } + + sprintf(buffer, "%f", content); + fOwnedContent = buffer; +} + + +BJsonEvent::BJsonEvent(int64 content) { + fEventType = B_JSON_NUMBER; + fContent = NULL; + fOwnedContent = NULL; + + static const char* zeroValue = "0"; + static const char* oneValue = "1"; + + switch (content) { + case 0: + fContent = zeroValue; + break; + case 1: + fContent = oneValue; + break; + default: + { + int actualLength = snprintf(0, 0, "%" B_PRId64, content) + 1; + char* buffer = (char*) malloc(sizeof(char) * actualLength); + + if (buffer == NULL) { + fprintf(stderr, "memory exhaustion\n"); + // given the risk, this is the only sensible thing to do + // here. + exit(EXIT_FAILURE); + } + + sprintf(buffer, "%" B_PRId64, content); + fOwnedContent = buffer; + break; + } + } +} + + +BJsonEvent::BJsonEvent(json_event_type eventType) + : + fEventType(eventType), + fContent(NULL), + fOwnedContent(NULL) +{ +} + + +BJsonEvent::~BJsonEvent() +{ + if (NULL != fOwnedContent) + free(fOwnedContent); +} + + +json_event_type +BJsonEvent::EventType() const +{ + return fEventType; +} + + +const char* +BJsonEvent::Content() const +{ + if (NULL != fOwnedContent) + return fOwnedContent; + return fContent; +} + + +double +BJsonEvent::ContentDouble() const +{ + return strtod(Content(), NULL); +} + + +int64 +BJsonEvent::ContentInteger() const +{ + return strtoll(Content(), NULL, 10); +} diff --git a/src/kits/shared/JsonEventListener.cpp b/src/kits/shared/JsonEventListener.cpp new file mode 100644 index 0000000000..acc8fbedc7 --- /dev/null +++ b/src/kits/shared/JsonEventListener.cpp @@ -0,0 +1,16 @@ +/* + * Copyright 2017, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +#include "JsonEventListener.h" + + +BJsonEventListener::BJsonEventListener() +{ +} + + +BJsonEventListener::~BJsonEventListener() +{ +} \ No newline at end of file diff --git a/src/kits/shared/JsonMessageWriter.cpp b/src/kits/shared/JsonMessageWriter.cpp new file mode 100644 index 0000000000..2bb44e58fa --- /dev/null +++ b/src/kits/shared/JsonMessageWriter.cpp @@ -0,0 +1,520 @@ +/* + * Copyright 2017, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +#include "JsonMessageWriter.h" + +#include + + +namespace BPrivate { + +/*! The class and sub-classes of it are used as a stack internal to the + BJsonMessageWriter class. As the JSON is parsed, the stack of these + internal listeners follows the stack of the JSON parsing in terms of + containers; arrays and objects. +*/ + +class BStackedMessageEventListener : public BJsonEventListener { +public: + BStackedMessageEventListener( + BJsonMessageWriter* writer, + BStackedMessageEventListener* parent, + uint32 messageWhat); + BStackedMessageEventListener( + BJsonMessageWriter* writer, + BStackedMessageEventListener* parent, + BMessage* message); + ~BStackedMessageEventListener(); + + bool Handle(const BJsonEvent& event); + void HandleError(status_t status, int32 line, + const char* message); + void Complete(); + + void AddMessage(BMessage* value); + + status_t ErrorStatus(); + virtual const char* NextItemName() = 0; + + BStackedMessageEventListener* + Parent(); + +protected: + void AddBool(bool value); + void AddNull(); + void AddDouble(double value); + void AddString(const char* value); + + virtual bool WillAdd(); + virtual void DidAdd(); + + void SetStackedListenerOnWriter( + BStackedMessageEventListener* + stackedListener); + + BJsonMessageWriter* fWriter; + bool fOwnsMessage; + BStackedMessageEventListener + *fParent; + BMessage* fMessage; +}; + + +class BStackedArrayMessageEventListener : public BStackedMessageEventListener { +public: + BStackedArrayMessageEventListener( + BJsonMessageWriter* writer, + BStackedMessageEventListener* parent); + BStackedArrayMessageEventListener( + BJsonMessageWriter* writer, + BStackedMessageEventListener* parent, + BMessage* message); + ~BStackedArrayMessageEventListener(); + + bool Handle(const BJsonEvent& event); + + const char* NextItemName(); + +protected: + void DidAdd(); + +private: + uint32 fCount; + BString fNextItemName; + +}; + + +class BStackedObjectMessageEventListener : public BStackedMessageEventListener { +public: + BStackedObjectMessageEventListener( + BJsonMessageWriter* writer, + BStackedMessageEventListener* parent); + BStackedObjectMessageEventListener( + BJsonMessageWriter* writer, + BStackedMessageEventListener* parent, + BMessage* message); + ~BStackedObjectMessageEventListener(); + + bool Handle(const BJsonEvent& event); + + const char* NextItemName(); + +protected: + bool WillAdd(); + void DidAdd(); +private: + BString fNextItemName; +}; + +} // namespace BPrivate + +using BPrivate::BStackedMessageEventListener; +using BPrivate::BStackedArrayMessageEventListener; +using BPrivate::BStackedObjectMessageEventListener; + + +// #pragma mark - BStackedMessageEventListener + + +BStackedMessageEventListener::BStackedMessageEventListener( + BJsonMessageWriter* writer, + BStackedMessageEventListener* parent, + uint32 messageWhat) +{ + fWriter = writer; + fParent = parent; + fOwnsMessage = true; + fMessage = new BMessage(messageWhat); +} + + +BStackedMessageEventListener::BStackedMessageEventListener( + BJsonMessageWriter* writer, + BStackedMessageEventListener* parent, + BMessage* message) +{ + fWriter = writer; + fParent = parent; + fOwnsMessage = false; + fMessage = message; +} + + +BStackedMessageEventListener::~BStackedMessageEventListener() +{ + if (fOwnsMessage) + delete fMessage; +} + + +bool +BStackedMessageEventListener::Handle(const BJsonEvent& event) +{ + if (fWriter->ErrorStatus() != B_OK) + return false; + + switch (event.EventType()) { + + case B_JSON_NUMBER: + AddDouble(event.ContentDouble()); + break; + + case B_JSON_STRING: + AddString(event.Content()); + break; + + case B_JSON_TRUE: + AddBool(true); + break; + + case B_JSON_FALSE: + AddBool(false); + break; + + case B_JSON_NULL: + AddNull(); + break; + + case B_JSON_OBJECT_START: + SetStackedListenerOnWriter(new BStackedObjectMessageEventListener( + fWriter, this)); + break; + + case B_JSON_ARRAY_START: + SetStackedListenerOnWriter(new BStackedArrayMessageEventListener( + fWriter, this)); + break; + + default: + HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, + "unexpected type of json item to add to container"); + return false; + } + + return ErrorStatus() == B_OK; +} + + +void +BStackedMessageEventListener::HandleError(status_t status, int32 line, + const char* message) +{ + fWriter->HandleError(status, line, message); +} + + +void +BStackedMessageEventListener::Complete() +{ + // illegal state. + HandleError(JSON_EVENT_LISTENER_ANY_LINE, B_NOT_ALLOWED, + "Complete() called on stacked message listener"); +} + + +void +BStackedMessageEventListener::AddMessage(BMessage* message) +{ + if (WillAdd()) { + fMessage->AddMessage(NextItemName(), message); + DidAdd(); + } +} + + +status_t +BStackedMessageEventListener::ErrorStatus() +{ + return fWriter->ErrorStatus(); +} + + +BStackedMessageEventListener* +BStackedMessageEventListener::Parent() +{ + return fParent; +} + + +void +BStackedMessageEventListener::AddBool(bool value) +{ + if (WillAdd()) { + fMessage->AddBool(NextItemName(), value); + DidAdd(); + } +} + +void +BStackedMessageEventListener::AddNull() +{ + if (WillAdd()) { + fMessage->AddPointer(NextItemName(), (void*)NULL); + DidAdd(); + } +} + +void +BStackedMessageEventListener::AddDouble(double value) +{ + if (WillAdd()) { + fMessage->AddDouble(NextItemName(), value); + DidAdd(); + } +} + +void +BStackedMessageEventListener::AddString(const char* value) +{ + if (WillAdd()) { + fMessage->AddString(NextItemName(), value); + DidAdd(); + } +} + + +bool +BStackedMessageEventListener::WillAdd() +{ + return true; +} + + +void +BStackedMessageEventListener::DidAdd() +{ + // noop - present for overriding +} + + +void +BStackedMessageEventListener::SetStackedListenerOnWriter( + BStackedMessageEventListener* stackedListener) +{ + fWriter->SetStackedListener(stackedListener); +} + + +// #pragma mark - BStackedArrayMessageEventListener + + +BStackedArrayMessageEventListener::BStackedArrayMessageEventListener( + BJsonMessageWriter* writer, + BStackedMessageEventListener* parent) + : BStackedMessageEventListener(writer, parent, B_JSON_MESSAGE_WHAT_ARRAY) +{ + fCount = 0; +} + + +BStackedArrayMessageEventListener::BStackedArrayMessageEventListener( + BJsonMessageWriter* writer, + BStackedMessageEventListener* parent, + BMessage* message) + : BStackedMessageEventListener(writer, parent, message) +{ + message->what = B_JSON_MESSAGE_WHAT_ARRAY; + fCount = 0; +} + + +BStackedArrayMessageEventListener::~BStackedArrayMessageEventListener() +{ +} + + +bool +BStackedArrayMessageEventListener::Handle(const BJsonEvent& event) +{ + if (fWriter->ErrorStatus() != B_OK) + return false; + + switch (event.EventType()) { + case B_JSON_ARRAY_END: + if (fParent != NULL) + fParent->AddMessage(fMessage); + SetStackedListenerOnWriter(fParent); + delete this; + break; + + default: + return BStackedMessageEventListener::Handle(event); + } + + return true; +} + + +const char* +BStackedArrayMessageEventListener::NextItemName() +{ + fNextItemName.SetToFormat("%" B_PRIu32, fCount); + return fNextItemName.String(); +} + + +void +BStackedArrayMessageEventListener::DidAdd() +{ + BStackedMessageEventListener::DidAdd(); + fCount++; +} + + +// #pragma mark - BStackedObjectMessageEventListener + + +BStackedObjectMessageEventListener::BStackedObjectMessageEventListener( + BJsonMessageWriter* writer, + BStackedMessageEventListener* parent) + : BStackedMessageEventListener(writer, parent, B_JSON_MESSAGE_WHAT_OBJECT) +{ +} + + +BStackedObjectMessageEventListener::BStackedObjectMessageEventListener( + BJsonMessageWriter* writer, + BStackedMessageEventListener* parent, + BMessage* message) + : BStackedMessageEventListener(writer, parent, message) +{ + message->what = B_JSON_MESSAGE_WHAT_OBJECT; +} + + +BStackedObjectMessageEventListener::~BStackedObjectMessageEventListener() +{ +} + + +bool +BStackedObjectMessageEventListener::Handle(const BJsonEvent& event) +{ + if (fWriter->ErrorStatus() != B_OK) + return false; + + switch (event.EventType()) { + case B_JSON_OBJECT_END: + if (fParent != NULL) + fParent->AddMessage(fMessage); + SetStackedListenerOnWriter(fParent); + delete this; + break; + + case B_JSON_OBJECT_NAME: + fNextItemName.SetTo(event.Content()); + break; + + default: + return BStackedMessageEventListener::Handle(event); + } + + return true; +} + + +const char* +BStackedObjectMessageEventListener::NextItemName() +{ + return fNextItemName.String(); +} + + +bool +BStackedObjectMessageEventListener::WillAdd() +{ + if (0 == fNextItemName.Length()) { + HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, + "missing name for adding value into an object"); + return false; + } + + return true; +} + + +void +BStackedObjectMessageEventListener::DidAdd() +{ + BStackedMessageEventListener::DidAdd(); + fNextItemName.SetTo("", 0); +} + + +// #pragma mark - BJsonMessageWriter + + +BJsonMessageWriter::BJsonMessageWriter(BMessage& message) +{ + fTopLevelMessage = &message; + fStackedListener = NULL; +} + + +BJsonMessageWriter::~BJsonMessageWriter() +{ + BStackedMessageEventListener* listener = fStackedListener; + + while (listener != NULL) { + BStackedMessageEventListener* nextListener = listener->Parent(); + delete listener; + listener = nextListener; + } + + fStackedListener = NULL; +} + + +bool +BJsonMessageWriter::Handle(const BJsonEvent& event) +{ + if (fErrorStatus != B_OK) + return false; + + if (fStackedListener != NULL) + return fStackedListener->Handle(event); + else { + switch(event.EventType()) { + case B_JSON_OBJECT_START: + SetStackedListener(new BStackedObjectMessageEventListener( + this, NULL, fTopLevelMessage)); + break; + + case B_JSON_ARRAY_START: + fTopLevelMessage->what = B_JSON_MESSAGE_WHAT_ARRAY; + SetStackedListener(new BStackedArrayMessageEventListener( + this, NULL, fTopLevelMessage)); + break; + + default: + HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, + "a message object can only handle an object or an array" + "at the top level"); + return false; + + } + } + + return true; // keep going +} + + +void +BJsonMessageWriter::Complete() +{ + if (fStackedListener != NULL) { + HandleError(B_BAD_DATA, JSON_EVENT_LISTENER_ANY_LINE, + "unexpected end of input data processing structure"); + } +} + + +void +BJsonMessageWriter::SetStackedListener( + BStackedMessageEventListener* listener) +{ + fStackedListener = listener; +} \ No newline at end of file diff --git a/src/kits/shared/JsonTextWriter.cpp b/src/kits/shared/JsonTextWriter.cpp new file mode 100644 index 0000000000..4f948b26f7 --- /dev/null +++ b/src/kits/shared/JsonTextWriter.cpp @@ -0,0 +1,697 @@ +/* + * Copyright 2017, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +#include "JsonTextWriter.h" + +#include +#include + +#include + + +namespace BPrivate { + + +static bool +b_json_is_7bit_clean(uint8 c) +{ + return c >= 0x20 && c < 0x7f; +} + + +static bool +b_json_is_illegal(uint8 c) { + return c < 0x20 || c == 0x7f; +} + + +static const char* +b_json_simple_esc_sequence(char c) +{ + switch (c) { + case '"': + return "\\\""; + case '\\': + return "\\\\"; + case '/': + return "\\/"; + case '\b': + return "\\b"; + case '\f': + return "\\f"; + case '\n': + return "\\n"; + case '\r': + return "\\r"; + case '\t': + return "\\t"; + default: + return NULL; + } +} + + +/*! The class and sub-classes of it are used as a stack internal to the + BJsonTextWriter class. As the JSON is parsed, the stack of these + internal listeners follows the stack of the JSON parsing in terms of + containers; arrays and objects. +*/ + +class BJsonTextWriterStackedEventListener : public BJsonEventListener { +public: + BJsonTextWriterStackedEventListener( + BJsonTextWriter* writer, + BJsonTextWriterStackedEventListener* parent); + ~BJsonTextWriterStackedEventListener(); + + bool Handle(const BJsonEvent& event); + void HandleError(status_t status, int32 line, + const char* message); + void Complete(); + + status_t ErrorStatus(); + + BJsonTextWriterStackedEventListener* + Parent(); + +protected: + + status_t StreamNumberNode(const BJsonEvent& event); + + status_t StreamStringVerbatim(const char* string); + status_t StreamStringVerbatim(const char* string, + off_t offset, size_t length); + + status_t StreamStringEncoded(const char* string); + status_t StreamStringEncoded(const char* string, + off_t offset, size_t length); + + status_t StreamQuotedEncodedString(const char* string); + status_t StreamQuotedEncodedString(const char* string, + off_t offset, size_t length); + + status_t StreamChar(char c); + + virtual bool WillAdd(); + virtual void DidAdd(); + + void SetStackedListenerOnWriter( + BJsonTextWriterStackedEventListener* + stackedListener); + + BJsonTextWriter* + fWriter; + BJsonTextWriterStackedEventListener* + fParent; + uint32 fCount; + +}; + + +class BJsonTextWriterArrayStackedEventListener + : public BJsonTextWriterStackedEventListener { +public: + BJsonTextWriterArrayStackedEventListener( + BJsonTextWriter* writer, + BJsonTextWriterStackedEventListener* parent); + ~BJsonTextWriterArrayStackedEventListener(); + + bool Handle(const BJsonEvent& event); + +protected: + bool WillAdd(); +}; + + +class BJsonTextWriterObjectStackedEventListener + : public BJsonTextWriterStackedEventListener { +public: + BJsonTextWriterObjectStackedEventListener( + BJsonTextWriter* writer, + BJsonTextWriterStackedEventListener* parent); + ~BJsonTextWriterObjectStackedEventListener(); + + bool Handle(const BJsonEvent& event); +}; + +} // namespace BPrivate + + +using BPrivate::BJsonTextWriterStackedEventListener; +using BPrivate::BJsonTextWriterArrayStackedEventListener; +using BPrivate::BJsonTextWriterObjectStackedEventListener; + + +// #pragma mark - BJsonTextWriterStackedEventListener + + +BJsonTextWriterStackedEventListener::BJsonTextWriterStackedEventListener( + BJsonTextWriter* writer, + BJsonTextWriterStackedEventListener* parent) +{ + fWriter = writer; + fParent = parent; + fCount = 0; +} + + +BJsonTextWriterStackedEventListener::~BJsonTextWriterStackedEventListener() +{ +} + + +bool +BJsonTextWriterStackedEventListener::Handle(const BJsonEvent& event) +{ + status_t writeResult = B_OK; + + if (fWriter->ErrorStatus() != B_OK) + return false; + + switch (event.EventType()) { + + case B_JSON_NUMBER: + case B_JSON_STRING: + case B_JSON_TRUE: + case B_JSON_FALSE: + case B_JSON_NULL: + case B_JSON_OBJECT_START: + case B_JSON_ARRAY_START: + if (!WillAdd()) + return false; + break; + + default: + break; + } + + switch (event.EventType()) { + + case B_JSON_NUMBER: + writeResult = StreamNumberNode(event); + break; + + case B_JSON_STRING: + writeResult = StreamQuotedEncodedString(event.Content()); + break; + + case B_JSON_TRUE: + writeResult = StreamStringVerbatim("true", 0, 4); + break; + + case B_JSON_FALSE: + writeResult = StreamStringVerbatim("false", 0, 5); + break; + + case B_JSON_NULL: + writeResult = StreamStringVerbatim("null", 0, 4); + break; + + case B_JSON_OBJECT_START: + writeResult = StreamChar('{'); + + if (writeResult == B_OK) { + SetStackedListenerOnWriter( + new BJsonTextWriterObjectStackedEventListener(fWriter, this)); + } + break; + + case B_JSON_ARRAY_START: + writeResult = StreamChar('['); + + if (writeResult == B_OK) { + SetStackedListenerOnWriter( + new BJsonTextWriterArrayStackedEventListener(fWriter, this)); + } + break; + + default: + HandleError(B_NOT_ALLOWED, JSON_EVENT_LISTENER_ANY_LINE, + "unexpected type of json item to add to container"); + return false; + } + + if (writeResult == B_OK) + DidAdd(); + else { + HandleError(writeResult, JSON_EVENT_LISTENER_ANY_LINE, + "error writing output"); + } + + return ErrorStatus() == B_OK; +} + + +void +BJsonTextWriterStackedEventListener::HandleError(status_t status, int32 line, + const char* message) +{ + fWriter->HandleError(status, line, message); +} + + +void +BJsonTextWriterStackedEventListener::Complete() +{ + // illegal state. + HandleError(JSON_EVENT_LISTENER_ANY_LINE, B_NOT_ALLOWED, + "Complete() called on stacked message listener"); +} + + +status_t +BJsonTextWriterStackedEventListener::ErrorStatus() +{ + return fWriter->ErrorStatus(); +} + + +BJsonTextWriterStackedEventListener* +BJsonTextWriterStackedEventListener::Parent() +{ + return fParent; +} + + +status_t +BJsonTextWriterStackedEventListener::StreamNumberNode(const BJsonEvent& event) +{ + return fWriter->StreamNumberNode(event); +} + + +status_t +BJsonTextWriterStackedEventListener::StreamStringVerbatim(const char* string) +{ + return fWriter->StreamStringVerbatim(string); +} + + +status_t +BJsonTextWriterStackedEventListener::StreamStringVerbatim(const char* string, + off_t offset, size_t length) +{ + return fWriter->StreamStringVerbatim(string, offset, length); +} + + +status_t +BJsonTextWriterStackedEventListener::StreamStringEncoded(const char* string) +{ + return fWriter->StreamStringEncoded(string); +} + + +status_t +BJsonTextWriterStackedEventListener::StreamStringEncoded(const char* string, + off_t offset, size_t length) +{ + return fWriter->StreamStringEncoded(string, offset, length); +} + + +status_t +BJsonTextWriterStackedEventListener::StreamQuotedEncodedString(const char* string) +{ + return fWriter->StreamQuotedEncodedString(string); +} + + +status_t +BJsonTextWriterStackedEventListener::StreamQuotedEncodedString(const char* string, + off_t offset, size_t length) +{ + return fWriter->StreamQuotedEncodedString(string, offset, length); +} + + +status_t +BJsonTextWriterStackedEventListener::StreamChar(char c) +{ + return fWriter->StreamChar(c); +} + + +bool +BJsonTextWriterStackedEventListener::WillAdd() +{ + return true; // carry on +} + + +void +BJsonTextWriterStackedEventListener::DidAdd() +{ + fCount++; +} + + +void +BJsonTextWriterStackedEventListener::SetStackedListenerOnWriter( + BJsonTextWriterStackedEventListener* stackedListener) +{ + fWriter->SetStackedListener(stackedListener); +} + + +// #pragma mark - BJsonTextWriterArrayStackedEventListener + + +BJsonTextWriterArrayStackedEventListener::BJsonTextWriterArrayStackedEventListener( + BJsonTextWriter* writer, + BJsonTextWriterStackedEventListener* parent) + : BJsonTextWriterStackedEventListener(writer, parent) +{ +} + + +BJsonTextWriterArrayStackedEventListener + ::~BJsonTextWriterArrayStackedEventListener() +{ +} + + +bool +BJsonTextWriterArrayStackedEventListener::Handle(const BJsonEvent& event) +{ + status_t writeResult = B_OK; + + if (fWriter->ErrorStatus() != B_OK) + return false; + + switch (event.EventType()) { + case B_JSON_ARRAY_END: + { + writeResult = StreamChar(']'); + + if (writeResult == B_OK) { + SetStackedListenerOnWriter(fParent); + delete this; + return true; // must exit immediately after delete this. + } + break; + } + + default: + return BJsonTextWriterStackedEventListener::Handle(event); + } + + if(writeResult != B_OK) { + HandleError(writeResult, JSON_EVENT_LISTENER_ANY_LINE, + "error writing output"); + } + + return ErrorStatus() == B_OK; +} + + +bool +BJsonTextWriterArrayStackedEventListener::WillAdd() +{ + status_t writeResult = B_OK; + + if (writeResult == B_OK && fCount > 0) + writeResult = StreamChar(','); + + if (writeResult != B_OK) { + HandleError(B_IO_ERROR, JSON_EVENT_LISTENER_ANY_LINE, + "error writing data"); + return false; + } + + return BJsonTextWriterStackedEventListener::WillAdd(); +} + + +// #pragma mark - BJsonTextWriterObjectStackedEventListener + + +BJsonTextWriterObjectStackedEventListener::BJsonTextWriterObjectStackedEventListener( + BJsonTextWriter* writer, + BJsonTextWriterStackedEventListener* parent) + : BJsonTextWriterStackedEventListener(writer, parent) +{ +} + + +BJsonTextWriterObjectStackedEventListener + ::~BJsonTextWriterObjectStackedEventListener() +{ +} + + +bool +BJsonTextWriterObjectStackedEventListener::Handle(const BJsonEvent& event) +{ + status_t writeResult = B_OK; + + if (fWriter->ErrorStatus() != B_OK) + return false; + + switch (event.EventType()) { + case B_JSON_OBJECT_END: + { + writeResult = StreamChar('}'); + + if (writeResult == B_OK) { + SetStackedListenerOnWriter(fParent); + delete this; + return true; // just exit after delete this. + } + break; + } + + case B_JSON_OBJECT_NAME: + { + if (writeResult == B_OK && fCount > 0) + writeResult = StreamChar(','); + + if (writeResult == B_OK) + writeResult = StreamQuotedEncodedString(event.Content()); + + if (writeResult == B_OK) + writeResult = StreamChar(':'); + + break; + } + + default: + return BJsonTextWriterStackedEventListener::Handle(event); + } + + if (writeResult != B_OK) { + HandleError(writeResult, JSON_EVENT_LISTENER_ANY_LINE, + "error writing data"); + } + + return ErrorStatus() == B_OK; +} + + +// #pragma mark - BJsonTextWriter + + +BJsonTextWriter::BJsonTextWriter( + BDataIO* dataIO) + : + fDataIO(dataIO) +{ + + // this is a preparation for this buffer to easily be used later + // to efficiently output encoded unicode characters. + + fUnicodeAssemblyBuffer[0] = '\\'; + fUnicodeAssemblyBuffer[1] = 'u'; + + fStackedListener = new BJsonTextWriterStackedEventListener(this, NULL); +} + + +BJsonTextWriter::~BJsonTextWriter() +{ + BJsonTextWriterStackedEventListener* listener = fStackedListener; + + while (listener != NULL) { + BJsonTextWriterStackedEventListener* nextListener = listener->Parent(); + delete listener; + listener = nextListener; + } + + fStackedListener = NULL; +} + + +bool +BJsonTextWriter::Handle(const BJsonEvent& event) +{ + return fStackedListener->Handle(event); +} + + +void +BJsonTextWriter::Complete() +{ + // upon construction, this object will add one listener to the + // stack. On complete, this listener should still be there; + // otherwise this implies an unterminated structure such as array + // / object. + + if (fStackedListener->Parent() != NULL) { + HandleError(B_BAD_DATA, JSON_EVENT_LISTENER_ANY_LINE, + "unexpected end of input data"); + } +} + + +void +BJsonTextWriter::SetStackedListener( + BJsonTextWriterStackedEventListener* stackedListener) +{ + fStackedListener = stackedListener; +} + + +status_t +BJsonTextWriter::StreamNumberNode(const BJsonEvent& event) +{ + return StreamStringVerbatim(event.Content()); +} + + +status_t +BJsonTextWriter::StreamStringVerbatim(const char* string) +{ + return StreamStringVerbatim(string, 0, strlen(string)); +} + + +status_t +BJsonTextWriter::StreamStringVerbatim(const char* string, + off_t offset, size_t length) +{ + return fDataIO->WriteExactly(&string[offset], length); +} + + +status_t +BJsonTextWriter::StreamStringEncoded(const char* string) +{ + return StreamStringEncoded(string, 0, strlen(string)); +} + + +/*! Note that this method will expect a UTF-8 encoded string. */ + +status_t +BJsonTextWriter::StreamStringEncoded(const char* string, + off_t offset, size_t length) +{ + status_t writeResult = B_OK; + uint8* string8bit = (uint8*)string; + + while (writeResult == B_OK && 0 != length) { + uint8 c = string8bit[offset]; + const char* simpleEsc = b_json_simple_esc_sequence(c); + + // simple escape sequence involving the backslash + one character. + + if (simpleEsc != NULL) { + writeResult = StreamStringVerbatim(simpleEsc, 0, 2); + + if (writeResult == B_OK) { + offset++; + length--; + } + } else { + + if (b_json_is_7bit_clean(c)) { + + // roll forward while the characters are simple and then + // output them at as a block verbatim. + + uint32 count7BitClean = 1; + + while (count7BitClean < length + && b_json_is_7bit_clean( + string8bit[offset + count7BitClean])) { + count7BitClean++; + } + + writeResult = StreamStringVerbatim(&string[offset], 0, + count7BitClean); + + if (writeResult == B_OK) { + offset += count7BitClean; + length -= count7BitClean; + } + } else { + if (b_json_is_illegal(c)) { + fprintf(stderr, "! string encoding error - illegal " + "character [%" B_PRIu32 "]\n", static_cast(c)); + offset++; + length--; + } else { + + // if the character is < 128 then it can be rendered + // verbatim - check how many are like this and then + // render those verbatim. + + const char* stringInitial = &string[offset]; + uint32 unicodeCharacter = BUnicodeChar::FromUTF8( + &stringInitial); + + sprintf(&fUnicodeAssemblyBuffer[2], "%04" B_PRIx32, + unicodeCharacter); + writeResult = StreamStringVerbatim(fUnicodeAssemblyBuffer, + 0, 6); + + if (writeResult == B_OK) { + uint32 sequence_length + = (uint32)(stringInitial - &string[offset]); + offset += sequence_length; + length -= sequence_length; + } + } + } + } + } + + return writeResult; +} + + +status_t +BJsonTextWriter::StreamQuotedEncodedString(const char* string) +{ + return StreamQuotedEncodedString(string, 0, strlen(string)); +} + + +status_t +BJsonTextWriter::StreamQuotedEncodedString(const char* string, + off_t offset, size_t length) +{ + status_t write_result = B_OK; + + if (write_result == B_OK) + write_result = StreamChar('\"'); + + if (write_result == B_OK) + write_result = StreamStringEncoded(string, offset, length); + + if (write_result == B_OK) + write_result = StreamChar('\"'); + + return write_result; +} + + +status_t +BJsonTextWriter::StreamChar(char c) +{ + return fDataIO->WriteExactly(&c, 1); +} + + diff --git a/src/kits/shared/JsonWriter.cpp b/src/kits/shared/JsonWriter.cpp new file mode 100644 index 0000000000..8a95270ef8 --- /dev/null +++ b/src/kits/shared/JsonWriter.cpp @@ -0,0 +1,144 @@ +/* + * Copyright 2017, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +#include "JsonWriter.h" + +#include +#include + +#include + + +BJsonWriter::BJsonWriter() + : + fErrorStatus(B_OK) +{ +} + + +BJsonWriter::~BJsonWriter() +{ +} + + +void +BJsonWriter::HandleError(status_t status, int32 line, + const char* message) +{ + if(fErrorStatus == B_OK) { + if (message == NULL) + message = "?"; + + fErrorStatus = status; + fprintf(stderr, "! json err @line %" B_PRIi32 " - %s : %s\n", line, + strerror(status), message); + } +} + + +status_t +BJsonWriter::ErrorStatus() +{ + return fErrorStatus; +} + + +status_t +BJsonWriter::WriteBoolean(bool value) +{ + if (value) + return WriteTrue(); + + return WriteFalse(); +} + + +status_t +BJsonWriter::WriteTrue() +{ + Handle(BJsonEvent(B_JSON_TRUE)); + return fErrorStatus; +} + + +status_t +BJsonWriter::WriteFalse() +{ + Handle(BJsonEvent(B_JSON_FALSE)); + return fErrorStatus; +} + + +status_t +BJsonWriter::WriteNull() +{ + Handle(BJsonEvent(B_JSON_NULL)); + return fErrorStatus; +} + + +status_t +BJsonWriter::WriteInteger(int64 value) +{ + Handle(BJsonEvent(value)); + return fErrorStatus; +} + + +status_t +BJsonWriter::WriteDouble(double value) +{ + Handle(BJsonEvent(value)); + return fErrorStatus; +} + + +status_t +BJsonWriter::WriteString(const char* value) +{ + Handle(BJsonEvent(value)); + return fErrorStatus; +} + + +status_t +BJsonWriter::WriteObjectStart() +{ + Handle(BJsonEvent(B_JSON_OBJECT_START)); + return fErrorStatus; +} + + +status_t +BJsonWriter::WriteObjectName(const char* value) +{ + Handle(BJsonEvent(B_JSON_OBJECT_NAME, value)); + return fErrorStatus; +} + + +status_t +BJsonWriter::WriteObjectEnd() +{ + Handle(BJsonEvent(B_JSON_OBJECT_END)); + return fErrorStatus; +} + + +status_t +BJsonWriter::WriteArrayStart() +{ + Handle(BJsonEvent(B_JSON_ARRAY_START)); + return fErrorStatus; +} + + +status_t +BJsonWriter::WriteArrayEnd() +{ + Handle(BJsonEvent(B_JSON_ARRAY_END)); + return fErrorStatus; +} + diff --git a/src/tests/kits/shared/Jamfile b/src/tests/kits/shared/Jamfile index 1f4e3838d3..ee4591e674 100644 --- a/src/tests/kits/shared/Jamfile +++ b/src/tests/kits/shared/Jamfile @@ -10,6 +10,10 @@ UnitTestLib libsharedtest.so : CalendarViewTest.cpp DriverSettingsMessageAdapterTest.cpp + JsonEndToEndTest.cpp + JsonErrorHandlingTest.cpp + JsonTextWriterTest.cpp + JsonToMessageTest.cpp GeolocationTest.cpp NaturalCompareTest.cpp diff --git a/src/tests/kits/shared/JsonEndToEndTest.cpp b/src/tests/kits/shared/JsonEndToEndTest.cpp new file mode 100644 index 0000000000..061504b391 --- /dev/null +++ b/src/tests/kits/shared/JsonEndToEndTest.cpp @@ -0,0 +1,215 @@ +/* + * Copyright 2017, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ +#include "JsonEndToEndTest.h" + +#include + +#include +#include + +#include +#include + +#include "JsonSamples.h" + + +using namespace BPrivate; + + +JsonEndToEndTest::JsonEndToEndTest() +{ +} + + +JsonEndToEndTest::~JsonEndToEndTest() +{ +} + + +void +JsonEndToEndTest::TestParseAndWrite(char* input, char* expectedOutput) +{ + BDataIO* inputData = new BMemoryIO(input, strlen(input)); + ObjectDeleter inputDataDeleter(inputData); + BMallocIO* outputData = new BMallocIO(); + ObjectDeleter outputDataDeleter(outputData); + BPrivate::BJsonTextWriter* listener + = new BJsonTextWriter(outputData); + ObjectDeleter listenerDeleter(listener); + +// ---------------------- + BPrivate::BJson::Parse(inputData, listener); +// ---------------------- + + CPPUNIT_ASSERT_EQUAL(B_OK, listener->ErrorStatus()); + fprintf(stderr, "in >%s<\n", input); + fprintf(stderr, "expected out >%s<\n", expectedOutput); + fprintf(stderr, "actual out >%s<\n", (char*)outputData->Buffer()); + CPPUNIT_ASSERT_MESSAGE("expected did no equal actual output", + 0 == strncmp(expectedOutput, (char*)outputData->Buffer(), + strlen(expectedOutput))); +} + + +void +JsonEndToEndTest::TestNullA() +{ + TestParseAndWrite(JSON_SAMPLE_NULL_A_IN, + JSON_SAMPLE_NULL_A_EXPECTED_OUT); +} + + +void +JsonEndToEndTest::TestTrueA() +{ + TestParseAndWrite(JSON_SAMPLE_TRUE_A_IN, + JSON_SAMPLE_TRUE_A_EXPECTED_OUT); +} + + +void +JsonEndToEndTest::TestFalseA() +{ + TestParseAndWrite(JSON_SAMPLE_FALSE_A_IN, + JSON_SAMPLE_FALSE_A_EXPECTED_OUT); +} + + +void +JsonEndToEndTest::TestNumberA() +{ + TestParseAndWrite(JSON_SAMPLE_NUMBER_A_IN, + JSON_SAMPLE_NUMBER_A_EXPECTED_OUT); +} + + +void +JsonEndToEndTest::TestStringA() +{ + TestParseAndWrite(JSON_SAMPLE_STRING_A_IN, + JSON_SAMPLE_STRING_A_EXPECTED_OUT); +} + + +void +JsonEndToEndTest::TestStringB() +{ + TestParseAndWrite(JSON_SAMPLE_STRING_B_IN, + JSON_SAMPLE_STRING_B_EXPECTED_OUT); +} + + +/* In this test, there are some UTF-8 characters. */ + +void +JsonEndToEndTest::TestStringA2() +{ + TestParseAndWrite(JSON_SAMPLE_STRING_A2_IN, + JSON_SAMPLE_STRING_A_EXPECTED_OUT); +} + + +void +JsonEndToEndTest::TestArrayA() +{ + TestParseAndWrite(JSON_SAMPLE_ARRAY_A_IN, JSON_SAMPLE_ARRAY_A_EXPECTED_OUT); +} + + +void +JsonEndToEndTest::TestArrayB() +{ + TestParseAndWrite(JSON_SAMPLE_ARRAY_B_IN, JSON_SAMPLE_ARRAY_B_EXPECTED_OUT); +} + + +void +JsonEndToEndTest::TestObjectA() +{ + TestParseAndWrite(JSON_SAMPLE_OBJECT_A_IN, + JSON_SAMPLE_OBJECT_A_EXPECTED_OUT); +} + + +/*! This method will test an element being unterminated; such an object that + is missing the terminating "}" symbol or a string that has no closing + quote. This is tested here because the writer +*/ + +void +JsonEndToEndTest::TestUnterminated(const char *input) +{ + BDataIO* inputData = new BMemoryIO(input, strlen(input)); + ObjectDeleter inputDataDeleter(inputData); + BMallocIO* outputData = new BMallocIO(); + ObjectDeleter outputDataDeleter(outputData); + BPrivate::BJsonTextWriter* listener + = new BJsonTextWriter(outputData); + ObjectDeleter listenerDeleter(listener); + +// ---------------------- + BPrivate::BJson::Parse(inputData, listener); +// ---------------------- + + CPPUNIT_ASSERT_EQUAL(B_BAD_DATA, listener->ErrorStatus()); +} + + +void +JsonEndToEndTest::TestStringUnterminated() +{ + TestUnterminated(JSON_SAMPLE_BROKEN_UNTERMINATED_STRING); +} + +void +JsonEndToEndTest::TestArrayUnterminated() +{ + TestUnterminated(JSON_SAMPLE_BROKEN_UNTERMINATED_ARRAY); +} + +void +JsonEndToEndTest::TestObjectUnterminated() +{ + TestUnterminated(JSON_SAMPLE_BROKEN_UNTERMINATED_OBJECT); +} + + +/*static*/ void +JsonEndToEndTest::AddTests(BTestSuite& parent) +{ + CppUnit::TestSuite& suite = *new CppUnit::TestSuite("JsonEndToEndTest"); + + suite.addTest(new CppUnit::TestCaller( + "JsonEndToEndTest::TestNullA", &JsonEndToEndTest::TestNullA)); + suite.addTest(new CppUnit::TestCaller( + "JsonEndToEndTest::TestTrueA", &JsonEndToEndTest::TestTrueA)); + suite.addTest(new CppUnit::TestCaller( + "JsonEndToEndTest::TestFalseA", &JsonEndToEndTest::TestFalseA)); + suite.addTest(new CppUnit::TestCaller( + "JsonEndToEndTest::TestNumberA", &JsonEndToEndTest::TestNumberA)); + suite.addTest(new CppUnit::TestCaller( + "JsonEndToEndTest::TestStringA", &JsonEndToEndTest::TestStringA)); + suite.addTest(new CppUnit::TestCaller( + "JsonEndToEndTest::TestStringA2", &JsonEndToEndTest::TestStringA2)); + suite.addTest(new CppUnit::TestCaller( + "JsonEndToEndTest::TestStringB", &JsonEndToEndTest::TestStringB)); + suite.addTest(new CppUnit::TestCaller( + "JsonEndToEndTest::TestArrayA", &JsonEndToEndTest::TestArrayA)); + suite.addTest(new CppUnit::TestCaller( + "JsonEndToEndTest::TestArrayB", &JsonEndToEndTest::TestArrayB)); + suite.addTest(new CppUnit::TestCaller( + "JsonEndToEndTest::TestObjectA", &JsonEndToEndTest::TestObjectA)); + suite.addTest(new CppUnit::TestCaller( + "JsonEndToEndTest::TestStringUnterminated", + &JsonEndToEndTest::TestStringUnterminated)); + suite.addTest(new CppUnit::TestCaller( + "JsonEndToEndTest::TestArrayUnterminated", + &JsonEndToEndTest::TestArrayUnterminated)); + suite.addTest(new CppUnit::TestCaller( + "JsonEndToEndTest::TestObjectUnterminated", + &JsonEndToEndTest::TestObjectUnterminated)); + + parent.addTest("JsonEndToEndTest", &suite); +} diff --git a/src/tests/kits/shared/JsonEndToEndTest.h b/src/tests/kits/shared/JsonEndToEndTest.h new file mode 100644 index 0000000000..07b9712cb3 --- /dev/null +++ b/src/tests/kits/shared/JsonEndToEndTest.h @@ -0,0 +1,45 @@ +/* + * Copyright 2017, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ +#ifndef JSON_END_TO_END_TEST_H +#define JSON_END_TO_END_TEST_H + + +#include +#include + + +class Sample; + + +class JsonEndToEndTest : public CppUnit::TestCase { +public: + JsonEndToEndTest(); + virtual ~JsonEndToEndTest(); + + void TestNullA(); + void TestTrueA(); + void TestFalseA(); + void TestNumberA(); + void TestStringA(); + void TestStringA2(); + void TestStringB(); + void TestArrayA(); + void TestArrayB(); + void TestObjectA(); + + void TestStringUnterminated(); + void TestArrayUnterminated(); + void TestObjectUnterminated(); + + static void AddTests(BTestSuite& suite); +private: + void TestUnterminated(const char* input); + + void TestParseAndWrite(char* input, + char* expectedOutput); +}; + + +#endif // JSON_END_TO_END_TEST_H diff --git a/src/tests/kits/shared/JsonErrorHandlingTest.cpp b/src/tests/kits/shared/JsonErrorHandlingTest.cpp new file mode 100644 index 0000000000..6c54e383a8 --- /dev/null +++ b/src/tests/kits/shared/JsonErrorHandlingTest.cpp @@ -0,0 +1,354 @@ +/* + * Copyright 2017, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ +#include "JsonErrorHandlingTest.h" + +#include + +#include +#include + +#include +#include + +#include "JsonSamples.h" + + +using namespace BPrivate; + +class ErrorCapturingListener : public BJsonEventListener { +public: + ErrorCapturingListener(); + virtual ~ErrorCapturingListener(); + + bool Handle(const BJsonEvent& event); + void HandleError(status_t status, int32 line, + const char* message); + void Complete() {}; + + status_t ErrorStatus(); + int32 GetErrorLine(); + BString GetErrorMessage(); + bool HasEventsAfterError(); + json_event_type FirstEventTypeAfterError(); +private: + status_t fErrorStatus; + int32 fErrorLine; + BString fErrorMessage; + json_event_type fFirstEventTypeAfterError; + int32 fEventCountAfterError; + +}; + + +/*! This DataIO concrete implementation is designed to open and then to fail + in order to simulate what might happen if there were an IO problem when + parsing some JSON. +*/ + +class FailingDataIO : public BDataIO { +public: + FailingDataIO(); + virtual ~FailingDataIO(); + + ssize_t Read(void* buffer, size_t size); + ssize_t Write(const void* buffer, size_t size); + + status_t Flush(); +}; + + +// #pragma mark - FailingDataIO + + +FailingDataIO::FailingDataIO() +{ +} + + +FailingDataIO::~FailingDataIO() +{ +} + + +ssize_t +FailingDataIO::Read(void* buffer, size_t size) +{ + return B_IO_ERROR; +} + + +ssize_t +FailingDataIO::Write(const void* buffer, size_t size) +{ + fprintf(stdout, "attempt to write"); + return B_IO_ERROR; +} + + +status_t +FailingDataIO::Flush() +{ + return B_IO_ERROR; +} + + +// #pragma mark - ErrorCapturingListener + + +ErrorCapturingListener::ErrorCapturingListener() +{ + fErrorStatus = B_OK; + fFirstEventTypeAfterError = B_JSON_NULL; // least likely + fEventCountAfterError = 0; +} + + +ErrorCapturingListener::~ErrorCapturingListener() +{ +} + + +bool +ErrorCapturingListener::Handle(const BJsonEvent& event) +{ + if (fErrorStatus != B_OK) { + if (fEventCountAfterError == 0) + fFirstEventTypeAfterError = event.EventType(); + + fEventCountAfterError++; + } + return true; // keep going. +} + + +void +ErrorCapturingListener::HandleError(status_t status, int32 line, + const char *message) +{ + fErrorStatus = status; + fErrorLine = line; + + if (message != NULL) + fErrorMessage = BString(message); + else + fErrorMessage = BString(); +} + + +status_t +ErrorCapturingListener::ErrorStatus() +{ + return fErrorStatus; +} + + +int32 +ErrorCapturingListener::GetErrorLine() +{ + return fErrorLine; +} + + +BString +ErrorCapturingListener::GetErrorMessage() +{ + return fErrorMessage; +} + + +json_event_type +ErrorCapturingListener::FirstEventTypeAfterError() +{ + return fFirstEventTypeAfterError; +} + + +bool +ErrorCapturingListener::HasEventsAfterError() +{ + return fEventCountAfterError > 0; +} + + +JsonErrorHandlingTest::JsonErrorHandlingTest() +{ +} + + +JsonErrorHandlingTest::~JsonErrorHandlingTest() +{ +} + + +void +JsonErrorHandlingTest::TestParseWithBadStringEscape(const char* input, + int32 line, status_t expectedStatus, char expectedBadEscapeChar) +{ + BString expectedMessage; + expectedMessage.SetToFormat("unexpected escaped character [%c] " + "in string parsing", expectedBadEscapeChar); + + TestParseWithErrorMessage(input, line, expectedStatus, + expectedMessage.String()); +} + + +void +JsonErrorHandlingTest::TestParseWithUnterminatedElement(const char* input, + int32 line, status_t expectedStatus) +{ + BString expectedMessage; + expectedMessage.SetToFormat("unterminated element"); + + TestParseWithErrorMessage(input, line, expectedStatus, + expectedMessage.String()); +} + + +void +JsonErrorHandlingTest::TestParseWithUnexpectedCharacter(const char* input, + int32 line, status_t expectedStatus, char expectedChar) +{ + BString expectedMessage; + expectedMessage.SetToFormat("unexpected character [%" B_PRIu8 + "] (%c) when parsing element", static_cast(expectedChar), + expectedChar); + + TestParseWithErrorMessage(input, line, expectedStatus, + expectedMessage.String()); +} + + +void +JsonErrorHandlingTest::TestParseWithErrorMessage(const char* input, int32 line, + status_t expectedStatus, const char* expectedMessage) +{ + fprintf(stderr, "in >%s<\n", input); + BDataIO *inputData = new BMemoryIO(input, strlen(input)); + ObjectDeleter inputDataDeleter(inputData); + TestParseWithErrorMessage(inputData, line, expectedStatus, expectedMessage); +} + + +void +JsonErrorHandlingTest::TestParseWithErrorMessage(BDataIO* inputData, int32 line, + status_t expectedStatus, const char* expectedMessage) +{ + ErrorCapturingListener* listener = new ErrorCapturingListener(); + ObjectDeleter listenerDeleter(listener); + +// ---------------------- + BPrivate::BJson::Parse(inputData, listener); +// ---------------------- + + fprintf(stderr, "expected error at line %" B_PRIi32 " - %s : %s\n", + line, + strerror(expectedStatus), + expectedMessage); + + fprintf(stderr, "actual error at line %" B_PRIi32 " - %s : %s\n", + listener->GetErrorLine(), + strerror(listener->ErrorStatus()), + listener->GetErrorMessage().String()); + + if (listener->HasEventsAfterError()) { + fprintf(stderr, "first event after error [%d]\n", + listener->FirstEventTypeAfterError()); + } + + CPPUNIT_ASSERT(!listener->HasEventsAfterError()); + CPPUNIT_ASSERT_EQUAL(expectedStatus, listener->ErrorStatus()); + CPPUNIT_ASSERT_EQUAL(line, listener->GetErrorLine()); + CPPUNIT_ASSERT(0 == strcmp(expectedMessage, + listener->GetErrorMessage().String())); +} + + +void +JsonErrorHandlingTest::TestCompletelyUnknown() +{ + TestParseWithUnexpectedCharacter( + JSON_SAMPLE_BROKEN_COMPLETELY_UNKNOWN, 1, B_BAD_DATA, 'z'); +} + + +void +JsonErrorHandlingTest::TestObjectWithPrematureSeparator() +{ + TestParseWithErrorMessage(JSON_SAMPLE_BROKEN_OBJECT_PREMATURE_SEPARATOR, 1, + B_BAD_DATA, "unexpected item separator when parsing start of object"); +} + + +void +JsonErrorHandlingTest::TestStringUnterminated() +{ + TestParseWithErrorMessage(JSON_SAMPLE_BROKEN_UNTERMINATED_STRING, 1, + B_BAD_DATA, "unexpected end of input"); +} + + +void +JsonErrorHandlingTest::TestBadStringEscape() +{ + TestParseWithBadStringEscape( + JSON_SAMPLE_BROKEN_BAD_STRING_ESCAPE, 1, B_BAD_DATA, 'v'); +} + + +void +JsonErrorHandlingTest::TestBadNumber() +{ + BString expectedMessage; + expectedMessage.SetToFormat("malformed number", + JSON_SAMPLE_BROKEN_NUMBER); + + TestParseWithErrorMessage(JSON_SAMPLE_BROKEN_NUMBER, 1, B_BAD_DATA, + expectedMessage.String()); +} + + +void +JsonErrorHandlingTest::TestIOIssue() +{ + BDataIO *inputData = new FailingDataIO(); + ObjectDeleter inputDataDeleter(inputData); + TestParseWithErrorMessage(inputData, -1, B_IO_ERROR, + "io related read error"); +} + + +/*static*/ void +JsonErrorHandlingTest::AddTests(BTestSuite& parent) +{ + CppUnit::TestSuite& suite = *new CppUnit::TestSuite( + "JsonErrorHandlingTest"); + + suite.addTest(new CppUnit::TestCaller( + "JsonErrorHandlingTest::TestCompletelyUnknown", + &JsonErrorHandlingTest::TestCompletelyUnknown)); + + suite.addTest(new CppUnit::TestCaller( + "JsonErrorHandlingTest::TestObjectWithPrematureSeparator", + &JsonErrorHandlingTest::TestObjectWithPrematureSeparator)); + + suite.addTest(new CppUnit::TestCaller( + "JsonErrorHandlingTest::TestStringUnterminated", + &JsonErrorHandlingTest::TestStringUnterminated)); + + suite.addTest(new CppUnit::TestCaller( + "JsonErrorHandlingTest::TestBadStringEscape", + &JsonErrorHandlingTest::TestBadStringEscape)); + + suite.addTest(new CppUnit::TestCaller( + "JsonErrorHandlingTest::TestBadNumber", + &JsonErrorHandlingTest::TestBadNumber)); + + suite.addTest(new CppUnit::TestCaller( + "JsonErrorHandlingTest::TestIOIssue", + &JsonErrorHandlingTest::TestIOIssue)); + + parent.addTest("JsonErrorHandlingTest", &suite); +} \ No newline at end of file diff --git a/src/tests/kits/shared/JsonErrorHandlingTest.h b/src/tests/kits/shared/JsonErrorHandlingTest.h new file mode 100644 index 0000000000..55ca6b1e06 --- /dev/null +++ b/src/tests/kits/shared/JsonErrorHandlingTest.h @@ -0,0 +1,61 @@ +/* + * Copyright 2017, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ +#ifndef JSON_ERROR_HANDLING_TEST_H +#define JSON_ERROR_HANDLING_TEST_H + + +#include +#include + +#include + +class Sample; + + +class JsonErrorHandlingTest : public CppUnit::TestCase { +public: + JsonErrorHandlingTest(); + virtual ~JsonErrorHandlingTest(); + + void TestCompletelyUnknown(); + void TestObjectWithPrematureSeparator(); + + void TestStringUnterminated(); + + void TestBadStringEscape(); + + void TestBadNumber(); + + void TestIOIssue(); + + static void AddTests(BTestSuite& suite); +private: + void TestParseWithUnexpectedCharacter( + const char* input, int32 line, + status_t expectedStatus, char expectedChar); + + void TestParseWithUnterminatedElement( + const char* input, int32 line, + status_t expectedStatus); + + void TestParseWithBadStringEscape( + const char* input, + int32 line, status_t expectedStatus, + char expectedBadEscapeChar); + + void TestParseWithErrorMessage( + const char* input, int32 line, + status_t expectedStatus, + const char* expectedMessage); + + void TestParseWithErrorMessage( + BDataIO* data, int32 line, + status_t expectedStatus, + const char* expectedMessage); + +}; + + +#endif // JSON_ERROR_HANDLING_TEST_H diff --git a/src/tests/kits/shared/JsonSamples.h b/src/tests/kits/shared/JsonSamples.h new file mode 100644 index 0000000000..1e67d1b05c --- /dev/null +++ b/src/tests/kits/shared/JsonSamples.h @@ -0,0 +1,707 @@ +/* + * Copyright 2017, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ +#ifndef JSON_SAMPLES_H +#define JSON_SAMPLES_H + + +#define JSON_SAMPLE_FALSE_A_IN "false" +#define JSON_SAMPLE_FALSE_A_EXPECTED_OUT JSON_SAMPLE_FALSE_A_IN + +#define JSON_SAMPLE_TRUE_A_IN "true" +#define JSON_SAMPLE_TRUE_A_EXPECTED_OUT JSON_SAMPLE_TRUE_A_IN + +#define JSON_SAMPLE_NULL_A_IN "null" +#define JSON_SAMPLE_NULL_A_EXPECTED_OUT JSON_SAMPLE_NULL_A_IN + +#define JSON_SAMPLE_NUMBER_A_LITERAL 3.142857142857143 +#define JSON_SAMPLE_NUMBER_A_IN "3.142857142857143" +#define JSON_SAMPLE_NUMBER_A_EXPECTED_OUT JSON_SAMPLE_NUMBER_A_IN + +#define JSON_SAMPLE_NUMBER_B_LITERAL 1122334455 +#define JSON_SAMPLE_NUMBER_B_IN "1122334455" +#define JSON_SAMPLE_NUMBER_B_EXPECTED_OUT JSON_SAMPLE_NUMBER_B_IN + +/*! This sample is a string that contains a newline in the middle as well as a + unicode character escaped with an "\x..." sequence. The second version 'A2' + has the same unicode character (non US-ASCII) represented as UTF-8 directly. +*/ + +#define JSON_SAMPLE_STRING_A_IN "\"Lake\\nTaup\\u014d\"" +#define JSON_SAMPLE_STRING_A2_IN "\"Lake\\nTaup\xc5\x8d\"" +#define JSON_SAMPLE_STRING_A_EXPECTED_OUT JSON_SAMPLE_STRING_A_IN + +#define JSON_SAMPLE_STRING_B_IN "\"\\u21c8\"" +#define JSON_SAMPLE_STRING_B_EXPECTED_OUT JSON_SAMPLE_STRING_B_IN + +/*! This sample is an array containing other elements. */ + +#define JSON_SAMPLE_ARRAY_A_IN "[\"1234\", 4567 , \n" \ + " [ \"A\" ,\"b\", \"C\\u00e9zanne\"] ]" +#define JSON_SAMPLE_ARRAY_A_EXPECTED_OUT "[\"1234\",4567," \ + "[\"A\",\"b\",\"C\\u00e9zanne\"]]" + +#define JSON_SAMPLE_ARRAY_B_IN "[\"Whirinaki\", \"Wellington\" , \n" \ + " { \"key\" : [ \"value\" ] }\n" \ + "]" +#define JSON_SAMPLE_ARRAY_B_EXPECTED_OUT "[\"Whirinaki\",\"Wellington\"," \ + "{\"key\":[\"value\"]}]" + +/*! This sample is an object type containing other elements. */ + +#define JSON_SAMPLE_OBJECT_A_IN "{\"weather\":\"raining\", \"humidity\" : " \ + "\"too-high\", \"daysOfWeek\": [\"MON\", \"TUE\", \"WED\", \"THR\"," \ + "\"FRI\"]}" +#define JSON_SAMPLE_OBJECT_A_EXPECTED_OUT "{\"weather\":\"raining\"," \ + "\"humidity\":\"too-high\",\"daysOfWeek\":[\"MON\",\"TUE\",\"WED\",\"THR\"," \ + "\"FRI\"]}" + +/*! This sample contains some other types of JSON nodes such as true, false + and null. +*/ + +#define JSON_SAMPLE_OBJECT_B_IN "{\"testTrue\":true, \"testFalse\" : " \ + "false, \"testNull\": null }" + +/*! A very simple object that can be used to test basic functionality. */ + +#define JSON_SAMPLE_OBJECT_C_IN "{\"key\":\"value\"}" + +/*! This sample has a key-value separator as the first character after + opening the object. +*/ + +#define JSON_SAMPLE_BROKEN_OBJECT_PREMATURE_SEPARATOR "{,\"key\":\"value\"}" + +#define JSON_SAMPLE_BROKEN_COMPLETELY_UNKNOWN "zzz" + +#define JSON_SAMPLE_BROKEN_UNTERMINATED_OBJECT "[ 123, {\"key\":\"value\"" + +#define JSON_SAMPLE_BROKEN_UNTERMINATED_ARRAY "{\"key\":\"value\",\n" \ + "\"key2\":\n" \ + "[" + +#define JSON_SAMPLE_BROKEN_UNTERMINATED_STRING "\"without end" + +#define JSON_SAMPLE_BROKEN_BAD_STRING_ESCAPE "\"a dastardly \\v escape sequence\"" + +#define JSON_SAMPLE_BROKEN_NUMBER "-123e123E456e" + +/*! This is a larger example and comes from the request-response + cycle of a JSON-RPC request from the HaikuDepot desktop application + into the HaikuDepotServer system. +*/ + +#define JSON_SAMPLE_HDS_FETCH_BATCH_PKGS { \ + 0x7b, 0x22, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x22, 0x3a, 0x22, \ + 0x32, 0x2e, 0x30, 0x22, 0x2c, 0x22, 0x69, 0x64, 0x22, 0x3a, 0x36, 0x2c, \ + 0x22, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x3a, 0x7b, 0x22, 0x70, \ + 0x6b, 0x67, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x6e, 0x61, 0x6d, 0x65, \ + 0x22, 0x3a, 0x22, 0x62, 0x65, 0x73, 0x68, 0x61, 0x72, 0x65, 0x5f, 0x73, \ + 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0x2c, 0x22, 0x6d, 0x6f, 0x64, 0x69, \ + 0x66, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, \ + 0x3a, 0x31, 0x34, 0x37, 0x36, 0x31, 0x39, 0x39, 0x33, 0x36, 0x34, 0x30, \ + 0x37, 0x30, 0x2c, 0x22, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, \ + 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x22, 0x3a, \ + 0x22, 0x32, 0x22, 0x2c, 0x22, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x22, 0x3a, \ + 0x22, 0x34, 0x32, 0x22, 0x2c, 0x22, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x22, \ + 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x22, 0x70, 0x72, 0x65, 0x52, 0x65, \ + 0x6c, 0x65, 0x61, 0x73, 0x65, 0x22, 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, \ + 0x22, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x33, \ + 0x2c, 0x22, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, \ + 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x22, 0x78, 0x38, 0x36, \ + 0x5f, 0x67, 0x63, 0x63, 0x32, 0x22, 0x2c, 0x22, 0x74, 0x69, 0x74, 0x6c, \ + 0x65, 0x22, 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x22, 0x72, 0x65, 0x70, \ + 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x53, 0x6f, 0x75, 0x72, 0x63, \ + 0x65, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x22, 0x66, 0x61, 0x74, 0x65, \ + 0x6c, 0x6b, 0x5f, 0x78, 0x38, 0x36, 0x5f, 0x67, 0x63, 0x63, 0x32, 0x22, \ + 0x2c, 0x22, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, \ + 0x43, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x22, 0x66, 0x61, 0x74, 0x65, 0x6c, \ + 0x6b, 0x22, 0x2c, 0x22, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, \ + 0x3a, 0x22, 0x42, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x20, 0x53, 0x65, \ + 0x72, 0x76, 0x65, 0x72, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x41, 0x74, 0x72, \ + 0x75, 0x73, 0x20, 0x43, 0x68, 0x61, 0x74, 0x62, 0x6f, 0x74, 0x22, 0x2c, \ + 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, \ + 0x22, 0x3a, 0x22, 0x54, 0x68, 0x69, 0x73, 0x20, 0x70, 0x61, 0x63, 0x6b, \ + 0x61, 0x67, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, \ + 0x20, 0x74, 0x68, 0x65, 0x20, 0x5c, 0x22, 0x6d, 0x75, 0x73, 0x63, 0x6c, \ + 0x65, 0x64, 0x5c, 0x22, 0x20, 0x28, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x20, \ + 0x55, 0x73, 0x65, 0x72, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, \ + 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x4c, 0x69, 0x6e, 0x6b, 0x69, \ + 0x6e, 0x67, 0x20, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, \ + 0x6e, 0x74, 0x20, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x29, 0x20, 0x73, \ + 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x28, 0x76, 0x65, 0x72, 0x73, 0x69, \ + 0x6f, 0x6e, 0x20, 0x33, 0x2e, 0x32, 0x30, 0x20, 0x70, 0x6f, 0x72, 0x74, \ + 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x48, 0x61, 0x69, 0x6b, 0x75, 0x20, \ + 0x47, 0x43, 0x43, 0x32, 0x20, 0x62, 0x79, 0x20, 0x41, 0x47, 0x4d, 0x53, \ + 0x20, 0x69, 0x6e, 0x20, 0x32, 0x30, 0x31, 0x36, 0x29, 0x20, 0x61, 0x6e, \ + 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x41, 0x74, 0x72, 0x75, 0x73, 0x20, \ + 0x63, 0x68, 0x61, 0x74, 0x20, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x20, 0x32, \ + 0x2e, 0x34, 0x32, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x6d, 0x6f, \ + 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x73, 0x20, 0x74, 0x65, 0x78, 0x74, 0x20, \ + 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, 0x69, 0x6f, 0x6e, \ + 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x72, \ + 0x76, 0x65, 0x72, 0x2e, 0x20, 0x20, 0x54, 0x68, 0x61, 0x74, 0x20, 0x70, \ + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x73, 0x20, 0x65, 0x76, 0x65, 0x72, \ + 0x79, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6e, \ + 0x65, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x65, 0x74, 0x20, 0x75, \ + 0x70, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x6f, 0x77, 0x6e, 0x20, 0x42, \ + 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, \ + 0x72, 0x2e, 0x5c, 0x6e, 0x5c, 0x6e, 0x41, 0x66, 0x74, 0x65, 0x72, 0x20, \ + 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x2c, 0x20, \ + 0x79, 0x6f, 0x75, 0x20, 0x6e, 0x65, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, \ + 0x64, 0x6f, 0x20, 0x61, 0x20, 0x66, 0x65, 0x77, 0x20, 0x74, 0x68, 0x69, \ + 0x6e, 0x67, 0x73, 0x3a, 0x5c, 0x6e, 0x5c, 0x6e, 0x31, 0x29, 0x20, 0x53, \ + 0x65, 0x65, 0x20, 0x69, 0x66, 0x20, 0x69, 0x74, 0x20, 0x69, 0x6e, 0x73, \ + 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x2e, 0x20, 0x20, 0x49, 0x74, 0x20, \ + 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, \ + 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, \ + 0x61, 0x66, 0x74, 0x65, 0x72, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, \ + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, \ + 0x61, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x6c, \ + 0x79, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x20, 0x75, 0x70, 0x20, 0x61, \ + 0x66, 0x74, 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x72, 0x65, 0x62, \ + 0x6f, 0x6f, 0x74, 0x2e, 0x20, 0x20, 0x49, 0x6e, 0x20, 0x50, 0x72, 0x6f, \ + 0x63, 0x65, 0x73, 0x73, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, \ + 0x6c, 0x65, 0x72, 0x20, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, \ + 0x65, 0x20, 0x5c, 0x22, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x20, \ + 0x61, 0x6e, 0x64, 0x20, 0x43, 0x50, 0x55, 0x20, 0x75, 0x73, 0x61, 0x67, \ + 0x65, 0x5c, 0x22, 0x20, 0x6d, 0x65, 0x6e, 0x75, 0x20, 0x79, 0x6f, 0x75, \ + 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x73, 0x65, 0x65, 0x20, \ + 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, \ + 0x5c, 0x22, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x6d, 0x75, 0x73, 0x63, 0x6c, \ + 0x65, 0x64, 0x5c, 0x22, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x5c, 0x22, 0x2f, \ + 0x62, 0x69, 0x6e, 0x41, 0x74, 0x72, 0x75, 0x73, 0x5c, 0x22, 0x2e, 0x5c, \ + 0x6e, 0x5c, 0x6e, 0x32, 0x29, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x69, \ + 0x74, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x42, 0x65, 0x53, 0x68, 0x61, \ + 0x72, 0x65, 0x2e, 0x20, 0x20, 0x55, 0x73, 0x65, 0x20, 0x79, 0x6f, 0x75, \ + 0x72, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x72, 0x27, 0x73, \ + 0x20, 0x49, 0x50, 0x20, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x20, \ + 0x6f, 0x72, 0x20, 0x6c, 0x65, 0x73, 0x73, 0x20, 0x72, 0x65, 0x6c, 0x69, \ + 0x61, 0x62, 0x6c, 0x79, 0x20, 0x5c, 0x22, 0x6c, 0x6f, 0x63, 0x61, 0x6c, \ + 0x68, 0x6f, 0x73, 0x74, 0x5c, 0x22, 0x20, 0x61, 0x73, 0x20, 0x74, 0x68, \ + 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, \ + 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x74, 0x6f, 0x20, \ + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x20, 0x74, 0x6f, 0x2e, 0x5c, \ + 0x6e, 0x5c, 0x6e, 0x33, 0x29, 0x20, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, \ + 0x64, 0x20, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x32, 0x39, 0x36, 0x30, 0x20, \ + 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x72, 0x6f, \ + 0x75, 0x74, 0x65, 0x72, 0x20, 0x74, 0x6f, 0x20, 0x79, 0x6f, 0x75, 0x72, \ + 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x72, 0x20, 0x28, 0x6f, \ + 0x74, 0x68, 0x65, 0x72, 0x77, 0x69, 0x73, 0x65, 0x20, 0x6e, 0x6f, 0x62, \ + 0x6f, 0x64, 0x79, 0x20, 0x6f, 0x75, 0x74, 0x73, 0x69, 0x64, 0x65, 0x20, \ + 0x63, 0x61, 0x6e, 0x20, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x20, 0x79, \ + 0x6f, 0x75, 0x72, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x29, 0x2e, \ + 0x5c, 0x6e, 0x5c, 0x6e, 0x34, 0x29, 0x20, 0x55, 0x70, 0x6f, 0x6e, 0x20, \ + 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, \ + 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x6f, 0x6f, 0x74, 0x20, 0x73, 0x63, \ + 0x72, 0x69, 0x70, 0x74, 0x20, 0x69, 0x73, 0x20, 0x63, 0x6f, 0x70, 0x69, \ + 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x2f, 0x62, 0x6f, 0x6f, 0x74, 0x2f, \ + 0x68, 0x6f, 0x6d, 0x65, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, \ + 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x2f, 0x62, 0x6f, 0x6f, \ + 0x74, 0x2f, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x2f, 0x62, 0x65, 0x73, \ + 0x68, 0x61, 0x72, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, \ + 0x73, 0x68, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x79, 0x6f, 0x75, \ + 0x20, 0x63, 0x61, 0x6e, 0x20, 0x65, 0x64, 0x69, 0x74, 0x20, 0x74, 0x68, \ + 0x65, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x20, 0x6f, 0x70, \ + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2c, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, \ + 0x64, 0x69, 0x6e, 0x67, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x27, 0x62, \ + 0x6f, 0x74, 0x73, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x61, 0x6e, 0x64, \ + 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, \ + 0x6c, 0x20, 0x49, 0x50, 0x20, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, \ + 0x20, 0x66, 0x6f, 0x72, 0x20, 0x4d, 0x75, 0x73, 0x63, 0x6c, 0x65, 0x20, \ + 0x74, 0x6f, 0x20, 0x75, 0x73, 0x65, 0x20, 0x28, 0x6f, 0x74, 0x68, 0x65, \ + 0x72, 0x77, 0x69, 0x73, 0x65, 0x20, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, \ + 0x61, 0x6c, 0x20, 0x75, 0x73, 0x65, 0x72, 0x73, 0x20, 0x77, 0x69, 0x6c, \ + 0x6c, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x74, 0x72, 0x6f, 0x75, 0x62, \ + 0x6c, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, \ + 0x67, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x6f, 0x69, 0x6e, 0x67, 0x20, \ + 0x66, 0x69, 0x6c, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, \ + 0x72, 0x73, 0x29, 0x2e, 0x20, 0x20, 0x54, 0x68, 0x65, 0x20, 0x6c, 0x6f, \ + 0x67, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, \ + 0x61, 0x6c, 0x73, 0x6f, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, \ + 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x72, 0x65, 0x2c, 0x20, 0x72, 0x65, \ + 0x6d, 0x6f, 0x76, 0x65, 0x20, 0x74, 0x68, 0x65, 0x6d, 0x20, 0x69, 0x66, \ + 0x20, 0x79, 0x6f, 0x75, 0x27, 0x72, 0x65, 0x20, 0x75, 0x73, 0x69, 0x6e, \ + 0x67, 0x20, 0x61, 0x20, 0x72, 0x65, 0x61, 0x64, 0x2d, 0x6f, 0x6e, 0x6c, \ + 0x79, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x73, 0x79, 0x73, 0x74, 0x65, \ + 0x6d, 0x2e, 0x5c, 0x6e, 0x5c, 0x6e, 0x35, 0x29, 0x20, 0x45, 0x64, 0x69, \ + 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x42, 0x6f, 0x74, 0x44, 0x61, 0x74, \ + 0x61, 0x20, 0x74, 0x65, 0x78, 0x74, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, \ + 0x20, 0x69, 0x6e, 0x20, 0x2f, 0x62, 0x6f, 0x6f, 0x74, 0x2f, 0x68, 0x6f, \ + 0x6d, 0x65, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x73, 0x65, \ + 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x2f, 0x42, 0x6f, 0x74, 0x44, 0x61, \ + 0x74, 0x61, 0x20, 0x74, 0x6f, 0x20, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, \ + 0x69, 0x7a, 0x65, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x63, 0x68, 0x61, \ + 0x74, 0x62, 0x6f, 0x74, 0x27, 0x73, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2e, \ + 0x5c, 0x6e, 0x5c, 0x6e, 0x48, 0x61, 0x70, 0x70, 0x79, 0x20, 0x42, 0x65, \ + 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x21, 0x22, 0x2c, 0x22, 0x70, \ + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, \ + 0x22, 0x3a, 0x34, 0x30, 0x33, 0x36, 0x33, 0x37, 0x33, 0x7d, 0x5d, 0x2c, \ + 0x22, 0x70, 0x6b, 0x67, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, \ + 0x43, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x3a, 0x5b, 0x22, 0x69, 0x6e, 0x74, \ + 0x65, 0x72, 0x6e, 0x65, 0x74, 0x61, 0x6e, 0x64, 0x6e, 0x65, 0x74, 0x77, \ + 0x6f, 0x72, 0x6b, 0x22, 0x5d, 0x2c, 0x22, 0x70, 0x6b, 0x67, 0x53, 0x63, \ + 0x72, 0x65, 0x65, 0x6e, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x22, 0x3a, 0x5b, \ + 0x5d, 0x2c, 0x22, 0x70, 0x6b, 0x67, 0x49, 0x63, 0x6f, 0x6e, 0x73, 0x22, \ + 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x22, 0x64, 0x65, 0x72, 0x69, 0x76, \ + 0x65, 0x64, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x6e, 0x75, \ + 0x6c, 0x6c, 0x2c, 0x22, 0x70, 0x72, 0x6f, 0x6d, 0x69, 0x6e, 0x65, 0x6e, \ + 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x3a, \ + 0x31, 0x30, 0x30, 0x30, 0x2c, 0x22, 0x70, 0x6b, 0x67, 0x43, 0x68, 0x61, \ + 0x6e, 0x67, 0x65, 0x6c, 0x6f, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, \ + 0x74, 0x22, 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x7d, 0x2c, 0x7b, 0x22, 0x6e, \ + 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x22, 0x62, 0x6f, 0x6f, 0x6b, 0x6d, 0x61, \ + 0x72, 0x6b, 0x65, 0x72, 0x22, 0x2c, 0x22, 0x6d, 0x6f, 0x64, 0x69, 0x66, \ + 0x79, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x3a, \ + 0x31, 0x34, 0x38, 0x38, 0x37, 0x38, 0x35, 0x33, 0x33, 0x31, 0x36, 0x33, \ + 0x31, 0x2c, 0x22, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, \ + 0x3a, 0x5b, 0x7b, 0x22, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x22, 0x3a, 0x22, \ + 0x31, 0x22, 0x2c, 0x22, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x22, 0x3a, 0x22, \ + 0x33, 0x22, 0x2c, 0x22, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x22, 0x3a, 0x6e, \ + 0x75, 0x6c, 0x6c, 0x2c, 0x22, 0x70, 0x72, 0x65, 0x52, 0x65, 0x6c, 0x65, \ + 0x61, 0x73, 0x65, 0x22, 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x22, 0x72, \ + 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x32, 0x2c, 0x22, \ + 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, \ + 0x43, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x22, 0x78, 0x38, 0x36, 0x5f, 0x67, \ + 0x63, 0x63, 0x32, 0x22, 0x2c, 0x22, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x22, \ + 0x3a, 0x22, 0x42, 0x6f, 0x6f, 0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x72, \ + 0x22, 0x2c, 0x22, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, \ + 0x79, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x22, \ + 0x3a, 0x22, 0x66, 0x61, 0x74, 0x65, 0x6c, 0x6b, 0x5f, 0x78, 0x38, 0x36, \ + 0x5f, 0x67, 0x63, 0x63, 0x32, 0x22, 0x2c, 0x22, 0x72, 0x65, 0x70, 0x6f, \ + 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x3a, \ + 0x22, 0x66, 0x61, 0x74, 0x65, 0x6c, 0x6b, 0x22, 0x2c, 0x22, 0x73, 0x75, \ + 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x3a, 0x22, 0x42, 0x6f, 0x6f, 0x6b, \ + 0x6d, 0x61, 0x72, 0x6b, 0x20, 0x6d, 0x61, 0x6b, 0x65, 0x72, 0x22, 0x2c, \ + 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, \ + 0x22, 0x3a, 0x22, 0x42, 0x6f, 0x6f, 0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x65, \ + 0x72, 0x20, 0x6d, 0x61, 0x6b, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, \ + 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x20, 0x62, 0x6f, 0x6f, 0x6b, 0x6d, \ + 0x61, 0x72, 0x6b, 0x73, 0x20, 0x28, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, \ + 0x20, 0x77, 0x69, 0x64, 0x65, 0x20, 0x6c, 0x69, 0x6e, 0x6b, 0x73, 0x20, \ + 0x74, 0x6f, 0x20, 0x77, 0x65, 0x62, 0x20, 0x73, 0x69, 0x74, 0x65, 0x73, \ + 0x29, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x48, 0x61, \ + 0x69, 0x6b, 0x75, 0x20, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, \ + 0x67, 0x20, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x20, 0x49, 0x74, \ + 0x61, 0x6c, 0x73, 0x6f, 0x20, 0x61, 0x64, 0x64, 0x73, 0x20, 0x74, 0x68, \ + 0x65, 0x20, 0x62, 0x6f, 0x6f, 0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x73, 0x20, \ + 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x44, 0x65, 0x73, 0x6b, 0x62, \ + 0x61, 0x72, 0x20, 0x6d, 0x65, 0x6e, 0x75, 0x2e, 0x22, 0x2c, 0x22, 0x70, \ + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, \ + 0x22, 0x3a, 0x36, 0x34, 0x34, 0x31, 0x35, 0x7d, 0x5d, 0x2c, 0x22, 0x70, \ + 0x6b, 0x67, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x43, 0x6f, \ + 0x64, 0x65, 0x73, 0x22, 0x3a, 0x5b, 0x22, 0x70, 0x72, 0x6f, 0x64, 0x75, \ + 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x22, 0x2c, 0x22, 0x73, 0x79, \ + 0x73, 0x74, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x75, 0x74, 0x69, 0x6c, 0x69, \ + 0x74, 0x69, 0x65, 0x73, 0x22, 0x2c, 0x22, 0x69, 0x6e, 0x74, 0x65, 0x72, \ + 0x6e, 0x65, 0x74, 0x61, 0x6e, 0x64, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, \ + 0x6b, 0x22, 0x5d, 0x2c, 0x22, 0x70, 0x6b, 0x67, 0x53, 0x63, 0x72, 0x65, \ + 0x65, 0x6e, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, \ + 0x63, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x22, 0x37, 0x36, 0x35, 0x30, 0x39, \ + 0x64, 0x64, 0x36, 0x2d, 0x32, 0x35, 0x39, 0x36, 0x2d, 0x34, 0x32, 0x34, \ + 0x37, 0x2d, 0x61, 0x38, 0x65, 0x34, 0x2d, 0x34, 0x37, 0x36, 0x39, 0x65, \ + 0x34, 0x35, 0x61, 0x36, 0x66, 0x37, 0x35, 0x22, 0x2c, 0x22, 0x6c, 0x65, \ + 0x6e, 0x67, 0x74, 0x68, 0x22, 0x3a, 0x32, 0x32, 0x31, 0x32, 0x35, 0x2c, \ + 0x22, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x3a, 0x34, 0x33, 0x32, \ + 0x2c, 0x22, 0x77, 0x69, 0x64, 0x74, 0x68, 0x22, 0x3a, 0x35, 0x31, 0x31, \ + 0x7d, 0x2c, 0x7b, 0x22, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x22, 0x33, \ + 0x32, 0x32, 0x61, 0x62, 0x39, 0x66, 0x33, 0x2d, 0x65, 0x35, 0x36, 0x66, \ + 0x2d, 0x34, 0x64, 0x31, 0x61, 0x2d, 0x39, 0x37, 0x34, 0x33, 0x2d, 0x35, \ + 0x63, 0x65, 0x66, 0x65, 0x39, 0x39, 0x32, 0x38, 0x32, 0x37, 0x66, 0x22, \ + 0x2c, 0x22, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0x3a, 0x35, 0x34, \ + 0x31, 0x30, 0x34, 0x2c, 0x22, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, \ + 0x3a, 0x33, 0x30, 0x39, 0x2c, 0x22, 0x77, 0x69, 0x64, 0x74, 0x68, 0x22, \ + 0x3a, 0x33, 0x32, 0x36, 0x7d, 0x5d, 0x2c, 0x22, 0x70, 0x6b, 0x67, 0x49, \ + 0x63, 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x22, \ + 0x64, 0x65, 0x72, 0x69, 0x76, 0x65, 0x64, 0x52, 0x61, 0x74, 0x69, 0x6e, \ + 0x67, 0x22, 0x3a, 0x32, 0x2e, 0x30, 0x2c, 0x22, 0x70, 0x72, 0x6f, 0x6d, \ + 0x69, 0x6e, 0x65, 0x6e, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x69, \ + 0x6e, 0x67, 0x22, 0x3a, 0x31, 0x30, 0x30, 0x30, 0x2c, 0x22, 0x70, 0x6b, \ + 0x67, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x6c, 0x6f, 0x67, 0x43, 0x6f, \ + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x7d, \ + 0x2c, 0x7b, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x22, 0x66, 0x61, \ + 0x6c, 0x6c, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x22, 0x2c, 0x22, 0x6d, \ + 0x6f, 0x64, 0x69, 0x66, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, \ + 0x6d, 0x70, 0x22, 0x3a, 0x31, 0x34, 0x36, 0x33, 0x36, 0x38, 0x34, 0x36, \ + 0x35, 0x38, 0x33, 0x39, 0x38, 0x2c, 0x22, 0x76, 0x65, 0x72, 0x73, 0x69, \ + 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x6d, 0x61, 0x6a, 0x6f, \ + 0x72, 0x22, 0x3a, 0x22, 0x31, 0x22, 0x2c, 0x22, 0x6d, 0x69, 0x6e, 0x6f, \ + 0x72, 0x22, 0x3a, 0x22, 0x32, 0x22, 0x2c, 0x22, 0x6d, 0x69, 0x63, 0x72, \ + 0x6f, 0x22, 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x22, 0x70, 0x72, 0x65, \ + 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x22, 0x3a, 0x6e, 0x75, 0x6c, \ + 0x6c, 0x2c, 0x22, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, \ + 0x3a, 0x31, 0x2c, 0x22, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, \ + 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x22, 0x78, \ + 0x38, 0x36, 0x5f, 0x67, 0x63, 0x63, 0x32, 0x22, 0x2c, 0x22, 0x74, 0x69, \ + 0x74, 0x6c, 0x65, 0x22, 0x3a, 0x22, 0x46, 0x61, 0x6c, 0x6c, 0x20, 0x4c, \ + 0x65, 0x61, 0x76, 0x65, 0x73, 0x22, 0x2c, 0x22, 0x72, 0x65, 0x70, 0x6f, \ + 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, \ + 0x43, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x22, 0x66, 0x61, 0x74, 0x65, 0x6c, \ + 0x6b, 0x5f, 0x78, 0x38, 0x36, 0x5f, 0x67, 0x63, 0x63, 0x32, 0x22, 0x2c, \ + 0x22, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x43, \ + 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x22, 0x66, 0x61, 0x74, 0x65, 0x6c, 0x6b, \ + 0x22, 0x2c, 0x22, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x3a, \ + 0x22, 0x46, 0x61, 0x6c, 0x6c, 0x20, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73, \ + 0x20, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x73, 0x61, 0x76, 0x65, 0x72, \ + 0x22, 0x2c, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, \ + 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x41, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x6c, \ + 0x79, 0x20, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x73, 0x61, 0x76, 0x65, \ + 0x72, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x66, 0x61, 0x6c, 0x6c, 0x69, \ + 0x6e, 0x67, 0x20, 0x48, 0x61, 0x69, 0x6b, 0x75, 0x20, 0x6c, 0x65, 0x61, \ + 0x76, 0x65, 0x73, 0x2e, 0x22, 0x2c, 0x22, 0x70, 0x61, 0x79, 0x6c, 0x6f, \ + 0x61, 0x64, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0x3a, 0x31, 0x32, \ + 0x34, 0x31, 0x32, 0x7d, 0x5d, 0x2c, 0x22, 0x70, 0x6b, 0x67, 0x43, 0x61, \ + 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x73, 0x22, \ + 0x3a, 0x5b, 0x22, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x61, 0x6e, 0x64, \ + 0x75, 0x74, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x5d, 0x2c, \ + 0x22, 0x70, 0x6b, 0x67, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x73, 0x68, \ + 0x6f, 0x74, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x63, 0x6f, 0x64, 0x65, \ + 0x22, 0x3a, 0x22, 0x64, 0x64, 0x62, 0x37, 0x62, 0x63, 0x66, 0x63, 0x2d, \ + 0x64, 0x35, 0x32, 0x31, 0x2d, 0x34, 0x61, 0x66, 0x36, 0x2d, 0x38, 0x32, \ + 0x38, 0x33, 0x2d, 0x34, 0x62, 0x30, 0x31, 0x66, 0x39, 0x36, 0x63, 0x63, \ + 0x31, 0x34, 0x39, 0x22, 0x2c, 0x22, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, \ + 0x22, 0x3a, 0x32, 0x36, 0x31, 0x33, 0x36, 0x35, 0x2c, 0x22, 0x68, 0x65, \ + 0x69, 0x67, 0x68, 0x74, 0x22, 0x3a, 0x31, 0x30, 0x32, 0x34, 0x2c, 0x22, \ + 0x77, 0x69, 0x64, 0x74, 0x68, 0x22, 0x3a, 0x31, 0x32, 0x38, 0x30, 0x7d, \ + 0x5d, 0x2c, 0x22, 0x70, 0x6b, 0x67, 0x49, 0x63, 0x6f, 0x6e, 0x73, 0x22, \ + 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x22, 0x64, 0x65, 0x72, 0x69, 0x76, \ + 0x65, 0x64, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x6e, 0x75, \ + 0x6c, 0x6c, 0x2c, 0x22, 0x70, 0x72, 0x6f, 0x6d, 0x69, 0x6e, 0x65, 0x6e, \ + 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x3a, \ + 0x31, 0x30, 0x30, 0x30, 0x2c, 0x22, 0x70, 0x6b, 0x67, 0x43, 0x68, 0x61, \ + 0x6e, 0x67, 0x65, 0x6c, 0x6f, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, \ + 0x74, 0x22, 0x3a, 0x22, 0x31, 0x2e, 0x32, 0x20, 0x63, 0x68, 0x61, 0x6e, \ + 0x67, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, \ + 0x62, 0x79, 0x20, 0x68, 0x72, 0x65, 0x76, 0x35, 0x30, 0x32, 0x32, 0x36, \ + 0x20, 0x20, 0x74, 0x6f, 0x20, 0x75, 0x73, 0x65, 0x20, 0x42, 0x75, 0x69, \ + 0x6c, 0x64, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x65, 0x74, \ + 0x74, 0x69, 0x6e, 0x67, 0x73, 0x56, 0x69, 0x65, 0x77, 0x5c, 0x6e, 0x5c, \ + 0x6e, 0x31, 0x2e, 0x31, 0x20, 0x6d, 0x61, 0x6b, 0x65, 0x20, 0x73, 0x6f, \ + 0x6d, 0x65, 0x20, 0x6c, 0x65, 0x76, 0x65, 0x73, 0x20, 0x63, 0x68, 0x61, \ + 0x6e, 0x67, 0x65, 0x20, 0x6f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, \ + 0x69, 0x6f, 0x6e, 0x5c, 0x6e, 0x5c, 0x6e, 0x31, 0x2e, 0x30, 0x20, 0x6f, \ + 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x20, 0x72, 0x65, 0x6c, 0x65, \ + 0x61, 0x73, 0x65, 0x22, 0x7d, 0x2c, 0x7b, 0x22, 0x6e, 0x61, 0x6d, 0x65, \ + 0x22, 0x3a, 0x22, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x62, 0x75, \ + 0x69, 0x6c, 0x64, 0x65, 0x72, 0x22, 0x2c, 0x22, 0x6d, 0x6f, 0x64, 0x69, \ + 0x66, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, \ + 0x3a, 0x31, 0x34, 0x38, 0x38, 0x38, 0x30, 0x34, 0x37, 0x30, 0x30, 0x36, \ + 0x33, 0x30, 0x2c, 0x22, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, \ + 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x22, 0x3a, \ + 0x22, 0x34, 0x22, 0x2c, 0x22, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x22, 0x3a, \ + 0x22, 0x35, 0x22, 0x2c, 0x22, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x22, 0x3a, \ + 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x22, 0x70, 0x72, 0x65, 0x52, 0x65, 0x6c, \ + 0x65, 0x61, 0x73, 0x65, 0x22, 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x22, \ + 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x31, 0x2c, \ + 0x22, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, \ + 0x65, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x22, 0x78, 0x38, 0x36, 0x5f, \ + 0x67, 0x63, 0x63, 0x32, 0x22, 0x2c, 0x22, 0x74, 0x69, 0x74, 0x6c, 0x65, \ + 0x22, 0x3a, 0x22, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x42, 0x75, \ + 0x69, 0x6c, 0x64, 0x65, 0x72, 0x22, 0x2c, 0x22, 0x72, 0x65, 0x70, 0x6f, \ + 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, \ + 0x43, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x22, 0x66, 0x61, 0x74, 0x65, 0x6c, \ + 0x6b, 0x5f, 0x78, 0x38, 0x36, 0x5f, 0x67, 0x63, 0x63, 0x32, 0x22, 0x2c, \ + 0x22, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x43, \ + 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x22, 0x66, 0x61, 0x74, 0x65, 0x6c, 0x6b, \ + 0x22, 0x2c, 0x22, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x3a, \ + 0x22, 0x55, 0x74, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x20, 0x74, 0x6f, 0x20, \ + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6f, \ + 0x70, 0x65, 0x6e, 0x20, 0x2e, 0x70, 0x6b, 0x67, 0x20, 0x66, 0x69, 0x6c, \ + 0x65, 0x73, 0x22, 0x2c, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, \ + 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x54, 0x68, 0x69, 0x73, 0x20, \ + 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, \ + 0x77, 0x61, 0x73, 0x20, 0x42, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x27, \ + 0x73, 0x20, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x20, 0x6f, 0x66, 0x20, \ + 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x2e, 0x70, 0x6b, \ + 0x67, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, \ + 0x6f, 0x6e, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x20, 0x49, 0x74, \ + 0x20, 0x77, 0x61, 0x73, 0x20, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, \ + 0x64, 0x20, 0x73, 0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x65, 0x6c, 0x79, \ + 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x42, 0x65, 0x4f, 0x53, 0x20, 0x61, \ + 0x73, 0x20, 0x70, 0x61, 0x72, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, \ + 0x65, 0x20, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, 0x20, \ + 0x74, 0x6f, 0x6f, 0x6c, 0x73, 0x2e, 0x20, 0x49, 0x74, 0x20, 0x69, 0x73, \ + 0x20, 0x75, 0x6e, 0x6c, 0x69, 0x6b, 0x65, 0x6c, 0x79, 0x20, 0x74, 0x68, \ + 0x61, 0x74, 0x20, 0x61, 0x6e, 0x79, 0x6f, 0x6e, 0x65, 0x20, 0x77, 0x69, \ + 0x6c, 0x6c, 0x20, 0x73, 0x74, 0x69, 0x6c, 0x6c, 0x20, 0x77, 0x61, 0x6e, \ + 0x74, 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x61, 0x6b, 0x65, 0x20, 0x74, 0x68, \ + 0x65, 0x73, 0x65, 0x20, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, \ + 0x20, 0x6e, 0x6f, 0x77, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x77, 0x65, \ + 0x20, 0x61, 0x72, 0x65, 0x20, 0x6d, 0x6f, 0x76, 0x69, 0x6e, 0x67, 0x20, \ + 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x27, 0x68, 0x70, 0x6b, 0x67, \ + 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x20, 0x42, 0x75, 0x74, \ + 0x20, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x42, 0x75, 0x69, 0x6c, \ + 0x64, 0x65, 0x72, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, \ + 0x6e, 0x6c, 0x79, 0x20, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x20, 0x77, 0x61, \ + 0x79, 0x20, 0x74, 0x6f, 0x20, 0x72, 0x69, 0x70, 0x20, 0x61, 0x20, 0x2e, \ + 0x70, 0x6b, 0x67, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x61, 0x70, 0x61, \ + 0x72, 0x74, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x67, 0x65, 0x74, 0x20, 0x61, \ + 0x74, 0x20, 0x69, 0x74, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, \ + 0x74, 0x73, 0x2e, 0x20, 0x49, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, \ + 0x65, 0x72, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x20, 0x73, 0x74, 0x69, 0x6c, \ + 0x6c, 0x20, 0x6f, 0x66, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x73, \ + 0x74, 0x20, 0x74, 0x6f, 0x20, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, \ + 0x65, 0x72, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x70, 0x6f, 0x72, 0x74, \ + 0x65, 0x72, 0x73, 0x2e, 0x22, 0x2c, 0x22, 0x70, 0x61, 0x79, 0x6c, 0x6f, \ + 0x61, 0x64, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0x3a, 0x34, 0x39, \ + 0x38, 0x38, 0x38, 0x36, 0x7d, 0x5d, 0x2c, 0x22, 0x70, 0x6b, 0x67, 0x43, \ + 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x73, \ + 0x22, 0x3a, 0x5b, 0x22, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x61, 0x6e, \ + 0x64, 0x75, 0x74, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x2c, \ + 0x22, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x6d, 0x65, 0x6e, 0x74, \ + 0x22, 0x5d, 0x2c, 0x22, 0x70, 0x6b, 0x67, 0x53, 0x63, 0x72, 0x65, 0x65, \ + 0x6e, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x63, \ + 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x22, 0x65, 0x66, 0x62, 0x34, 0x32, 0x32, \ + 0x31, 0x34, 0x2d, 0x30, 0x31, 0x66, 0x36, 0x2d, 0x34, 0x38, 0x37, 0x63, \ + 0x2d, 0x61, 0x33, 0x39, 0x33, 0x2d, 0x36, 0x32, 0x61, 0x33, 0x61, 0x31, \ + 0x34, 0x64, 0x31, 0x36, 0x30, 0x31, 0x22, 0x2c, 0x22, 0x6c, 0x65, 0x6e, \ + 0x67, 0x74, 0x68, 0x22, 0x3a, 0x35, 0x34, 0x32, 0x31, 0x33, 0x2c, 0x22, \ + 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x3a, 0x33, 0x36, 0x30, 0x2c, \ + 0x22, 0x77, 0x69, 0x64, 0x74, 0x68, 0x22, 0x3a, 0x33, 0x32, 0x37, 0x7d, \ + 0x5d, 0x2c, 0x22, 0x70, 0x6b, 0x67, 0x49, 0x63, 0x6f, 0x6e, 0x73, 0x22, \ + 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x22, 0x64, 0x65, 0x72, 0x69, 0x76, \ + 0x65, 0x64, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x34, 0x2e, \ + 0x30, 0x2c, 0x22, 0x70, 0x72, 0x6f, 0x6d, 0x69, 0x6e, 0x65, 0x6e, 0x63, \ + 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x31, \ + 0x30, 0x30, 0x2c, 0x22, 0x70, 0x6b, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x67, \ + 0x65, 0x6c, 0x6f, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, \ + 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x7d, 0x2c, 0x7b, 0x22, 0x6e, 0x61, 0x6d, \ + 0x65, 0x22, 0x3a, 0x22, 0x72, 0x61, 0x77, 0x61, 0x65, 0x73, 0x22, 0x2c, \ + 0x22, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x73, \ + 0x74, 0x61, 0x6d, 0x70, 0x22, 0x3a, 0x31, 0x34, 0x39, 0x31, 0x36, 0x35, \ + 0x32, 0x30, 0x33, 0x39, 0x30, 0x38, 0x39, 0x2c, 0x22, 0x76, 0x65, 0x72, \ + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x6d, 0x61, \ + 0x6a, 0x6f, 0x72, 0x22, 0x3a, 0x22, 0x31, 0x22, 0x2c, 0x22, 0x6d, 0x69, \ + 0x6e, 0x6f, 0x72, 0x22, 0x3a, 0x22, 0x31, 0x22, 0x2c, 0x22, 0x6d, 0x69, \ + 0x63, 0x72, 0x6f, 0x22, 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x22, 0x70, \ + 0x72, 0x65, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x22, 0x3a, 0x6e, \ + 0x75, 0x6c, 0x6c, 0x2c, 0x22, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, \ + 0x6e, 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, \ + 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x3a, \ + 0x22, 0x78, 0x38, 0x36, 0x5f, 0x67, 0x63, 0x63, 0x32, 0x22, 0x2c, 0x22, \ + 0x74, 0x69, 0x74, 0x6c, 0x65, 0x22, 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, \ + 0x22, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x53, \ + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x22, \ + 0x66, 0x61, 0x74, 0x65, 0x6c, 0x6b, 0x5f, 0x78, 0x38, 0x36, 0x5f, 0x67, \ + 0x63, 0x63, 0x32, 0x22, 0x2c, 0x22, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, \ + 0x74, 0x6f, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x22, 0x66, \ + 0x61, 0x74, 0x65, 0x6c, 0x6b, 0x22, 0x2c, 0x22, 0x73, 0x75, 0x6d, 0x6d, \ + 0x61, 0x72, 0x79, 0x22, 0x3a, 0x22, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, \ + 0x64, 0x2d, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x65, 0x6e, 0x63, 0x72, 0x79, \ + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x75, 0x74, 0x69, 0x6c, 0x69, 0x74, \ + 0x79, 0x22, 0x2c, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, \ + 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x52, 0x61, 0x77, 0x61, 0x65, 0x73, \ + 0x20, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x73, 0x20, 0x61, 0x20, \ + 0x66, 0x69, 0x6c, 0x65, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x74, \ + 0x68, 0x65, 0x20, 0x41, 0x64, 0x76, 0x61, 0x6e, 0x64, 0x63, 0x65, 0x64, \ + 0x20, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, \ + 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x28, 0x41, 0x45, \ + 0x53, 0x29, 0x2e, 0x20, 0x20, 0x41, 0x45, 0x53, 0x20, 0x75, 0x73, 0x65, \ + 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x52, 0x69, 0x6a, 0x6e, 0x64, 0x61, \ + 0x65, 0x6c, 0x20, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, \ + 0x2c, 0x20, 0x61, 0x20, 0x31, 0x32, 0x38, 0x2d, 0x62, 0x69, 0x74, 0x20, \ + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, \ + 0x2c, 0x20, 0x74, 0x6f, 0x20, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, \ + 0x2e, 0x20, 0x20, 0x53, 0x69, 0x6e, 0x63, 0x65, 0x20, 0x74, 0x68, 0x65, \ + 0x20, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, \ + 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x20, 0x31, 0x36, 0x2d, \ + 0x62, 0x79, 0x74, 0x65, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x2c, \ + 0x20, 0x72, 0x61, 0x77, 0x61, 0x65, 0x73, 0x20, 0x77, 0x69, 0x6c, 0x6c, \ + 0x20, 0x70, 0x61, 0x64, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x75, 0x6e, 0x65, \ + 0x76, 0x65, 0x6e, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x20, 0x77, \ + 0x69, 0x74, 0x68, 0x20, 0x30, 0x73, 0x2c, 0x20, 0x70, 0x6f, 0x73, 0x73, \ + 0x69, 0x62, 0x6c, 0x79, 0x20, 0x69, 0x6e, 0x63, 0x72, 0x65, 0x61, 0x73, \ + 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x69, 0x7a, 0x65, \ + 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, \ + 0x20, 0x75, 0x70, 0x20, 0x74, 0x6f, 0x20, 0x31, 0x35, 0x20, 0x62, 0x79, \ + 0x74, 0x65, 0x73, 0x2e, 0x22, 0x2c, 0x22, 0x70, 0x61, 0x79, 0x6c, 0x6f, \ + 0x61, 0x64, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0x3a, 0x36, 0x32, \ + 0x37, 0x39, 0x33, 0x7d, 0x5d, 0x2c, 0x22, 0x70, 0x6b, 0x67, 0x43, 0x61, \ + 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x73, 0x22, \ + 0x3a, 0x5b, 0x22, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x61, 0x6e, 0x64, \ + 0x75, 0x74, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x2c, 0x22, \ + 0x73, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x61, 0x6e, 0x64, 0x6d, 0x61, \ + 0x74, 0x68, 0x65, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x73, 0x22, 0x5d, 0x2c, \ + 0x22, 0x70, 0x6b, 0x67, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x73, 0x68, \ + 0x6f, 0x74, 0x73, 0x22, 0x3a, 0x5b, 0x5d, 0x2c, 0x22, 0x70, 0x6b, 0x67, \ + 0x49, 0x63, 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, \ + 0x22, 0x64, 0x65, 0x72, 0x69, 0x76, 0x65, 0x64, 0x52, 0x61, 0x74, 0x69, \ + 0x6e, 0x67, 0x22, 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x22, 0x70, 0x72, \ + 0x6f, 0x6d, 0x69, 0x6e, 0x65, 0x6e, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, \ + 0x72, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x31, 0x30, 0x30, 0x30, 0x2c, 0x22, \ + 0x70, 0x6b, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x6c, 0x6f, 0x67, \ + 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x6e, 0x75, 0x6c, \ + 0x6c, 0x7d, 0x2c, 0x7b, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x22, \ + 0x74, 0x65, 0x78, 0x74, 0x73, 0x61, 0x76, 0x65, 0x72, 0x22, 0x2c, 0x22, \ + 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, \ + 0x61, 0x6d, 0x70, 0x22, 0x3a, 0x31, 0x34, 0x39, 0x31, 0x36, 0x35, 0x32, \ + 0x30, 0x33, 0x39, 0x38, 0x30, 0x39, 0x2c, 0x22, 0x76, 0x65, 0x72, 0x73, \ + 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x6d, 0x61, 0x6a, \ + 0x6f, 0x72, 0x22, 0x3a, 0x22, 0x31, 0x22, 0x2c, 0x22, 0x6d, 0x69, 0x6e, \ + 0x6f, 0x72, 0x22, 0x3a, 0x22, 0x30, 0x22, 0x2c, 0x22, 0x6d, 0x69, 0x63, \ + 0x72, 0x6f, 0x22, 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x22, 0x70, 0x72, \ + 0x65, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x22, 0x3a, 0x6e, 0x75, \ + 0x6c, 0x6c, 0x2c, 0x22, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, \ + 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, \ + 0x63, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x22, \ + 0x78, 0x38, 0x36, 0x5f, 0x67, 0x63, 0x63, 0x32, 0x22, 0x2c, 0x22, 0x74, \ + 0x69, 0x74, 0x6c, 0x65, 0x22, 0x3a, 0x22, 0x54, 0x65, 0x78, 0x74, 0x53, \ + 0x61, 0x76, 0x65, 0x72, 0x22, 0x2c, 0x22, 0x72, 0x65, 0x70, 0x6f, 0x73, \ + 0x69, 0x74, 0x6f, 0x72, 0x79, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, \ + 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x22, 0x66, 0x61, 0x74, 0x65, 0x6c, 0x6b, \ + 0x5f, 0x78, 0x38, 0x36, 0x5f, 0x67, 0x63, 0x63, 0x32, 0x22, 0x2c, 0x22, \ + 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x43, 0x6f, \ + 0x64, 0x65, 0x22, 0x3a, 0x22, 0x66, 0x61, 0x74, 0x65, 0x6c, 0x6b, 0x22, \ + 0x2c, 0x22, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x3a, 0x22, \ + 0x54, 0x65, 0x78, 0x74, 0x53, 0x61, 0x76, 0x65, 0x72, 0x20, 0x73, 0x63, \ + 0x72, 0x65, 0x65, 0x6e, 0x73, 0x61, 0x76, 0x65, 0x72, 0x22, 0x2c, 0x22, \ + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, \ + 0x3a, 0x22, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x73, 0x20, 0x79, \ + 0x6f, 0x75, 0x72, 0x20, 0x74, 0x65, 0x78, 0x74, 0x20, 0x61, 0x74, 0x20, \ + 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x74, \ + 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x72, 0x61, 0x6e, 0x64, \ + 0x6f, 0x6d, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x73, 0x2e, 0x22, 0x2c, \ + 0x22, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x65, 0x6e, 0x67, \ + 0x74, 0x68, 0x22, 0x3a, 0x31, 0x32, 0x31, 0x36, 0x38, 0x7d, 0x5d, 0x2c, \ + 0x22, 0x70, 0x6b, 0x67, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, \ + 0x43, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x3a, 0x5b, 0x22, 0x73, 0x79, 0x73, \ + 0x74, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x75, 0x74, 0x69, 0x6c, 0x69, 0x74, \ + 0x69, 0x65, 0x73, 0x22, 0x5d, 0x2c, 0x22, 0x70, 0x6b, 0x67, 0x53, 0x63, \ + 0x72, 0x65, 0x65, 0x6e, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x22, 0x3a, 0x5b, \ + 0x5d, 0x2c, 0x22, 0x70, 0x6b, 0x67, 0x49, 0x63, 0x6f, 0x6e, 0x73, 0x22, \ + 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x22, 0x64, 0x65, 0x72, 0x69, 0x76, \ + 0x65, 0x64, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x6e, 0x75, \ + 0x6c, 0x6c, 0x2c, 0x22, 0x70, 0x72, 0x6f, 0x6d, 0x69, 0x6e, 0x65, 0x6e, \ + 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x3a, \ + 0x31, 0x30, 0x30, 0x30, 0x2c, 0x22, 0x70, 0x6b, 0x67, 0x43, 0x68, 0x61, \ + 0x6e, 0x67, 0x65, 0x6c, 0x6f, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, \ + 0x74, 0x22, 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x7d, 0x2c, 0x7b, 0x22, 0x6e, \ + 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x22, 0x76, 0x6e, 0x63, 0x73, 0x65, 0x72, \ + 0x76, 0x65, 0x72, 0x22, 0x2c, 0x22, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, \ + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x3a, 0x31, \ + 0x34, 0x39, 0x31, 0x36, 0x35, 0x32, 0x30, 0x34, 0x30, 0x32, 0x34, 0x34, \ + 0x2c, 0x22, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a, \ + 0x5b, 0x7b, 0x22, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x22, 0x3a, 0x22, 0x76, \ + 0x6e, 0x63, 0x34, 0x5f, 0x30, 0x22, 0x2c, 0x22, 0x6d, 0x69, 0x6e, 0x6f, \ + 0x72, 0x22, 0x3a, 0x22, 0x61, 0x67, 0x6d, 0x73, 0x5f, 0x31, 0x22, 0x2c, \ + 0x22, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x22, 0x3a, 0x22, 0x32, 0x37, 0x22, \ + 0x2c, 0x22, 0x70, 0x72, 0x65, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, \ + 0x22, 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x22, 0x72, 0x65, 0x76, 0x69, \ + 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x32, 0x2c, 0x22, 0x61, 0x72, 0x63, \ + 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, \ + 0x65, 0x22, 0x3a, 0x22, 0x78, 0x38, 0x36, 0x5f, 0x67, 0x63, 0x63, 0x32, \ + 0x22, 0x2c, 0x22, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x22, 0x3a, 0x22, 0x56, \ + 0x4e, 0x43, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0x2c, 0x22, 0x72, \ + 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x53, 0x6f, 0x75, \ + 0x72, 0x63, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x22, 0x66, 0x61, \ + 0x74, 0x65, 0x6c, 0x6b, 0x5f, 0x78, 0x38, 0x36, 0x5f, 0x67, 0x63, 0x63, \ + 0x32, 0x22, 0x2c, 0x22, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, \ + 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x3a, 0x22, 0x66, 0x61, 0x74, \ + 0x65, 0x6c, 0x6b, 0x22, 0x2c, 0x22, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, \ + 0x79, 0x22, 0x3a, 0x22, 0x56, 0x4e, 0x43, 0x53, 0x65, 0x72, 0x76, 0x65, \ + 0x72, 0x20, 0x6c, 0x65, 0x74, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x75, \ + 0x73, 0x65, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x42, 0x65, 0x4f, 0x53, \ + 0x20, 0x2f, 0x20, 0x48, 0x61, 0x69, 0x6b, 0x75, 0x20, 0x63, 0x6f, 0x6d, \ + 0x70, 0x75, 0x74, 0x65, 0x72, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x61, \ + 0x6e, 0x79, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x74, 0x68, 0x65, 0x72, \ + 0x65, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x49, 0x6e, 0x74, 0x65, \ + 0x72, 0x6e, 0x65, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, \ + 0x69, 0x6f, 0x6e, 0x2e, 0x20, 0x20, 0x59, 0x6f, 0x75, 0x20, 0x63, 0x61, \ + 0x6e, 0x20, 0x74, 0x68, 0x69, 0x6e, 0x6b, 0x20, 0x6f, 0x66, 0x20, 0x69, \ + 0x74, 0x20, 0x61, 0x73, 0x20, 0x61, 0x20, 0x72, 0x65, 0x61, 0x6c, 0x6c, \ + 0x79, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x20, 0x6b, 0x65, 0x79, 0x62, 0x6f, \ + 0x61, 0x72, 0x64, 0x2c, 0x20, 0x6d, 0x6f, 0x75, 0x73, 0x65, 0x20, 0x61, \ + 0x6e, 0x64, 0x20, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x20, 0x63, 0x61, 0x62, \ + 0x6c, 0x65, 0x2e, 0x22, 0x2c, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, \ + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x41, 0x20, 0x56, 0x4e, \ + 0x43, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x28, 0x61, 0x76, \ + 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x65, 0x6c, 0x73, 0x65, \ + 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x57, 0x69, \ + 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x2c, 0x20, 0x4d, 0x61, 0x63, 0x2c, 0x20, \ + 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x2c, 0x20, 0x6f, 0x74, 0x68, 0x65, 0x72, \ + 0x73, 0x29, 0x20, 0x73, 0x68, 0x6f, 0x77, 0x73, 0x20, 0x79, 0x6f, 0x75, \ + 0x20, 0x77, 0x68, 0x61, 0x74, 0x27, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x74, \ + 0x68, 0x65, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x20, 0x42, 0x65, \ + 0x4f, 0x53, 0x20, 0x2f, 0x20, 0x48, 0x61, 0x69, 0x6b, 0x75, 0x20, 0x63, \ + 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x72, 0x27, 0x73, 0x20, 0x73, 0x63, \ + 0x72, 0x65, 0x65, 0x6e, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x73, 0x65, 0x6e, \ + 0x64, 0x73, 0x20, 0x6b, 0x65, 0x79, 0x73, 0x74, 0x72, 0x6f, 0x6b, 0x65, \ + 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x6f, 0x75, 0x73, 0x65, 0x20, \ + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6f, 0x76, 0x65, 0x72, \ + 0x20, 0x74, 0x68, 0x65, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, \ + 0x74, 0x2e, 0x20, 0x20, 0x54, 0x68, 0x65, 0x20, 0x56, 0x4e, 0x43, 0x53, \ + 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, \ + 0x72, 0x65, 0x20, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x6f, \ + 0x6e, 0x20, 0x42, 0x65, 0x4f, 0x53, 0x20, 0x6f, 0x72, 0x20, 0x48, 0x61, \ + 0x69, 0x6b, 0x75, 0x20, 0x75, 0x73, 0x65, 0x73, 0x20, 0x74, 0x68, 0x61, \ + 0x74, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x64, 0x61, 0x74, \ + 0x61, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x69, 0x6d, 0x75, 0x6c, 0x61, 0x74, \ + 0x65, 0x20, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x20, 0x70, 0x72, 0x65, \ + 0x73, 0x73, 0x65, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x61, 0x20, 0x66, 0x61, \ + 0x6b, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x20, \ + 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x6f, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, \ + 0x73, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x6e, 0x20, 0x69, 0x6d, 0x61, 0x67, \ + 0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x6d, 0x6f, 0x75, 0x73, 0x65, 0x2e, \ + 0x20, 0x20, 0x49, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x70, 0x70, \ + 0x6f, 0x73, 0x69, 0x74, 0x65, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, \ + 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x56, 0x4e, 0x43, 0x53, 0x65, 0x72, 0x76, \ + 0x65, 0x72, 0x20, 0x73, 0x63, 0x61, 0x6e, 0x73, 0x20, 0x79, 0x6f, 0x75, \ + 0x72, 0x20, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x66, 0x6f, 0x72, \ + 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x2c, 0x20, 0x63, 0x6f, \ + 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, \ + 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x67, \ + 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x20, 0x64, 0x61, 0x74, 0x61, \ + 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x6d, 0x69, \ + 0x74, 0x73, 0x20, 0x69, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, \ + 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x5c, 0x6e, 0x5c, 0x6e, \ + 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x70, 0x6f, \ + 0x72, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x56, 0x4e, 0x43, 0x20, 0x75, 0x73, \ + 0x69, 0x6e, 0x67, 0x20, 0x52, 0x65, 0x61, 0x6c, 0x56, 0x4e, 0x43, 0x27, \ + 0x73, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x34, 0x2e, \ + 0x30, 0x20, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x20, 0x73, 0x6f, 0x75, 0x72, \ + 0x63, 0x65, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x28, 0x77, 0x68, 0x69, \ + 0x63, 0x68, 0x20, 0x68, 0x61, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x65, 0x78, \ + 0x74, 0x72, 0x65, 0x6d, 0x65, 0x6c, 0x79, 0x20, 0x77, 0x65, 0x6c, 0x6c, \ + 0x20, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x20, 0x63, 0x6c, \ + 0x61, 0x73, 0x73, 0x20, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x75, 0x72, \ + 0x65, 0x2c, 0x20, 0x6d, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x74, \ + 0x20, 0x65, 0x61, 0x73, 0x79, 0x20, 0x74, 0x6f, 0x20, 0x64, 0x6f, 0x20, \ + 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x6f, 0x72, 0x74, 0x29, 0x2e, 0x20, \ + 0x20, 0x54, 0x68, 0x65, 0x72, 0x65, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6c, \ + 0x6f, 0x74, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x56, 0x4e, 0x43, 0x20, 0x63, \ + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x74, \ + 0x68, 0x65, 0x72, 0x65, 0x2c, 0x20, 0x62, 0x75, 0x74, 0x20, 0x49, 0x20, \ + 0x63, 0x61, 0x6e, 0x20, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, \ + 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x52, 0x65, 0x61, 0x6c, 0x56, 0x4e, \ + 0x43, 0x20, 0x6f, 0x6e, 0x65, 0x73, 0x20, 0x61, 0x73, 0x20, 0x77, 0x6f, \ + 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x20, 0x76, 0x65, 0x72, 0x79, 0x20, 0x77, \ + 0x65, 0x6c, 0x6c, 0x20, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x57, 0x69, \ + 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x2e, 0x20, 0x20, 0x59, 0x6f, 0x75, 0x20, \ + 0x63, 0x61, 0x6e, 0x20, 0x67, 0x65, 0x74, 0x20, 0x74, 0x68, 0x65, 0x69, \ + 0x72, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x2c, 0x20, 0x73, \ + 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x73, \ + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x61, \ + 0x74, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, \ + 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x76, 0x6e, 0x63, 0x2e, 0x63, 0x6f, 0x6d, \ + 0x2f, 0x22, 0x2c, 0x22, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x4c, \ + 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0x3a, 0x32, 0x32, 0x34, 0x35, 0x37, \ + 0x32, 0x7d, 0x5d, 0x2c, 0x22, 0x70, 0x6b, 0x67, 0x43, 0x61, 0x74, 0x65, \ + 0x67, 0x6f, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x3a, 0x5b, \ + 0x22, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x61, 0x6e, 0x64, \ + 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x22, 0x5d, 0x2c, 0x22, 0x70, \ + 0x6b, 0x67, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x73, 0x68, 0x6f, 0x74, \ + 0x73, 0x22, 0x3a, 0x5b, 0x5d, 0x2c, 0x22, 0x70, 0x6b, 0x67, 0x49, 0x63, \ + 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x22, 0x64, \ + 0x65, 0x72, 0x69, 0x76, 0x65, 0x64, 0x52, 0x61, 0x74, 0x69, 0x6e, 0x67, \ + 0x22, 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x22, 0x70, 0x72, 0x6f, 0x6d, \ + 0x69, 0x6e, 0x65, 0x6e, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x69, \ + 0x6e, 0x67, 0x22, 0x3a, 0x31, 0x30, 0x30, 0x30, 0x2c, 0x22, 0x70, 0x6b, \ + 0x67, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x6c, 0x6f, 0x67, 0x43, 0x6f, \ + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x6e, 0x75, 0x6c, 0x6c, 0x7d, \ + 0x5d, 0x7d, 0x7d, 0x0a, 0x00 \ +} + + +#endif // JSON_SAMPLES_H \ No newline at end of file diff --git a/src/tests/kits/shared/JsonTextWriterTest.cpp b/src/tests/kits/shared/JsonTextWriterTest.cpp new file mode 100644 index 0000000000..99b427f118 --- /dev/null +++ b/src/tests/kits/shared/JsonTextWriterTest.cpp @@ -0,0 +1,207 @@ +/* + * Copyright 2017, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ +#include "JsonTextWriterTest.h" + +#include + +#include +#include + +#include +#include + +#include "JsonSamples.h" + + +#define LOOPS_TEST_OBJECT_A_FOR_PERFORMANCE 100000 + + +using namespace BPrivate; + + +JsonTextWriterTest::JsonTextWriterTest() +{ +} + + +JsonTextWriterTest::~JsonTextWriterTest() +{ +} + + +void +JsonTextWriterTest::TestArrayA() +{ + BMallocIO* outputData = new BMallocIO(); + ObjectDeleter outputDataDeleter(outputData); + BJsonTextWriter writer(outputData); + + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteArrayStart()); + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteString("1234")); + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteInteger(4567)); + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteArrayStart()); + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteString("A")); + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteString("b")); + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteString("C\xc3\xa9zanne")); + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteArrayEnd()); + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteArrayEnd()); + writer.Complete(); + + BString outputString((char*)outputData->Buffer(), + outputData->BufferLength()); + fprintf(stderr, "expected out >%s<\n", JSON_SAMPLE_ARRAY_A_EXPECTED_OUT); + fprintf(stderr, "actual out >%s< (%" B_PRIuSIZE ")\n", outputString.String(), + outputData->BufferLength()); + + CPPUNIT_ASSERT_EQUAL(BString(JSON_SAMPLE_ARRAY_A_EXPECTED_OUT), + outputString); +} + + +void +JsonTextWriterTest::TestObjectA() +{ + BMallocIO* outputData = new BMallocIO(); + ObjectDeleter outputDataDeleter(outputData); + BJsonTextWriter writer(outputData); + + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteObjectStart()); + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteObjectName("weather")); + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteString("raining")); + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteObjectName("humidity")); + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteString("too-high")); + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteObjectName("daysOfWeek")); + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteArrayStart()); + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteString("MON")); + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteString("TUE")); + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteString("WED")); + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteString("THR")); + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteString("FRI")); + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteArrayEnd()); + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteObjectEnd()); + writer.Complete(); + + BString outputString((char*)outputData->Buffer(), + outputData->BufferLength()); + fprintf(stderr, "expected out >%s<\n", JSON_SAMPLE_OBJECT_A_EXPECTED_OUT); + fprintf(stderr, "actual out >%s< (%" B_PRIuSIZE ")\n", outputString.String(), + outputData->BufferLength()); + + CPPUNIT_ASSERT_EQUAL(BString(JSON_SAMPLE_OBJECT_A_EXPECTED_OUT), + outputString); +} + + +void +JsonTextWriterTest::TestObjectAForPerformance() +{ + int i; + + for (i = 0; i < LOOPS_TEST_OBJECT_A_FOR_PERFORMANCE; i++) { + TestObjectA(); + } +} + + +void +JsonTextWriterTest::TestInteger() +{ + BMallocIO* outputData = new BMallocIO(); + ObjectDeleter outputDataDeleter(outputData); + BJsonTextWriter writer(outputData); + static char* expectedOut = JSON_SAMPLE_NUMBER_B_EXPECTED_OUT; + + CPPUNIT_ASSERT_EQUAL(B_OK, + writer.WriteInteger(JSON_SAMPLE_NUMBER_B_LITERAL)); + writer.Complete(); + + BString outputString((char *)outputData->Buffer(), + outputData->BufferLength()); + fprintf(stderr, "expected out >%s<\n", expectedOut); + fprintf(stderr, "actual out >%s< (%" B_PRIuSIZE ")\n", outputString.String(), + outputData->BufferLength()); + + CPPUNIT_ASSERT_EQUAL(BString(expectedOut), + outputString); +} + + +void +JsonTextWriterTest::TestDouble() +{ + BMallocIO* outputData = new BMallocIO(); + ObjectDeleter outputDataDeleter(outputData); + BJsonTextWriter writer(outputData); + static char* expectedOut = "3.142857"; + + CPPUNIT_ASSERT_EQUAL(B_OK, + writer.WriteDouble(JSON_SAMPLE_NUMBER_A_LITERAL)); + writer.Complete(); + + BString outputString((char *)outputData->Buffer(), + outputData->BufferLength()); + fprintf(stderr, "expected out >%s<\n", expectedOut); + fprintf(stderr, "actual out >%s< (%" B_PRIuSIZE ")\n", outputString.String(), + outputData->BufferLength()); + + CPPUNIT_ASSERT_EQUAL(BString(expectedOut), + outputString); +} + + +void +JsonTextWriterTest::TestFalse() +{ + BMallocIO* outputData = new BMallocIO(); + ObjectDeleter outputDataDeleter(outputData); + BJsonTextWriter writer(outputData); + static char* expectedOut = "false"; + + CPPUNIT_ASSERT_EQUAL(B_OK, writer.WriteFalse()); + writer.Complete(); + + BString outputString((char*)outputData->Buffer(), + outputData->BufferLength()); + fprintf(stderr, "expected out >%s<\n", expectedOut); + fprintf(stderr, "actual out >%s< (%" B_PRIuSIZE ")\n", + outputString.String(), outputData->BufferLength()); + + CPPUNIT_ASSERT_EQUAL(BString(expectedOut), + outputString); +} + + +/*static*/ void +JsonTextWriterTest::AddTests(BTestSuite& parent) +{ + CppUnit::TestSuite& suite = *new CppUnit::TestSuite( + "JsonTextWriterTest"); + + suite.addTest(new CppUnit::TestCaller( + "JsonTextWriterTest::TestDouble", + &JsonTextWriterTest::TestDouble)); + + suite.addTest(new CppUnit::TestCaller( + "JsonTextWriterTest::TestInteger", + &JsonTextWriterTest::TestInteger)); + + suite.addTest(new CppUnit::TestCaller( + "JsonTextWriterTest::TestFalse", + &JsonTextWriterTest::TestFalse)); + + suite.addTest(new CppUnit::TestCaller( + "JsonTextWriterTest::TestArrayA", + &JsonTextWriterTest::TestArrayA)); + + suite.addTest(new CppUnit::TestCaller( + "JsonTextWriterTest::TestObjectA", + &JsonTextWriterTest::TestObjectA)); + +// suite.addTest(new CppUnit::TestCaller( +// "JsonTextWriterTest::TestObjectAForPerformance", +// &JsonTextWriterTest::TestObjectAForPerformance)); + + parent.addTest("JsonTextWriterTest", &suite); +} \ No newline at end of file diff --git a/src/tests/kits/shared/JsonTextWriterTest.h b/src/tests/kits/shared/JsonTextWriterTest.h new file mode 100644 index 0000000000..1f47003f9d --- /dev/null +++ b/src/tests/kits/shared/JsonTextWriterTest.h @@ -0,0 +1,35 @@ +/* + * Copyright 2017, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ +#ifndef JSON_WRITER_TEST_H +#define JSON_WRITER_TEST_H + + +#include +#include + +#include + +class Sample; + + +class JsonTextWriterTest : public CppUnit::TestCase { +public: + JsonTextWriterTest(); + virtual ~JsonTextWriterTest(); + + void TestArrayA(); + void TestObjectA(); + void TestObjectAForPerformance(); + void TestDouble(); + void TestInteger(); + void TestFalse(); + + static void AddTests(BTestSuite& suite); +private: + +}; + + +#endif // JSON_WRITER_TEST_H diff --git a/src/tests/kits/shared/JsonToMessageTest.cpp b/src/tests/kits/shared/JsonToMessageTest.cpp new file mode 100644 index 0000000000..348cbd97fc --- /dev/null +++ b/src/tests/kits/shared/JsonToMessageTest.cpp @@ -0,0 +1,292 @@ +/* + * Copyright 2017, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ +#include "JsonToMessageTest.h" + +#include + +#include +#include + +#include "JsonSamples.h" + + +#define LOOPS_TEST_OBJECT_A_FOR_PERFORMANCE 100000 + +using namespace BPrivate; + + +JsonToMessageTest::JsonToMessageTest() +{ +} + + +JsonToMessageTest::~JsonToMessageTest() +{ +} + + +void +JsonToMessageTest::TestArrayA() +{ + BMessage message; + BMessage subMessage; + BString stringValue; + double doubleValue; + + // ---------------------- + BJson::Parse(JSON_SAMPLE_ARRAY_A_IN, message); + // ---------------------- + + CPPUNIT_ASSERT_EQUAL_MESSAGE("!find [0]", B_OK, + message.FindString("0", &stringValue)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("!eq [0]", BString("1234"), stringValue); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("!find [1]", B_OK, + message.FindDouble("1", &doubleValue)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("!eq [1]", 4567, doubleValue); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("!find [2]", B_OK, + message.FindMessage("2", &subMessage)); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("!find [2.0]", B_OK, + subMessage.FindString("0", &stringValue)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("!eq [2.0]", BString("A"), stringValue); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("!find [2.1]", B_OK, + subMessage.FindString("1", &stringValue)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("!eq [2.1]", BString("b"), stringValue); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("!find [2.2]", B_OK, + subMessage.FindString("2", &stringValue)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("!eq [2.2]", BString("C\xc3\xa9zanne"), + stringValue); +} + + +void +JsonToMessageTest::TestArrayB() +{ + BMessage message; + BMessage subMessage; + BMessage subSubMessage; + BString stringValue; + + // ---------------------- + BJson::Parse(JSON_SAMPLE_ARRAY_B_IN, message); + // ---------------------- + + CPPUNIT_ASSERT_EQUAL_MESSAGE("!find [0]", B_OK, + message.FindString("0", &stringValue)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("!eq [0]", BString("Whirinaki"), stringValue); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("!find [1]", B_OK, + message.FindString("1", &stringValue)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("!eq [1]", BString("Wellington"), stringValue); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("!find [2]", B_OK, + message.FindMessage("2", &subMessage)); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("!find [2.0]", B_OK, + subMessage.FindMessage("key", &subSubMessage)); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("!find [2.0.0]", B_OK, + subSubMessage.FindString("0", &stringValue)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("!eq [2.0.0]", BString("value"), stringValue); +} + + +void +JsonToMessageTest::TestObjectA() +{ + BMessage message; + BMessage subMessage; + BString stringValue; + + // ---------------------- + BJson::Parse(JSON_SAMPLE_OBJECT_A_IN, message); + // ---------------------- + + CPPUNIT_ASSERT_EQUAL(B_OK, message.FindString("weather", &stringValue)); + CPPUNIT_ASSERT_EQUAL(BString("raining"), stringValue); + + CPPUNIT_ASSERT_EQUAL(B_OK, message.FindString("humidity", &stringValue)); + CPPUNIT_ASSERT_EQUAL(BString("too-high"), stringValue); + + CPPUNIT_ASSERT_EQUAL(B_OK, message.FindMessage("daysOfWeek", &subMessage)); + + CPPUNIT_ASSERT_EQUAL(B_OK, subMessage.FindString("0", &stringValue)); + CPPUNIT_ASSERT_EQUAL(BString("MON"), stringValue); + + CPPUNIT_ASSERT_EQUAL(B_OK, subMessage.FindString("1", &stringValue)); + CPPUNIT_ASSERT_EQUAL(BString("TUE"), stringValue); + + CPPUNIT_ASSERT_EQUAL(B_OK, subMessage.FindString("2", &stringValue)); + CPPUNIT_ASSERT_EQUAL(BString("WED"), stringValue); + + CPPUNIT_ASSERT_EQUAL(B_OK, subMessage.FindString("3", &stringValue)); + CPPUNIT_ASSERT_EQUAL(BString("THR"), stringValue); + + CPPUNIT_ASSERT_EQUAL(B_OK, subMessage.FindString("4", &stringValue)); + CPPUNIT_ASSERT_EQUAL(BString("FRI"), stringValue); +} + + +/*! This is not a real test, but is a convenient point at which to implement a + performance test. +*/ + +void +JsonToMessageTest::TestObjectAForPerformance() +{ + int i; + + for (i=0; i < LOOPS_TEST_OBJECT_A_FOR_PERFORMANCE; i++) + TestObjectA(); +} + + +void +JsonToMessageTest::TestObjectB() +{ + BMessage message; + bool boolValue; + void *ptrValue; // this is how NULL is represented. + + // ---------------------- + BJson::Parse(JSON_SAMPLE_OBJECT_B_IN, message); + // ---------------------- + + CPPUNIT_ASSERT_EQUAL(B_OK, message.FindBool("testTrue", &boolValue)); + CPPUNIT_ASSERT_EQUAL(true, boolValue); + + CPPUNIT_ASSERT_EQUAL(B_OK, message.FindBool("testFalse", &boolValue)); + CPPUNIT_ASSERT_EQUAL(false, boolValue); + + CPPUNIT_ASSERT_EQUAL(B_OK, message.FindPointer("testNull", &ptrValue)); + CPPUNIT_ASSERT_EQUAL(0, (uint32) ptrValue); +} + + +void +JsonToMessageTest::TestObjectC() +{ + BMessage message; + BMessage subMessage; + BString stringValue; + + // ---------------------- + BJson::Parse(JSON_SAMPLE_OBJECT_C_IN, message); + // ---------------------- + + CPPUNIT_ASSERT_EQUAL(B_OK, message.FindString("key", &stringValue)); + CPPUNIT_ASSERT_EQUAL(BString("value"), stringValue); +} + + +void +JsonToMessageTest::TestUnterminatedObject() +{ + BMessage message; + + // ---------------------- + status_t result = BJson::Parse(JSON_SAMPLE_BROKEN_UNTERMINATED_OBJECT, + message); + // ---------------------- + + CPPUNIT_ASSERT_EQUAL(B_BAD_DATA, result); +} + + +void +JsonToMessageTest::TestUnterminatedArray() +{ + BMessage message; + + // ---------------------- + status_t result = BJson::Parse(JSON_SAMPLE_BROKEN_UNTERMINATED_ARRAY, + message); + // ---------------------- + + CPPUNIT_ASSERT_EQUAL(B_BAD_DATA, result); +} + + +void JsonToMessageTest::TestHaikuDepotFetchBatch() +{ + const unsigned char input[] = JSON_SAMPLE_HDS_FETCH_BATCH_PKGS; + BMessage message; + BMessage resultMessage; + BMessage pkgsMessage; + BMessage pkgMessage1; + double modifyTimestampDouble; + double expectedModifyTimestampDouble = 1488785331631.0; + + // ---------------------- + status_t result = BJson::Parse((char*)input, + message); + // ---------------------- + + CPPUNIT_ASSERT_EQUAL(B_OK, result); + + // this is quite a large test input so a random sample "thing to + // check" is chosen to indicate that the parse was successful. + + CPPUNIT_ASSERT_EQUAL_MESSAGE("result", B_OK, + message.FindMessage("result", &resultMessage)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("pkgs", B_OK, + resultMessage.FindMessage("pkgs", &pkgsMessage)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("1", B_OK, + pkgsMessage.FindMessage("1", &pkgMessage1)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("modifyTimestamp", B_OK, + pkgMessage1.FindDouble("modifyTimestamp", &modifyTimestampDouble)); + + CPPUNIT_ASSERT_EQUAL(expectedModifyTimestampDouble, modifyTimestampDouble); +} + + +/*static*/ void +JsonToMessageTest::AddTests(BTestSuite& parent) +{ + CppUnit::TestSuite& suite = *new CppUnit::TestSuite( + "JsonToMessageTest"); + + suite.addTest(new CppUnit::TestCaller( + "JsonToMessageTest::TestArrayA", + &JsonToMessageTest::TestArrayA)); + + suite.addTest(new CppUnit::TestCaller( + "JsonToMessageTest::TestArrayB", + &JsonToMessageTest::TestArrayB)); + + suite.addTest(new CppUnit::TestCaller( + "JsonToMessageTest::TestObjectA", + &JsonToMessageTest::TestObjectA)); + + suite.addTest(new CppUnit::TestCaller( + "JsonToMessageTest::TestObjectB", + &JsonToMessageTest::TestObjectB)); + + suite.addTest(new CppUnit::TestCaller( + "JsonToMessageTest::TestObjectC", + &JsonToMessageTest::TestObjectC)); + + suite.addTest(new CppUnit::TestCaller( + "JsonToMessageTest::TestUnterminatedObject", + &JsonToMessageTest::TestUnterminatedObject)); + + suite.addTest(new CppUnit::TestCaller( + "JsonToMessageTest::TestUnterminatedArray", + &JsonToMessageTest::TestUnterminatedArray)); + + suite.addTest(new CppUnit::TestCaller( + "JsonToMessageTest::TestHaikuDepotFetchBatch", + &JsonToMessageTest::TestHaikuDepotFetchBatch)); + +// suite.addTest(new CppUnit::TestCaller( +// "JsonToMessageTest::TestObjectAForPerformance", +// &JsonToMessageTest::TestObjectAForPerformance)); + + parent.addTest("JsonToMessageTest", &suite); +} \ No newline at end of file diff --git a/src/tests/kits/shared/JsonToMessageTest.h b/src/tests/kits/shared/JsonToMessageTest.h new file mode 100644 index 0000000000..ebdf33bdb5 --- /dev/null +++ b/src/tests/kits/shared/JsonToMessageTest.h @@ -0,0 +1,38 @@ +/* + * Copyright 2017, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ +#ifndef JSON_TO_MESSAGE_TEST_H +#define JSON_TO_MESSAGE_TEST_H + + +#include +#include + +#include + +class Sample; + + +class JsonToMessageTest : public CppUnit::TestCase { +public: + JsonToMessageTest(); + virtual ~JsonToMessageTest(); + + void TestArrayA(); + void TestArrayB(); + void TestObjectAForPerformance(); + void TestObjectA(); + void TestObjectB(); + void TestObjectC(); + void TestUnterminatedObject(); + void TestUnterminatedArray(); + void TestHaikuDepotFetchBatch(); + + static void AddTests(BTestSuite& suite); +private: + +}; + + +#endif // JSON_TO_MESSAGE_TEST_H diff --git a/src/tests/kits/shared/SharedTestAddon.cpp b/src/tests/kits/shared/SharedTestAddon.cpp index c5130f5a6c..665b8eacdb 100644 --- a/src/tests/kits/shared/SharedTestAddon.cpp +++ b/src/tests/kits/shared/SharedTestAddon.cpp @@ -1,4 +1,5 @@ /* + * Copyright 2017, Andrew Lindesay, apl@lindesay.co.nz * Copyright 2012-2015, Axel Dörfler, axeld@pinc-software.de. * Distributed under the terms of the MIT License. */ @@ -11,6 +12,10 @@ #include "DriverSettingsMessageAdapterTest.h" #include "GeolocationTest.h" #include "NaturalCompareTest.h" +#include "JsonEndToEndTest.h" +#include "JsonErrorHandlingTest.h" +#include "JsonTextWriterTest.h" +#include "JsonToMessageTest.h" BTestSuite* @@ -22,6 +27,10 @@ getTestSuite() DriverSettingsMessageAdapterTest::AddTests(*suite); GeolocationTest::AddTests(*suite); NaturalCompareTest::AddTests(*suite); + JsonEndToEndTest::AddTests(*suite); + JsonErrorHandlingTest::AddTests(*suite); + JsonTextWriterTest::AddTests(*suite); + JsonToMessageTest::AddTests(*suite); return suite; }