/** * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.waveprotocol.wave.examples.fedone.waveserver; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; import org.jivesoftware.util.Base64; import org.waveprotocol.wave.crypto.SignatureException; import org.waveprotocol.wave.crypto.UnknownSignerException; import org.waveprotocol.wave.examples.fedone.common.HashedVersion; import org.waveprotocol.wave.examples.fedone.common.WaveletOperationSerializer; import static org.waveprotocol.wave.examples.fedone.common.WaveletOperationSerializer.deserialize; import org.waveprotocol.wave.examples.fedone.util.Log; import org.waveprotocol.wave.examples.fedone.waveserver.CertificateManager.SignerInfoPrefetchResultListener; import org.waveprotocol.wave.federation.FederationErrorProto.FederationError; import org.waveprotocol.wave.federation.Proto.ProtocolAppliedWaveletDelta; import org.waveprotocol.wave.federation.Proto.ProtocolHashedVersion; import org.waveprotocol.wave.federation.Proto.ProtocolSignature; import org.waveprotocol.wave.federation.Proto.ProtocolSignedDelta; import org.waveprotocol.wave.federation.Proto.ProtocolSignerInfo; import org.waveprotocol.wave.federation.Proto.ProtocolWaveletDelta; import org.waveprotocol.wave.model.id.WaveletName; import org.waveprotocol.wave.model.operation.OperationException; import org.waveprotocol.wave.model.operation.wave.WaveletDelta; import org.waveprotocol.wave.model.operation.wave.WaveletOperation; import org.waveprotocol.wave.model.util.Pair; import org.waveprotocol.wave.waveserver.WaveletFederationProvider; import org.waveprotocol.wave.waveserver.WaveletFederationProvider.HistoryResponseListener; import java.util.LinkedList; import java.util.List; import java.util.NavigableSet; import java.util.concurrent.atomic.AtomicInteger; /** * Remote wavelets differ from local ones in that deltas are not submitted for OT, * rather they are updated when a remote wave service provider has applied and sent * a delta. * * */ class RemoteWaveletContainerImpl extends WaveletContainerImpl implements RemoteWaveletContainer { private static final Log LOG = Log.get(RemoteWaveletContainerImpl.class); /** * Stores all pending deltas for this wavelet, whos insertions would cause * discontinuous blocks of deltas. This must only be accessed under writeLock. */ private final NavigableSet<ByteStringMessage<ProtocolAppliedWaveletDelta>> pendingDeltas = Sets.newTreeSet(appliedDeltaComparator); /** * Create a new RemoteWaveletContainerImpl. Just pass through to the parent * constructor. */ public RemoteWaveletContainerImpl(WaveletName waveletName) { super(waveletName); state = State.LOADING; } /** Convenience method to assert state. */ protected void assertStateOkOrLoading() throws WaveletStateException { if (state != State.LOADING) { assertStateOk(); } } @Override public boolean committed(ProtocolHashedVersion hashedVersion) throws WaveletStateException { acquireWriteLock(); try { assertStateOkOrLoading(); lastCommittedVersion = hashedVersion; // Pass to clients iff our known version is here or greater. return currentVersion.getVersion() >= hashedVersion.getVersion(); } finally { releaseWriteLock(); } } @Override public void update(final List<ByteStringMessage<ProtocolAppliedWaveletDelta>> appliedDeltas, final String domain, final WaveletFederationProvider federationProvider, final CertificateManager certificateManager, final RemoteWaveletDeltaCallback deltaCallback) throws WaveServerException { LOG.info("Got update: " + appliedDeltas); // Fetch any signer info that we don't already have final AtomicInteger numSignerInfoPrefetched = new AtomicInteger(1); // extra 1 for sentinel SignerInfoPrefetchResultListener prefetchListener = new SignerInfoPrefetchResultListener() { @Override public void onFailure(FederationError error) { LOG.warning("Signer info prefetch failed: " + error); countDown(); } @Override public void onSuccess(ProtocolSignerInfo signerInfo) { LOG.info("Signer info prefetch success for " + signerInfo.getDomain()); countDown(); } private void countDown() { if (numSignerInfoPrefetched.decrementAndGet() == 0) { try { internalUpdate(appliedDeltas, domain, federationProvider, certificateManager, deltaCallback); } catch (WaveServerException e) { LOG.warning("Wave server exception when running update", e); deltaCallback.onFailure(e.getMessage()); } } } }; for (ByteStringMessage<ProtocolAppliedWaveletDelta> appliedDelta : appliedDeltas) { ProtocolSignedDelta toVerify = appliedDelta.getMessage().getSignedOriginalDelta(); for (ProtocolSignature sig : toVerify.getSignatureList()) { if (certificateManager.retrieveSignerInfo(sig.getSignerId()) == null) { LOG.info("Fetching signer info " + Base64.encodeBytes(sig.getSignerId().toByteArray())); numSignerInfoPrefetched.incrementAndGet(); certificateManager.prefetchDeltaSignerInfo(federationProvider, sig.getSignerId(), waveletName, AppliedDeltaUtil.getHashedVersionAppliedAt(appliedDelta.getMessage()), prefetchListener); } } } // If we didn't fetch any signer info, run internalUpdate immediately if (numSignerInfoPrefetched.decrementAndGet() == 0) { internalUpdate(appliedDeltas, domain, federationProvider, certificateManager, deltaCallback); } } /** * Called by {@link #update} when all signer info is guaranteed to be available. * * @param appliedDeltas * @param domain * @param federationProvider * @param certificateManager * @param deltaCallback * @throws WaveServerException */ private void internalUpdate(List<ByteStringMessage<ProtocolAppliedWaveletDelta>> appliedDeltas, final String domain, final WaveletFederationProvider federationProvider, final CertificateManager certificateManager, final RemoteWaveletDeltaCallback deltaCallback) throws WaveServerException { LOG.info("Passed signer info check, now applying all " + appliedDeltas.size() + " deltas"); acquireWriteLock(); try { assertStateOkOrLoading(); List<ProtocolWaveletDelta> result = new LinkedList<ProtocolWaveletDelta>(); ProtocolHashedVersion expectedVersion = WaveletOperationSerializer.serialize(currentVersion); boolean haveRequestedHistory = false; // Verify signatures of all deltas for (ByteStringMessage<ProtocolAppliedWaveletDelta> appliedDelta : appliedDeltas) { try { certificateManager.verifyDelta(appliedDelta.getMessage().getSignedOriginalDelta()); } catch (SignatureException e) { LOG.warning("Verification failure for " + domain + " incoming " + waveletName, e); throw new WaveServerException("Verification failure", e); } catch (UnknownSignerException e) { LOG.severe("Unknown signer for " + domain + " incoming " + waveletName + ", this is BAD! We were supposed to have prefetched it!", e); throw new WaveServerException("Unknown signer", e); } } // Insert all available deltas into pendingDeltas. for (ByteStringMessage<ProtocolAppliedWaveletDelta> appliedDelta : appliedDeltas) { LOG.info("Delta incoming: " + appliedDelta); ProtocolWaveletDelta actualDelta; try { actualDelta = ProtocolWaveletDelta.parseFrom(appliedDelta.getMessage().getSignedOriginalDelta().getDelta()); LOG.info("actual delta: " + actualDelta); } catch (InvalidProtocolBufferException e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } pendingDeltas.add(appliedDelta); } // Traverse pendingDeltas while we have any to process. while (pendingDeltas.size() > 0) { ByteStringMessage<ProtocolAppliedWaveletDelta> appliedDelta = pendingDeltas.first(); ProtocolHashedVersion appliedAt; try { appliedAt = getVersionAppliedAt(appliedDelta.getMessage()); } catch (InvalidProtocolBufferException e) { setState(State.CORRUPTED); throw new WaveServerException( "Authoritative server sent delta with badly formed original wavelet delta", e); } // If we don't have the right version it implies there is a history we need, so set up a // callback to request it and fall out of this update if (appliedAt.getVersion() > expectedVersion.getVersion()) { LOG.info("Missing history from " + expectedVersion.getVersion() + "-" + appliedAt.getVersion() + ", requesting from upstream for " + waveletName); if (federationProvider != null) { // TODO: only one request history should be pending at any one time? // We should derive a new one whenever the active one is finished, // based on the current state of pendingDeltas. federationProvider.requestHistory(waveletName, domain, expectedVersion, appliedAt, -1, new HistoryResponseListener() { @Override public void onFailure(FederationError error) { LOG.severe("Callback failure: " + error); } @Override public void onSuccess(List<ByteString> deltaList, ProtocolHashedVersion lastCommittedVersion, long versionTruncatedAt) { LOG.info("Got response callback: " + waveletName + ", lcv " + lastCommittedVersion + " sizeof(deltaSet) = " + deltaList.size()); // Turn the ByteStrings in to a useful representation List<ByteStringMessage<ProtocolAppliedWaveletDelta>> appliedDeltaList = Lists.newArrayList(); for (ByteString appliedDelta : deltaList) { try { LOG.info("Delta incoming from history: " + appliedDelta); appliedDeltaList.add(ByteStringMessage.from( ProtocolAppliedWaveletDelta.getDefaultInstance(), appliedDelta)); } catch (InvalidProtocolBufferException e) { LOG.warning("Invalid protocol buffer when requesting history!"); state = State.CORRUPTED; break; } } // Try updating again with the new history try { update(appliedDeltaList, domain, federationProvider, certificateManager, deltaCallback); } catch (WaveServerException e) { // TODO: deal with this LOG.severe("Exception when updating from history", e); } } }); haveRequestedHistory = true; } else { LOG.severe("History request resulted in non-contiguous deltas!"); } break; } // This delta is at the correct (current) version - apply it. if (appliedAt.getVersion() == expectedVersion.getVersion()) { // Confirm that the applied at hash matches the expected hash. if (!appliedAt.equals(expectedVersion)) { state = State.CORRUPTED; throw new WaveServerException("Incoming delta applied at version " + appliedAt.getVersion() + " is not applied to the correct hash"); } LOG.info("Applying delta for version " + appliedAt.getVersion()); try { DeltaApplicationResult applicationResult = transformAndApplyRemoteDelta(appliedDelta); long opsApplied = applicationResult.getHashedVersionAfterApplication().getVersion() - expectedVersion.getVersion(); if (opsApplied != appliedDelta.getMessage().getOperationsApplied()) { throw new OperationException("Operations applied here do not match the authoritative" + " server claim (got " + opsApplied + ", expected " + appliedDelta.getMessage().getOperationsApplied() + "."); } // Add transformed result to return list. result.add(applicationResult.getDelta()); LOG.fine("Applied delta: " + appliedDelta); } catch (OperationException e) { state = State.CORRUPTED; throw new WaveServerException("Couldn't apply authoritative delta", e); } catch (InvalidProtocolBufferException e) { state = State.CORRUPTED; throw new WaveServerException("Couldn't apply authoritative delta", e); } catch (InvalidHashException e) { state = State.CORRUPTED; throw new WaveServerException("Couldn't apply authoritative delta", e); } catch (EmptyDeltaException e) { // The host shouldn't be forwarding empty deltas! state = State.CORRUPTED; throw new WaveServerException("Couldn't apply authoritative delta", e); } // This is the version 0 case - now we have a valid wavelet! if (state == State.LOADING) { state = State.OK; } // TODO: does waveletData update? expectedVersion = WaveletOperationSerializer.serialize(currentVersion); } else { LOG.warning("Got delta from the past: " + appliedDelta); } pendingDeltas.remove(appliedDelta); } if (!haveRequestedHistory) { DeltaSequence deltaSequence = new DeltaSequence(result, expectedVersion); if (LOG.isFineLoggable()) { LOG.fine("Returning contiguous block: " + deltaSequence); } deltaCallback.onSuccess(deltaSequence); } else if (!result.isEmpty()) { LOG.severe("History requested but non-empty result, non-contiguous deltas?"); } else { LOG.info("History requested, ignoring callback"); } } finally { releaseWriteLock(); } } /** * Apply a serialised applied delta to a remote wavelet. This assumes the * caller has validated that the delta is at the correct version and can be * applied to the wavelet. Must be called with writelock held. * * @param appliedDelta that is to be applied to the wavelet in its serialised form * @return transformed operations are applied to this delta * @throws AccessControlException if the supplied Delta's historyHash does not * match the canonical history. */ private DeltaApplicationResult transformAndApplyRemoteDelta( ByteStringMessage<ProtocolAppliedWaveletDelta> appliedDelta) throws OperationException, AccessControlException, InvalidHashException, InvalidProtocolBufferException, EmptyDeltaException { // The serialised hashed version should actually match the currentVersion at this point, since // the caller of transformAndApply delta will have made sure the applied deltas are ordered HashedVersion hashedVersion = deserialize(getVersionAppliedAt(appliedDelta.getMessage())); if (!hashedVersion.equals(currentVersion)) { throw new IllegalStateException("Applied delta does not apply at current version"); } // Extract the serialised wavelet delta ByteStringMessage<ProtocolWaveletDelta> protocolDelta = ByteStringMessage.from( ProtocolWaveletDelta.getDefaultInstance(), appliedDelta.getMessage().getSignedOriginalDelta().getDelta()); Pair<WaveletDelta, HashedVersion> deltaAndVersion = WaveletOperationSerializer.deserialize(protocolDelta.getMessage()); // Transform operations against earlier deltas, if necessary VersionedWaveletDelta transformed = maybeTransformSubmittedDelta(deltaAndVersion.first, deltaAndVersion.second); if (transformed.version.equals(deltaAndVersion.second)) { // No transformation took place. // As a sanity check, the hash from the applied delta should NOT be set (an optimisation, but // part of the protocol). if (appliedDelta.getMessage().hasHashedVersionAppliedAt()) { LOG.warning("Hashes are the same but applied delta has hashed_version_applied_at"); // TODO: re-enable this exception for version 0.3 of the spec // throw new InvalidHashException("Applied delta and its contained delta have same hash"); } } // Apply operations. These shouldn't fail since they're the authoritative versions, so if they // do then the wavelet is corrupted (and the caller of this method will sort it out). applyWaveletOperations(transformed.delta.getOperations()); return commitAppliedDelta(appliedDelta, transformed.delta); } }