queuez_select_character.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. #include <array>
  2. #include <cstdio>
  3. #include "../../../../../core/logging/log.h"
  4. #include "../../queuez/queuez_state_validation.h"
  5. #include "../snapshot/internal.h"
  6. #include "queuez_push_reporting.h"
  7. #include "queuez_update_frame.h"
  8. namespace sunrise::server::bap::encrypted::push {
  9. /**
  10. * Appends the opcode-504 Family-4 move as one increment above the peer's current version.
  11. * @param scratch Lock-owned transform buffers.
  12. * @param select Staged after-image, object definitions, and both character keys.
  13. * @param key Active AES-GCM session key.
  14. * @param nonce Push-direction nonce after the correlated svc-11 response.
  15. * @param response Caller-owned output containing the existing response prefix.
  16. * @param written Existing byte count, updated after the complete push is appended.
  17. * @return True when the 3 object operations and the whole svc-123 frame fit.
  18. */
  19. bool append_select_character_notification(Scratch& scratch,
  20. const queuez::SelectCharacter& select,
  21. std::span<const std::byte, state::kAesKeySize> key,
  22. std::span<const std::byte, state::kBapNonceSize> nonce,
  23. std::span<std::byte> response,
  24. std::size_t& written) noexcept {
  25. snapshot::Prepared prepared{};
  26. if (!snapshot::prepare_selection_move(scratch, select, prepared)) {
  27. return false;
  28. }
  29. const std::size_t objectCount = prepared.family.objects.size();
  30. const std::size_t beforeBytes = written;
  31. if (!queuez_frame::append(scratch,
  32. prepared.family,
  33. prepared.rawClearSize,
  34. prepared.compressedClearSize,
  35. key,
  36. nonce,
  37. response,
  38. written)) {
  39. return false;
  40. }
  41. queuez_report::push(
  42. "select", queuez::kAccountFamilyType, objectCount, written - beforeBytes, 1);
  43. return true;
  44. }
  45. /** Appends the opcode-403 character upsert as one increment above the current peer version. */
  46. bool append_equipment_swap_notification(Scratch& scratch,
  47. const queuez::EquipmentSwap& swap,
  48. const state::PendingEquipmentSwap& mutation,
  49. std::span<const std::byte, state::kAesKeySize> key,
  50. std::span<const std::byte, state::kBapNonceSize> nonce,
  51. std::span<std::byte> response,
  52. std::size_t& written) noexcept {
  53. snapshot::Prepared prepared{};
  54. if (!snapshot::prepare_equipment_swap(scratch, swap, mutation, prepared)) {
  55. return false;
  56. }
  57. const std::size_t objectCount = prepared.family.objects.size();
  58. if (objectCount == 1) {
  59. const middleware::queuez::Object& object = prepared.family.objects.front();
  60. std::array<char, core::log::kLineCapacity> line{};
  61. const int count =
  62. std::snprintf(line.data(),
  63. line.size(),
  64. "ev=equip stage=queuez_object result=ok family_version=%d object_id=%u "
  65. "object_version=0x%llX encoding=%u payload_bytes=%zu",
  66. prepared.family.version,
  67. object.id,
  68. static_cast<unsigned long long>(object.version),
  69. static_cast<unsigned>(object.encoding),
  70. object.payload.size());
  71. if (count > 0) {
  72. core::log::write(core::log::Channel::server,
  73. core::log::Level::debug,
  74. {line.data(), static_cast<std::size_t>(count)});
  75. }
  76. }
  77. const std::size_t beforeBytes = written;
  78. if (!queuez_frame::append(scratch,
  79. prepared.family,
  80. prepared.rawClearSize,
  81. prepared.compressedClearSize,
  82. key,
  83. nonce,
  84. response,
  85. written)) {
  86. return false;
  87. }
  88. queuez_report::push("equip", queuez::kAccountFamilyType, objectCount, written - beforeBytes, 1);
  89. return true;
  90. }
  91. /** Appends one opcode-406 selected-character item-state upsert. */
  92. bool append_item_state_notification(Scratch& scratch,
  93. const queuez::EquipmentSwap& update,
  94. const state::PendingItemState& mutation,
  95. std::span<const std::byte, state::kAesKeySize> key,
  96. std::span<const std::byte, state::kBapNonceSize> nonce,
  97. std::span<std::byte> response,
  98. std::size_t& written) noexcept {
  99. snapshot::Prepared prepared{};
  100. if (!snapshot::prepare_item_state(scratch, update, mutation, prepared)) {
  101. return false;
  102. }
  103. const std::size_t objectCount = prepared.family.objects.size();
  104. const std::size_t beforeBytes = written;
  105. if (objectCount != 1 || prepared.family.objects.front().id != update.characterDefinitionId
  106. || prepared.family.objects.front().version != update.characterSoid
  107. || prepared.family.objects.front().encoding != middleware::queuez::Encoding::oodle
  108. || prepared.family.objects.front().payload.empty()
  109. || !queuez_frame::append(scratch,
  110. prepared.family,
  111. prepared.rawClearSize,
  112. prepared.compressedClearSize,
  113. key,
  114. nonce,
  115. response,
  116. written)) {
  117. return false;
  118. }
  119. queuez_report::push(
  120. "item_state", queuez::kAccountFamilyType, objectCount, written - beforeBytes, 1);
  121. return true;
  122. }
  123. /** Appends one socket item upsert and its charged account balances when required. */
  124. bool append_socket_plug_notification(Scratch& scratch,
  125. const queuez::SocketPlug& socketPlug,
  126. const state::PendingSocketPlug& mutation,
  127. std::span<const std::byte, state::kAesKeySize> key,
  128. std::span<const std::byte, state::kBapNonceSize> nonce,
  129. std::span<std::byte> response,
  130. std::size_t& written) noexcept {
  131. snapshot::Prepared prepared{};
  132. if (!snapshot::prepare_socket_plug(scratch, socketPlug, mutation, prepared)) {
  133. return false;
  134. }
  135. const std::size_t objectCount = prepared.family.objects.size();
  136. const std::size_t expectedObjectCount = socketPlug.updatesAccount ? 2U : 1U;
  137. const std::size_t beforeBytes = written;
  138. if (objectCount != expectedObjectCount
  139. || prepared.family.objects.front().id != socketPlug.itemInstanceDefinitionId
  140. || prepared.family.objects.front().version != socketPlug.targetInstanceSoid
  141. || prepared.family.objects.front().encoding != middleware::queuez::Encoding::oodle
  142. || prepared.family.objects.front().payload.empty()
  143. || (socketPlug.updatesAccount
  144. && (prepared.family.objects[1].id != socketPlug.accountDefinitionId
  145. || prepared.family.objects[1].version != socketPlug.accountSoid
  146. || prepared.family.objects[1].encoding != middleware::queuez::Encoding::oodle
  147. || prepared.family.objects[1].payload.empty()))
  148. || !queuez_frame::append(scratch,
  149. prepared.family,
  150. prepared.rawClearSize,
  151. prepared.compressedClearSize,
  152. key,
  153. nonce,
  154. response,
  155. written)) {
  156. return false;
  157. }
  158. queuez_report::push(
  159. "socket_plug", queuez::kAccountFamilyType, objectCount, written - beforeBytes, 1);
  160. return true;
  161. }
  162. /** Appends one atomic new-instance-before-character Family-4 acquisition update. */
  163. bool append_item_acquisition_notification(Scratch& scratch,
  164. const queuez::ItemAcquisition& acquisition,
  165. const state::PendingItemAcquisition& mutation,
  166. std::span<const std::byte, state::kAesKeySize> key,
  167. std::span<const std::byte, state::kBapNonceSize> nonce,
  168. std::span<std::byte> response,
  169. std::size_t& written) noexcept {
  170. snapshot::Prepared prepared{};
  171. if (!snapshot::prepare_item_acquisition(scratch, acquisition, mutation, prepared)) {
  172. return false;
  173. }
  174. const std::size_t objectCount = prepared.family.objects.size();
  175. const std::size_t beforeBytes = written;
  176. const std::size_t expectedObjectCount = acquisition.updatesAccount ? 3U : 2U;
  177. if (objectCount != expectedObjectCount
  178. || prepared.family.objects[0].id != acquisition.itemInstanceDefinitionId
  179. || prepared.family.objects[0].version != acquisition.acquiredInstanceSoid
  180. || prepared.family.objects[0].encoding != middleware::queuez::Encoding::oodle
  181. || prepared.family.objects[0].payload.empty()
  182. || prepared.family.objects[1].id != acquisition.characterDefinitionId
  183. || prepared.family.objects[1].version != acquisition.characterSoid
  184. || prepared.family.objects[1].encoding != middleware::queuez::Encoding::oodle
  185. || prepared.family.objects[1].payload.empty()
  186. || (acquisition.updatesAccount
  187. && (prepared.family.objects[2].id != acquisition.accountDefinitionId
  188. || prepared.family.objects[2].version != acquisition.accountSoid
  189. || prepared.family.objects[2].encoding != middleware::queuez::Encoding::oodle
  190. || prepared.family.objects[2].payload.empty()))
  191. || !queuez_frame::append(scratch,
  192. prepared.family,
  193. prepared.rawClearSize,
  194. prepared.compressedClearSize,
  195. key,
  196. nonce,
  197. response,
  198. written)) {
  199. return false;
  200. }
  201. queuez_report::push(
  202. "acquire", queuez::kAccountFamilyType, objectCount, written - beforeBytes, 1);
  203. return true;
  204. }
  205. /** Appends an optional new profile resident followed by the full account after-image. */
  206. bool append_profile_item_acquisition_notification(
  207. Scratch& scratch,
  208. const queuez::ProfileItemAcquisition& acquisition,
  209. const state::PendingProfileItemAcquisition& mutation,
  210. std::span<const std::byte, state::kAesKeySize> key,
  211. std::span<const std::byte, state::kBapNonceSize> nonce,
  212. std::span<std::byte> response,
  213. std::size_t& written) noexcept {
  214. snapshot::Prepared prepared{};
  215. if (!snapshot::prepare_profile_item_acquisition(scratch, acquisition, mutation, prepared)) {
  216. return false;
  217. }
  218. const std::size_t objectCount = prepared.family.objects.size();
  219. const std::size_t beforeBytes = written;
  220. const std::size_t expectedObjectCount = acquisition.appendedResident ? 2U : 1U;
  221. const std::size_t accountIndex = acquisition.appendedResident ? 1U : 0U;
  222. if (prepared.family.type != queuez::kAccountFamilyType
  223. || prepared.family.rootSoid != acquisition.after.family4RootSoid
  224. || prepared.family.version != acquisition.after.family4Version || prepared.family.flags != 0
  225. || objectCount != expectedObjectCount || accountIndex >= objectCount
  226. || prepared.family.objects[accountIndex].id != acquisition.accountDefinitionId
  227. || prepared.family.objects[accountIndex].version != acquisition.accountSoid
  228. || prepared.family.objects[accountIndex].encoding != middleware::queuez::Encoding::oodle
  229. || prepared.family.objects[accountIndex].payload.empty()
  230. || (acquisition.appendedResident
  231. && (prepared.family.objects.front().id != acquisition.itemInstanceDefinitionId
  232. || prepared.family.objects.front().version != acquisition.acquiredInstanceSoid
  233. || prepared.family.objects.front().encoding != middleware::queuez::Encoding::oodle
  234. || prepared.family.objects.front().payload.empty()))
  235. || !queuez_frame::append(scratch,
  236. prepared.family,
  237. prepared.rawClearSize,
  238. prepared.compressedClearSize,
  239. key,
  240. nonce,
  241. response,
  242. written)) {
  243. return false;
  244. }
  245. queuez_report::push(
  246. "profile_acquire", queuez::kAccountFamilyType, objectCount, written - beforeBytes, 1);
  247. return true;
  248. }
  249. /** Appends one atomic dismantle update, including any account-wide material payout. */
  250. bool append_item_dismantle_notification(Scratch& scratch,
  251. const queuez::ItemDismantle& dismantle,
  252. const state::PendingItemDismantle& mutation,
  253. std::span<const std::byte, state::kAesKeySize> key,
  254. std::span<const std::byte, state::kBapNonceSize> nonce,
  255. std::span<std::byte> response,
  256. std::size_t& written) noexcept {
  257. snapshot::Prepared prepared{};
  258. if (!snapshot::prepare_item_dismantle(scratch, dismantle, mutation, prepared)) {
  259. return false;
  260. }
  261. const std::size_t objectCount = prepared.family.objects.size();
  262. const std::size_t beforeBytes = written;
  263. const std::size_t expectedObjectCount = dismantle.updatesAccount ? 3U : 2U;
  264. if (objectCount != expectedObjectCount
  265. || prepared.family.objects[0].id != dismantle.characterDefinitionId
  266. || prepared.family.objects[0].version != dismantle.characterSoid
  267. || prepared.family.objects[1].id != dismantle.itemInstanceDefinitionId
  268. || prepared.family.objects[1].version != dismantle.dismantledInstanceSoid
  269. || prepared.family.objects[1].encoding != middleware::queuez::Encoding::oodle
  270. || !prepared.family.objects[1].payload.empty()
  271. || (dismantle.updatesAccount
  272. && (prepared.family.objects[2].id != dismantle.accountDefinitionId
  273. || prepared.family.objects[2].version != dismantle.accountSoid
  274. || prepared.family.objects[2].encoding != middleware::queuez::Encoding::oodle
  275. || prepared.family.objects[2].payload.empty()))
  276. || !queuez_frame::append(scratch,
  277. prepared.family,
  278. prepared.rawClearSize,
  279. prepared.compressedClearSize,
  280. key,
  281. nonce,
  282. response,
  283. written)) {
  284. return false;
  285. }
  286. queuez_report::push(
  287. "dismantle", queuez::kAccountFamilyType, objectCount, written - beforeBytes, 1);
  288. return true;
  289. }
  290. } // namespace sunrise::server::bap::encrypted::push