/** * Copyright (C) 2014 Serval Project Inc. * * This file is part of Serval Software (http://www.servalproject.org) * * Serval Software is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This source code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this source code; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.servalproject.servaldna.rhizome; import java.util.Map; import java.util.HashMap; import java.util.HashSet; import java.io.UnsupportedEncodingException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import org.servalproject.servaldna.AbstractId; import org.servalproject.servaldna.SubscriberId; import org.servalproject.servaldna.BundleId; import org.servalproject.servaldna.FileHash; import org.servalproject.servaldna.BundleKey; public class RhizomeIncompleteManifest { public BundleId id; public Long version; public Long filesize; public FileHash filehash; public SubscriberId sender; public SubscriberId recipient; public BundleKey BK; public Long tail; public Integer crypt; public Long date; public String service; public String name; private HashMap extraFields; public RhizomeIncompleteManifest() { this.extraFields = new HashMap(); } @SuppressWarnings("unchecked") public RhizomeIncompleteManifest(RhizomeManifest m) { this.id = m.id; this.version = m.version; this.filesize = m.filesize; this.filehash = m.filehash; this.sender = m.sender; this.recipient = m.recipient; this.BK = m.BK; this.crypt = m.crypt; this.tail = m.tail; this.date = m.date; this.service = m.service; this.name = m.name; this.extraFields = (HashMap) m.extraFields.clone(); // unchecked cast } /** Return the Rhizome manifest in its text format representation. * * @author Andrew Bettison */ public void toTextFormat(OutputStream os) throws IOException { OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8"); if (id != null) osw.write("id=" + id.toHex() + "\n"); if (version != null) osw.write("version=" + version + "\n"); if (filesize != null) osw.write("filesize=" + filesize + "\n"); if (filehash != null) osw.write("filehash=" + filehash.toHex() + "\n"); if (sender != null) osw.write("sender=" + sender.toHex() + "\n"); if (recipient != null) osw.write("recipient=" + recipient.toHex() + "\n"); if (BK != null) osw.write("BK=" + BK.toHex() + "\n"); if (crypt != null) osw.write("crypt=" + crypt + "\n"); if (tail != null) osw.write("tail=" + tail + "\n"); if (date != null) osw.write("date=" + date + "\n"); if (service != null) osw.write("service=" + service + "\n"); if (name != null) osw.write("name=" + name + "\n"); for (Map.Entry e: extraFields.entrySet()) osw.write(e.getKey() + "=" + e.getValue() + "\n"); osw.flush(); } /** Construct a Rhizome manifest from its text format representation. * * @author Andrew Bettison */ public static RhizomeIncompleteManifest fromTextFormat(byte[] bytes) throws RhizomeManifestParseException { RhizomeIncompleteManifest m = new RhizomeIncompleteManifest(); try { m.parseTextFormat(new ByteArrayInputStream(bytes, 0, bytes.length)); } catch (IOException e) { } return m; } /** Construct a Rhizome manifest from its text format representation. * * @author Andrew Bettison */ public static RhizomeIncompleteManifest fromTextFormat(byte[] bytes, int off, int len) throws RhizomeManifestParseException { RhizomeIncompleteManifest m = new RhizomeIncompleteManifest(); try { m.parseTextFormat(new ByteArrayInputStream(bytes, off, len)); } catch (IOException e) { } return m; } /** Convenience method: construct a Rhizome manifest from all the bytes read from a given * InputStream. * * @author Andrew Bettison */ static public RhizomeIncompleteManifest fromTextFormat(InputStream in) throws IOException, RhizomeManifestParseException { RhizomeIncompleteManifest m = new RhizomeIncompleteManifest(); m.parseTextFormat(in); return m; } /** Fill in manifest fields from a text format representation. * * @author Andrew Bettison */ public void parseTextFormat(InputStream in) throws IOException, RhizomeManifestParseException { try { InputStreamReader inr = new InputStreamReader(in, "UTF-8"); int pos = 0; int lnum = 1; int eq = -1; StringBuilder line = new StringBuilder(); while (true) { int c = inr.read(); if (c != -1 && c != '\n') { if (eq == -1 && c == '=') eq = line.length(); line.append((char)c); } else if (line.length() == 0) break; else if (eq < 1) throw new RhizomeManifestParseException("malformed (missing '=') at line " + lnum + ": " + line); else { String fieldName = line.substring(0, eq); String fieldValue = line.substring(eq + 1); if (!isFieldNameFirstChar(fieldName.charAt(0))) throw new RhizomeManifestParseException("invalid field name at line " + lnum + ": " + line); for (int i = 1; i < fieldName.length(); ++i) if (!isFieldNameChar(fieldName.charAt(i))) throw new RhizomeManifestParseException("invalid field name at line " + lnum + ": " + line); try { if (fieldName.equals("id")) this.id = parseField(this.id, new BundleId(fieldValue)); else if (fieldName.equals("version")) this.version = parseField(this.version, parseUnsignedLong(fieldValue)); else if (fieldName.equals("filesize")) this.filesize = parseField(this.filesize, parseUnsignedLong(fieldValue)); else if (fieldName.equals("filehash")) this.filehash = parseField(this.filehash, new FileHash(fieldValue)); else if (fieldName.equals("sender")) this.sender = parseField(this.sender, new SubscriberId(fieldValue)); else if (fieldName.equals("recipient")) this.recipient = parseField(this.recipient, new SubscriberId(fieldValue)); else if (fieldName.equals("BK")) this.BK = parseField(this.BK, new BundleKey(fieldValue)); else if (fieldName.equals("crypt")) this.crypt = parseField(this.crypt, Integer.parseInt(fieldValue)); else if (fieldName.equals("tail")) this.tail = parseField(this.tail, parseUnsignedLong(fieldValue)); else if (fieldName.equals("date")) this.date = parseField(this.date, parseUnsignedLong(fieldValue)); else if (fieldName.equals("service")) this.service = parseField(this.service, fieldValue); else if (fieldName.equals("name")) this.name = parseField(this.name, fieldValue); else if (this.extraFields.containsKey(fieldName)) throw new RhizomeManifestParseException("duplicate field"); else this.extraFields.put(fieldName, fieldValue); } catch (RhizomeManifestParseException e) { throw new RhizomeManifestParseException(e.getMessage() + " at line " + lnum + ": " + line); } catch (AbstractId.InvalidHexException e) { throw new RhizomeManifestParseException("invalid value at line " + lnum + ": " + line, e); } catch (NumberFormatException e) { throw new RhizomeManifestParseException("invalid value at line " + lnum + ": " + line, e); } line.setLength(0); eq = -1; ++lnum; } } if (line.length() > 0) throw new RhizomeManifestParseException("malformed (missing newline) at line " + lnum + ": " + line); } catch (UnsupportedEncodingException e) { throw new RhizomeManifestParseException(e); } } static private T parseField(T currentValue, T newValue) throws RhizomeManifestParseException { if (currentValue == null) return newValue; if (!currentValue.equals(newValue)) throw new RhizomeManifestParseException("duplicate field"); return currentValue; } private static boolean isFieldNameFirstChar(char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); } private static boolean isFieldNameChar(char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'); } private static Long parseUnsignedLong(String text) throws NumberFormatException { Long value = Long.valueOf(text); if (value < 0) throw new NumberFormatException("negative value not allowed"); return value; } }