You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ChunkFormatter.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. /*
  2. * Copyright (C) 2011, Google Inc.
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.storage.dht;
  44. import java.security.MessageDigest;
  45. import java.util.ArrayList;
  46. import java.util.Collections;
  47. import java.util.Comparator;
  48. import java.util.HashMap;
  49. import java.util.List;
  50. import java.util.Map;
  51. import java.util.zip.Deflater;
  52. import org.eclipse.jgit.generated.storage.dht.proto.GitStore;
  53. import org.eclipse.jgit.generated.storage.dht.proto.GitStore.ChunkMeta;
  54. import org.eclipse.jgit.generated.storage.dht.proto.GitStore.ChunkMeta.BaseChunk;
  55. import org.eclipse.jgit.generated.storage.dht.proto.GitStore.ObjectInfo.ObjectType;
  56. import org.eclipse.jgit.lib.AnyObjectId;
  57. import org.eclipse.jgit.lib.Constants;
  58. import org.eclipse.jgit.lib.ObjectId;
  59. import org.eclipse.jgit.storage.dht.spi.Database;
  60. import org.eclipse.jgit.storage.dht.spi.WriteBuffer;
  61. import org.eclipse.jgit.transport.PackedObjectInfo;
  62. import org.eclipse.jgit.util.NB;
  63. /**
  64. * Formats one {@link PackChunk} for storage in the DHT.
  65. * <p>
  66. * Each formatter instance can be used only once.
  67. */
  68. class ChunkFormatter {
  69. static final int TRAILER_SIZE = 4;
  70. private final RepositoryKey repo;
  71. private final DhtInserterOptions options;
  72. private final byte[] varIntBuf;
  73. private final int maxObjects;
  74. private Map<ChunkKey, BaseChunkInfo> baseChunks;
  75. private List<StoredObject> objectList;
  76. private byte[] chunkData;
  77. private int ptr;
  78. private int mark;
  79. private int currentObjectType;
  80. private BaseChunkInfo currentObjectBase;
  81. private PackChunk.Members builder;
  82. private GitStore.ChunkInfo.Source source;
  83. private boolean fragment;
  84. private int objectType;
  85. private int objectsTotal, objectsWhole, objectsRefDelta, objectsOfsDelta;
  86. private ChunkInfo chunkInfo;
  87. ChunkFormatter(RepositoryKey repo, DhtInserterOptions options) {
  88. this.repo = repo;
  89. this.options = options;
  90. this.varIntBuf = new byte[32];
  91. this.chunkData = new byte[options.getChunkSize()];
  92. this.maxObjects = options.getMaxObjectCount();
  93. this.objectType = -1;
  94. }
  95. void setSource(GitStore.ChunkInfo.Source src) {
  96. source = src;
  97. }
  98. void setObjectType(int type) {
  99. objectType = type;
  100. }
  101. void setFragment() {
  102. fragment = true;
  103. }
  104. ChunkKey getChunkKey() {
  105. return getChunkInfo().getChunkKey();
  106. }
  107. ChunkInfo getChunkInfo() {
  108. return chunkInfo;
  109. }
  110. ChunkMeta getChunkMeta() {
  111. return builder.getMeta();
  112. }
  113. PackChunk getPackChunk() throws DhtException {
  114. return builder.build();
  115. }
  116. void setChunkIndex(List<PackedObjectInfo> objs) {
  117. builder.setChunkIndex(ChunkIndex.create(objs));
  118. }
  119. ChunkKey end(MessageDigest md) {
  120. if (md == null)
  121. md = Constants.newMessageDigest();
  122. // Embed a small amount of randomness into the chunk content,
  123. // and thus impact its name. This prevents malicious clients from
  124. // being able to predict what a chunk is called, which keeps them
  125. // from replacing an existing chunk.
  126. //
  127. chunkData = cloneArray(chunkData, ptr + TRAILER_SIZE);
  128. NB.encodeInt32(chunkData, ptr, options.nextChunkSalt());
  129. ptr += 4;
  130. md.update(chunkData, 0, ptr);
  131. ChunkKey key = ChunkKey.create(repo, ObjectId.fromRaw(md.digest()));
  132. GitStore.ChunkInfo.Builder info = GitStore.ChunkInfo.newBuilder();
  133. info.setSource(source);
  134. info.setObjectType(GitStore.ChunkInfo.ObjectType.valueOf(objectType));
  135. if (fragment)
  136. info.setIsFragment(true);
  137. info.setChunkSize(chunkData.length);
  138. GitStore.ChunkInfo.ObjectCounts.Builder cnts = info.getObjectCountsBuilder();
  139. cnts.setTotal(objectsTotal);
  140. if (objectsWhole > 0)
  141. cnts.setWhole(objectsWhole);
  142. if (objectsRefDelta > 0)
  143. cnts.setRefDelta(objectsRefDelta);
  144. if (objectsOfsDelta > 0)
  145. cnts.setOfsDelta(objectsOfsDelta);
  146. builder = new PackChunk.Members();
  147. builder.setChunkKey(key);
  148. builder.setChunkData(chunkData);
  149. if (baseChunks != null) {
  150. List<BaseChunk> list = new ArrayList<BaseChunk>(baseChunks.size());
  151. for (BaseChunkInfo b : baseChunks.values()) {
  152. if (0 < b.useCount) {
  153. BaseChunk.Builder c = BaseChunk.newBuilder();
  154. c.setRelativeStart(b.relativeStart);
  155. c.setChunkKey(b.key.asString());
  156. list.add(c.build());
  157. }
  158. }
  159. Collections.sort(list, new Comparator<BaseChunk>() {
  160. public int compare(BaseChunk a, BaseChunk b) {
  161. return Long.signum(a.getRelativeStart()
  162. - b.getRelativeStart());
  163. }
  164. });
  165. ChunkMeta.Builder b = ChunkMeta.newBuilder();
  166. b.addAllBaseChunk(list);
  167. ChunkMeta meta = b.build();
  168. builder.setMeta(meta);
  169. info.setMetaSize(meta.getSerializedSize());
  170. }
  171. if (objectList != null && !objectList.isEmpty()) {
  172. byte[] index = ChunkIndex.create(objectList);
  173. builder.setChunkIndex(index);
  174. info.setIndexSize(index.length);
  175. }
  176. chunkInfo = new ChunkInfo(key, info.build());
  177. return getChunkKey();
  178. }
  179. /**
  180. * Safely put the chunk to the database.
  181. * <p>
  182. * This method is slow. It first puts the chunk info, waits for success,
  183. * then puts the chunk itself, waits for success, and finally queues up the
  184. * object index with its chunk links in the supplied buffer.
  185. *
  186. * @param db
  187. * @param dbWriteBuffer
  188. * @throws DhtException
  189. */
  190. void safePut(Database db, WriteBuffer dbWriteBuffer) throws DhtException {
  191. WriteBuffer chunkBuf = db.newWriteBuffer();
  192. db.repository().put(repo, getChunkInfo(), chunkBuf);
  193. chunkBuf.flush();
  194. db.chunk().put(builder, chunkBuf);
  195. chunkBuf.flush();
  196. linkObjects(db, dbWriteBuffer);
  197. }
  198. void unsafePut(Database db, WriteBuffer dbWriteBuffer) throws DhtException {
  199. db.repository().put(repo, getChunkInfo(), dbWriteBuffer);
  200. db.chunk().put(builder, dbWriteBuffer);
  201. linkObjects(db, dbWriteBuffer);
  202. }
  203. private void linkObjects(Database db, WriteBuffer dbWriteBuffer)
  204. throws DhtException {
  205. if (objectList != null && !objectList.isEmpty()) {
  206. for (StoredObject obj : objectList) {
  207. db.objectIndex().add(ObjectIndexKey.create(repo, obj),
  208. obj.link(getChunkKey()), dbWriteBuffer);
  209. }
  210. }
  211. }
  212. boolean whole(Deflater def, int type, byte[] data, int off, final int size,
  213. ObjectId objId) {
  214. if (free() < 10 || maxObjects <= objectsTotal)
  215. return false;
  216. header(type, size);
  217. objectsWhole++;
  218. currentObjectType = type;
  219. int endOfHeader = ptr;
  220. def.setInput(data, off, size);
  221. def.finish();
  222. do {
  223. int left = free();
  224. if (left == 0) {
  225. rollback();
  226. return false;
  227. }
  228. int n = def.deflate(chunkData, ptr, left);
  229. if (n == 0) {
  230. rollback();
  231. return false;
  232. }
  233. ptr += n;
  234. } while (!def.finished());
  235. if (objectList == null)
  236. objectList = new ArrayList<StoredObject>();
  237. final int packedSize = ptr - endOfHeader;
  238. objectList.add(new StoredObject(objId, type, mark, packedSize, size));
  239. if (objectType < 0)
  240. objectType = type;
  241. else if (objectType != type)
  242. objectType = ChunkInfo.OBJ_MIXED;
  243. return true;
  244. }
  245. boolean whole(int type, long inflatedSize) {
  246. if (free() < 10 || maxObjects <= objectsTotal)
  247. return false;
  248. header(type, inflatedSize);
  249. objectsWhole++;
  250. currentObjectType = type;
  251. return true;
  252. }
  253. boolean ofsDelta(long inflatedSize, long negativeOffset) {
  254. final int ofsPtr = encodeVarInt(negativeOffset);
  255. final int ofsLen = varIntBuf.length - ofsPtr;
  256. if (free() < 10 + ofsLen || maxObjects <= objectsTotal)
  257. return false;
  258. header(Constants.OBJ_OFS_DELTA, inflatedSize);
  259. objectsOfsDelta++;
  260. currentObjectType = Constants.OBJ_OFS_DELTA;
  261. currentObjectBase = null;
  262. if (append(varIntBuf, ofsPtr, ofsLen))
  263. return true;
  264. rollback();
  265. return false;
  266. }
  267. boolean refDelta(long inflatedSize, AnyObjectId baseId) {
  268. if (free() < 30 || maxObjects <= objectsTotal)
  269. return false;
  270. header(Constants.OBJ_REF_DELTA, inflatedSize);
  271. objectsRefDelta++;
  272. currentObjectType = Constants.OBJ_REF_DELTA;
  273. baseId.copyRawTo(chunkData, ptr);
  274. ptr += 20;
  275. return true;
  276. }
  277. void useBaseChunk(long relativeStart, ChunkKey baseChunkKey) {
  278. if (baseChunks == null)
  279. baseChunks = new HashMap<ChunkKey, BaseChunkInfo>();
  280. BaseChunkInfo base = baseChunks.get(baseChunkKey);
  281. if (base == null) {
  282. base = new BaseChunkInfo(relativeStart, baseChunkKey);
  283. baseChunks.put(baseChunkKey, base);
  284. }
  285. base.useCount++;
  286. currentObjectBase = base;
  287. }
  288. void appendDeflateOutput(Deflater def) {
  289. while (!def.finished()) {
  290. int left = free();
  291. if (left == 0)
  292. return;
  293. int n = def.deflate(chunkData, ptr, left);
  294. if (n == 0)
  295. return;
  296. ptr += n;
  297. }
  298. }
  299. boolean append(byte[] data, int off, int len) {
  300. if (free() < len)
  301. return false;
  302. System.arraycopy(data, off, chunkData, ptr, len);
  303. ptr += len;
  304. return true;
  305. }
  306. boolean isEmpty() {
  307. return ptr == 0;
  308. }
  309. int getObjectCount() {
  310. return objectsTotal;
  311. }
  312. int position() {
  313. return ptr;
  314. }
  315. int size() {
  316. return ptr;
  317. }
  318. int free() {
  319. return (chunkData.length - TRAILER_SIZE) - ptr;
  320. }
  321. byte[] getRawChunkDataArray() {
  322. return chunkData;
  323. }
  324. int getCurrentObjectType() {
  325. return currentObjectType;
  326. }
  327. void rollback() {
  328. ptr = mark;
  329. adjustObjectCount(-1, currentObjectType);
  330. }
  331. void adjustObjectCount(int delta, int type) {
  332. objectsTotal += delta;
  333. switch (type) {
  334. case Constants.OBJ_COMMIT:
  335. case Constants.OBJ_TREE:
  336. case Constants.OBJ_BLOB:
  337. case Constants.OBJ_TAG:
  338. objectsWhole += delta;
  339. break;
  340. case Constants.OBJ_OFS_DELTA:
  341. objectsOfsDelta += delta;
  342. if (currentObjectBase != null && --currentObjectBase.useCount == 0)
  343. baseChunks.remove(currentObjectBase.key);
  344. currentObjectBase = null;
  345. break;
  346. case Constants.OBJ_REF_DELTA:
  347. objectsRefDelta += delta;
  348. break;
  349. }
  350. }
  351. private void header(int type, long inflatedSize) {
  352. mark = ptr;
  353. objectsTotal++;
  354. long nextLength = inflatedSize >>> 4;
  355. chunkData[ptr++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (type << 4) | (inflatedSize & 0x0F));
  356. inflatedSize = nextLength;
  357. while (inflatedSize > 0) {
  358. nextLength >>>= 7;
  359. chunkData[ptr++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (inflatedSize & 0x7F));
  360. inflatedSize = nextLength;
  361. }
  362. }
  363. private int encodeVarInt(long value) {
  364. int n = varIntBuf.length - 1;
  365. varIntBuf[n] = (byte) (value & 0x7F);
  366. while ((value >>= 7) > 0)
  367. varIntBuf[--n] = (byte) (0x80 | (--value & 0x7F));
  368. return n;
  369. }
  370. private static byte[] cloneArray(byte[] src, int len) {
  371. byte[] dst = new byte[len];
  372. System.arraycopy(src, 0, dst, 0, len);
  373. return dst;
  374. }
  375. private static class BaseChunkInfo {
  376. final long relativeStart;
  377. final ChunkKey key;
  378. int useCount;
  379. BaseChunkInfo(long relativeStart, ChunkKey key) {
  380. this.relativeStart = relativeStart;
  381. this.key = key;
  382. }
  383. }
  384. private static class StoredObject extends PackedObjectInfo {
  385. private final int type;
  386. private final int packed;
  387. private final int inflated;
  388. StoredObject(AnyObjectId id, int type, int offset, int packed, int size) {
  389. super(id);
  390. setOffset(offset);
  391. this.type = type;
  392. this.packed = packed;
  393. this.inflated = size;
  394. }
  395. ObjectInfo link(ChunkKey key) {
  396. GitStore.ObjectInfo.Builder b = GitStore.ObjectInfo.newBuilder();
  397. b.setObjectType(ObjectType.valueOf(type));
  398. b.setOffset((int) getOffset());
  399. b.setPackedSize(packed);
  400. b.setInflatedSize(inflated);
  401. return new ObjectInfo(key, b.build());
  402. }
  403. }
  404. }