blob: af0566726271069a07a1f33a587eed68794d4c85 [file] [log] [blame]
# Copyright 2014 Google Inc. All Rights Reserved.
#
# 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.
#
# pylint:disable=invalid-name
"""Helper functions for logging."""
import errno
import hmac
import os
import struct
import sys
import helpers
LOGLEVEL = 0
ANONYMIZE = True
STATUS_DIR = None
def Log(s, *args):
if args:
print s % args
else:
print s
sys.stdout.flush()
def Debug(s, *args):
if LOGLEVEL >= 1:
Log(s, *args)
def Debug2(s, *args):
if LOGLEVEL >= 2:
Log(s, *args)
SOFT = 'AEIOUY' 'V'
HARD = 'BCDFGHJKLMNPQRSTVWXYZ' 'AEIOU'
def Trigraph(num):
"""Given a value from 0..4095, encode it as a cons+vowel+cons sequence."""
ns = len(SOFT)
nh = len(HARD)
assert nh * ns * nh >= 4096
c3 = num % nh
c2 = (num / nh) % ns
c1 = num / nh / ns
return HARD[c1] + SOFT[c2] + HARD[c3]
def WordFromBinary(s):
"""Encode a binary blob into a string of pronounceable syllables."""
out = []
while s:
part = s[:3]
s = s[3:]
while len(part) < 4:
part = '\0' + part
bits = struct.unpack('!I', part)[0]
out += [(bits >> 12) & 0xfff,
(bits >> 0) & 0xfff]
return ''.join(Trigraph(i) for i in out)
# Note(apenwarr): There are a few ways to do this. I elected to go with
# short human-usable strings (allowing for the small possibility of
# collisions) since the log messages will probably be "mostly" used by
# humans.
#
# An alternative would be to use "format preserving encryption" (basically
# a secure 1:1 mapping of unencrypted to anonymized, in the same number of
# bits) and then produce longer "words" with no possibility of collision.
# But with our current WordFromBinary() implementation, that would be
# 12 characters long, which is kind of inconvenient and we probably don't
# need that level of care. Inside waveguide we use the real MAC addresses
# so collisions won't cause a real problem.
#
# TODO(apenwarr): consider not anonymizing the OUI.
# That way we could see any behaviour differences between vendors.
# Sadly, that might make it too easy to brute force a MAC address back out;
# the remaining 3 bytes have too little entropy.
#
def AnonymizeMAC(consensus_key, macbin):
"""Anonymize a binary MAC address using the given key."""
assert len(macbin) == 6
if consensus_key and ANONYMIZE:
return WordFromBinary(hmac.new(consensus_key, macbin).digest())[:6]
else:
return helpers.DecodeMAC(macbin)
def WriteEventFile(name):
"""Create a file in STATUS_DIR if it does not already exist.
This is useful for reporting that an event has occurred. We use O_EXCL
to prevent any filesystem churn at all if the file still exists, so it's
very fast. A program watching for the event can unlink the file, then
wait for it to be re-created as an indication that the event has
occurred.
Args:
name: the name of the file to create.
"""
fullname = os.path.join(STATUS_DIR, name)
try:
fd = os.open(fullname, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0666)
except OSError, e:
if e.errno != errno.EEXIST:
raise
else:
os.close(fd)